Can create networks

This commit is contained in:
Pierre HUBERT 2023-10-31 09:26:42 +01:00
parent 335aec788e
commit f2237d4f2f
12 changed files with 328 additions and 65 deletions

View File

@ -38,4 +38,4 @@ image = "0.24.7"
rand = "0.8.5" rand = "0.8.5"
bytes = "1.5.0" bytes = "1.5.0"
tokio = "1.32.0" tokio = "1.32.0"
futures = "0.3.28" futures = "0.3.28"

View File

@ -1,11 +1,12 @@
use crate::app_config::AppConfig; 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 crate::libvirt_rest_structures::*;
use actix::{Actor, Context, Handler, Message}; use actix::{Actor, Context, Handler, Message};
use image::ImageOutputFormat; use image::ImageOutputFormat;
use std::io::Cursor; use std::io::Cursor;
use virt::connect::Connect; use virt::connect::Connect;
use virt::domain::Domain; use virt::domain::Domain;
use virt::network::Network;
use virt::stream::Stream; use virt::stream::Stream;
use virt::sys; use virt::sys;
use virt::sys::VIR_DOMAIN_XML_SECURE; use virt::sys::VIR_DOMAIN_XML_SECURE;
@ -63,11 +64,11 @@ impl Handler<GetHypervisorInfo> for LibVirtActor {
} }
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<Vec<DomainXMLUuid>>")] #[rtype(result = "anyhow::Result<Vec<XMLUuid>>")]
pub struct GetDomainsListReq; pub struct GetDomainsListReq;
impl Handler<GetDomainsListReq> for LibVirtActor { 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 { fn handle(&mut self, _msg: GetDomainsListReq, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Get full list of domains"); log::debug!("Get full list of domains");
@ -75,7 +76,7 @@ impl Handler<GetDomainsListReq> for LibVirtActor {
let mut ids = Vec::with_capacity(domains.len()); let mut ids = Vec::with_capacity(domains.len());
for d in domains { 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) Ok(ids)
@ -84,7 +85,7 @@ impl Handler<GetDomainsListReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXML>")] #[rtype(result = "anyhow::Result<DomainXML>")]
pub struct GetDomainXMLReq(pub DomainXMLUuid); pub struct GetDomainXMLReq(pub XMLUuid);
impl Handler<GetDomainXMLReq> for LibVirtActor { impl Handler<GetDomainXMLReq> for LibVirtActor {
type Result = anyhow::Result<DomainXML>; type Result = anyhow::Result<DomainXML>;
@ -99,11 +100,11 @@ impl Handler<GetDomainXMLReq> for LibVirtActor {
} }
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXMLUuid>")] #[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineDomainReq(pub DomainXML); pub struct DefineDomainReq(pub DomainXML);
impl Handler<DefineDomainReq> for LibVirtActor { 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 { 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 // 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); log::debug!("Define domain:\n{}", xml);
let domain = Domain::define_xml(&self.m, &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)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct DeleteDomainReq { pub struct DeleteDomainReq {
pub id: DomainXMLUuid, pub id: XMLUuid,
pub keep_files: bool, pub keep_files: bool,
} }
@ -162,7 +163,7 @@ impl Handler<DeleteDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<DomainState>")] #[rtype(result = "anyhow::Result<DomainState>")]
pub struct GetDomainStateReq(pub DomainXMLUuid); pub struct GetDomainStateReq(pub XMLUuid);
impl Handler<GetDomainStateReq> for LibVirtActor { impl Handler<GetDomainStateReq> for LibVirtActor {
type Result = anyhow::Result<DomainState>; type Result = anyhow::Result<DomainState>;
@ -188,7 +189,7 @@ impl Handler<GetDomainStateReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct StartDomainReq(pub DomainXMLUuid); pub struct StartDomainReq(pub XMLUuid);
impl Handler<StartDomainReq> for LibVirtActor { impl Handler<StartDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -203,7 +204,7 @@ impl Handler<StartDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct ShutdownDomainReq(pub DomainXMLUuid); pub struct ShutdownDomainReq(pub XMLUuid);
impl Handler<ShutdownDomainReq> for LibVirtActor { impl Handler<ShutdownDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -218,7 +219,7 @@ impl Handler<ShutdownDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct KillDomainReq(pub DomainXMLUuid); pub struct KillDomainReq(pub XMLUuid);
impl Handler<KillDomainReq> for LibVirtActor { impl Handler<KillDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -233,7 +234,7 @@ impl Handler<KillDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct ResetDomainReq(pub DomainXMLUuid); pub struct ResetDomainReq(pub XMLUuid);
impl Handler<ResetDomainReq> for LibVirtActor { impl Handler<ResetDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -248,7 +249,7 @@ impl Handler<ResetDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct SuspendDomainReq(pub DomainXMLUuid); pub struct SuspendDomainReq(pub XMLUuid);
impl Handler<SuspendDomainReq> for LibVirtActor { impl Handler<SuspendDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -263,7 +264,7 @@ impl Handler<SuspendDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct ResumeDomainReq(pub DomainXMLUuid); pub struct ResumeDomainReq(pub XMLUuid);
impl Handler<ResumeDomainReq> for LibVirtActor { impl Handler<ResumeDomainReq> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -278,7 +279,7 @@ impl Handler<ResumeDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<Vec<u8>>")] #[rtype(result = "anyhow::Result<Vec<u8>>")]
pub struct ScreenshotDomainReq(pub DomainXMLUuid); pub struct ScreenshotDomainReq(pub XMLUuid);
impl Handler<ScreenshotDomainReq> for LibVirtActor { impl Handler<ScreenshotDomainReq> for LibVirtActor {
type Result = anyhow::Result<Vec<u8>>; type Result = anyhow::Result<Vec<u8>>;
@ -311,7 +312,7 @@ impl Handler<ScreenshotDomainReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<bool>")] #[rtype(result = "anyhow::Result<bool>")]
pub struct IsDomainAutostart(pub DomainXMLUuid); pub struct IsDomainAutostart(pub XMLUuid);
impl Handler<IsDomainAutostart> for LibVirtActor { impl Handler<IsDomainAutostart> for LibVirtActor {
type Result = anyhow::Result<bool>; type Result = anyhow::Result<bool>;
@ -328,7 +329,7 @@ impl Handler<IsDomainAutostart> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<()>")] #[rtype(result = "anyhow::Result<()>")]
pub struct SetDomainAutostart(pub DomainXMLUuid, pub bool); pub struct SetDomainAutostart(pub XMLUuid, pub bool);
impl Handler<SetDomainAutostart> for LibVirtActor { impl Handler<SetDomainAutostart> for LibVirtActor {
type Result = anyhow::Result<()>; type Result = anyhow::Result<()>;
@ -344,3 +345,35 @@ impl Handler<SetDomainAutostart> for LibVirtActor {
Ok(()) 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()?)
}
}

View File

@ -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::rand_utils::rand_str;
use crate::utils::time_utils::time; use crate::utils::time_utils::time;
use actix::{Actor, Addr, AsyncContext, Context, Handler, Message}; use actix::{Actor, Addr, AsyncContext, Context, Handler, Message};
@ -19,7 +19,7 @@ enum VNCTokenError {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct VNCToken { struct VNCToken {
token: String, token: String,
vm: DomainXMLUuid, vm: XMLUuid,
expire: u64, expire: u64,
} }
@ -45,7 +45,7 @@ impl Actor for VNCTokensActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<String>")] #[rtype(result = "anyhow::Result<String>")]
pub struct IssueTokenReq(DomainXMLUuid); pub struct IssueTokenReq(XMLUuid);
impl Handler<IssueTokenReq> for VNCTokensActor { impl Handler<IssueTokenReq> for VNCTokensActor {
type Result = anyhow::Result<String>; type Result = anyhow::Result<String>;
@ -63,11 +63,11 @@ impl Handler<IssueTokenReq> for VNCTokensActor {
} }
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXMLUuid>")] #[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct ConsumeTokenReq(String); pub struct ConsumeTokenReq(String);
impl Handler<ConsumeTokenReq> for VNCTokensActor { 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 { fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Attempt to consume a token {:?}", msg.0); log::debug!("Attempt to consume a token {:?}", msg.0);
@ -97,12 +97,12 @@ impl VNCTokensManager {
} }
/// Issue a new VNC access token for a domain /// 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? self.0.send(IssueTokenReq(id)).await?
} }
/// Consume a VNC access token /// 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? self.0.send(ConsumeTokenReq(token)).await?
} }
} }

View File

@ -1,4 +1,4 @@
use crate::libvirt_lib_structures::DomainXMLUuid; use crate::libvirt_lib_structures::XMLUuid;
use clap::Parser; use clap::Parser;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -167,7 +167,7 @@ impl AppConfig {
self.storage_path().join("disks") 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()) self.disks_storage_path().join(id.as_string())
} }
} }

View File

@ -7,6 +7,7 @@ use std::io::ErrorKind;
pub mod auth_controller; pub mod auth_controller;
pub mod iso_controller; pub mod iso_controller;
pub mod network_controller;
pub mod server_controller; pub mod server_controller;
pub mod vm_controller; pub mod vm_controller;

View 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 }))
}

View File

@ -1,7 +1,7 @@
use crate::actors::vnc_actor::VNCActor; use crate::actors::vnc_actor::VNCActor;
use crate::actors::vnc_tokens_actor::VNCTokensManager; use crate::actors::vnc_tokens_actor::VNCTokensManager;
use crate::controllers::{HttpResult, LibVirtReq}; 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 crate::libvirt_rest_structures::VMInfo;
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use actix_web_actors::ws; use actix_web_actors::ws;
@ -15,7 +15,7 @@ struct VMInfoAndState {
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
struct VMUuid { struct VMUuid {
uuid: DomainXMLUuid, uuid: XMLUuid,
} }
/// Create a new VM /// Create a new VM
@ -52,7 +52,7 @@ pub async fn list_all(client: LibVirtReq) -> HttpResult {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct SingleVMUUidReq { pub struct SingleVMUUidReq {
uid: DomainXMLUuid, uid: XMLUuid,
} }
/// Get the information about a single VM /// Get the information about a single VM

View File

@ -1,6 +1,6 @@
use crate::actors::libvirt_actor; use crate::actors::libvirt_actor;
use crate::actors::libvirt_actor::LibVirtActor; 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 crate::libvirt_rest_structures::HypervisorInfo;
use actix::Addr; use actix::Addr;
@ -24,74 +24,76 @@ impl LibVirtClient {
} }
/// Get the information about a single domain /// 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? self.0.send(libvirt_actor::GetDomainXMLReq(id)).await?
} }
/// Update a domain /// 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? self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
} }
/// Delete a domain /// 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 self.0
.send(libvirt_actor::DeleteDomainReq { id, keep_files }) .send(libvirt_actor::DeleteDomainReq { id, keep_files })
.await? .await?
} }
/// Get the state of a domain /// 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? self.0.send(libvirt_actor::GetDomainStateReq(id)).await?
} }
/// Start a domain /// 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? self.0.send(libvirt_actor::StartDomainReq(id)).await?
} }
/// Shutdown a domain /// 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? self.0.send(libvirt_actor::ShutdownDomainReq(id)).await?
} }
/// Kill a domain /// 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? self.0.send(libvirt_actor::KillDomainReq(id)).await?
} }
/// Reset a domain /// 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? self.0.send(libvirt_actor::ResetDomainReq(id)).await?
} }
/// Suspend a domain /// 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? self.0.send(libvirt_actor::SuspendDomainReq(id)).await?
} }
/// Resume a domain /// 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? self.0.send(libvirt_actor::ResumeDomainReq(id)).await?
} }
/// Take a screenshot of the domain /// 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? self.0.send(libvirt_actor::ScreenshotDomainReq(id)).await?
} }
/// Get auto-start status of a domain /// 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? self.0.send(libvirt_actor::IsDomainAutostart(id)).await?
} }
pub async fn set_domain_autostart( /// Update autostart value of a domain
&self, pub async fn set_domain_autostart(&self, id: XMLUuid, autostart: bool) -> anyhow::Result<()> {
id: DomainXMLUuid,
autostart: bool,
) -> anyhow::Result<()> {
self.0 self.0
.send(libvirt_actor::SetDomainAutostart(id, autostart)) .send(libvirt_actor::SetDomainAutostart(id, autostart))
.await? .await?
} }
/// Update a network configuration
pub async fn update_network(&self, network: NetworkXML) -> anyhow::Result<XMLUuid> {
self.0.send(libvirt_actor::DefineNetwork(network)).await?
}
} }

View File

@ -1,7 +1,9 @@
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] use std::net::{IpAddr, Ipv4Addr};
pub struct DomainXMLUuid(pub uuid::Uuid);
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> { pub fn parse_from_str(s: &str) -> anyhow::Result<Self> {
Ok(Self(uuid::Uuid::parse_str(s)?)) Ok(Self(uuid::Uuid::parse_str(s)?))
} }
@ -188,7 +190,7 @@ pub struct DomainXML {
pub r#type: String, pub r#type: String,
pub name: String, pub name: String,
pub uuid: Option<DomainXMLUuid>, pub uuid: Option<XMLUuid>,
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>,
@ -218,3 +220,89 @@ pub enum DomainState {
PowerManagementSuspended, PowerManagementSuspended,
Other, 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>,
}

View File

@ -2,13 +2,15 @@ use crate::app_config::AppConfig;
use crate::constants; use crate::constants;
use crate::libvirt_lib_structures::{ use crate::libvirt_lib_structures::{
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML, DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, OSLoaderXML, OSTypeXML, DomainMemoryXML, DomainXML, FeaturesXML, GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML,
ACPIXML, OSXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML,
NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML,
}; };
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
use crate::utils::disks_utils::Disk; use crate::utils::disks_utils::Disk;
use crate::utils::files_utils; use crate::utils::files_utils;
use lazy_regex::regex; use lazy_regex::regex;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ops::{Div, Mul}; use std::ops::{Div, Mul};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -63,8 +65,8 @@ pub enum VMArchitecture {
pub struct VMInfo { pub struct VMInfo {
/// VM name (alphanumeric characters only) /// VM name (alphanumeric characters only)
pub name: String, pub name: String,
pub uuid: Option<DomainXMLUuid>, pub uuid: Option<XMLUuid>,
pub genid: Option<DomainXMLUuid>, pub genid: Option<XMLUuid>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub boot_type: BootType, pub boot_type: BootType,
@ -77,7 +79,8 @@ pub struct VMInfo {
pub iso_file: Option<String>, 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 /// 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>, pub disks: Vec<Disk>,
// TODO : network interface // TODO : network interfaces
// TODO : number of CPUs
} }
impl VMInfo { impl VMInfo {
@ -93,7 +96,7 @@ impl VMInfo {
} }
n n
} else { } else {
DomainXMLUuid::new_random() XMLUuid::new_random()
}; };
if let Some(n) = &self.genid { if let Some(n) = &self.genid {
@ -252,7 +255,7 @@ impl VMInfo {
Ok(Self { Ok(Self {
name: domain.name, name: domain.name,
uuid: domain.uuid, uuid: domain.uuid,
genid: domain.genid.map(DomainXMLUuid), genid: domain.genid.map(XMLUuid),
title: domain.title, title: domain.title,
description: domain.description, description: domain.description,
boot_type: match domain.os.loader { 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) 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)] #[cfg(test)]
mod test { mod test {
use crate::libvirt_rest_structures::convert_to_mb; use crate::libvirt_rest_structures::convert_to_mb;

View File

@ -22,7 +22,7 @@ use virtweb_backend::constants::{
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
}; };
use virtweb_backend::controllers::{ 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::libvirt_client::LibVirtClient;
use virtweb_backend::middlewares::auth_middleware::AuthChecker; use virtweb_backend::middlewares::auth_middleware::AuthChecker;
@ -176,6 +176,11 @@ async fn main() -> std::io::Result<()> {
web::get().to(vm_controller::vnc_token), web::get().to(vm_controller::vnc_token),
) )
.route("/api/vnc", web::get().to(vm_controller::vnc)) .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)? .bind(&AppConfig::get().listen_address)?
.run() .run()

View File

@ -1,6 +1,6 @@
use crate::app_config::AppConfig; use crate::app_config::AppConfig;
use crate::constants; use crate::constants;
use crate::libvirt_lib_structures::DomainXMLUuid; use crate::libvirt_lib_structures::XMLUuid;
use crate::utils::files_utils; use crate::utils::files_utils;
use lazy_regex::regex; use lazy_regex::regex;
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
@ -78,13 +78,13 @@ impl Disk {
} }
/// Get disk path /// 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); let domain_dir = AppConfig::get().vm_storage_path(id);
domain_dir.join(&self.name) domain_dir.join(&self.name)
} }
/// Apply disk configuration /// Apply disk configuration
pub fn apply_config(&self, id: DomainXMLUuid) -> anyhow::Result<()> { pub fn apply_config(&self, id: XMLUuid) -> anyhow::Result<()> {
self.check_config()?; self.check_config()?;
let file = self.disk_path(id); let file = self.disk_path(id);