diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs index 2e6c9b1238..fb2179a6f1 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs @@ -17,6 +17,7 @@ use kata_types::{ config::KATA_PATH, }; use persist::sandbox_persist::Persist; +use std::cmp::Ordering; use std::collections::HashMap; use std::path::Path; use std::process::Stdio; @@ -244,15 +245,48 @@ impl QemuInner { Ok(()) } - pub(crate) async fn resize_vcpu(&self, old_vcpus: u32, new_vcpus: u32) -> Result<(u32, u32)> { + pub(crate) async fn resize_vcpu( + &mut self, + old_vcpus: u32, + mut new_vcpus: u32, + ) -> Result<(u32, u32)> { info!( sl!(), "QemuInner::resize_vcpu(): {} -> {}", old_vcpus, new_vcpus ); + + // TODO The following sanity checks apparently have to be performed by + // any hypervisor - wouldn't it make sense to move them to the caller? if new_vcpus == old_vcpus { return Ok((old_vcpus, new_vcpus)); } - todo!() + + if new_vcpus == 0 { + return Err(anyhow!("resize to 0 vcpus requested")); + } + + if new_vcpus > self.config.cpu_info.default_maxvcpus { + warn!( + sl!(), + "Cannot allocate more vcpus than the max allowed number of vcpus. The maximum allowed amount of vcpus will be used instead."); + new_vcpus = self.config.cpu_info.default_maxvcpus; + } + + if let Some(ref mut qmp) = self.qmp { + match new_vcpus.cmp(&old_vcpus) { + Ordering::Greater => { + let hotplugged = qmp.hotplug_vcpus(new_vcpus - old_vcpus)?; + new_vcpus = old_vcpus + hotplugged; + } + Ordering::Less => { + let hotunplugged = qmp.hotunplug_vcpus(old_vcpus - new_vcpus)?; + new_vcpus = old_vcpus - hotunplugged; + } + Ordering::Equal => {} + } + } + + Ok((old_vcpus, new_vcpus)) } pub(crate) async fn get_pids(&self) -> Result> { diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs b/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs index 99ba1bd2ce..162bd5608a 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs @@ -128,7 +128,7 @@ impl Hypervisor for Qemu { } async fn resize_vcpu(&self, old_vcpus: u32, new_vcpus: u32) -> Result<(u32, u32)> { - let inner = self.inner.read().await; + let mut inner = self.inner.write().await; inner.resize_vcpu(old_vcpus, new_vcpus).await } diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs b/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs index 27a6b2c255..3b96f24ca1 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs @@ -9,6 +9,9 @@ use std::io::BufReader; use std::os::unix::net::UnixStream; use std::time::Duration; +use qapi::qmp; +use qapi_spec::Dictionary; + pub struct Qmp { qmp: qapi::Qmp, UnixStream>>, } @@ -47,4 +50,86 @@ impl Qmp { Ok(qmp) } + + pub fn hotplug_vcpus(&mut self, vcpu_cnt: u32) -> Result { + let hotpluggable_cpus = self.qmp.execute(&qmp::query_hotpluggable_cpus {})?; + //info!(sl!(), "hotpluggable CPUs: {:#?}", hotpluggable_cpus); + + let mut hotplugged = 0; + for vcpu in &hotpluggable_cpus { + if hotplugged >= vcpu_cnt { + break; + } + let core_id = match vcpu.props.core_id { + Some(id) => id, + None => continue, + }; + if vcpu.qom_path.is_some() { + info!(sl!(), "hotpluggable vcpu {} hotplugged already", core_id); + continue; + } + let socket_id = match vcpu.props.socket_id { + Some(id) => id, + None => continue, + }; + let thread_id = match vcpu.props.thread_id { + Some(id) => id, + None => continue, + }; + + let mut cpu_args = Dictionary::new(); + cpu_args.insert("socket-id".to_owned(), socket_id.into()); + cpu_args.insert("core-id".to_owned(), core_id.into()); + cpu_args.insert("thread-id".to_owned(), thread_id.into()); + self.qmp.execute(&qmp::device_add { + bus: None, + id: Some(vcpu_id_from_core_id(core_id)), + driver: hotpluggable_cpus[0].type_.clone(), + arguments: cpu_args, + })?; + + hotplugged += 1; + } + + info!( + sl!(), + "Qmp::hotplug_vcpus(): hotplugged {}/{} vcpus", hotplugged, vcpu_cnt + ); + + Ok(hotplugged) + } + + pub fn hotunplug_vcpus(&mut self, vcpu_cnt: u32) -> Result { + let hotpluggable_cpus = self.qmp.execute(&qmp::query_hotpluggable_cpus {})?; + + let mut hotunplugged = 0; + for vcpu in &hotpluggable_cpus { + if hotunplugged >= vcpu_cnt { + break; + } + let core_id = match vcpu.props.core_id { + Some(id) => id, + None => continue, + }; + if vcpu.qom_path.is_none() { + info!(sl!(), "hotpluggable vcpu {} not hotplugged yet", core_id); + continue; + } + self.qmp.execute(&qmp::device_del { + id: vcpu_id_from_core_id(core_id), + })?; + hotunplugged += 1; + } + + info!( + sl!(), + "Qmp::hotunplug_vcpus(): hotunplugged {}/{} vcpus", hotunplugged, vcpu_cnt + ); + + Ok(hotunplugged) + } +} + +fn vcpu_id_from_core_id(core_id: i64) -> String { + format!("cpu-{}", core_id) }