Created first domain

This commit is contained in:
Pierre HUBERT 2023-10-04 11:18:50 +02:00
parent 7073b9b7f1
commit 2bc64442f4
12 changed files with 314 additions and 28 deletions

View File

@ -1280,6 +1280,29 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "lazy-regex"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa"
dependencies = [
"lazy-regex-proc_macros",
"once_cell",
"regex",
]
[[package]]
name = "lazy-regex-proc_macros"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.29",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1814,6 +1837,18 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-xml-rs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
dependencies = [
"log",
"serde",
"thiserror",
"xml-rs",
]
[[package]]
name = "serde_derive"
version = "1.0.188"
@ -1995,6 +2030,26 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "time"
version = "0.3.28"
@ -2181,6 +2236,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom",
"serde",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -2235,15 +2300,19 @@ dependencies = [
"clap",
"env_logger",
"futures-util",
"lazy-regex",
"lazy_static",
"light-openid",
"log",
"reqwest",
"serde",
"serde-xml-rs",
"serde_json",
"sysinfo",
"tempfile",
"thiserror",
"url",
"uuid",
"virt",
]
@ -2458,6 +2527,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "xml-rs"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab77e97b50aee93da431f2cee7cd0f43b4d1da3c408042f2d7d164187774f0a"
[[package]]
name = "zstd"
version = "0.12.4"

View File

@ -20,6 +20,7 @@ actix-cors = "0.6.4"
actix-files = "0.6.2"
serde = { version = "1.0.175", features = ["derive"] }
serde_json = "1.0.105"
serde-xml-rs = "0.6.0"
futures-util = "0.3.28"
anyhow = "1.0.75"
actix-multipart = "0.6.1"
@ -27,4 +28,7 @@ tempfile = "3.8.0"
reqwest = { version = "0.11.18", features = ["stream"] }
url = "2.4.0"
virt = "0.3.0"
sysinfo = { version = "0.29.10", features = ["serde"] }
sysinfo = { version = "0.29.10", features = ["serde"] }
uuid = { version = "1.4.1", features = ["v4", "serde"] }
lazy-regex = "3.0.2"
thiserror = "1.0.47"

View File

@ -1,6 +1,9 @@
use crate::app_config::AppConfig;
use crate::libvirt_lib_structures::{DomainXML, DomainXMLUuid};
use crate::libvirt_rest_structures::*;
use actix::{Actor, Context, Handler, Message};
use virt::connect::Connect;
use virt::domain::Domain;
pub struct LibVirtActor {
m: Connect,
@ -28,30 +31,6 @@ impl Actor for LibVirtActor {
#[rtype(result = "anyhow::Result<HypervisorInfo>")]
pub struct GetHypervisorInfo;
#[derive(serde::Serialize)]
pub struct HypervisorInfo {
pub r#type: String,
pub hyp_version: u32,
pub lib_version: u32,
pub capabilities: String,
pub free_memory: u64,
pub hostname: String,
pub node: HypervisorNodeInfo,
}
#[derive(serde::Serialize)]
pub struct HypervisorNodeInfo {
pub cpu_model: String,
/// Memory size in kilobytes
pub memory_size: u64,
pub number_of_active_cpus: u32,
pub cpu_frequency_mhz: u32,
pub number_of_numa_cell: u32,
pub number_of_cpu_socket_per_node: u32,
pub number_of_core_per_sockets: u32,
pub number_of_threads_per_core: u32,
}
impl Handler<GetHypervisorInfo> for LibVirtActor {
type Result = anyhow::Result<HypervisorInfo>;
@ -77,3 +56,18 @@ impl Handler<GetHypervisorInfo> for LibVirtActor {
})
}
}
#[derive(Message)]
#[rtype(result = "anyhow::Result<DomainXMLUuid>")]
pub struct DefineDomainReq(pub DomainXML);
impl Handler<DefineDomainReq> for LibVirtActor {
type Result = anyhow::Result<DomainXMLUuid>;
fn handle(&mut self, msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
let xml = serde_xml_rs::to_string(&msg.0)?;
log::debug!("Define domain:\n{}", xml);
let domain = Domain::define_xml(&self.m, &xml)?;
DomainXMLUuid::parse_from_str(&domain.get_uuid_string()?)
}
}

View File

@ -25,3 +25,9 @@ pub const ALLOWED_ISO_MIME_TYPES: [&str; 3] = [
/// ISO max size
pub const ISO_MAX_SIZE: usize = 10 * 1000 * 1000 * 1000;
/// Min VM memory size (MB)
pub const MIN_VM_MEMORY: usize = 100;
/// Max VM memory size (MB)
pub const MAX_VM_MEMORY: usize = 64000;

View File

@ -8,6 +8,7 @@ use std::io::ErrorKind;
pub mod auth_controller;
pub mod iso_controller;
pub mod server_controller;
pub mod vm_controller;
/// Custom error to ease controller writing
#[derive(Debug)]

View File

@ -1,8 +1,8 @@
use crate::actors::libvirt_actor::HypervisorInfo;
use crate::app_config::AppConfig;
use crate::constants;
use crate::controllers::{HttpResult, LibVirtReq};
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
use crate::libvirt_rest_structures::HypervisorInfo;
use actix_web::{HttpResponse, Responder};
use sysinfo::{System, SystemExt};

View File

@ -0,0 +1,17 @@
use crate::controllers::{HttpResult, LibVirtReq};
use crate::libvirt_rest_structures::VMInfo;
use actix_web::{web, HttpResponse};
/// Create a new VM
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
let domain = match req.0.to_domain() {
Ok(d) => d,
Err(e) => {
log::error!("Failed to extract domain info! {e}");
return Ok(HttpResponse::BadRequest().body(e.to_string()));
}
};
let id = client.update_domain(domain).await?;
Ok(HttpResponse::Ok().json(id))
}

View File

@ -4,5 +4,7 @@ pub mod constants;
pub mod controllers;
pub mod extractors;
pub mod libvirt_client;
pub mod libvirt_lib_structures;
pub mod libvirt_rest_structures;
pub mod middlewares;
pub mod utils;

View File

@ -1,5 +1,7 @@
use crate::actors::libvirt_actor;
use crate::actors::libvirt_actor::{HypervisorInfo, LibVirtActor};
use crate::actors::libvirt_actor::LibVirtActor;
use crate::libvirt_lib_structures::{DomainXML, DomainXMLUuid};
use crate::libvirt_rest_structures::HypervisorInfo;
use actix::Addr;
#[derive(Clone)]
@ -10,4 +12,9 @@ impl LibVirtClient {
pub async fn get_info(&self) -> anyhow::Result<HypervisorInfo> {
self.0.send(libvirt_actor::GetHypervisorInfo).await?
}
/// Update a domain
pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<DomainXMLUuid> {
self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
}
}

View File

@ -0,0 +1,58 @@
#[derive(serde::Serialize, serde::Deserialize)]
pub struct DomainXMLUuid(pub uuid::Uuid);
impl DomainXMLUuid {
pub fn parse_from_str(s: &str) -> anyhow::Result<Self> {
Ok(Self(uuid::Uuid::parse_str(s)?))
}
pub fn is_valid(&self) -> bool {
self.0.get_version_num() == 4
}
}
/// OS information
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSXML {
pub r#type: OSTypeXML,
}
/// OS Type information
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "os")]
pub struct OSTypeXML {
#[serde(rename(serialize = "@arch"))]
pub arch: String,
#[serde(rename = "$value")]
pub body: String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "memory")]
pub struct DomainMemoryXML {
#[serde(rename(serialize = "@unit"))]
pub unit: String,
#[serde(rename = "$value")]
pub memory: String,
}
/// Domain information, see https://libvirt.org/formatdomain.html
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "domain")]
pub struct DomainXML {
/// Domain type (kvm)
#[serde(rename(serialize = "@type"))]
pub r#type: String,
pub name: String,
pub uuid: Option<DomainXMLUuid>,
pub genid: Option<uuid::Uuid>,
pub title: Option<String>,
pub description: Option<String>,
pub os: OSXML,
/// The maximum allocation of memory for the guest at boot time
pub memory: DomainMemoryXML,
}

View File

@ -0,0 +1,118 @@
use crate::constants;
use crate::libvirt_lib_structures::{DomainMemoryXML, DomainXML, DomainXMLUuid, OSTypeXML, OSXML};
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtractionError;
use lazy_regex::regex;
#[derive(thiserror::Error, Debug)]
enum LibVirtStructError {
#[error("StructureExtractionError: {0}")]
StructureExtractionError(&'static str),
}
#[derive(serde::Serialize)]
pub struct HypervisorInfo {
pub r#type: String,
pub hyp_version: u32,
pub lib_version: u32,
pub capabilities: String,
pub free_memory: u64,
pub hostname: String,
pub node: HypervisorNodeInfo,
}
#[derive(serde::Serialize)]
pub struct HypervisorNodeInfo {
pub cpu_model: String,
/// Memory size in kilobytes
pub memory_size: u64,
pub number_of_active_cpus: u32,
pub cpu_frequency_mhz: u32,
pub number_of_numa_cell: u32,
pub number_of_cpu_socket_per_node: u32,
pub number_of_core_per_sockets: u32,
pub number_of_threads_per_core: u32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum BootType {
Legacy,
UEFI,
UEFISecureBoot,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum VMArchitecture {
#[serde(rename = "i686")]
I686,
#[serde(rename = "x86_64")]
X86_64,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VMInfo {
/// VM name (alphanumeric characters only)
pub name: String,
pub uuid: Option<DomainXMLUuid>,
pub genid: Option<DomainXMLUuid>,
pub title: Option<String>,
pub description: Option<String>,
pub boot_type: Option<BootType>,
pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes
pub memory: usize,
}
impl VMInfo {
/// Turn this VM into a domain
pub fn to_domain(self) -> anyhow::Result<DomainXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtractionError("VM name is invalid!").into());
}
if let Some(n) = &self.uuid {
if n.is_valid() {
return Err(StructureExtractionError("VM UUID is invalid!").into());
}
}
if let Some(n) = &self.genid {
if n.is_valid() {
return Err(StructureExtractionError("VM genid is invalid!").into());
}
}
if let Some(n) = &self.title {
if n.contains('\n') {
return Err(StructureExtractionError("VM title contain newline char!").into());
}
}
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
return Err(StructureExtractionError("VM memory is invalid!").into());
}
Ok(DomainXML {
r#type: "kvm".to_string(),
name: self.name,
uuid: self.uuid,
genid: self.genid.map(|i| i.0),
title: self.title,
description: self.description,
os: OSXML {
r#type: OSTypeXML {
arch: match self.architecture {
VMArchitecture::I686 => "i686",
VMArchitecture::X86_64 => "x86_64",
}
.to_string(),
body: "hvm".to_string(),
},
},
memory: DomainMemoryXML {
unit: "MB".to_string(),
memory: self.memory.to_string(),
},
})
}
}

View File

@ -20,7 +20,9 @@ use virtweb_backend::constants;
use virtweb_backend::constants::{
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
};
use virtweb_backend::controllers::{auth_controller, iso_controller, server_controller};
use virtweb_backend::controllers::{
auth_controller, iso_controller, server_controller, vm_controller,
};
use virtweb_backend::libvirt_client::LibVirtClient;
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
use virtweb_backend::utils::files_utils;
@ -132,6 +134,8 @@ async fn main() -> std::io::Result<()> {
"/api/iso/{filename}",
web::delete().to(iso_controller::delete_file),
)
// Virtual machines controller
.route("/api/vm/create", web::post().to(vm_controller::create))
})
.bind(&AppConfig::get().listen_address)?
.run()