diff --git a/virtweb_backend/Cargo.toml b/virtweb_backend/Cargo.toml index 8254796..83ab1a7 100644 --- a/virtweb_backend/Cargo.toml +++ b/virtweb_backend/Cargo.toml @@ -38,4 +38,4 @@ image = "0.24.7" rand = "0.8.5" bytes = "1.5.0" tokio = "1.32.0" -futures = "0.3.28" \ No newline at end of file +futures = "0.3.28" diff --git a/virtweb_backend/src/actors/libvirt_actor.rs b/virtweb_backend/src/actors/libvirt_actor.rs index 334cb95..3006e06 100644 --- a/virtweb_backend/src/actors/libvirt_actor.rs +++ b/virtweb_backend/src/actors/libvirt_actor.rs @@ -1,11 +1,12 @@ use crate::app_config::AppConfig; -use crate::libvirt_lib_structures::{DomainState, DomainXML, DomainXMLUuid}; +use crate::libvirt_lib_structures::{DomainState, DomainXML, NetworkXML, XMLUuid}; use crate::libvirt_rest_structures::*; use actix::{Actor, Context, Handler, Message}; use image::ImageOutputFormat; use std::io::Cursor; use virt::connect::Connect; use virt::domain::Domain; +use virt::network::Network; use virt::stream::Stream; use virt::sys; use virt::sys::VIR_DOMAIN_XML_SECURE; @@ -63,11 +64,11 @@ impl Handler for LibVirtActor { } #[derive(Message)] -#[rtype(result = "anyhow::Result>")] +#[rtype(result = "anyhow::Result>")] pub struct GetDomainsListReq; impl Handler for LibVirtActor { - type Result = anyhow::Result>; + type Result = anyhow::Result>; fn handle(&mut self, _msg: GetDomainsListReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get full list of domains"); @@ -75,7 +76,7 @@ impl Handler for LibVirtActor { let mut ids = Vec::with_capacity(domains.len()); for d in domains { - ids.push(DomainXMLUuid::parse_from_str(&d.get_uuid_string()?)?); + ids.push(XMLUuid::parse_from_str(&d.get_uuid_string()?)?); } Ok(ids) @@ -84,7 +85,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result")] -pub struct GetDomainXMLReq(pub DomainXMLUuid); +pub struct GetDomainXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; @@ -99,11 +100,11 @@ impl Handler for LibVirtActor { } #[derive(Message)] -#[rtype(result = "anyhow::Result")] +#[rtype(result = "anyhow::Result")] pub struct DefineDomainReq(pub DomainXML); impl Handler for LibVirtActor { - type Result = anyhow::Result; + type Result = anyhow::Result; fn handle(&mut self, mut msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result { // A issue with the disks definition serialization needs them to be serialized aside @@ -121,14 +122,14 @@ impl Handler for LibVirtActor { log::debug!("Define domain:\n{}", xml); let domain = Domain::define_xml(&self.m, &xml)?; - DomainXMLUuid::parse_from_str(&domain.get_uuid_string()?) + XMLUuid::parse_from_str(&domain.get_uuid_string()?) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct DeleteDomainReq { - pub id: DomainXMLUuid, + pub id: XMLUuid, pub keep_files: bool, } @@ -162,7 +163,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result")] -pub struct GetDomainStateReq(pub DomainXMLUuid); +pub struct GetDomainStateReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; @@ -188,7 +189,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct StartDomainReq(pub DomainXMLUuid); +pub struct StartDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -203,7 +204,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct ShutdownDomainReq(pub DomainXMLUuid); +pub struct ShutdownDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -218,7 +219,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct KillDomainReq(pub DomainXMLUuid); +pub struct KillDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -233,7 +234,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct ResetDomainReq(pub DomainXMLUuid); +pub struct ResetDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -248,7 +249,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct SuspendDomainReq(pub DomainXMLUuid); +pub struct SuspendDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -263,7 +264,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct ResumeDomainReq(pub DomainXMLUuid); +pub struct ResumeDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -278,7 +279,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result>")] -pub struct ScreenshotDomainReq(pub DomainXMLUuid); +pub struct ScreenshotDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result>; @@ -311,7 +312,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result")] -pub struct IsDomainAutostart(pub DomainXMLUuid); +pub struct IsDomainAutostart(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; @@ -328,7 +329,7 @@ impl Handler for LibVirtActor { #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] -pub struct SetDomainAutostart(pub DomainXMLUuid, pub bool); +pub struct SetDomainAutostart(pub XMLUuid, pub bool); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; @@ -344,3 +345,35 @@ impl Handler for LibVirtActor { Ok(()) } } + +#[derive(Message)] +#[rtype(result = "anyhow::Result")] +pub struct DefineNetwork(pub NetworkXML); + +impl Handler for LibVirtActor { + type Result = anyhow::Result; + + fn handle(&mut self, mut msg: DefineNetwork, _ctx: &mut Self::Context) -> Self::Result { + log::debug!("Define network: {:?}", msg.0); + + // A issue with the IPs definition serialization needs them to be serialized aside + let mut ips_xml = Vec::with_capacity(msg.0.ips.len()); + for ip in msg.0.ips { + log::debug!("Serialize {ip:?}"); + let ip_xml = serde_xml_rs::to_string(&ip)?; + let start_offset = ip_xml.find("", &format!("{ips_xml}"), 1); + + log::debug!("Define network XML: {network_xml}"); + + let network = Network::define_xml(&self.m, &network_xml)?; + XMLUuid::parse_from_str(&network.get_uuid_string()?) + } +} diff --git a/virtweb_backend/src/actors/vnc_tokens_actor.rs b/virtweb_backend/src/actors/vnc_tokens_actor.rs index 3f8c664..2d043b8 100644 --- a/virtweb_backend/src/actors/vnc_tokens_actor.rs +++ b/virtweb_backend/src/actors/vnc_tokens_actor.rs @@ -1,4 +1,4 @@ -use crate::libvirt_lib_structures::DomainXMLUuid; +use crate::libvirt_lib_structures::XMLUuid; use crate::utils::rand_utils::rand_str; use crate::utils::time_utils::time; use actix::{Actor, Addr, AsyncContext, Context, Handler, Message}; @@ -19,7 +19,7 @@ enum VNCTokenError { #[derive(Debug, Clone)] struct VNCToken { token: String, - vm: DomainXMLUuid, + vm: XMLUuid, expire: u64, } @@ -45,7 +45,7 @@ impl Actor for VNCTokensActor { #[derive(Message)] #[rtype(result = "anyhow::Result")] -pub struct IssueTokenReq(DomainXMLUuid); +pub struct IssueTokenReq(XMLUuid); impl Handler for VNCTokensActor { type Result = anyhow::Result; @@ -63,11 +63,11 @@ impl Handler for VNCTokensActor { } #[derive(Message)] -#[rtype(result = "anyhow::Result")] +#[rtype(result = "anyhow::Result")] pub struct ConsumeTokenReq(String); impl Handler for VNCTokensActor { - type Result = anyhow::Result; + type Result = anyhow::Result; fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Attempt to consume a token {:?}", msg.0); @@ -97,12 +97,12 @@ impl VNCTokensManager { } /// Issue a new VNC access token for a domain - pub async fn issue_token(&self, id: DomainXMLUuid) -> anyhow::Result { + pub async fn issue_token(&self, id: XMLUuid) -> anyhow::Result { self.0.send(IssueTokenReq(id)).await? } /// Consume a VNC access token - pub async fn consume_token(&self, token: String) -> anyhow::Result { + pub async fn consume_token(&self, token: String) -> anyhow::Result { self.0.send(ConsumeTokenReq(token)).await? } } diff --git a/virtweb_backend/src/app_config.rs b/virtweb_backend/src/app_config.rs index 6ae8800..4b434af 100644 --- a/virtweb_backend/src/app_config.rs +++ b/virtweb_backend/src/app_config.rs @@ -1,4 +1,4 @@ -use crate::libvirt_lib_structures::DomainXMLUuid; +use crate::libvirt_lib_structures::XMLUuid; use clap::Parser; use std::path::{Path, PathBuf}; @@ -167,7 +167,7 @@ impl AppConfig { self.storage_path().join("disks") } - pub fn vm_storage_path(&self, id: DomainXMLUuid) -> PathBuf { + pub fn vm_storage_path(&self, id: XMLUuid) -> PathBuf { self.disks_storage_path().join(id.as_string()) } } diff --git a/virtweb_backend/src/controllers/mod.rs b/virtweb_backend/src/controllers/mod.rs index 1e0f577..a2607c3 100644 --- a/virtweb_backend/src/controllers/mod.rs +++ b/virtweb_backend/src/controllers/mod.rs @@ -7,6 +7,7 @@ use std::io::ErrorKind; pub mod auth_controller; pub mod iso_controller; +pub mod network_controller; pub mod server_controller; pub mod vm_controller; diff --git a/virtweb_backend/src/controllers/network_controller.rs b/virtweb_backend/src/controllers/network_controller.rs new file mode 100644 index 0000000..f4e3e56 --- /dev/null +++ b/virtweb_backend/src/controllers/network_controller.rs @@ -0,0 +1,23 @@ +use crate::controllers::{HttpResult, LibVirtReq}; +use crate::libvirt_lib_structures::XMLUuid; +use crate::libvirt_rest_structures::NetworkInfo; +use actix_web::{web, HttpResponse}; + +#[derive(serde::Serialize, serde::Deserialize)] +struct NetworkID { + id: XMLUuid, +} + +/// Create a new network +pub async fn create(client: LibVirtReq, req: web::Json) -> HttpResult { + let network = match req.0.to_virt_network() { + Ok(d) => d, + Err(e) => { + log::error!("Failed to extract network info! {e}"); + return Ok(HttpResponse::BadRequest().body(e.to_string())); + } + }; + let id = client.update_network(network).await?; + + Ok(HttpResponse::Ok().json(NetworkID { id })) +} diff --git a/virtweb_backend/src/controllers/vm_controller.rs b/virtweb_backend/src/controllers/vm_controller.rs index dbf72d8..5c39db8 100644 --- a/virtweb_backend/src/controllers/vm_controller.rs +++ b/virtweb_backend/src/controllers/vm_controller.rs @@ -1,7 +1,7 @@ use crate::actors::vnc_actor::VNCActor; use crate::actors::vnc_tokens_actor::VNCTokensManager; use crate::controllers::{HttpResult, LibVirtReq}; -use crate::libvirt_lib_structures::{DomainState, DomainXMLUuid}; +use crate::libvirt_lib_structures::{DomainState, XMLUuid}; use crate::libvirt_rest_structures::VMInfo; use actix_web::{web, HttpRequest, HttpResponse}; use actix_web_actors::ws; @@ -15,7 +15,7 @@ struct VMInfoAndState { #[derive(serde::Serialize)] struct VMUuid { - uuid: DomainXMLUuid, + uuid: XMLUuid, } /// Create a new VM @@ -52,7 +52,7 @@ pub async fn list_all(client: LibVirtReq) -> HttpResult { #[derive(serde::Deserialize)] pub struct SingleVMUUidReq { - uid: DomainXMLUuid, + uid: XMLUuid, } /// Get the information about a single VM diff --git a/virtweb_backend/src/libvirt_client.rs b/virtweb_backend/src/libvirt_client.rs index af48500..ba9d6b1 100644 --- a/virtweb_backend/src/libvirt_client.rs +++ b/virtweb_backend/src/libvirt_client.rs @@ -1,6 +1,6 @@ use crate::actors::libvirt_actor; use crate::actors::libvirt_actor::LibVirtActor; -use crate::libvirt_lib_structures::{DomainState, DomainXML, DomainXMLUuid}; +use crate::libvirt_lib_structures::{DomainState, DomainXML, NetworkXML, XMLUuid}; use crate::libvirt_rest_structures::HypervisorInfo; use actix::Addr; @@ -24,74 +24,76 @@ impl LibVirtClient { } /// Get the information about a single domain - pub async fn get_single_domain(&self, id: DomainXMLUuid) -> anyhow::Result { + pub async fn get_single_domain(&self, id: XMLUuid) -> anyhow::Result { self.0.send(libvirt_actor::GetDomainXMLReq(id)).await? } /// Update a domain - pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result { + pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result { self.0.send(libvirt_actor::DefineDomainReq(xml)).await? } /// Delete a domain - pub async fn delete_domain(&self, id: DomainXMLUuid, keep_files: bool) -> anyhow::Result<()> { + pub async fn delete_domain(&self, id: XMLUuid, keep_files: bool) -> anyhow::Result<()> { self.0 .send(libvirt_actor::DeleteDomainReq { id, keep_files }) .await? } /// Get the state of a domain - pub async fn get_domain_state(&self, id: DomainXMLUuid) -> anyhow::Result { + pub async fn get_domain_state(&self, id: XMLUuid) -> anyhow::Result { self.0.send(libvirt_actor::GetDomainStateReq(id)).await? } /// Start a domain - pub async fn start_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn start_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::StartDomainReq(id)).await? } /// Shutdown a domain - pub async fn shutdown_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn shutdown_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::ShutdownDomainReq(id)).await? } /// Kill a domain - pub async fn kill_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn kill_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::KillDomainReq(id)).await? } /// Reset a domain - pub async fn reset_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn reset_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::ResetDomainReq(id)).await? } /// Suspend a domain - pub async fn suspend_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn suspend_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::SuspendDomainReq(id)).await? } /// Resume a domain - pub async fn resume_domain(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub async fn resume_domain(&self, id: XMLUuid) -> anyhow::Result<()> { self.0.send(libvirt_actor::ResumeDomainReq(id)).await? } /// Take a screenshot of the domain - pub async fn screenshot_domain(&self, id: DomainXMLUuid) -> anyhow::Result> { + pub async fn screenshot_domain(&self, id: XMLUuid) -> anyhow::Result> { self.0.send(libvirt_actor::ScreenshotDomainReq(id)).await? } /// Get auto-start status of a domain - pub async fn is_domain_autostart(&self, id: DomainXMLUuid) -> anyhow::Result { + pub async fn is_domain_autostart(&self, id: XMLUuid) -> anyhow::Result { self.0.send(libvirt_actor::IsDomainAutostart(id)).await? } - pub async fn set_domain_autostart( - &self, - id: DomainXMLUuid, - autostart: bool, - ) -> anyhow::Result<()> { + /// Update autostart value of a domain + pub async fn set_domain_autostart(&self, id: XMLUuid, autostart: bool) -> anyhow::Result<()> { self.0 .send(libvirt_actor::SetDomainAutostart(id, autostart)) .await? } + + /// Update a network configuration + pub async fn update_network(&self, network: NetworkXML) -> anyhow::Result { + self.0.send(libvirt_actor::DefineNetwork(network)).await? + } } diff --git a/virtweb_backend/src/libvirt_lib_structures.rs b/virtweb_backend/src/libvirt_lib_structures.rs index 93bdb1c..bd0a60f 100644 --- a/virtweb_backend/src/libvirt_lib_structures.rs +++ b/virtweb_backend/src/libvirt_lib_structures.rs @@ -1,7 +1,9 @@ -#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] -pub struct DomainXMLUuid(pub uuid::Uuid); +use std::net::{IpAddr, Ipv4Addr}; -impl DomainXMLUuid { +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] +pub struct XMLUuid(pub uuid::Uuid); + +impl XMLUuid { pub fn parse_from_str(s: &str) -> anyhow::Result { Ok(Self(uuid::Uuid::parse_str(s)?)) } @@ -188,7 +190,7 @@ pub struct DomainXML { pub r#type: String, pub name: String, - pub uuid: Option, + pub uuid: Option, pub genid: Option, pub title: Option, pub description: Option, @@ -218,3 +220,89 @@ pub enum DomainState { PowerManagementSuspended, Other, } + +/// Network forward information +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "forward")] +pub struct NetworkForwardXML { + #[serde(rename(serialize = "@mode"))] + pub mode: String, + #[serde( + default, + rename(serialize = "@mode"), + skip_serializing_if = "Option::is_none" + )] + pub dev: Option, +} + +/// Network DNS information +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "dns")] +pub struct NetworkDNSXML { + pub forwarder: NetworkDNSForwarderXML, +} + +/// Network DNS information +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "fowarder")] +pub struct NetworkDNSForwarderXML { + /// Address of the DNS server + #[serde(rename(serialize = "@addr"))] + pub addr: Ipv4Addr, +} + +/// Network DNS information +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "domain")] +pub struct NetworkDomainXML { + #[serde(rename(serialize = "@name"))] + pub name: String, +} + +/// Network ip information +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "ip")] +pub struct NetworkIPXML { + #[serde(default, rename(serialize = "@family"))] + pub family: String, + #[serde(rename(serialize = "@address"))] + pub address: IpAddr, + #[serde(rename(serialize = "@prefix"))] + pub prefix: u32, + pub dhcp: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "dhcp")] +pub struct NetworkDHCPXML { + pub range: NetworkDHCPRangeXML, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "dhcp")] +pub struct NetworkDHCPRangeXML { + #[serde(rename(serialize = "@start"))] + pub start: IpAddr, + #[serde(rename(serialize = "@end"))] + pub end: IpAddr, +} + +/// Network information, see https://libvirt.org/formatnetwork.html +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename = "network")] +pub struct NetworkXML { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub uuid: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub forward: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dns: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, + pub ips: Vec, +} diff --git a/virtweb_backend/src/libvirt_rest_structures.rs b/virtweb_backend/src/libvirt_rest_structures.rs index 33140f2..c0cae2d 100644 --- a/virtweb_backend/src/libvirt_rest_structures.rs +++ b/virtweb_backend/src/libvirt_rest_structures.rs @@ -2,13 +2,15 @@ use crate::app_config::AppConfig; use crate::constants; use crate::libvirt_lib_structures::{ DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML, - DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, OSLoaderXML, OSTypeXML, - ACPIXML, OSXML, + DomainMemoryXML, DomainXML, FeaturesXML, GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML, + NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML, + NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML, }; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::utils::disks_utils::Disk; use crate::utils::files_utils; use lazy_regex::regex; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::ops::{Div, Mul}; #[derive(thiserror::Error, Debug)] @@ -63,8 +65,8 @@ pub enum VMArchitecture { pub struct VMInfo { /// VM name (alphanumeric characters only) pub name: String, - pub uuid: Option, - pub genid: Option, + pub uuid: Option, + pub genid: Option, pub title: Option, pub description: Option, pub boot_type: BootType, @@ -77,7 +79,8 @@ pub struct VMInfo { pub iso_file: Option, /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest pub disks: Vec, - // TODO : network interface + // TODO : network interfaces + // TODO : number of CPUs } impl VMInfo { @@ -93,7 +96,7 @@ impl VMInfo { } n } else { - DomainXMLUuid::new_random() + XMLUuid::new_random() }; if let Some(n) = &self.genid { @@ -252,7 +255,7 @@ impl VMInfo { Ok(Self { name: domain.name, uuid: domain.uuid, - genid: domain.genid.map(DomainXMLUuid), + genid: domain.genid.map(XMLUuid), title: domain.title, description: domain.description, boot_type: match domain.os.loader { @@ -314,6 +317,114 @@ fn convert_to_mb(unit: &str, value: usize) -> anyhow::Result { Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize) } +#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug)] +pub enum NetworkForwardMode { + NAT, + Isolated, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct IPV4Config { + bridge_address: Ipv4Addr, + prefix: u32, + dhcp_range: Option<[Ipv4Addr; 2]>, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct IPV6Config { + bridge_address: Ipv6Addr, + prefix: u32, + dhcp_range: Option<[Ipv6Addr; 2]>, +} + +/// Network configuration +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct NetworkInfo { + name: String, + uuid: Option, + title: Option, + description: Option, + forward_mode: NetworkForwardMode, + device: Option, + dns_server: Option, + domain: Option, + ip_v4: Option, + ip_v6: Option, +} + +impl NetworkInfo { + pub fn to_virt_network(self) -> anyhow::Result { + if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { + return Err(StructureExtraction("network name is invalid!").into()); + } + + if let Some(n) = &self.title { + if n.contains('\n') { + return Err(StructureExtraction("Network title contain newline char!").into()); + } + } + + if let Some(dev) = &self.device { + if !regex!("^[a-zA-Z0-9]+$").is_match(dev) { + return Err(StructureExtraction("Network device name is invalid!").into()); + } + } + + let mut ips = Vec::with_capacity(2); + + if let Some(ipv4) = self.ip_v4 { + if ipv4.prefix > 32 { + return Err(StructureExtraction("IPv4 prefix is invalid!").into()); + } + + ips.push(NetworkIPXML { + family: "ipv4".to_string(), + address: IpAddr::V4(ipv4.bridge_address), + prefix: ipv4.prefix, + dhcp: ipv4.dhcp_range.map(|[start, end]| NetworkDHCPXML { + range: NetworkDHCPRangeXML { + start: IpAddr::V4(start), + end: IpAddr::V4(end), + }, + }), + }) + } + + if let Some(ipv6) = self.ip_v6 { + ips.push(NetworkIPXML { + family: "ipv6".to_string(), + address: IpAddr::V6(ipv6.bridge_address), + prefix: ipv6.prefix, + dhcp: ipv6.dhcp_range.map(|[start, end]| NetworkDHCPXML { + range: NetworkDHCPRangeXML { + start: IpAddr::V6(start), + end: IpAddr::V6(end), + }, + }), + }) + } + + Ok(NetworkXML { + name: self.name, + uuid: self.uuid, + title: self.title, + description: self.description, + forward: match self.forward_mode { + NetworkForwardMode::NAT => Some(NetworkForwardXML { + mode: "nat".to_string(), + dev: self.device, + }), + NetworkForwardMode::Isolated => None, + }, + dns: self.dns_server.map(|addr| NetworkDNSXML { + forwarder: NetworkDNSForwarderXML { addr }, + }), + domain: self.domain.map(|name| NetworkDomainXML { name }), + ips, + }) + } +} + #[cfg(test)] mod test { use crate::libvirt_rest_structures::convert_to_mb; diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 53e0c28..0b86c42 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -22,7 +22,7 @@ use virtweb_backend::constants::{ MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME, }; use virtweb_backend::controllers::{ - auth_controller, iso_controller, server_controller, vm_controller, + auth_controller, iso_controller, network_controller, server_controller, vm_controller, }; use virtweb_backend::libvirt_client::LibVirtClient; use virtweb_backend::middlewares::auth_middleware::AuthChecker; @@ -176,6 +176,11 @@ async fn main() -> std::io::Result<()> { web::get().to(vm_controller::vnc_token), ) .route("/api/vnc", web::get().to(vm_controller::vnc)) + // Network controller + .route( + "/api/network/create", + web::post().to(network_controller::create), + ) }) .bind(&AppConfig::get().listen_address)? .run() diff --git a/virtweb_backend/src/utils/disks_utils.rs b/virtweb_backend/src/utils/disks_utils.rs index b450e96..00054d7 100644 --- a/virtweb_backend/src/utils/disks_utils.rs +++ b/virtweb_backend/src/utils/disks_utils.rs @@ -1,6 +1,6 @@ use crate::app_config::AppConfig; use crate::constants; -use crate::libvirt_lib_structures::DomainXMLUuid; +use crate::libvirt_lib_structures::XMLUuid; use crate::utils::files_utils; use lazy_regex::regex; use std::os::linux::fs::MetadataExt; @@ -78,13 +78,13 @@ impl Disk { } /// Get disk path - pub fn disk_path(&self, id: DomainXMLUuid) -> PathBuf { + pub fn disk_path(&self, id: XMLUuid) -> PathBuf { let domain_dir = AppConfig::get().vm_storage_path(id); domain_dir.join(&self.name) } /// Apply disk configuration - pub fn apply_config(&self, id: DomainXMLUuid) -> anyhow::Result<()> { + pub fn apply_config(&self, id: XMLUuid) -> anyhow::Result<()> { self.check_config()?; let file = self.disk_path(id);