From ce393995f9fae69e116bb5c6b7005b32c81b6d61 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Wed, 4 Oct 2023 19:03:20 +0200 Subject: [PATCH] Can get domain info --- virtweb_backend/src/actors/libvirt_actor.rs | 17 +++ .../src/controllers/vm_controller.rs | 19 ++++ virtweb_backend/src/libvirt_client.rs | 5 + virtweb_backend/src/libvirt_lib_structures.rs | 21 +++- .../src/libvirt_rest_structures.rs | 104 ++++++++++++++++-- virtweb_backend/src/main.rs | 1 + 6 files changed, 154 insertions(+), 13 deletions(-) diff --git a/virtweb_backend/src/actors/libvirt_actor.rs b/virtweb_backend/src/actors/libvirt_actor.rs index d2e2ab5..37bfe47 100644 --- a/virtweb_backend/src/actors/libvirt_actor.rs +++ b/virtweb_backend/src/actors/libvirt_actor.rs @@ -4,6 +4,7 @@ use crate::libvirt_rest_structures::*; use actix::{Actor, Context, Handler, Message}; use virt::connect::Connect; use virt::domain::Domain; +use virt::sys::VIR_DOMAIN_XML_SECURE; pub struct LibVirtActor { m: Connect, @@ -57,6 +58,22 @@ impl Handler for LibVirtActor { } } +#[derive(Message)] +#[rtype(result = "anyhow::Result")] +pub struct GetDomainXMLReq(pub DomainXMLUuid); + +impl Handler for LibVirtActor { + type Result = anyhow::Result; + + 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)] #[rtype(result = "anyhow::Result")] pub struct DefineDomainReq(pub DomainXML); diff --git a/virtweb_backend/src/controllers/vm_controller.rs b/virtweb_backend/src/controllers/vm_controller.rs index 6476128..a63bf91 100644 --- a/virtweb_backend/src/controllers/vm_controller.rs +++ b/virtweb_backend/src/controllers/vm_controller.rs @@ -1,4 +1,5 @@ use crate::controllers::{HttpResult, LibVirtReq}; +use crate::libvirt_lib_structures::DomainXMLUuid; use crate::libvirt_rest_structures::VMInfo; use actix_web::{web, HttpResponse}; @@ -15,3 +16,21 @@ pub async fn create(client: LibVirtReq, req: web::Json) -> HttpResult { 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) -> 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)?)) +} diff --git a/virtweb_backend/src/libvirt_client.rs b/virtweb_backend/src/libvirt_client.rs index 69525ef..8a9d0ef 100644 --- a/virtweb_backend/src/libvirt_client.rs +++ b/virtweb_backend/src/libvirt_client.rs @@ -13,6 +13,11 @@ impl LibVirtClient { 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 { + self.0.send(libvirt_actor::GetDomainXMLReq(id)).await? + } + /// Update a domain pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result { self.0.send(libvirt_actor::DefineDomainReq(xml)).await? diff --git a/virtweb_backend/src/libvirt_lib_structures.rs b/virtweb_backend/src/libvirt_lib_structures.rs index e9e75c1..7802fa0 100644 --- a/virtweb_backend/src/libvirt_lib_structures.rs +++ b/virtweb_backend/src/libvirt_lib_structures.rs @@ -1,4 +1,4 @@ -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct DomainXMLUuid(pub uuid::Uuid); impl DomainXMLUuid { @@ -6,6 +6,10 @@ impl DomainXMLUuid { Ok(Self(uuid::Uuid::parse_str(s)?)) } + pub fn as_string(&self) -> String { + self.0.to_string() + } + pub fn is_valid(&self) -> bool { self.0.get_version_num() == 4 } @@ -16,6 +20,7 @@ impl DomainXMLUuid { #[serde(rename = "os")] pub struct OSXML { pub r#type: OSTypeXML, + pub loader: Option, } /// OS Type information @@ -28,6 +33,14 @@ pub struct OSTypeXML { 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)] #[serde(rename = "memory")] pub struct DomainMemoryXML { @@ -35,7 +48,7 @@ pub struct DomainMemoryXML { pub unit: String, #[serde(rename = "$value")] - pub memory: String, + pub memory: usize, } /// 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 pub memory: DomainMemoryXML, + + pub on_poweroff: String, + pub on_reboot: String, + pub on_crash: String, } diff --git a/virtweb_backend/src/libvirt_rest_structures.rs b/virtweb_backend/src/libvirt_rest_structures.rs index 0109177..1a8e627 100644 --- a/virtweb_backend/src/libvirt_rest_structures.rs +++ b/virtweb_backend/src/libvirt_rest_structures.rs @@ -1,12 +1,19 @@ use crate::constants; -use crate::libvirt_lib_structures::{DomainMemoryXML, DomainXML, DomainXMLUuid, OSTypeXML, OSXML}; -use crate::libvirt_rest_structures::LibVirtStructError::StructureExtractionError; +use crate::libvirt_lib_structures::{ + DomainMemoryXML, DomainXML, DomainXMLUuid, OSLoaderXML, OSTypeXML, OSXML, +}; +use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use lazy_regex::regex; +use std::ops::{Div, Mul}; #[derive(thiserror::Error, Debug)] enum LibVirtStructError { #[error("StructureExtractionError: {0}")] - StructureExtractionError(&'static str), + StructureExtraction(&'static str), + #[error("DomainExtractionError: {0}")] + DomainExtraction(String), + #[error("MBConvertError: {0}")] + MBConvert(String), } #[derive(serde::Serialize)] @@ -35,7 +42,6 @@ pub struct HypervisorNodeInfo { #[derive(serde::Serialize, serde::Deserialize)] pub enum BootType { - Legacy, UEFI, UEFISecureBoot, } @@ -56,7 +62,7 @@ pub struct VMInfo { pub genid: Option, pub title: Option, pub description: Option, - pub boot_type: Option, + pub boot_type: BootType, pub architecture: VMArchitecture, /// VM allocated memory, in megabytes pub memory: usize, @@ -66,29 +72,29 @@ impl VMInfo { /// Turn this VM into a domain pub fn to_domain(self) -> anyhow::Result { 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 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 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 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 { - return Err(StructureExtractionError("VM memory is invalid!").into()); + return Err(StructureExtraction("VM memory is invalid!").into()); } Ok(DomainXML { @@ -108,11 +114,87 @@ impl VMInfo { .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 { 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 { + 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 { + 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); + } +} diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 5323688..bf60551 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -136,6 +136,7 @@ async fn main() -> std::io::Result<()> { ) // Virtual machines controller .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)? .run()