Can get domain info

This commit is contained in:
Pierre HUBERT 2023-10-04 19:03:20 +02:00
parent 2bc64442f4
commit ce393995f9
6 changed files with 154 additions and 13 deletions

View File

@ -4,6 +4,7 @@ use crate::libvirt_rest_structures::*;
use actix::{Actor, Context, Handler, Message}; use actix::{Actor, Context, Handler, Message};
use virt::connect::Connect; use virt::connect::Connect;
use virt::domain::Domain; use virt::domain::Domain;
use virt::sys::VIR_DOMAIN_XML_SECURE;
pub struct LibVirtActor { pub struct LibVirtActor {
m: Connect, m: Connect,
@ -57,6 +58,22 @@ impl Handler<GetHypervisorInfo> for LibVirtActor {
} }
} }
#[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXML>")]
pub struct GetDomainXMLReq(pub DomainXMLUuid);
impl Handler<GetDomainXMLReq> for LibVirtActor {
type Result = anyhow::Result<DomainXML>;
fn handle(&mut self, msg: GetDomainXMLReq, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Get domain XML:\n{}", msg.0.as_string());
let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?;
let xml = domain.get_xml_desc(VIR_DOMAIN_XML_SECURE)?;
log::debug!("XML = {}", xml);
Ok(serde_xml_rs::from_str(&xml)?)
}
}
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXMLUuid>")] #[rtype(result = "anyhow::Result<DomainXMLUuid>")]
pub struct DefineDomainReq(pub DomainXML); pub struct DefineDomainReq(pub DomainXML);

View File

@ -1,4 +1,5 @@
use crate::controllers::{HttpResult, LibVirtReq}; use crate::controllers::{HttpResult, LibVirtReq};
use crate::libvirt_lib_structures::DomainXMLUuid;
use crate::libvirt_rest_structures::VMInfo; use crate::libvirt_rest_structures::VMInfo;
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
@ -15,3 +16,21 @@ pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
Ok(HttpResponse::Ok().json(id)) Ok(HttpResponse::Ok().json(id))
} }
#[derive(serde::Deserialize)]
pub struct SingleVMUUidReq {
uid: DomainXMLUuid,
}
/// Get the information about a single VM
pub async fn get_single(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
let info = match client.get_single_domain(id.uid).await {
Ok(i) => i,
Err(e) => {
log::error!("Failed to get domain info! {e}");
return Ok(HttpResponse::InternalServerError().json(e.to_string()));
}
};
Ok(HttpResponse::Ok().json(VMInfo::from_domain(info)?))
}

View File

@ -13,6 +13,11 @@ impl LibVirtClient {
self.0.send(libvirt_actor::GetHypervisorInfo).await? self.0.send(libvirt_actor::GetHypervisorInfo).await?
} }
/// Get the information about a single domain
pub async fn get_single_domain(&self, id: DomainXMLUuid) -> anyhow::Result<DomainXML> {
self.0.send(libvirt_actor::GetDomainXMLReq(id)).await?
}
/// Update a domain /// Update a domain
pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<DomainXMLUuid> { pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<DomainXMLUuid> {
self.0.send(libvirt_actor::DefineDomainReq(xml)).await? self.0.send(libvirt_actor::DefineDomainReq(xml)).await?

View File

@ -1,4 +1,4 @@
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct DomainXMLUuid(pub uuid::Uuid); pub struct DomainXMLUuid(pub uuid::Uuid);
impl DomainXMLUuid { impl DomainXMLUuid {
@ -6,6 +6,10 @@ impl DomainXMLUuid {
Ok(Self(uuid::Uuid::parse_str(s)?)) Ok(Self(uuid::Uuid::parse_str(s)?))
} }
pub fn as_string(&self) -> String {
self.0.to_string()
}
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.0.get_version_num() == 4 self.0.get_version_num() == 4
} }
@ -16,6 +20,7 @@ impl DomainXMLUuid {
#[serde(rename = "os")] #[serde(rename = "os")]
pub struct OSXML { pub struct OSXML {
pub r#type: OSTypeXML, pub r#type: OSTypeXML,
pub loader: Option<OSLoaderXML>,
} }
/// OS Type information /// OS Type information
@ -28,6 +33,14 @@ pub struct OSTypeXML {
pub body: String, pub body: String,
} }
/// OS Loader information
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "loader")]
pub struct OSLoaderXML {
#[serde(rename(serialize = "@secure"))]
pub secure: String,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")] #[serde(rename = "memory")]
pub struct DomainMemoryXML { pub struct DomainMemoryXML {
@ -35,7 +48,7 @@ pub struct DomainMemoryXML {
pub unit: String, pub unit: String,
#[serde(rename = "$value")] #[serde(rename = "$value")]
pub memory: String, pub memory: usize,
} }
/// Domain information, see https://libvirt.org/formatdomain.html /// Domain information, see https://libvirt.org/formatdomain.html
@ -55,4 +68,8 @@ pub struct DomainXML {
/// The maximum allocation of memory for the guest at boot time /// The maximum allocation of memory for the guest at boot time
pub memory: DomainMemoryXML, pub memory: DomainMemoryXML,
pub on_poweroff: String,
pub on_reboot: String,
pub on_crash: String,
} }

View File

@ -1,12 +1,19 @@
use crate::constants; use crate::constants;
use crate::libvirt_lib_structures::{DomainMemoryXML, DomainXML, DomainXMLUuid, OSTypeXML, OSXML}; use crate::libvirt_lib_structures::{
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtractionError; DomainMemoryXML, DomainXML, DomainXMLUuid, OSLoaderXML, OSTypeXML, OSXML,
};
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
use lazy_regex::regex; use lazy_regex::regex;
use std::ops::{Div, Mul};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
enum LibVirtStructError { enum LibVirtStructError {
#[error("StructureExtractionError: {0}")] #[error("StructureExtractionError: {0}")]
StructureExtractionError(&'static str), StructureExtraction(&'static str),
#[error("DomainExtractionError: {0}")]
DomainExtraction(String),
#[error("MBConvertError: {0}")]
MBConvert(String),
} }
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
@ -35,7 +42,6 @@ pub struct HypervisorNodeInfo {
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub enum BootType { pub enum BootType {
Legacy,
UEFI, UEFI,
UEFISecureBoot, UEFISecureBoot,
} }
@ -56,7 +62,7 @@ pub struct VMInfo {
pub genid: Option<DomainXMLUuid>, pub genid: Option<DomainXMLUuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub boot_type: Option<BootType>, pub boot_type: BootType,
pub architecture: VMArchitecture, pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes /// VM allocated memory, in megabytes
pub memory: usize, pub memory: usize,
@ -66,29 +72,29 @@ impl VMInfo {
/// Turn this VM into a domain /// Turn this VM into a domain
pub fn to_domain(self) -> anyhow::Result<DomainXML> { pub fn to_domain(self) -> anyhow::Result<DomainXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtractionError("VM name is invalid!").into()); return Err(StructureExtraction("VM name is invalid!").into());
} }
if let Some(n) = &self.uuid { if let Some(n) = &self.uuid {
if n.is_valid() { if n.is_valid() {
return Err(StructureExtractionError("VM UUID is invalid!").into()); return Err(StructureExtraction("VM UUID is invalid!").into());
} }
} }
if let Some(n) = &self.genid { if let Some(n) = &self.genid {
if n.is_valid() { if n.is_valid() {
return Err(StructureExtractionError("VM genid is invalid!").into()); return Err(StructureExtraction("VM genid is invalid!").into());
} }
} }
if let Some(n) = &self.title { if let Some(n) = &self.title {
if n.contains('\n') { if n.contains('\n') {
return Err(StructureExtractionError("VM title contain newline char!").into()); return Err(StructureExtraction("VM title contain newline char!").into());
} }
} }
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY { if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
return Err(StructureExtractionError("VM memory is invalid!").into()); return Err(StructureExtraction("VM memory is invalid!").into());
} }
Ok(DomainXML { Ok(DomainXML {
@ -108,11 +114,87 @@ impl VMInfo {
.to_string(), .to_string(),
body: "hvm".to_string(), body: "hvm".to_string(),
}, },
loader: Some(OSLoaderXML {
secure: match self.boot_type {
BootType::UEFI => "no".to_string(),
BootType::UEFISecureBoot => "yes".to_string(),
},
}),
}, },
memory: DomainMemoryXML { memory: DomainMemoryXML {
unit: "MB".to_string(), unit: "MB".to_string(),
memory: self.memory.to_string(), memory: self.memory,
}, },
on_poweroff: "preserve".to_string(),
on_reboot: "restart".to_string(),
on_crash: "preserve".to_string(),
})
}
/// Turn a domain into a vm
pub fn from_domain(domain: DomainXML) -> anyhow::Result<Self> {
Ok(Self {
name: domain.name,
uuid: domain.uuid,
genid: domain.genid.map(DomainXMLUuid),
title: domain.title,
description: domain.description,
boot_type: match domain.os.loader {
None => BootType::UEFI,
Some(l) => match l.secure.as_str() {
"yes" => BootType::UEFISecureBoot,
_ => BootType::UEFI,
},
},
architecture: match domain.os.r#type.arch.as_str() {
"i686" => VMArchitecture::I686,
"x86_64" => VMArchitecture::X86_64,
a => {
return Err(LibVirtStructError::DomainExtraction(format!(
"Unknown architecture: {a}! "
))
.into());
}
},
memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?,
}) })
} }
} }
/// Convert unit to MB
fn convert_to_mb(unit: &str, value: usize) -> anyhow::Result<usize> {
let fact = match unit {
"bytes" | "b" => 1f64,
"KB" => 1000f64,
"MB" => 1000f64 * 1000f64,
"GB" => 1000f64 * 1000f64 * 1000f64,
"TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
"k" | "KiB" => 1024f64,
"M" | "MiB" => 1024f64 * 1024f64,
"G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
"T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
_ => {
return Err(LibVirtStructError::MBConvert(format!("Unknown size unit: {unit}")).into());
}
};
Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize)
}
#[cfg(test)]
mod test {
use crate::libvirt_rest_structures::convert_to_mb;
#[test]
fn convert_units_mb() {
assert_eq!(convert_to_mb("MB", 1).unwrap(), 1);
assert_eq!(convert_to_mb("MB", 1000).unwrap(), 1000);
assert_eq!(convert_to_mb("GB", 1000).unwrap(), 1000 * 1000);
assert_eq!(convert_to_mb("GB", 1).unwrap(), 1000);
assert_eq!(convert_to_mb("GiB", 3).unwrap(), 3222);
assert_eq!(convert_to_mb("KiB", 488281).unwrap(), 500);
}
}

View File

@ -136,6 +136,7 @@ async fn main() -> std::io::Result<()> {
) )
// Virtual machines controller // Virtual machines controller
.route("/api/vm/create", web::post().to(vm_controller::create)) .route("/api/vm/create", web::post().to(vm_controller::create))
.route("/api/vm/{uid}", web::get().to(vm_controller::get_single))
}) })
.bind(&AppConfig::get().listen_address)? .bind(&AppConfig::get().listen_address)?
.run() .run()