Skip to content

Commit a8819ed

Browse files
JamesC1305ShadowCurse
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 073d5ac commit a8819ed

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
@@ -616,24 +616,24 @@ impl Vmm {
616616
config: HotplugVcpuConfig,
617617
) -> Result<MachineConfigUpdate, HotplugVcpuError> {
618618
use crate::logger::IncMetric;
619-
if config.vcpu_count < 1 {
619+
if config.add < 1 {
620620
return Err(HotplugVcpuError::VcpuCountTooLow);
621621
} else if self
622622
.vcpus_handles
623623
.len()
624-
.checked_add(config.vcpu_count.into())
624+
.checked_add(config.add.into())
625625
.ok_or(HotplugVcpuError::VcpuCountTooHigh)?
626626
> MAX_SUPPORTED_VCPUS.into()
627627
{
628628
return Err(HotplugVcpuError::VcpuCountTooHigh);
629629
}
630630

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

634634
#[allow(clippy::cast_possible_truncation)]
635635
let start_idx = self.vcpus_handles.len().try_into().unwrap();
636-
for cpu_idx in start_idx..(start_idx + config.vcpu_count) {
636+
for cpu_idx in start_idx..(start_idx + config.add) {
637637
let exit_evt = self
638638
.vcpus_exit_evt
639639
.try_clone()
@@ -653,7 +653,7 @@ impl Vmm {
653653
.map_err(HotplugVcpuError::VcpuStart)?;
654654

655655
#[allow(clippy::cast_lossless)]
656-
METRICS.hotplug.vcpus_added.add(config.vcpu_count.into());
656+
METRICS.hotplug.vcpus_added.add(config.add.into());
657657

658658
// Update VM config to reflect new CPUs added
659659
#[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)