Can set the number of VCPUs

This commit is contained in:
Pierre HUBERT 2023-12-07 17:09:33 +01:00
parent d1a9b6c3bb
commit 5f0f56a9f9
10 changed files with 148 additions and 12 deletions

View File

@ -1624,6 +1624,40 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1634,6 +1668,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-rational" name = "num-rational"
version = "0.4.1" version = "0.4.1"
@ -1641,6 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
] ]
@ -2583,6 +2629,7 @@ dependencies = [
"lazy_static", "lazy_static",
"light-openid", "light-openid",
"log", "log",
"num",
"rand", "rand",
"reqwest", "reqwest",
"serde", "serde",

View File

@ -39,4 +39,5 @@ rand = "0.8.5"
bytes = "1.5.0" bytes = "1.5.0"
tokio = "1.32.0" tokio = "1.32.0"
futures = "0.3.28" futures = "0.3.28"
ipnetwork = "0.20.0" ipnetwork = "0.20.0"
num = "0.4.1"

View File

@ -91,6 +91,23 @@ pub async fn server_info(client: LibVirtReq) -> HttpResult {
})) }))
} }
pub async fn number_vcpus() -> HttpResult {
let mut system = System::new();
system.refresh_cpu();
let number_cpus = system.cpus().len();
assert_ne!(number_cpus, 0, "Got invlid number of CPU!");
let mut possible_numbers = vec![1];
if number_cpus > 1 {
for i in 0..(number_cpus / 2) {
possible_numbers.push(2 + 2 * i);
}
}
Ok(HttpResponse::Ok().json(possible_numbers))
}
pub async fn networks_list() -> HttpResult { pub async fn networks_list() -> HttpResult {
let mut system = System::new(); let mut system = System::new();
system.refresh_networks_list(); system.refresh_networks_list();

View File

@ -192,6 +192,13 @@ pub struct DomainCPUTopology {
pub threads: usize, pub threads: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")]
pub struct DomainVCPUXML {
#[serde(rename = "$value")]
pub body: usize,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "cpu")] #[serde(rename = "cpu")]
pub struct DomainCPUXML { pub struct DomainCPUXML {
@ -221,6 +228,9 @@ 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,
/// Number of vCPU
pub vcpu: DomainVCPUXML,
/// CPU information /// CPU information
pub cpu: DomainCPUXML, pub cpu: DomainCPUXML,

View File

@ -2,15 +2,17 @@ use crate::app_config::AppConfig;
use crate::constants; use crate::constants;
use crate::libvirt_lib_structures::{ use crate::libvirt_lib_structures::{
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML, DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainXML, FeaturesXML, GraphicsXML, DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainVCPUXML, DomainXML, FeaturesXML,
NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML,
NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid,
ACPIXML, OSXML,
}; };
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
use crate::utils::disks_utils::Disk; use crate::utils::disks_utils::Disk;
use crate::utils::files_utils; use crate::utils::files_utils;
use ipnetwork::{Ipv4Network, Ipv6Network}; use ipnetwork::{Ipv4Network, Ipv6Network};
use lazy_regex::regex; use lazy_regex::regex;
use num::Integer;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ops::{Div, Mul}; use std::ops::{Div, Mul};
@ -74,6 +76,8 @@ pub struct VMInfo {
pub architecture: VMArchitecture, pub architecture: VMArchitecture,
/// VM allocated memory, in megabytes /// VM allocated memory, in megabytes
pub memory: usize, pub memory: usize,
/// Number of vCPU for the VM
pub number_vcpu: usize,
/// Enable VNC access through admin console /// Enable VNC access through admin console
pub vnc_access: bool, pub vnc_access: bool,
/// Attach an ISO file /// Attach an ISO file
@ -81,7 +85,6 @@ pub struct VMInfo {
/// 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 /// 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>, pub disks: Vec<Disk>,
// TODO : network interfaces // TODO : network interfaces
// TODO : number of CPUs
} }
impl VMInfo { impl VMInfo {
@ -116,6 +119,10 @@ impl VMInfo {
return Err(StructureExtraction("VM memory is invalid!").into()); 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![]; let mut disks = vec![];
if let Some(iso_file) = &self.iso_file { if let Some(iso_file) = &self.iso_file {
@ -245,12 +252,22 @@ impl VMInfo {
memory: self.memory, memory: self.memory,
}, },
vcpu: DomainVCPUXML {
body: self.number_vcpu,
},
cpu: DomainCPUXML { cpu: DomainCPUXML {
mode: "host-passthrough".to_string(), mode: "host-passthrough".to_string(),
topology: Some(DomainCPUTopology { topology: Some(DomainCPUTopology {
sockets: 1, sockets: 1,
cores: 1, cores: match self.number_vcpu {
threads: 1, 1 => 1,
v => v / 2,
},
threads: match self.number_vcpu {
1 => 1,
_ => 2,
},
}), }),
}, },
@ -285,6 +302,7 @@ impl VMInfo {
.into()); .into());
} }
}, },
number_vcpu: domain.vcpu.body,
memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?, memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?,
vnc_access: domain.devices.graphics.is_some(), vnc_access: domain.devices.graphics.is_some(),
iso_file: domain iso_file: domain

View File

@ -102,6 +102,10 @@ async fn main() -> std::io::Result<()> {
"/api/server/info", "/api/server/info",
web::get().to(server_controller::server_info), web::get().to(server_controller::server_info),
) )
.route(
"/api/server/number_vcpus",
web::get().to(server_controller::number_vcpus),
)
.route( .route(
"/api/server/networks", "/api/server/networks",
web::get().to(server_controller::networks_list), web::get().to(server_controller::networks_list),

View File

@ -172,6 +172,18 @@ export class ServerApi {
).data; ).data;
} }
/**
* Get host supported vCPUs configurations
*/
static async NumberVCPUs(): Promise<number[]> {
return (
await APIClient.exec({
method: "GET",
uri: "/server/number_vcpus",
})
).data;
}
/** /**
* Get host networks card list * Get host networks card list
*/ */

View File

@ -39,6 +39,7 @@ interface VMInfoInterface {
boot_type: "UEFI" | "UEFISecureBoot"; boot_type: "UEFI" | "UEFISecureBoot";
architecture: "i686" | "x86_64"; architecture: "i686" | "x86_64";
memory: number; memory: number;
number_vcpu: number;
vnc_access: boolean; vnc_access: boolean;
iso_file?: string; iso_file?: string;
disks: VMDisk[]; disks: VMDisk[];
@ -52,6 +53,7 @@ export class VMInfo implements VMInfoInterface {
description?: string; description?: string;
boot_type: "UEFI" | "UEFISecureBoot"; boot_type: "UEFI" | "UEFISecureBoot";
architecture: "i686" | "x86_64"; architecture: "i686" | "x86_64";
number_vcpu: number;
memory: number; memory: number;
vnc_access: boolean; vnc_access: boolean;
iso_file?: string; iso_file?: string;
@ -65,6 +67,7 @@ export class VMInfo implements VMInfoInterface {
this.description = int.description; this.description = int.description;
this.boot_type = int.boot_type; this.boot_type = int.boot_type;
this.architecture = int.architecture; this.architecture = int.architecture;
this.number_vcpu = int.number_vcpu;
this.memory = int.memory; this.memory = int.memory;
this.vnc_access = int.vnc_access; this.vnc_access = int.vnc_access;
this.iso_file = int.iso_file; this.iso_file = int.iso_file;
@ -77,6 +80,7 @@ export class VMInfo implements VMInfoInterface {
boot_type: "UEFI", boot_type: "UEFI",
architecture: "x86_64", architecture: "x86_64",
memory: 1024, memory: 1024,
number_vcpu: 1,
vnc_access: true, vnc_access: true,
disks: [], disks: [],
}); });

View File

@ -80,7 +80,7 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
}} }}
> >
<VncScreen <VncScreen
url={token!.url} url={token.url}
onDisconnect={() => { onDisconnect={() => {
console.info("VNC disconnected " + token?.url); console.info("VNC disconnected " + token?.url);
disconnected(); disconnected();

View File

@ -22,10 +22,14 @@ interface DetailsProps {
} }
export function VMDetails(p: DetailsProps): React.ReactElement { export function VMDetails(p: DetailsProps): React.ReactElement {
const [list, setList] = React.useState<IsoFile[] | any>(); const [isoList, setIsoList] = React.useState<IsoFile[] | any>();
const [vcpuCombinations, setVCPUCombinations] = React.useState<
number[] | any
>();
const load = async () => { const load = async () => {
setList(await IsoFilesApi.GetList()); setIsoList(await IsoFilesApi.GetList());
setVCPUCombinations(await ServerApi.NumberVCPUs());
}; };
return ( return (
@ -33,13 +37,19 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
loadKey={"1"} loadKey={"1"}
load={load} load={load}
errMsg="Failed to load the list of ISO files" errMsg="Failed to load the list of ISO files"
build={() => <VMDetailsInner isoList={list} {...p} />} build={() => (
<VMDetailsInner
isoList={isoList}
vcpuCombinations={vcpuCombinations}
{...p}
/>
)}
/> />
); );
} }
function VMDetailsInner( function VMDetailsInner(
p: DetailsProps & { isoList: IsoFile[] } p: DetailsProps & { isoList: IsoFile[]; vcpuCombinations: number[] }
): React.ReactElement { ): React.ReactElement {
return ( return (
<Grid container spacing={2}> <Grid container spacing={2}>
@ -146,6 +156,19 @@ function VMDetailsInner(
} }
/> />
<SelectInput
editable={p.editable}
label="Number of vCPU"
options={p.vcpuCombinations.map((v) => {
return { label: v.toString(), value: v.toString() };
})}
value={p.vm.number_vcpu.toString()}
onValueChange={(v) => {
p.vm.number_vcpu = Number(v ?? "0");
p.onChange?.();
}}
/>
<CheckboxInput <CheckboxInput
editable={p.editable} editable={p.editable}
label="Enable VNC access" label="Enable VNC access"