Can assign a group to VMs
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Pierre HUBERT 2024-11-02 17:31:05 +01:00
parent 91fe291341
commit 55b49699eb
4 changed files with 95 additions and 36 deletions

View File

@ -21,7 +21,7 @@ struct VMUuid {
/// Create a new VM /// Create a new VM
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult { pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
let domain = match req.0.as_tomain() { let domain = match req.0.as_domain() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract domain info! {e}"); log::error!("Failed to extract domain info! {e}");
@ -83,6 +83,8 @@ pub async fn get_single(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> H
} }
}; };
log::debug!("INFO={info:#?}");
let state = client.get_domain_state(id.uid).await?; let state = client.get_domain_state(id.uid).await?;
Ok(HttpResponse::Ok().json(VMInfoAndState { Ok(HttpResponse::Ok().json(VMInfoAndState {
@ -112,7 +114,7 @@ pub async fn update(
id: web::Path<SingleVMUUidReq>, id: web::Path<SingleVMUUidReq>,
req: web::Json<VMInfo>, req: web::Json<VMInfo>,
) -> HttpResult { ) -> HttpResult {
let mut domain = match req.0.as_tomain() { let mut domain = match req.0.as_domain() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract domain info! {e}"); log::error!("Failed to extract domain info! {e}");

View File

@ -1,7 +1,25 @@
use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_lib_structures::XMLUuid;
/// VirtWeb specific metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "virtweb", default)]
pub struct DomainMetadataVirtWebXML {
#[serde(rename = "@xmlns:virtweb", default)]
pub ns: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
}
/// Domain metadata
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone)]
#[serde(rename = "metadata")]
pub struct DomainMetadataXML {
#[serde(rename = "virtweb:metadata", default)]
pub virtweb: DomainMetadataVirtWebXML,
}
/// OS information /// OS information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")] #[serde(rename = "os")]
pub struct OSXML { pub struct OSXML {
#[serde(rename = "@firmware", default)] #[serde(rename = "@firmware", default)]
@ -11,7 +29,7 @@ pub struct OSXML {
} }
/// OS Type information /// OS Type information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")] #[serde(rename = "os")]
pub struct OSTypeXML { pub struct OSTypeXML {
#[serde(rename = "@arch")] #[serde(rename = "@arch")]
@ -23,7 +41,7 @@ pub struct OSTypeXML {
} }
/// OS Loader information /// OS Loader information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "loader")] #[serde(rename = "loader")]
pub struct OSLoaderXML { pub struct OSLoaderXML {
#[serde(rename = "@secure")] #[serde(rename = "@secure")]
@ -31,39 +49,39 @@ pub struct OSLoaderXML {
} }
/// Hypervisor features /// Hypervisor features
#[derive(serde::Serialize, serde::Deserialize, Default)] #[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "features")] #[serde(rename = "features")]
pub struct FeaturesXML { pub struct FeaturesXML {
pub acpi: ACPIXML, pub acpi: ACPIXML,
} }
/// ACPI feature /// ACPI feature
#[derive(serde::Serialize, serde::Deserialize, Default)] #[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
#[serde(rename = "acpi")] #[serde(rename = "acpi")]
pub struct ACPIXML {} pub struct ACPIXML {}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "mac")] #[serde(rename = "mac")]
pub struct NetMacAddress { pub struct NetMacAddress {
#[serde(rename = "@address")] #[serde(rename = "@address")]
pub address: String, pub address: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")] #[serde(rename = "source")]
pub struct NetIntSourceXML { pub struct NetIntSourceXML {
#[serde(rename = "@network")] #[serde(rename = "@network")]
pub network: String, pub network: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")] #[serde(rename = "model")]
pub struct NetIntModelXML { pub struct NetIntModelXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
pub r#type: String, pub r#type: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")] #[serde(rename = "filterref")]
pub struct NetIntFilterParameterXML { pub struct NetIntFilterParameterXML {
#[serde(rename = "@name")] #[serde(rename = "@name")]
@ -72,7 +90,7 @@ pub struct NetIntFilterParameterXML {
pub value: String, pub value: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "filterref")] #[serde(rename = "filterref")]
pub struct NetIntfilterRefXML { pub struct NetIntfilterRefXML {
#[serde(rename = "@filter")] #[serde(rename = "@filter")]
@ -81,7 +99,7 @@ pub struct NetIntfilterRefXML {
pub parameters: Vec<NetIntFilterParameterXML>, pub parameters: Vec<NetIntFilterParameterXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "interface")] #[serde(rename = "interface")]
pub struct DomainNetInterfaceXML { pub struct DomainNetInterfaceXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -95,14 +113,14 @@ pub struct DomainNetInterfaceXML {
pub filterref: Option<NetIntfilterRefXML>, pub filterref: Option<NetIntfilterRefXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "input")] #[serde(rename = "input")]
pub struct DomainInputXML { pub struct DomainInputXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
pub r#type: String, pub r#type: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "backend")] #[serde(rename = "backend")]
pub struct TPMBackendXML { pub struct TPMBackendXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -112,7 +130,7 @@ pub struct TPMBackendXML {
pub r#version: String, pub r#version: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "tpm")] #[serde(rename = "tpm")]
pub struct TPMDeviceXML { pub struct TPMDeviceXML {
#[serde(rename = "@model")] #[serde(rename = "@model")]
@ -121,7 +139,7 @@ pub struct TPMDeviceXML {
} }
/// Devices information /// Devices information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "devices")] #[serde(rename = "devices")]
pub struct DevicesXML { pub struct DevicesXML {
/// Graphics (used for VNC) /// Graphics (used for VNC)
@ -150,7 +168,7 @@ pub struct DevicesXML {
} }
/// Graphics information /// Graphics information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "graphics")] #[serde(rename = "graphics")]
pub struct GraphicsXML { pub struct GraphicsXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -160,14 +178,14 @@ pub struct GraphicsXML {
} }
/// Video device information /// Video device information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "video")] #[serde(rename = "video")]
pub struct VideoXML { pub struct VideoXML {
pub model: VideoModelXML, pub model: VideoModelXML,
} }
/// Video model device information /// Video model device information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "model")] #[serde(rename = "model")]
pub struct VideoModelXML { pub struct VideoModelXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -175,7 +193,7 @@ pub struct VideoModelXML {
} }
/// Disk information /// Disk information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "disk")] #[serde(rename = "disk")]
pub struct DiskXML { pub struct DiskXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -193,7 +211,7 @@ pub struct DiskXML {
pub address: Option<DiskAddressXML>, pub address: Option<DiskAddressXML>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "driver")] #[serde(rename = "driver")]
pub struct DiskDriverXML { pub struct DiskDriverXML {
#[serde(rename = "@name")] #[serde(rename = "@name")]
@ -204,14 +222,14 @@ pub struct DiskDriverXML {
pub r#cache: String, pub r#cache: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")] #[serde(rename = "source")]
pub struct DiskSourceXML { pub struct DiskSourceXML {
#[serde(rename = "@file")] #[serde(rename = "@file")]
pub file: String, pub file: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "target")] #[serde(rename = "target")]
pub struct DiskTargetXML { pub struct DiskTargetXML {
#[serde(rename = "@dev")] #[serde(rename = "@dev")]
@ -220,18 +238,18 @@ pub struct DiskTargetXML {
pub bus: String, pub bus: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "readonly")] #[serde(rename = "readonly")]
pub struct DiskReadOnlyXML {} pub struct DiskReadOnlyXML {}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "boot")] #[serde(rename = "boot")]
pub struct DiskBootXML { pub struct DiskBootXML {
#[serde(rename = "@order")] #[serde(rename = "@order")]
pub order: String, pub order: String,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "address")] #[serde(rename = "address")]
pub struct DiskAddressXML { pub struct DiskAddressXML {
#[serde(rename = "@type")] #[serde(rename = "@type")]
@ -251,7 +269,7 @@ pub struct DiskAddressXML {
} }
/// Domain RAM information /// Domain RAM information
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")] #[serde(rename = "memory")]
pub struct DomainMemoryXML { pub struct DomainMemoryXML {
#[serde(rename = "@unit")] #[serde(rename = "@unit")]
@ -261,7 +279,7 @@ pub struct DomainMemoryXML {
pub memory: usize, pub memory: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "topology")] #[serde(rename = "topology")]
pub struct DomainCPUTopology { pub struct DomainCPUTopology {
#[serde(rename = "@sockets")] #[serde(rename = "@sockets")]
@ -272,14 +290,14 @@ pub struct DomainCPUTopology {
pub threads: usize, pub threads: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")] #[serde(rename = "cpu")]
pub struct DomainVCPUXML { pub struct DomainVCPUXML {
#[serde(rename = "$value")] #[serde(rename = "$value")]
pub body: usize, pub body: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")] #[serde(rename = "cpu")]
pub struct DomainCPUXML { pub struct DomainCPUXML {
#[serde(rename = "@mode")] #[serde(rename = "@mode")]
@ -288,7 +306,7 @@ pub struct DomainCPUXML {
} }
/// Domain information, see https://libvirt.org/formatdomain.html /// Domain information, see https://libvirt.org/formatdomain.html
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename = "domain")] #[serde(rename = "domain")]
pub struct DomainXML { pub struct DomainXML {
/// Domain type (kvm) /// Domain type (kvm)
@ -300,6 +318,9 @@ pub struct DomainXML {
pub genid: Option<uuid::Uuid>, pub genid: Option<uuid::Uuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<DomainMetadataXML>,
pub os: OSXML, pub os: OSXML,
#[serde(default)] #[serde(default)]
pub features: FeaturesXML, pub features: FeaturesXML,
@ -319,10 +340,32 @@ pub struct DomainXML {
pub on_crash: String, pub on_crash: String,
} }
const METADATA_START_MARKER: &str =
"<virtweb:metadata xmlns:virtweb=\"https://virtweb.communiquons.org\">";
const METADATA_END_MARKER: &str = "</virtweb:metadata>";
impl DomainXML { impl DomainXML {
/// Decode Domain structure from XML definition /// Decode Domain structure from XML definition
pub fn parse_xml(xml: &str) -> anyhow::Result<Self> { pub fn parse_xml(xml: &str) -> anyhow::Result<Self> {
Ok(quick_xml::de::from_str(xml)?) let mut res: Self = quick_xml::de::from_str(xml)?;
// Handle custom metadata parsing issue
//
// https://github.com/tafia/quick-xml/pull/797
if xml.contains(METADATA_START_MARKER) && xml.contains(METADATA_END_MARKER) {
let s = xml
.split_once(METADATA_START_MARKER)
.unwrap()
.1
.split_once(METADATA_END_MARKER)
.unwrap()
.0;
let s = format!("<virtweb>{s}</virtweb>");
let metadata: DomainMetadataVirtWebXML = quick_xml::de::from_str(&s)?;
res.metadata = Some(DomainMetadataXML { virtweb: metadata });
}
Ok(res)
} }
/// Turn this domain into its XML definition /// Turn this domain into its XML definition

View File

@ -59,6 +59,9 @@ pub struct VMInfo {
pub genid: Option<XMLUuid>, pub genid: Option<XMLUuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
/// Group associated with the VM (VirtWeb specific field)
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
pub boot_type: BootType, pub boot_type: BootType,
pub architecture: VMArchitecture, pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes /// VM allocated memory, in megabytes
@ -79,7 +82,7 @@ pub struct VMInfo {
impl VMInfo { impl VMInfo {
/// Turn this VM into a domain /// Turn this VM into a domain
pub fn as_tomain(&self) -> anyhow::Result<DomainXML> { pub fn as_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(StructureExtraction("VM name is invalid!").into()); return Err(StructureExtraction("VM name is invalid!").into());
} }
@ -105,6 +108,12 @@ impl VMInfo {
} }
} }
if let Some(group) = &self.group {
if !regex!("^[a-zA-Z0-9]+$").is_match(group) {
return Err(StructureExtraction("VM group name is invalid!").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(StructureExtraction("VM memory is invalid!").into()); return Err(StructureExtraction("VM memory is invalid!").into());
} }
@ -282,6 +291,12 @@ impl VMInfo {
title: self.title.clone(), title: self.title.clone(),
description: self.description.clone(), description: self.description.clone(),
metadata: Some(DomainMetadataXML {
virtweb: DomainMetadataVirtWebXML {
ns: "https://virtweb.communiquons.org".to_string(),
group: self.group.clone(),
},
}),
os: OSXML { os: OSXML {
r#type: OSTypeXML { r#type: OSTypeXML {
arch: match self.architecture { arch: match self.architecture {
@ -369,6 +384,7 @@ impl VMInfo {
genid: domain.genid.map(XMLUuid), genid: domain.genid.map(XMLUuid),
title: domain.title, title: domain.title,
description: domain.description, description: domain.description,
group: domain.metadata.clone().unwrap_or_default().virtweb.group,
boot_type: match domain.os.loader { boot_type: match domain.os.loader {
None => BootType::UEFI, None => BootType::UEFI,
Some(l) => match l.secure.as_str() { Some(l) => match l.secure.as_str() {

View File

@ -79,8 +79,6 @@ function VMListWidget(p: {
setRunningVMs(new Set([...runningVMs])); setRunningVMs(new Set([...runningVMs]));
}; };
console.log(runningVMs);
return ( return (
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table> <Table>