Created first domain
This commit is contained in:
@ -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()
|
||||
|
Reference in New Issue
Block a user