Can get domain info
This commit is contained in:
		@@ -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<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)]
 | 
			
		||||
#[rtype(result = "anyhow::Result<DomainXMLUuid>")]
 | 
			
		||||
pub struct DefineDomainReq(pub DomainXML);
 | 
			
		||||
 
 | 
			
		||||
@@ -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<VMInfo>) -> 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<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)?))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<DomainXML> {
 | 
			
		||||
        self.0.send(libvirt_actor::GetDomainXMLReq(id)).await?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update a domain
 | 
			
		||||
    pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<DomainXMLUuid> {
 | 
			
		||||
        self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
 | 
			
		||||
 
 | 
			
		||||
@@ -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<OSLoaderXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<DomainXMLUuid>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    pub boot_type: Option<BootType>,
 | 
			
		||||
    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<DomainXML> {
 | 
			
		||||
        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<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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user