Refacto structures definition
This commit is contained in:
virtweb_backend/src
actors
controllers
libvirt_client.rslibvirt_lib_structures.rslibvirt_lib_structures
libvirt_rest_structures.rslibvirt_rest_structures
utils
386
virtweb_backend/src/libvirt_rest_structures/vm.rs
Normal file
386
virtweb_backend/src/libvirt_rest_structures/vm.rs
Normal file
@ -0,0 +1,386 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::constants;
|
||||
use crate::libvirt_lib_structures::domain::*;
|
||||
use crate::libvirt_lib_structures::XMLUuid;
|
||||
use crate::libvirt_rest_structures::LibVirtStructError;
|
||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||
use crate::utils::disks_utils::Disk;
|
||||
use crate::utils::files_utils;
|
||||
use crate::utils::files_utils::convert_size_unit_to_mb;
|
||||
use lazy_regex::regex;
|
||||
use num::Integer;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub enum BootType {
|
||||
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 Network {
|
||||
mac: String,
|
||||
#[serde(flatten)]
|
||||
r#type: NetworkType,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NetworkType {
|
||||
UserspaceSLIRPStack,
|
||||
DefinedNetwork { network: String }, // TODO : complete network types
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct VMInfo {
|
||||
/// VM name (alphanumeric characters only)
|
||||
pub name: String,
|
||||
pub uuid: Option<XMLUuid>,
|
||||
pub genid: Option<XMLUuid>,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub boot_type: BootType,
|
||||
pub architecture: VMArchitecture,
|
||||
/// VM allocated memory, in megabytes
|
||||
pub memory: usize,
|
||||
/// Number of vCPU for the VM
|
||||
pub number_vcpu: usize,
|
||||
/// Enable VNC access through admin console
|
||||
pub vnc_access: bool,
|
||||
/// Attach ISO file(s)
|
||||
pub iso_files: Vec<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
|
||||
pub disks: Vec<Disk>,
|
||||
/// Network cards
|
||||
pub networks: Vec<Network>,
|
||||
/// Add a TPM v2.0 module
|
||||
pub tpm_module: bool,
|
||||
}
|
||||
|
||||
impl VMInfo {
|
||||
/// Turn this VM into a domain
|
||||
pub fn as_tomain(&self) -> anyhow::Result<DomainXML> {
|
||||
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
|
||||
return Err(StructureExtraction("VM name is invalid!").into());
|
||||
}
|
||||
|
||||
let uuid = if let Some(n) = self.uuid {
|
||||
if !n.is_valid() {
|
||||
return Err(StructureExtraction("VM UUID is invalid!").into());
|
||||
}
|
||||
n
|
||||
} else {
|
||||
XMLUuid::new_random()
|
||||
};
|
||||
|
||||
if let Some(n) = &self.genid {
|
||||
if !n.is_valid() {
|
||||
return Err(StructureExtraction("VM genid is invalid!").into());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(n) = &self.title {
|
||||
if n.contains('\n') {
|
||||
return Err(StructureExtraction("VM title contain newline char!").into());
|
||||
}
|
||||
}
|
||||
|
||||
if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
|
||||
return Err(StructureExtraction("VM memory is invalid!").into());
|
||||
}
|
||||
|
||||
if self.number_vcpu == 0 || (self.number_vcpu != 1 && self.number_vcpu.is_odd()) {
|
||||
return Err(StructureExtraction("Invalid number of vCPU specified!").into());
|
||||
}
|
||||
|
||||
let mut disks = vec![];
|
||||
|
||||
for iso_file in &self.iso_files {
|
||||
if !files_utils::check_file_name(iso_file) {
|
||||
return Err(StructureExtraction("ISO filename is invalid!").into());
|
||||
}
|
||||
|
||||
let path = AppConfig::get().iso_storage_path().join(iso_file);
|
||||
|
||||
if !path.exists() {
|
||||
return Err(StructureExtraction("Specified ISO file does not exists!").into());
|
||||
}
|
||||
|
||||
disks.push(DiskXML {
|
||||
r#type: "file".to_string(),
|
||||
device: "cdrom".to_string(),
|
||||
driver: DiskDriverXML {
|
||||
name: "qemu".to_string(),
|
||||
r#type: "raw".to_string(),
|
||||
cache: "none".to_string(),
|
||||
},
|
||||
source: DiskSourceXML {
|
||||
file: path.to_string_lossy().to_string(),
|
||||
},
|
||||
target: DiskTargetXML {
|
||||
dev: format!(
|
||||
"hd{}",
|
||||
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
|
||||
),
|
||||
bus: "sata".to_string(),
|
||||
},
|
||||
readonly: Some(DiskReadOnlyXML {}),
|
||||
boot: DiskBootXML {
|
||||
order: (disks.len() + 1).to_string(),
|
||||
},
|
||||
address: None,
|
||||
})
|
||||
}
|
||||
|
||||
let (vnc_graphics, vnc_video) = match self.vnc_access {
|
||||
true => (
|
||||
Some(GraphicsXML {
|
||||
r#type: "vnc".to_string(),
|
||||
socket: AppConfig::get()
|
||||
.vnc_socket_for_domain(&self.name)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
}),
|
||||
Some(VideoXML {
|
||||
model: VideoModelXML {
|
||||
r#type: "virtio".to_string(), //"qxl".to_string(),
|
||||
},
|
||||
}),
|
||||
),
|
||||
false => (None, None),
|
||||
};
|
||||
|
||||
// Check disks name for duplicates
|
||||
for disk in &self.disks {
|
||||
if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
|
||||
return Err(StructureExtraction("Two different disks have the same name!").into());
|
||||
}
|
||||
}
|
||||
|
||||
// Apply disks configuration
|
||||
for disk in &self.disks {
|
||||
disk.check_config()?;
|
||||
disk.apply_config(uuid)?;
|
||||
|
||||
if disk.delete {
|
||||
continue;
|
||||
}
|
||||
|
||||
disks.push(DiskXML {
|
||||
r#type: "file".to_string(),
|
||||
device: "disk".to_string(),
|
||||
driver: DiskDriverXML {
|
||||
name: "qemu".to_string(),
|
||||
r#type: "raw".to_string(),
|
||||
cache: "none".to_string(),
|
||||
},
|
||||
source: DiskSourceXML {
|
||||
file: disk.disk_path(uuid).to_string_lossy().to_string(),
|
||||
},
|
||||
target: DiskTargetXML {
|
||||
dev: format!(
|
||||
"vd{}",
|
||||
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
|
||||
),
|
||||
bus: "virtio".to_string(),
|
||||
},
|
||||
readonly: None,
|
||||
boot: DiskBootXML {
|
||||
order: (disks.len() + 1).to_string(),
|
||||
},
|
||||
address: None,
|
||||
})
|
||||
}
|
||||
|
||||
let mut networks = vec![];
|
||||
for n in &self.networks {
|
||||
networks.push(match &n.r#type {
|
||||
NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
|
||||
mac: NetMacAddress {
|
||||
address: n.mac.to_string(),
|
||||
},
|
||||
r#type: "user".to_string(),
|
||||
source: None,
|
||||
model: Some(NetIntModelXML {
|
||||
r#type: "virtio".to_string(),
|
||||
}),
|
||||
},
|
||||
NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
|
||||
mac: NetMacAddress {
|
||||
address: n.mac.to_string(),
|
||||
},
|
||||
r#type: "network".to_string(),
|
||||
source: Some(NetIntSourceXML {
|
||||
network: network.to_string(),
|
||||
}),
|
||||
model: Some(NetIntModelXML {
|
||||
r#type: "virtio".to_string(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(DomainXML {
|
||||
r#type: "kvm".to_string(),
|
||||
name: self.name.to_string(),
|
||||
uuid: Some(uuid),
|
||||
genid: self.genid.map(|i| i.0),
|
||||
title: self.title.clone(),
|
||||
description: self.description.clone(),
|
||||
|
||||
os: OSXML {
|
||||
r#type: OSTypeXML {
|
||||
arch: match self.architecture {
|
||||
VMArchitecture::I686 => "i686",
|
||||
VMArchitecture::X86_64 => "x86_64",
|
||||
}
|
||||
.to_string(),
|
||||
machine: "q35".to_string(),
|
||||
body: "hvm".to_string(),
|
||||
},
|
||||
firmware: "efi".to_string(),
|
||||
loader: Some(OSLoaderXML {
|
||||
secure: match self.boot_type {
|
||||
BootType::UEFI => "no".to_string(),
|
||||
BootType::UEFISecureBoot => "yes".to_string(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
features: FeaturesXML { acpi: ACPIXML {} },
|
||||
|
||||
devices: DevicesXML {
|
||||
graphics: vnc_graphics,
|
||||
video: vnc_video,
|
||||
disks,
|
||||
net_interfaces: networks,
|
||||
inputs: vec![
|
||||
DomainInputXML {
|
||||
r#type: "mouse".to_string(),
|
||||
},
|
||||
DomainInputXML {
|
||||
r#type: "keyboard".to_string(),
|
||||
},
|
||||
DomainInputXML {
|
||||
r#type: "tablet".to_string(),
|
||||
},
|
||||
],
|
||||
tpm: match self.tpm_module {
|
||||
true => Some(TPMDeviceXML {
|
||||
model: "tpm-tis".to_string(),
|
||||
backend: TPMBackendXML {
|
||||
r#type: "emulator".to_string(),
|
||||
version: "2.0".to_string(),
|
||||
},
|
||||
}),
|
||||
false => None,
|
||||
},
|
||||
},
|
||||
|
||||
memory: DomainMemoryXML {
|
||||
unit: "MB".to_string(),
|
||||
memory: self.memory,
|
||||
},
|
||||
|
||||
vcpu: DomainVCPUXML {
|
||||
body: self.number_vcpu,
|
||||
},
|
||||
|
||||
cpu: DomainCPUXML {
|
||||
mode: "host-passthrough".to_string(),
|
||||
topology: Some(DomainCPUTopology {
|
||||
sockets: 1,
|
||||
cores: match self.number_vcpu {
|
||||
1 => 1,
|
||||
v => v / 2,
|
||||
},
|
||||
threads: match self.number_vcpu {
|
||||
1 => 1,
|
||||
_ => 2,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
on_poweroff: "destroy".to_string(),
|
||||
on_reboot: "restart".to_string(),
|
||||
on_crash: "destroy".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(XMLUuid),
|
||||
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());
|
||||
}
|
||||
},
|
||||
number_vcpu: domain.vcpu.body,
|
||||
memory: convert_size_unit_to_mb(&domain.memory.unit, domain.memory.memory)?,
|
||||
vnc_access: domain.devices.graphics.is_some(),
|
||||
iso_files: domain
|
||||
.devices
|
||||
.disks
|
||||
.iter()
|
||||
.filter(|d| d.device == "cdrom")
|
||||
.map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
|
||||
.collect(),
|
||||
|
||||
disks: domain
|
||||
.devices
|
||||
.disks
|
||||
.iter()
|
||||
.filter(|d| d.device == "disk")
|
||||
.map(|d| Disk::load_from_file(&d.source.file).unwrap())
|
||||
.collect(),
|
||||
|
||||
networks: domain
|
||||
.devices
|
||||
.net_interfaces
|
||||
.iter()
|
||||
.map(|d| {
|
||||
Ok(Network {
|
||||
mac: d.mac.address.to_string(),
|
||||
r#type: match d.r#type.as_str() {
|
||||
"user" => NetworkType::UserspaceSLIRPStack,
|
||||
"network" => NetworkType::DefinedNetwork {
|
||||
network: d.source.as_ref().unwrap().network.to_string(),
|
||||
},
|
||||
a => {
|
||||
return Err(LibVirtStructError::DomainExtraction(format!(
|
||||
"Unknown network interface type: {a}! "
|
||||
)));
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
|
||||
tpm_module: domain.devices.tpm.is_some(),
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user