Skip to content

Commit 4faee33

Browse files
JamesC1305roypat
authored andcommitted
Add implementation of adding vCPUs to VMM
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]>
1 parent 5eaa199 commit 4faee33

File tree

3 files changed

+154
-27
lines changed

3 files changed

+154
-27
lines changed

src/vmm/src/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -610,24 +610,24 @@ impl Vmm {
610610
config: HotplugVcpuConfig,
611611
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
612612
use crate::logger::IncMetric;
613-
if config.vcpu_count < 1 {
613+
if config.add < 1 {
614614
return Err(HotplugVcpuError::VcpuCountTooLow);
615615
} else if self
616616
.vcpus_handles
617617
.len()
618-
.checked_add(config.vcpu_count.into())
618+
.checked_add(config.add.into())
619619
.ok_or(HotplugVcpuError::VcpuCountTooHigh)?
620620
> MAX_SUPPORTED_VCPUS.into()
621621
{
622622
return Err(HotplugVcpuError::VcpuCountTooHigh);
623623
}
624624

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

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

649649
#[allow(clippy::cast_lossless)]
650-
METRICS.hotplug.vcpus_added.add(config.vcpu_count.into());
650+
METRICS.hotplug.vcpus_added.add(config.add.into());
651651

652652
// Update VM config to reflect new CPUs added
653653
#[allow(clippy::cast_possible_truncation)]

src/vmm/src/rpc_interface.rs

+147-22
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ impl RuntimeApiController {
670670
self.vmm.lock().expect("Poisoned lock").version(),
671671
)),
672672
#[cfg(target_arch = "x86_64")]
673-
HotplugRequest(config) => Ok(VmmData::Empty),
673+
HotplugRequest(request_type) => self.handle_hotplug_request(request_type),
674674
PatchMMDS(value) => self.patch_mmds(value),
675675
Pause => self.pause(),
676676
PutMMDS(value) => self.put_mmds(value),
@@ -867,6 +867,31 @@ impl RuntimeApiController {
867867
.map_err(NetworkInterfaceError::DeviceUpdate)
868868
.map_err(VmmActionError::NetworkConfig)
869869
}
870+
871+
#[cfg(target_arch = "x86_64")]
872+
fn handle_hotplug_request(
873+
&mut self,
874+
cfg: HotplugRequestConfig,
875+
) -> Result<VmmData, VmmActionError> {
876+
match cfg {
877+
HotplugRequestConfig::Vcpu(cfg) => {
878+
let result = self.vmm.lock().expect("Poisoned lock").hotplug_vcpus(cfg);
879+
result
880+
.map_err(|err| VmmActionError::HotplugRequest(HotplugRequestError::Vcpu(err)))
881+
.and_then(|machine_cfg_update| self.update_vm_config(machine_cfg_update))
882+
}
883+
}
884+
}
885+
886+
// Currently, this method is only used for vCPU hotplugging, which is not implemented for
887+
// aarch64, hence we must allow `dead_code`
888+
#[allow(dead_code)]
889+
fn update_vm_config(&mut self, cfg: MachineConfigUpdate) -> Result<VmmData, VmmActionError> {
890+
self.vm_resources
891+
.update_vm_config(&cfg)
892+
.map(|()| VmmData::Empty)
893+
.map_err(VmmActionError::MachineConfig)
894+
}
870895
}
871896

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

877902
use seccompiler::BpfThreadMap;
903+
#[cfg(target_arch = "x86_64")]
904+
use vmm_config::hotplug::HotplugVcpuError;
878905

879906
use super::*;
907+
// Currently, default_vmm only used for testing hotplugging, which is only implemented for
908+
// x86_64, so `unused_imports` must be allowed for aarch64 systems
909+
#[cfg(target_arch = "x86_64")]
910+
use crate::builder::tests::default_vmm;
880911
use crate::cpu_config::templates::test_utils::build_test_template;
881912
use crate::cpu_config::templates::{CpuTemplateType, StaticCpuTemplate};
882913
use crate::devices::virtio::balloon::{BalloonConfig, BalloonError};
@@ -885,6 +916,8 @@ mod tests {
885916
use crate::devices::virtio::vsock::VsockError;
886917
use crate::mmds::data_store::MmdsVersion;
887918
use crate::vmm_config::balloon::BalloonBuilder;
919+
#[cfg(target_arch = "x86_64")]
920+
use crate::vmm_config::hotplug::HotplugVcpuConfig;
888921
use crate::vmm_config::machine_config::VmConfig;
889922
use crate::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
890923
use crate::vmm_config::vsock::VsockBuilder;
@@ -893,27 +926,55 @@ mod tests {
893926
impl PartialEq for VmmActionError {
894927
fn eq(&self, other: &VmmActionError) -> bool {
895928
use VmmActionError::*;
896-
matches!(
897-
(self, other),
898-
(BalloonConfig(_), BalloonConfig(_))
899-
| (BootSource(_), BootSource(_))
900-
| (CreateSnapshot(_), CreateSnapshot(_))
901-
| (DriveConfig(_), DriveConfig(_))
902-
| (InternalVmm(_), InternalVmm(_))
903-
| (LoadSnapshot(_), LoadSnapshot(_))
904-
| (MachineConfig(_), MachineConfig(_))
905-
| (Metrics(_), Metrics(_))
906-
| (Mmds(_), Mmds(_))
907-
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
908-
| (MmdsConfig(_), MmdsConfig(_))
909-
| (NetworkConfig(_), NetworkConfig(_))
910-
| (NotSupported(_), NotSupported(_))
911-
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
912-
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
913-
| (StartMicrovm(_), StartMicrovm(_))
914-
| (VsockConfig(_), VsockConfig(_))
915-
| (EntropyDevice(_), EntropyDevice(_))
916-
)
929+
#[cfg(target_arch = "x86_64")]
930+
{
931+
matches!(
932+
(self, other),
933+
(BalloonConfig(_), BalloonConfig(_))
934+
| (BootSource(_), BootSource(_))
935+
| (CreateSnapshot(_), CreateSnapshot(_))
936+
| (DriveConfig(_), DriveConfig(_))
937+
| (HotplugRequest(_), HotplugRequest(_))
938+
| (InternalVmm(_), InternalVmm(_))
939+
| (LoadSnapshot(_), LoadSnapshot(_))
940+
| (MachineConfig(_), MachineConfig(_))
941+
| (Metrics(_), Metrics(_))
942+
| (Mmds(_), Mmds(_))
943+
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
944+
| (MmdsConfig(_), MmdsConfig(_))
945+
| (NetworkConfig(_), NetworkConfig(_))
946+
| (NotSupported(_), NotSupported(_))
947+
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
948+
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
949+
| (StartMicrovm(_), StartMicrovm(_))
950+
| (VsockConfig(_), VsockConfig(_))
951+
| (EntropyDevice(_), EntropyDevice(_))
952+
)
953+
}
954+
#[cfg(target_arch = "aarch64")]
955+
{
956+
matches!(
957+
(self, other),
958+
(BalloonConfig(_), BalloonConfig(_))
959+
| (BootSource(_), BootSource(_))
960+
| (CreateSnapshot(_), CreateSnapshot(_))
961+
| (DriveConfig(_), DriveConfig(_))
962+
| (InternalVmm(_), InternalVmm(_))
963+
| (LoadSnapshot(_), LoadSnapshot(_))
964+
| (MachineConfig(_), MachineConfig(_))
965+
| (Metrics(_), Metrics(_))
966+
| (Mmds(_), Mmds(_))
967+
| (MmdsLimitExceeded(_), MmdsLimitExceeded(_))
968+
| (MmdsConfig(_), MmdsConfig(_))
969+
| (NetworkConfig(_), NetworkConfig(_))
970+
| (NotSupported(_), NotSupported(_))
971+
| (OperationNotSupportedPostBoot, OperationNotSupportedPostBoot)
972+
| (OperationNotSupportedPreBoot, OperationNotSupportedPreBoot)
973+
| (StartMicrovm(_), StartMicrovm(_))
974+
| (VsockConfig(_), VsockConfig(_))
975+
| (EntropyDevice(_), EntropyDevice(_))
976+
)
977+
}
917978
}
918979
}
919980

@@ -1106,6 +1167,8 @@ mod tests {
11061167
pub update_block_device_path_called: bool,
11071168
pub update_block_device_vhost_user_config_called: bool,
11081169
pub update_net_rate_limiters_called: bool,
1170+
#[cfg(target_arch = "x86_64")]
1171+
pub hotplug_vcpus_called: bool,
11091172
// when `true`, all self methods are forced to fail
11101173
pub force_errors: bool,
11111174
}
@@ -1216,6 +1279,24 @@ mod tests {
12161279
Ok(())
12171280
}
12181281

1282+
#[cfg(target_arch = "x86_64")]
1283+
pub fn hotplug_vcpus(
1284+
&mut self,
1285+
_: HotplugVcpuConfig,
1286+
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
1287+
if self.force_errors {
1288+
return Err(HotplugVcpuError::VcpuCountTooHigh);
1289+
}
1290+
self.hotplug_vcpus_called = true;
1291+
Ok(MachineConfigUpdate {
1292+
vcpu_count: Some(1),
1293+
mem_size_mib: None,
1294+
smt: None,
1295+
cpu_template: None,
1296+
track_dirty_pages: None,
1297+
huge_pages: None,
1298+
})
1299+
}
12191300
pub fn instance_info(&self) -> InstanceInfo {
12201301
InstanceInfo::default()
12211302
}
@@ -1830,6 +1911,12 @@ mod tests {
18301911
VmmAction::SendCtrlAltDel,
18311912
VmmActionError::OperationNotSupportedPreBoot,
18321913
);
1914+
1915+
#[cfg(target_arch = "x86_64")]
1916+
check_preboot_request_err(
1917+
VmmAction::HotplugRequest(HotplugRequestConfig::Vcpu(HotplugVcpuConfig { add: 4 })),
1918+
VmmActionError::OperationNotSupportedPreBoot,
1919+
);
18331920
}
18341921

18351922
fn check_runtime_request<F>(request: VmmAction, check_success: F)
@@ -2059,6 +2146,44 @@ mod tests {
20592146
);
20602147
}
20612148

2149+
#[test]
2150+
#[cfg(target_arch = "x86_64")]
2151+
fn test_runtime_hotplug_vcpu() {
2152+
// Case 1. Valid input
2153+
let mut vmm = default_vmm();
2154+
let config = HotplugVcpuConfig { add: 4 };
2155+
let result = vmm.hotplug_vcpus(config);
2156+
assert_eq!(vmm.vcpus_handles.len(), 4);
2157+
result.unwrap();
2158+
2159+
// Case 2. Vcpu count too low
2160+
let mut vmm = default_vmm();
2161+
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
2162+
assert_eq!(vmm.vcpus_handles.len(), 1);
2163+
let config = HotplugVcpuConfig { add: 0 };
2164+
let result = vmm.hotplug_vcpus(config);
2165+
result.unwrap_err();
2166+
assert_eq!(vmm.vcpus_handles.len(), 1);
2167+
2168+
// Case 3. Vcpu count too high
2169+
let mut vmm = default_vmm();
2170+
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
2171+
assert_eq!(vmm.vcpus_handles.len(), 1);
2172+
let config = HotplugVcpuConfig { add: 33 };
2173+
let result = vmm.hotplug_vcpus(config);
2174+
result.unwrap_err();
2175+
assert_eq!(vmm.vcpus_handles.len(), 1);
2176+
2177+
// Case 4. Attempted overflow of vcpus
2178+
let mut vmm = default_vmm();
2179+
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 2 }).unwrap();
2180+
assert_eq!(vmm.vcpus_handles.len(), 2);
2181+
let config = HotplugVcpuConfig { add: 255 };
2182+
let result = vmm.hotplug_vcpus(config);
2183+
result.unwrap_err();
2184+
assert_eq!(vmm.vcpus_handles.len(), 2);
2185+
}
2186+
20622187
#[test]
20632188
fn test_runtime_disallowed() {
20642189
check_runtime_request_err(

src/vmm/src/vmm_config/hotplug.rs

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub enum HotplugVcpuError {
3636
VcpuCreate(VcpuError),
3737
/// Failed to start vCPUs
3838
VcpuStart(StartVcpusError),
39+
/// No seccomp filter for thread category: {0}
40+
MissingSeccompFilters(String),
3941
}
4042

4143
/// Config for hotplugging vCPUS

0 commit comments

Comments
 (0)