Skip to content

Commit

Permalink
Add implementation of adding vCPUs to VMM
Browse files Browse the repository at this point in the history
Actually add the vCPUS to the VMM via API PUT requests to hotplug
endpoint. Must add at least 1 vCPU, and the total number of vCPUs after
hotplug must not exceed MAX_SUPPORTED_VCPUS (currently 32).

Signed-off-by: James Curtis <[email protected]>
  • Loading branch information
JamesC1305 authored and roypat committed Aug 7, 2024
1 parent 5eaa199 commit 4faee33
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 27 deletions.
10 changes: 5 additions & 5 deletions src/vmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,24 +610,24 @@ impl Vmm {
config: HotplugVcpuConfig,
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
use crate::logger::IncMetric;
if config.vcpu_count < 1 {
if config.add < 1 {
return Err(HotplugVcpuError::VcpuCountTooLow);
} else if self
.vcpus_handles
.len()
.checked_add(config.vcpu_count.into())
.checked_add(config.add.into())
.ok_or(HotplugVcpuError::VcpuCountTooHigh)?
> MAX_SUPPORTED_VCPUS.into()
{
return Err(HotplugVcpuError::VcpuCountTooHigh);
}

// Create and start new vcpus
let mut vcpus = Vec::with_capacity(config.vcpu_count.into());
let mut vcpus = Vec::with_capacity(config.add.into());

#[allow(clippy::cast_possible_truncation)]
let start_idx = self.vcpus_handles.len().try_into().unwrap();
for cpu_idx in start_idx..(start_idx + config.vcpu_count) {
for cpu_idx in start_idx..(start_idx + config.add) {
let exit_evt = self
.vcpus_exit_evt
.try_clone()
Expand All @@ -647,7 +647,7 @@ impl Vmm {
.map_err(HotplugVcpuError::VcpuStart)?;

#[allow(clippy::cast_lossless)]
METRICS.hotplug.vcpus_added.add(config.vcpu_count.into());
METRICS.hotplug.vcpus_added.add(config.add.into());

// Update VM config to reflect new CPUs added
#[allow(clippy::cast_possible_truncation)]
Expand Down
169 changes: 147 additions & 22 deletions src/vmm/src/rpc_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ impl RuntimeApiController {
self.vmm.lock().expect("Poisoned lock").version(),
)),
#[cfg(target_arch = "x86_64")]
HotplugRequest(config) => Ok(VmmData::Empty),
HotplugRequest(request_type) => self.handle_hotplug_request(request_type),
PatchMMDS(value) => self.patch_mmds(value),
Pause => self.pause(),
PutMMDS(value) => self.put_mmds(value),
Expand Down Expand Up @@ -867,6 +867,31 @@ impl RuntimeApiController {
.map_err(NetworkInterfaceError::DeviceUpdate)
.map_err(VmmActionError::NetworkConfig)
}

#[cfg(target_arch = "x86_64")]
fn handle_hotplug_request(
&mut self,
cfg: HotplugRequestConfig,
) -> Result<VmmData, VmmActionError> {
match cfg {
HotplugRequestConfig::Vcpu(cfg) => {
let result = self.vmm.lock().expect("Poisoned lock").hotplug_vcpus(cfg);
result
.map_err(|err| VmmActionError::HotplugRequest(HotplugRequestError::Vcpu(err)))
.and_then(|machine_cfg_update| self.update_vm_config(machine_cfg_update))
}
}
}

// Currently, this method is only used for vCPU hotplugging, which is not implemented for
// aarch64, hence we must allow `dead_code`
#[allow(dead_code)]
fn update_vm_config(&mut self, cfg: MachineConfigUpdate) -> Result<VmmData, VmmActionError> {
self.vm_resources
.update_vm_config(&cfg)
.map(|()| VmmData::Empty)
.map_err(VmmActionError::MachineConfig)
}
}

#[cfg(test)]
Expand All @@ -875,8 +900,14 @@ mod tests {
use std::path::PathBuf;

use seccompiler::BpfThreadMap;
#[cfg(target_arch = "x86_64")]
use vmm_config::hotplug::HotplugVcpuError;

use super::*;
// Currently, default_vmm only used for testing hotplugging, which is only implemented for
// x86_64, so `unused_imports` must be allowed for aarch64 systems
#[cfg(target_arch = "x86_64")]
use crate::builder::tests::default_vmm;
use crate::cpu_config::templates::test_utils::build_test_template;
use crate::cpu_config::templates::{CpuTemplateType, StaticCpuTemplate};
use crate::devices::virtio::balloon::{BalloonConfig, BalloonError};
Expand All @@ -885,6 +916,8 @@ mod tests {
use crate::devices::virtio::vsock::VsockError;
use crate::mmds::data_store::MmdsVersion;
use crate::vmm_config::balloon::BalloonBuilder;
#[cfg(target_arch = "x86_64")]
use crate::vmm_config::hotplug::HotplugVcpuConfig;
use crate::vmm_config::machine_config::VmConfig;
use crate::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
use crate::vmm_config::vsock::VsockBuilder;
Expand All @@ -893,27 +926,55 @@ mod tests {
impl PartialEq for VmmActionError {
fn eq(&self, other: &VmmActionError) -> bool {
use VmmActionError::*;
matches!(
(self, other),
(BalloonConfig(_), BalloonConfig(_))
| (BootSource(_), BootSource(_))
| (CreateSnapshot(_), CreateSnapshot(_))
| (DriveConfig(_), DriveConfig(_))
| (InternalVmm(_), InternalVmm(_))
| (LoadSnapshot(_), LoadSnapshot(_))
| (MachineConfig(_), MachineConfig(_))
| (Metrics(_), Metrics(_))
| (Mmds(_), Mmds(_))
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
| (MmdsConfig(_), MmdsConfig(_))
| (NetworkConfig(_), NetworkConfig(_))
| (NotSupported(_), NotSupported(_))
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
| (StartMicrovm(_), StartMicrovm(_))
| (VsockConfig(_), VsockConfig(_))
| (EntropyDevice(_), EntropyDevice(_))
)
#[cfg(target_arch = "x86_64")]
{
matches!(
(self, other),
(BalloonConfig(_), BalloonConfig(_))
| (BootSource(_), BootSource(_))
| (CreateSnapshot(_), CreateSnapshot(_))
| (DriveConfig(_), DriveConfig(_))
| (HotplugRequest(_), HotplugRequest(_))
| (InternalVmm(_), InternalVmm(_))
| (LoadSnapshot(_), LoadSnapshot(_))
| (MachineConfig(_), MachineConfig(_))
| (Metrics(_), Metrics(_))
| (Mmds(_), Mmds(_))
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
| (MmdsConfig(_), MmdsConfig(_))
| (NetworkConfig(_), NetworkConfig(_))
| (NotSupported(_), NotSupported(_))
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
| (StartMicrovm(_), StartMicrovm(_))
| (VsockConfig(_), VsockConfig(_))
| (EntropyDevice(_), EntropyDevice(_))
)
}
#[cfg(target_arch = "aarch64")]
{
matches!(
(self, other),
(BalloonConfig(_), BalloonConfig(_))
| (BootSource(_), BootSource(_))
| (CreateSnapshot(_), CreateSnapshot(_))
| (DriveConfig(_), DriveConfig(_))
| (InternalVmm(_), InternalVmm(_))
| (LoadSnapshot(_), LoadSnapshot(_))
| (MachineConfig(_), MachineConfig(_))
| (Metrics(_), Metrics(_))
| (Mmds(_), Mmds(_))
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
| (MmdsConfig(_), MmdsConfig(_))
| (NetworkConfig(_), NetworkConfig(_))
| (NotSupported(_), NotSupported(_))
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
| (StartMicrovm(_), StartMicrovm(_))
| (VsockConfig(_), VsockConfig(_))
| (EntropyDevice(_), EntropyDevice(_))
)
}
}
}

Expand Down Expand Up @@ -1106,6 +1167,8 @@ mod tests {
pub update_block_device_path_called: bool,
pub update_block_device_vhost_user_config_called: bool,
pub update_net_rate_limiters_called: bool,
#[cfg(target_arch = "x86_64")]
pub hotplug_vcpus_called: bool,
// when `true`, all self methods are forced to fail
pub force_errors: bool,
}
Expand Down Expand Up @@ -1216,6 +1279,24 @@ mod tests {
Ok(())
}

#[cfg(target_arch = "x86_64")]
pub fn hotplug_vcpus(
&mut self,
_: HotplugVcpuConfig,
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
if self.force_errors {
return Err(HotplugVcpuError::VcpuCountTooHigh);
}
self.hotplug_vcpus_called = true;
Ok(MachineConfigUpdate {
vcpu_count: Some(1),
mem_size_mib: None,
smt: None,
cpu_template: None,
track_dirty_pages: None,
huge_pages: None,
})
}
pub fn instance_info(&self) -> InstanceInfo {
InstanceInfo::default()
}
Expand Down Expand Up @@ -1830,6 +1911,12 @@ mod tests {
VmmAction::SendCtrlAltDel,
VmmActionError::OperationNotSupportedPreBoot,
);

#[cfg(target_arch = "x86_64")]
check_preboot_request_err(
VmmAction::HotplugRequest(HotplugRequestConfig::Vcpu(HotplugVcpuConfig { add: 4 })),
VmmActionError::OperationNotSupportedPreBoot,
);
}

fn check_runtime_request<F>(request: VmmAction, check_success: F)
Expand Down Expand Up @@ -2059,6 +2146,44 @@ mod tests {
);
}

#[test]
#[cfg(target_arch = "x86_64")]
fn test_runtime_hotplug_vcpu() {
// Case 1. Valid input
let mut vmm = default_vmm();
let config = HotplugVcpuConfig { add: 4 };
let result = vmm.hotplug_vcpus(config);
assert_eq!(vmm.vcpus_handles.len(), 4);
result.unwrap();

// Case 2. Vcpu count too low
let mut vmm = default_vmm();
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
assert_eq!(vmm.vcpus_handles.len(), 1);
let config = HotplugVcpuConfig { add: 0 };
let result = vmm.hotplug_vcpus(config);
result.unwrap_err();
assert_eq!(vmm.vcpus_handles.len(), 1);

// Case 3. Vcpu count too high
let mut vmm = default_vmm();
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
assert_eq!(vmm.vcpus_handles.len(), 1);
let config = HotplugVcpuConfig { add: 33 };
let result = vmm.hotplug_vcpus(config);
result.unwrap_err();
assert_eq!(vmm.vcpus_handles.len(), 1);

// Case 4. Attempted overflow of vcpus
let mut vmm = default_vmm();
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 2 }).unwrap();
assert_eq!(vmm.vcpus_handles.len(), 2);
let config = HotplugVcpuConfig { add: 255 };
let result = vmm.hotplug_vcpus(config);
result.unwrap_err();
assert_eq!(vmm.vcpus_handles.len(), 2);
}

#[test]
fn test_runtime_disallowed() {
check_runtime_request_err(
Expand Down
2 changes: 2 additions & 0 deletions src/vmm/src/vmm_config/hotplug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum HotplugVcpuError {
VcpuCreate(VcpuError),
/// Failed to start vCPUs
VcpuStart(StartVcpusError),
/// No seccomp filter for thread category: {0}
MissingSeccompFilters(String),
}

/// Config for hotplugging vCPUS
Expand Down

0 comments on commit 4faee33

Please sign in to comment.