Can get domain info
This commit is contained in:
parent
2bc64442f4
commit
ce393995f9
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user