Created first domain
This commit is contained in:
parent
7073b9b7f1
commit
2bc64442f4
75
virtweb_backend/Cargo.lock
generated
75
virtweb_backend/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
@ -28,3 +29,6 @@ reqwest = { version = "0.11.18", features = ["stream"] }
|
||||
url = "2.4.0"
|
||||
virt = "0.3.0"
|
||||
sysinfo = { version = "0.29.10", features = ["serde"] }
|
||||
uuid = { version = "1.4.1", features = ["v4", "serde"] }
|
||||
lazy-regex = "3.0.2"
|
||||
thiserror = "1.0.47"
|
@ -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()?)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)]
|
||||
|
@ -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};
|
||||
|
||||
|
17
virtweb_backend/src/controllers/vm_controller.rs
Normal file
17
virtweb_backend/src/controllers/vm_controller.rs
Normal 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))
|
||||
}
|
@ -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;
|
||||
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
58
virtweb_backend/src/libvirt_lib_structures.rs
Normal file
58
virtweb_backend/src/libvirt_lib_structures.rs
Normal 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,
|
||||
}
|
118
virtweb_backend/src/libvirt_rest_structures.rs
Normal file
118
virtweb_backend/src/libvirt_rest_structures.rs
Normal 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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user