Can create networks
This commit is contained in:
		| @@ -38,4 +38,4 @@ image = "0.24.7" | ||||
| rand = "0.8.5" | ||||
| bytes = "1.5.0" | ||||
| tokio = "1.32.0" | ||||
| futures = "0.3.28" | ||||
| futures = "0.3.28" | ||||
|   | ||||
| @@ -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<GetHypervisorInfo> for LibVirtActor { | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<Vec<DomainXMLUuid>>")] | ||||
| #[rtype(result = "anyhow::Result<Vec<XMLUuid>>")] | ||||
| pub struct GetDomainsListReq; | ||||
|  | ||||
| impl Handler<GetDomainsListReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<Vec<DomainXMLUuid>>; | ||||
|     type Result = anyhow::Result<Vec<XMLUuid>>; | ||||
|  | ||||
|     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<GetDomainsListReq> 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<GetDomainsListReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<DomainXML>")] | ||||
| pub struct GetDomainXMLReq(pub DomainXMLUuid); | ||||
| pub struct GetDomainXMLReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<GetDomainXMLReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<DomainXML>; | ||||
| @@ -99,11 +100,11 @@ impl Handler<GetDomainXMLReq> for LibVirtActor { | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<DomainXMLUuid>")] | ||||
| #[rtype(result = "anyhow::Result<XMLUuid>")] | ||||
| pub struct DefineDomainReq(pub DomainXML); | ||||
|  | ||||
| impl Handler<DefineDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<DomainXMLUuid>; | ||||
|     type Result = anyhow::Result<XMLUuid>; | ||||
|  | ||||
|     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<DefineDomainReq> 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<DeleteDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<DomainState>")] | ||||
| pub struct GetDomainStateReq(pub DomainXMLUuid); | ||||
| pub struct GetDomainStateReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<GetDomainStateReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<DomainState>; | ||||
| @@ -188,7 +189,7 @@ impl Handler<GetDomainStateReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct StartDomainReq(pub DomainXMLUuid); | ||||
| pub struct StartDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<StartDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -203,7 +204,7 @@ impl Handler<StartDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct ShutdownDomainReq(pub DomainXMLUuid); | ||||
| pub struct ShutdownDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<ShutdownDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -218,7 +219,7 @@ impl Handler<ShutdownDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct KillDomainReq(pub DomainXMLUuid); | ||||
| pub struct KillDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<KillDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -233,7 +234,7 @@ impl Handler<KillDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct ResetDomainReq(pub DomainXMLUuid); | ||||
| pub struct ResetDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<ResetDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -248,7 +249,7 @@ impl Handler<ResetDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct SuspendDomainReq(pub DomainXMLUuid); | ||||
| pub struct SuspendDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<SuspendDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -263,7 +264,7 @@ impl Handler<SuspendDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct ResumeDomainReq(pub DomainXMLUuid); | ||||
| pub struct ResumeDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<ResumeDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -278,7 +279,7 @@ impl Handler<ResumeDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<Vec<u8>>")] | ||||
| pub struct ScreenshotDomainReq(pub DomainXMLUuid); | ||||
| pub struct ScreenshotDomainReq(pub XMLUuid); | ||||
|  | ||||
| impl Handler<ScreenshotDomainReq> for LibVirtActor { | ||||
|     type Result = anyhow::Result<Vec<u8>>; | ||||
| @@ -311,7 +312,7 @@ impl Handler<ScreenshotDomainReq> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<bool>")] | ||||
| pub struct IsDomainAutostart(pub DomainXMLUuid); | ||||
| pub struct IsDomainAutostart(pub XMLUuid); | ||||
|  | ||||
| impl Handler<IsDomainAutostart> for LibVirtActor { | ||||
|     type Result = anyhow::Result<bool>; | ||||
| @@ -328,7 +329,7 @@ impl Handler<IsDomainAutostart> for LibVirtActor { | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct SetDomainAutostart(pub DomainXMLUuid, pub bool); | ||||
| pub struct SetDomainAutostart(pub XMLUuid, pub bool); | ||||
|  | ||||
| impl Handler<SetDomainAutostart> for LibVirtActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
| @@ -344,3 +345,35 @@ impl Handler<SetDomainAutostart> for LibVirtActor { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<XMLUuid>")] | ||||
| pub struct DefineNetwork(pub NetworkXML); | ||||
|  | ||||
| impl Handler<DefineNetwork> for LibVirtActor { | ||||
|     type Result = anyhow::Result<XMLUuid>; | ||||
|  | ||||
|     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("<ip").unwrap(); | ||||
|             ips_xml.push(ip_xml[start_offset..].to_string()); | ||||
|         } | ||||
|         msg.0.ips = vec![]; | ||||
|  | ||||
|         let mut network_xml = serde_xml_rs::to_string(&msg.0)?; | ||||
|  | ||||
|         let ips_xml = ips_xml.join("\n"); | ||||
|         network_xml = network_xml.replacen("</network>", &format!("{ips_xml}</network>"), 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()?) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<String>")] | ||||
| pub struct IssueTokenReq(DomainXMLUuid); | ||||
| pub struct IssueTokenReq(XMLUuid); | ||||
|  | ||||
| impl Handler<IssueTokenReq> for VNCTokensActor { | ||||
|     type Result = anyhow::Result<String>; | ||||
| @@ -63,11 +63,11 @@ impl Handler<IssueTokenReq> for VNCTokensActor { | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<DomainXMLUuid>")] | ||||
| #[rtype(result = "anyhow::Result<XMLUuid>")] | ||||
| pub struct ConsumeTokenReq(String); | ||||
|  | ||||
| impl Handler<ConsumeTokenReq> for VNCTokensActor { | ||||
|     type Result = anyhow::Result<DomainXMLUuid>; | ||||
|     type Result = anyhow::Result<XMLUuid>; | ||||
|  | ||||
|     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<String> { | ||||
|     pub async fn issue_token(&self, id: XMLUuid) -> anyhow::Result<String> { | ||||
|         self.0.send(IssueTokenReq(id)).await? | ||||
|     } | ||||
|  | ||||
|     /// Consume a VNC access token | ||||
|     pub async fn consume_token(&self, token: String) -> anyhow::Result<DomainXMLUuid> { | ||||
|     pub async fn consume_token(&self, token: String) -> anyhow::Result<XMLUuid> { | ||||
|         self.0.send(ConsumeTokenReq(token)).await? | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								virtweb_backend/src/controllers/network_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								virtweb_backend/src/controllers/network_controller.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<NetworkInfo>) -> 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 })) | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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<DomainXML> { | ||||
|     pub async fn get_single_domain(&self, id: XMLUuid) -> 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> { | ||||
|     pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<XMLUuid> { | ||||
|         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<DomainState> { | ||||
|     pub async fn get_domain_state(&self, id: XMLUuid) -> anyhow::Result<DomainState> { | ||||
|         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<Vec<u8>> { | ||||
|     pub async fn screenshot_domain(&self, id: XMLUuid) -> anyhow::Result<Vec<u8>> { | ||||
|         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<bool> { | ||||
|     pub async fn is_domain_autostart(&self, id: XMLUuid) -> anyhow::Result<bool> { | ||||
|         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<XMLUuid> { | ||||
|         self.0.send(libvirt_actor::DefineNetwork(network)).await? | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<Self> { | ||||
|         Ok(Self(uuid::Uuid::parse_str(s)?)) | ||||
|     } | ||||
| @@ -188,7 +190,7 @@ pub struct DomainXML { | ||||
|     pub r#type: String, | ||||
|  | ||||
|     pub name: String, | ||||
|     pub uuid: Option<DomainXMLUuid>, | ||||
|     pub uuid: Option<XMLUuid>, | ||||
|     pub genid: Option<uuid::Uuid>, | ||||
|     pub title: Option<String>, | ||||
|     pub description: Option<String>, | ||||
| @@ -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<String>, | ||||
| } | ||||
|  | ||||
| /// 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<NetworkDHCPXML>, | ||||
| } | ||||
|  | ||||
| #[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<XMLUuid>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub title: Option<String>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub description: Option<String>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub forward: Option<NetworkForwardXML>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub dns: Option<NetworkDNSXML>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub domain: Option<NetworkDomainXML>, | ||||
|     pub ips: Vec<NetworkIPXML>, | ||||
| } | ||||
|   | ||||
| @@ -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<DomainXMLUuid>, | ||||
|     pub genid: Option<DomainXMLUuid>, | ||||
|     pub uuid: Option<XMLUuid>, | ||||
|     pub genid: Option<XMLUuid>, | ||||
|     pub title: Option<String>, | ||||
|     pub description: Option<String>, | ||||
|     pub boot_type: BootType, | ||||
| @@ -77,7 +79,8 @@ pub struct VMInfo { | ||||
|     pub iso_file: Option<String>, | ||||
|     /// 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<Disk>, | ||||
|     // 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<usize> { | ||||
|     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<XMLUuid>, | ||||
|     title: Option<String>, | ||||
|     description: Option<String>, | ||||
|     forward_mode: NetworkForwardMode, | ||||
|     device: Option<String>, | ||||
|     dns_server: Option<Ipv4Addr>, | ||||
|     domain: Option<String>, | ||||
|     ip_v4: Option<IPV4Config>, | ||||
|     ip_v6: Option<IPV6Config>, | ||||
| } | ||||
|  | ||||
| impl NetworkInfo { | ||||
|     pub fn to_virt_network(self) -> anyhow::Result<NetworkXML> { | ||||
|         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; | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user