diff --git a/virtweb_backend/src/controllers/groups_controller.rs b/virtweb_backend/src/controllers/groups_controller.rs index b9b6c12..b61cc25 100644 --- a/virtweb_backend/src/controllers/groups_controller.rs +++ b/virtweb_backend/src/controllers/groups_controller.rs @@ -1,4 +1,6 @@ use crate::controllers::{HttpResult, LibVirtReq}; +use crate::extractors::group_vm_id_extractor::GroupVmIdExtractor; +use crate::libvirt_rest_structures::vm::VMInfo; use actix_web::HttpResponse; /// Get the list of groups @@ -14,3 +16,12 @@ pub async fn list(client: LibVirtReq) -> HttpResult { Ok(HttpResponse::Ok().json(groups)) } + +/// Get information about a VM +pub async fn vm_info(vms_ids: GroupVmIdExtractor) -> HttpResult { + let mut vms = Vec::new(); + for vm in vms_ids.0 { + vms.push(VMInfo::from_domain(vm)?) + } + Ok(HttpResponse::Ok().json(vms)) +} diff --git a/virtweb_backend/src/extractors/group_vm_id_extractor.rs b/virtweb_backend/src/extractors/group_vm_id_extractor.rs new file mode 100644 index 0000000..ac85cf0 --- /dev/null +++ b/virtweb_backend/src/extractors/group_vm_id_extractor.rs @@ -0,0 +1,66 @@ +use crate::controllers::LibVirtReq; +use crate::libvirt_lib_structures::domain::DomainXML; +use crate::libvirt_lib_structures::XMLUuid; +use crate::libvirt_rest_structures::vm::VMGroupId; +use actix_http::Payload; +use actix_web::error::ErrorBadRequest; +use actix_web::web::Query; +use actix_web::{web, Error, FromRequest, HttpRequest}; +use std::future::Future; +use std::pin::Pin; + +pub struct GroupVmIdExtractor(pub Vec); + +#[derive(serde::Deserialize)] +struct GroupIDInPath { + gid: VMGroupId, +} + +#[derive(serde::Deserialize)] +struct FilterVM { + vm_id: Option, +} + +impl FromRequest for GroupVmIdExtractor { + type Error = Error; + type Future = Pin>>>; + + fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { + let req = req.clone(); + + Box::pin(async move { + let Ok(group_id) = + web::Path::::from_request(&req, &mut Payload::None).await + else { + return Err(ErrorBadRequest("Group ID not specified in path!")); + }; + let group_id = group_id.into_inner().gid; + + let filter_vm = match Query::::from_request(&req, &mut Payload::None).await { + Ok(v) => v, + Err(e) => { + log::error!("Failed to extract VM id from request! {e}"); + return Err(ErrorBadRequest("Failed to extract VM id from request!")); + } + }; + + let Ok(client) = LibVirtReq::from_request(&req, &mut Payload::None).await else { + return Err(ErrorBadRequest("Failed to extract client handle!")); + }; + + let vms = match client.get_full_group_vm_list(&group_id).await { + Ok(vms) => vms, + Err(e) => { + log::error!("Failed to get the VM of the group {group_id:?}: {e}"); + return Err(ErrorBadRequest("Failed to get the VM of the group!")); + } + }; + + // Filter (if requested by the user) + Ok(GroupVmIdExtractor(match filter_vm.vm_id { + None => vms, + Some(id) => vms.into_iter().filter(|vms| vms.uuid == Some(id)).collect(), + })) + }) + } +} diff --git a/virtweb_backend/src/extractors/mod.rs b/virtweb_backend/src/extractors/mod.rs index d284d19..491626d 100644 --- a/virtweb_backend/src/extractors/mod.rs +++ b/virtweb_backend/src/extractors/mod.rs @@ -1,3 +1,4 @@ pub mod api_auth_extractor; pub mod auth_extractor; +pub mod group_vm_id_extractor; pub mod local_auth_extractor; diff --git a/virtweb_backend/src/libvirt_client.rs b/virtweb_backend/src/libvirt_client.rs index 9cbe77c..2f9290e 100644 --- a/virtweb_backend/src/libvirt_client.rs +++ b/virtweb_backend/src/libvirt_client.rs @@ -122,6 +122,21 @@ impl LibVirtClient { Ok(out) } + /// Get the full list of VMs of a given group + pub async fn get_full_group_vm_list( + &self, + group: &VMGroupId, + ) -> anyhow::Result> { + let vms = self.get_full_domains_list().await?; + let mut out = Vec::new(); + for vm in vms { + if VMInfo::from_domain(vm.clone())?.group == Some(group.clone()) { + out.push(vm); + } + } + Ok(out) + } + /// Update a network configuration pub async fn update_network( &self, diff --git a/virtweb_backend/src/libvirt_lib_structures/domain.rs b/virtweb_backend/src/libvirt_lib_structures/domain.rs index f8ec61e..7aab273 100644 --- a/virtweb_backend/src/libvirt_lib_structures/domain.rs +++ b/virtweb_backend/src/libvirt_lib_structures/domain.rs @@ -19,7 +19,7 @@ pub struct DomainMetadataXML { } /// OS information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "os")] pub struct OSXML { #[serde(rename = "@firmware", default)] @@ -29,7 +29,7 @@ pub struct OSXML { } /// OS Type information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "os")] pub struct OSTypeXML { #[serde(rename = "@arch")] @@ -41,7 +41,7 @@ pub struct OSTypeXML { } /// OS Loader information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "loader")] pub struct OSLoaderXML { #[serde(rename = "@secure")] @@ -49,39 +49,39 @@ pub struct OSLoaderXML { } /// Hypervisor features -#[derive(serde::Serialize, serde::Deserialize, Default, Debug)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)] #[serde(rename = "features")] pub struct FeaturesXML { pub acpi: ACPIXML, } /// ACPI feature -#[derive(serde::Serialize, serde::Deserialize, Default, Debug)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)] #[serde(rename = "acpi")] pub struct ACPIXML {} -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "mac")] pub struct NetMacAddress { #[serde(rename = "@address")] pub address: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "source")] pub struct NetIntSourceXML { #[serde(rename = "@network")] pub network: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "model")] pub struct NetIntModelXML { #[serde(rename = "@type")] pub r#type: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "filterref")] pub struct NetIntFilterParameterXML { #[serde(rename = "@name")] @@ -90,7 +90,7 @@ pub struct NetIntFilterParameterXML { pub value: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "filterref")] pub struct NetIntfilterRefXML { #[serde(rename = "@filter")] @@ -99,7 +99,7 @@ pub struct NetIntfilterRefXML { pub parameters: Vec, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "interface")] pub struct DomainNetInterfaceXML { #[serde(rename = "@type")] @@ -113,14 +113,14 @@ pub struct DomainNetInterfaceXML { pub filterref: Option, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "input")] pub struct DomainInputXML { #[serde(rename = "@type")] pub r#type: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "backend")] pub struct TPMBackendXML { #[serde(rename = "@type")] @@ -130,7 +130,7 @@ pub struct TPMBackendXML { pub r#version: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "tpm")] pub struct TPMDeviceXML { #[serde(rename = "@model")] @@ -139,7 +139,7 @@ pub struct TPMDeviceXML { } /// Devices information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "devices")] pub struct DevicesXML { /// Graphics (used for VNC) @@ -168,7 +168,7 @@ pub struct DevicesXML { } /// Graphics information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "graphics")] pub struct GraphicsXML { #[serde(rename = "@type")] @@ -178,14 +178,14 @@ pub struct GraphicsXML { } /// Video device information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "video")] pub struct VideoXML { pub model: VideoModelXML, } /// Video model device information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "model")] pub struct VideoModelXML { #[serde(rename = "@type")] @@ -193,7 +193,7 @@ pub struct VideoModelXML { } /// Disk information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "disk")] pub struct DiskXML { #[serde(rename = "@type")] @@ -211,7 +211,7 @@ pub struct DiskXML { pub address: Option, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "driver")] pub struct DiskDriverXML { #[serde(rename = "@name")] @@ -222,14 +222,14 @@ pub struct DiskDriverXML { pub r#cache: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "source")] pub struct DiskSourceXML { #[serde(rename = "@file")] pub file: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "target")] pub struct DiskTargetXML { #[serde(rename = "@dev")] @@ -238,18 +238,18 @@ pub struct DiskTargetXML { pub bus: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "readonly")] pub struct DiskReadOnlyXML {} -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "boot")] pub struct DiskBootXML { #[serde(rename = "@order")] pub order: String, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "address")] pub struct DiskAddressXML { #[serde(rename = "@type")] @@ -269,7 +269,7 @@ pub struct DiskAddressXML { } /// Domain RAM information -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "memory")] pub struct DomainMemoryXML { #[serde(rename = "@unit")] @@ -279,7 +279,7 @@ pub struct DomainMemoryXML { pub memory: usize, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "topology")] pub struct DomainCPUTopology { #[serde(rename = "@sockets")] @@ -290,14 +290,14 @@ pub struct DomainCPUTopology { pub threads: usize, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "cpu")] pub struct DomainVCPUXML { #[serde(rename = "$value")] pub body: usize, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "cpu")] pub struct DomainCPUXML { #[serde(rename = "@mode")] @@ -306,7 +306,7 @@ pub struct DomainCPUXML { } /// Domain information, see https://libvirt.org/formatdomain.html -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "domain")] pub struct DomainXML { /// Domain type (kvm) diff --git a/virtweb_backend/src/libvirt_lib_structures/mod.rs b/virtweb_backend/src/libvirt_lib_structures/mod.rs index 59adfa6..65e7491 100644 --- a/virtweb_backend/src/libvirt_lib_structures/mod.rs +++ b/virtweb_backend/src/libvirt_lib_structures/mod.rs @@ -1,4 +1,4 @@ -#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, Eq, PartialEq)] pub struct XMLUuid(pub uuid::Uuid); impl XMLUuid { diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 58cc251..0cf3a50 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -212,7 +212,10 @@ async fn main() -> std::io::Result<()> { .route("/api/vnc", web::get().to(vm_controller::vnc)) // Groups controller .route("/api/group/list", web::get().to(groups_controller::list)) - // TODO list VM + .route( + "/api/group/{gid}/vm/info", + web::get().to(groups_controller::vm_info), + ) // TODO start VM // TODO stop VM // TODO suspend VM