Can get domain info
This commit is contained in:
parent
2bc64442f4
commit
ce393995f9
@ -4,6 +4,7 @@ use crate::libvirt_rest_structures::*;
|
|||||||
use actix::{Actor, Context, Handler, Message};
|
use actix::{Actor, Context, Handler, Message};
|
||||||
use virt::connect::Connect;
|
use virt::connect::Connect;
|
||||||
use virt::domain::Domain;
|
use virt::domain::Domain;
|
||||||
|
use virt::sys::VIR_DOMAIN_XML_SECURE;
|
||||||
|
|
||||||
pub struct LibVirtActor {
|
pub struct LibVirtActor {
|
||||||
m: Connect,
|
m: Connect,
|
||||||
@ -57,6 +58,22 @@ impl Handler<GetHypervisorInfo> for LibVirtActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "anyhow::Result<DomainXML>")]
|
||||||
|
pub struct GetDomainXMLReq(pub DomainXMLUuid);
|
||||||
|
|
||||||
|
impl Handler<GetDomainXMLReq> for LibVirtActor {
|
||||||
|
type Result = anyhow::Result<DomainXML>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: GetDomainXMLReq, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
log::debug!("Get domain XML:\n{}", msg.0.as_string());
|
||||||
|
let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?;
|
||||||
|
let xml = domain.get_xml_desc(VIR_DOMAIN_XML_SECURE)?;
|
||||||
|
log::debug!("XML = {}", xml);
|
||||||
|
Ok(serde_xml_rs::from_str(&xml)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "anyhow::Result<DomainXMLUuid>")]
|
#[rtype(result = "anyhow::Result<DomainXMLUuid>")]
|
||||||
pub struct DefineDomainReq(pub DomainXML);
|
pub struct DefineDomainReq(pub DomainXML);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::controllers::{HttpResult, LibVirtReq};
|
use crate::controllers::{HttpResult, LibVirtReq};
|
||||||
|
use crate::libvirt_lib_structures::DomainXMLUuid;
|
||||||
use crate::libvirt_rest_structures::VMInfo;
|
use crate::libvirt_rest_structures::VMInfo;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
@ -15,3 +16,21 @@ pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok().json(id))
|
Ok(HttpResponse::Ok().json(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct SingleVMUUidReq {
|
||||||
|
uid: DomainXMLUuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the information about a single VM
|
||||||
|
pub async fn get_single(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
|
||||||
|
let info = match client.get_single_domain(id.uid).await {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to get domain info! {e}");
|
||||||
|
return Ok(HttpResponse::InternalServerError().json(e.to_string()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(VMInfo::from_domain(info)?))
|
||||||
|
}
|
||||||
|
@ -13,6 +13,11 @@ impl LibVirtClient {
|
|||||||
self.0.send(libvirt_actor::GetHypervisorInfo).await?
|
self.0.send(libvirt_actor::GetHypervisorInfo).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the information about a single domain
|
||||||
|
pub async fn get_single_domain(&self, id: DomainXMLUuid) -> anyhow::Result<DomainXML> {
|
||||||
|
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<DomainXMLUuid> {
|
||||||
self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
|
self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy)]
|
||||||
pub struct DomainXMLUuid(pub uuid::Uuid);
|
pub struct DomainXMLUuid(pub uuid::Uuid);
|
||||||
|
|
||||||
impl DomainXMLUuid {
|
impl DomainXMLUuid {
|
||||||
@ -6,6 +6,10 @@ impl DomainXMLUuid {
|
|||||||
Ok(Self(uuid::Uuid::parse_str(s)?))
|
Ok(Self(uuid::Uuid::parse_str(s)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
self.0.get_version_num() == 4
|
self.0.get_version_num() == 4
|
||||||
}
|
}
|
||||||
@ -16,6 +20,7 @@ impl DomainXMLUuid {
|
|||||||
#[serde(rename = "os")]
|
#[serde(rename = "os")]
|
||||||
pub struct OSXML {
|
pub struct OSXML {
|
||||||
pub r#type: OSTypeXML,
|
pub r#type: OSTypeXML,
|
||||||
|
pub loader: Option<OSLoaderXML>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OS Type information
|
/// OS Type information
|
||||||
@ -28,6 +33,14 @@ pub struct OSTypeXML {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OS Loader information
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename = "loader")]
|
||||||
|
pub struct OSLoaderXML {
|
||||||
|
#[serde(rename(serialize = "@secure"))]
|
||||||
|
pub secure: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename = "memory")]
|
#[serde(rename = "memory")]
|
||||||
pub struct DomainMemoryXML {
|
pub struct DomainMemoryXML {
|
||||||
@ -35,7 +48,7 @@ pub struct DomainMemoryXML {
|
|||||||
pub unit: String,
|
pub unit: String,
|
||||||
|
|
||||||
#[serde(rename = "$value")]
|
#[serde(rename = "$value")]
|
||||||
pub memory: String,
|
pub memory: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Domain information, see https://libvirt.org/formatdomain.html
|
/// Domain information, see https://libvirt.org/formatdomain.html
|
||||||
@ -55,4 +68,8 @@ pub struct DomainXML {
|
|||||||
|
|
||||||
/// The maximum allocation of memory for the guest at boot time
|
/// The maximum allocation of memory for the guest at boot time
|
||||||
pub memory: DomainMemoryXML,
|
pub memory: DomainMemoryXML,
|
||||||
|
|
||||||
|
pub on_poweroff: String,
|
||||||
|
pub on_reboot: String,
|
||||||
|
pub on_crash: String,
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::libvirt_lib_structures::{DomainMemoryXML, DomainXML, DomainXMLUuid, OSTypeXML, OSXML};
|
use crate::libvirt_lib_structures::{
|
||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtractionError;
|
DomainMemoryXML, DomainXML, DomainXMLUuid, OSLoaderXML, OSTypeXML, OSXML,
|
||||||
|
};
|
||||||
|
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||||
use lazy_regex::regex;
|
use lazy_regex::regex;
|
||||||
|
use std::ops::{Div, Mul};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum LibVirtStructError {
|
enum LibVirtStructError {
|
||||||
#[error("StructureExtractionError: {0}")]
|
#[error("StructureExtractionError: {0}")]
|
||||||
StructureExtractionError(&'static str),
|
StructureExtraction(&'static str),
|
||||||
|
#[error("DomainExtractionError: {0}")]
|
||||||
|
DomainExtraction(String),
|
||||||
|
#[error("MBConvertError: {0}")]
|
||||||
|
MBConvert(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@ -35,7 +42,6 @@ pub struct HypervisorNodeInfo {
|
|||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
pub enum BootType {
|
pub enum BootType {
|
||||||
Legacy,
|
|
||||||
UEFI,
|
UEFI,
|
||||||
UEFISecureBoot,
|
UEFISecureBoot,
|
||||||
}
|
}
|
||||||
@ -56,7 +62,7 @@ pub struct VMInfo {
|
|||||||
pub genid: Option<DomainXMLUuid>,
|
pub genid: Option<DomainXMLUuid>,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub boot_type: Option<BootType>,
|
pub boot_type: BootType,
|
||||||
pub architecture: VMArchitecture,
|
pub architecture: VMArchitecture,
|
||||||
/// VM allocated memory, in megabytes
|
/// VM allocated memory, in megabytes
|
||||||
pub memory: usize,
|
pub memory: usize,
|
||||||
@ -66,29 +72,29 @@ impl VMInfo {
|
|||||||
/// Turn this VM into a domain
|
/// Turn this VM into a domain
|
||||||
pub fn to_domain(self) -> anyhow::Result<DomainXML> {
|
pub fn to_domain(self) -> anyhow::Result<DomainXML> {
|
||||||
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
|
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
|
||||||
return Err(StructureExtractionError("VM name is invalid!").into());
|
return Err(StructureExtraction("VM name is invalid!").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.uuid {
|
if let Some(n) = &self.uuid {
|
||||||
if n.is_valid() {
|
if n.is_valid() {
|
||||||
return Err(StructureExtractionError("VM UUID is invalid!").into());
|
return Err(StructureExtraction("VM UUID is invalid!").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.genid {
|
if let Some(n) = &self.genid {
|
||||||
if n.is_valid() {
|
if n.is_valid() {
|
||||||
return Err(StructureExtractionError("VM genid is invalid!").into());
|
return Err(StructureExtraction("VM genid is invalid!").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.title {
|
if let Some(n) = &self.title {
|
||||||
if n.contains('\n') {
|
if n.contains('\n') {
|
||||||
return Err(StructureExtractionError("VM title contain newline char!").into());
|
return Err(StructureExtraction("VM title contain newline char!").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
|
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
|
||||||
return Err(StructureExtractionError("VM memory is invalid!").into());
|
return Err(StructureExtraction("VM memory is invalid!").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DomainXML {
|
Ok(DomainXML {
|
||||||
@ -108,11 +114,87 @@ impl VMInfo {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
body: "hvm".to_string(),
|
body: "hvm".to_string(),
|
||||||
},
|
},
|
||||||
|
loader: Some(OSLoaderXML {
|
||||||
|
secure: match self.boot_type {
|
||||||
|
BootType::UEFI => "no".to_string(),
|
||||||
|
BootType::UEFISecureBoot => "yes".to_string(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
memory: DomainMemoryXML {
|
memory: DomainMemoryXML {
|
||||||
unit: "MB".to_string(),
|
unit: "MB".to_string(),
|
||||||
memory: self.memory.to_string(),
|
memory: self.memory,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on_poweroff: "preserve".to_string(),
|
||||||
|
on_reboot: "restart".to_string(),
|
||||||
|
on_crash: "preserve".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn a domain into a vm
|
||||||
|
pub fn from_domain(domain: DomainXML) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: domain.name,
|
||||||
|
uuid: domain.uuid,
|
||||||
|
genid: domain.genid.map(DomainXMLUuid),
|
||||||
|
title: domain.title,
|
||||||
|
description: domain.description,
|
||||||
|
boot_type: match domain.os.loader {
|
||||||
|
None => BootType::UEFI,
|
||||||
|
Some(l) => match l.secure.as_str() {
|
||||||
|
"yes" => BootType::UEFISecureBoot,
|
||||||
|
_ => BootType::UEFI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
architecture: match domain.os.r#type.arch.as_str() {
|
||||||
|
"i686" => VMArchitecture::I686,
|
||||||
|
"x86_64" => VMArchitecture::X86_64,
|
||||||
|
a => {
|
||||||
|
return Err(LibVirtStructError::DomainExtraction(format!(
|
||||||
|
"Unknown architecture: {a}! "
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert unit to MB
|
||||||
|
fn convert_to_mb(unit: &str, value: usize) -> anyhow::Result<usize> {
|
||||||
|
let fact = match unit {
|
||||||
|
"bytes" | "b" => 1f64,
|
||||||
|
"KB" => 1000f64,
|
||||||
|
"MB" => 1000f64 * 1000f64,
|
||||||
|
"GB" => 1000f64 * 1000f64 * 1000f64,
|
||||||
|
"TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
|
||||||
|
|
||||||
|
"k" | "KiB" => 1024f64,
|
||||||
|
"M" | "MiB" => 1024f64 * 1024f64,
|
||||||
|
"G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
|
||||||
|
"T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return Err(LibVirtStructError::MBConvert(format!("Unknown size unit: {unit}")).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::libvirt_rest_structures::convert_to_mb;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_units_mb() {
|
||||||
|
assert_eq!(convert_to_mb("MB", 1).unwrap(), 1);
|
||||||
|
assert_eq!(convert_to_mb("MB", 1000).unwrap(), 1000);
|
||||||
|
assert_eq!(convert_to_mb("GB", 1000).unwrap(), 1000 * 1000);
|
||||||
|
assert_eq!(convert_to_mb("GB", 1).unwrap(), 1000);
|
||||||
|
assert_eq!(convert_to_mb("GiB", 3).unwrap(), 3222);
|
||||||
|
assert_eq!(convert_to_mb("KiB", 488281).unwrap(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -136,6 +136,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
)
|
)
|
||||||
// Virtual machines controller
|
// Virtual machines controller
|
||||||
.route("/api/vm/create", web::post().to(vm_controller::create))
|
.route("/api/vm/create", web::post().to(vm_controller::create))
|
||||||
|
.route("/api/vm/{uid}", web::get().to(vm_controller::get_single))
|
||||||
})
|
})
|
||||||
.bind(&AppConfig::get().listen_address)?
|
.bind(&AppConfig::get().listen_address)?
|
||||||
.run()
|
.run()
|
||||||
|
Loading…
Reference in New Issue
Block a user