Skip to content

Commit 942cd13

Browse files
committed
feat(virtio-mem): add to VmmConfig and PUT API
Allow to configure the virtio-mem device from the VmmConfig and the PUT API to /hotplug/memory. Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 8c592fe commit 942cd13

File tree

15 files changed

+367
-3
lines changed

15 files changed

+367
-3
lines changed

src/firecracker/src/api_server/parsed_request.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use super::request::net::{parse_patch_net, parse_put_net};
2727
use super::request::snapshot::{parse_patch_vm_state, parse_put_snapshot};
2828
use super::request::version::parse_get_version;
2929
use super::request::vsock::parse_put_vsock;
30+
use crate::api_server::request::hotplug::memory::parse_put_memory_hotplug;
3031
use crate::api_server::request::serial::parse_put_serial;
3132

3233
#[derive(Debug)]
@@ -101,6 +102,9 @@ impl TryFrom<&Request> for ParsedRequest {
101102
(Method::Put, "snapshot", Some(body)) => parse_put_snapshot(body, path_tokens.next()),
102103
(Method::Put, "vsock", Some(body)) => parse_put_vsock(body),
103104
(Method::Put, "entropy", Some(body)) => parse_put_entropy(body),
105+
(Method::Put, "hotplug", Some(body)) if path_tokens.next() == Some("memory") => {
106+
parse_put_memory_hotplug(body)
107+
}
104108
(Method::Put, _, None) => method_to_error(Method::Put),
105109
(Method::Patch, "balloon", Some(body)) => parse_patch_balloon(body, path_tokens.next()),
106110
(Method::Patch, "drives", Some(body)) => parse_patch_drive(body, path_tokens.next()),
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use micro_http::Body;
5+
use vmm::logger::{IncMetric, METRICS};
6+
use vmm::rpc_interface::VmmAction;
7+
use vmm::vmm_config::memory_hotplug::MemoryHotplugConfig;
8+
9+
use crate::api_server::parsed_request::{ParsedRequest, RequestError};
10+
11+
pub(crate) fn parse_put_memory_hotplug(body: &Body) -> Result<ParsedRequest, RequestError> {
12+
METRICS.put_api_requests.hotplug_memory_count.inc();
13+
let config = serde_json::from_slice::<MemoryHotplugConfig>(body.raw()).inspect_err(|_| {
14+
METRICS.put_api_requests.hotplug_memory_fails.inc();
15+
})?;
16+
Ok(ParsedRequest::new_sync(VmmAction::SetMemoryHotplugDevice(
17+
config,
18+
)))
19+
}
20+
21+
#[cfg(test)]
22+
mod tests {
23+
use vmm::devices::virtio::mem::{VIRTIO_MEM_DEFAULT_BLOCK_SIZE, VIRTIO_MEM_DEFAULT_SLOT_SIZE};
24+
use vmm::utils::bytes_to_mib;
25+
26+
use super::*;
27+
use crate::api_server::parsed_request::tests::vmm_action_from_request;
28+
29+
#[test]
30+
fn test_parse_put_memory_hotplug_request() {
31+
parse_put_memory_hotplug(&Body::new("invalid_payload")).unwrap_err();
32+
33+
// PUT with invalid fields.
34+
let body = r#"{
35+
"total_size_mib": "bar"
36+
}"#;
37+
parse_put_memory_hotplug(&Body::new(body)).unwrap_err();
38+
39+
// PUT with valid input fields with defaults.
40+
let body = r#"{
41+
"total_size_mib": 2048
42+
}"#;
43+
let expected_config = MemoryHotplugConfig {
44+
total_size_mib: 2048,
45+
block_size_mib: bytes_to_mib(VIRTIO_MEM_DEFAULT_BLOCK_SIZE),
46+
slot_size_mib: bytes_to_mib(VIRTIO_MEM_DEFAULT_SLOT_SIZE),
47+
};
48+
assert_eq!(
49+
vmm_action_from_request(parse_put_memory_hotplug(&Body::new(body)).unwrap()),
50+
VmmAction::SetMemoryHotplugDevice(expected_config)
51+
);
52+
53+
// PUT with valid input fields.
54+
let body = r#"{
55+
"total_size_mib": 2048,
56+
"block_size_mib": 64,
57+
"slot_size_mib": 64
58+
}"#;
59+
let expected_config = MemoryHotplugConfig {
60+
total_size_mib: 2048,
61+
block_size_mib: 64,
62+
slot_size_mib: 64,
63+
};
64+
assert_eq!(
65+
vmm_action_from_request(parse_put_memory_hotplug(&Body::new(body)).unwrap()),
66+
VmmAction::SetMemoryHotplugDevice(expected_config)
67+
);
68+
}
69+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
pub mod memory;

src/firecracker/src/api_server/request/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod boot_source;
77
pub mod cpu_configuration;
88
pub mod drive;
99
pub mod entropy;
10+
pub mod hotplug;
1011
pub mod instance_info;
1112
pub mod logger;
1213
pub mod machine_configuration;

src/vmm/src/device_manager/pci_mngr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ mod tests {
672672
let _restored_dev_manager =
673673
PciDevices::restore(restore_args, &device_manager_state.pci_state).unwrap();
674674

675+
// TODO(virtio-mem): add memory-hotplug device when snapshot-restore is implemented
675676
let expected_vm_resources = format!(
676677
r#"{{
677678
"balloon": {{
@@ -730,7 +731,8 @@ mod tests {
730731
}},
731732
"entropy": {{
732733
"rate_limiter": null
733-
}}
734+
}},
735+
"memory-hotplug": null
734736
}}"#,
735737
_block_files.last().unwrap().as_path().to_str().unwrap(),
736738
tmp_sock_file.as_path().to_str().unwrap()

src/vmm/src/device_manager/persist.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ mod tests {
693693
let _restored_dev_manager =
694694
MMIODeviceManager::restore(restore_args, &device_manager_state.mmio_state).unwrap();
695695

696+
// TODO(virtio-mem): add memory-hotplug device when snapshot-restore is implemented
696697
let expected_vm_resources = format!(
697698
r#"{{
698699
"balloon": {{
@@ -751,7 +752,8 @@ mod tests {
751752
}},
752753
"entropy": {{
753754
"rate_limiter": null
754-
}}
755+
}},
756+
"memory-hotplug": null
755757
}}"#,
756758
_block_files.last().unwrap().as_path().to_str().unwrap(),
757759
tmp_sock_file.as_path().to_str().unwrap()

src/vmm/src/logger/metrics.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ pub struct PutRequestsMetrics {
425425
pub serial_count: SharedIncMetric,
426426
/// Number of failed PUTs to /serial
427427
pub serial_fails: SharedIncMetric,
428+
/// Number of PUTs to /hotplug/memory
429+
pub hotplug_memory_count: SharedIncMetric,
430+
/// Number of failed PUTs to /hotplug/memory
431+
pub hotplug_memory_fails: SharedIncMetric,
428432
}
429433
impl PutRequestsMetrics {
430434
/// Const default construction.
@@ -452,6 +456,8 @@ impl PutRequestsMetrics {
452456
vsock_fails: SharedIncMetric::new(),
453457
serial_count: SharedIncMetric::new(),
454458
serial_fails: SharedIncMetric::new(),
459+
hotplug_memory_count: SharedIncMetric::new(),
460+
hotplug_memory_fails: SharedIncMetric::new(),
455461
}
456462
}
457463
}

src/vmm/src/resources.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::vmm_config::instance_info::InstanceInfo;
2525
use crate::vmm_config::machine_config::{
2626
HugePageConfig, MachineConfig, MachineConfigError, MachineConfigUpdate,
2727
};
28+
use crate::vmm_config::memory_hotplug::{MemoryHotplugConfig, MemoryHotplugConfigError};
2829
use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError, init_metrics};
2930
use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
3031
use crate::vmm_config::net::*;
@@ -62,6 +63,8 @@ pub enum ResourcesError {
6263
VsockDevice(#[from] VsockConfigError),
6364
/// Entropy device error: {0}
6465
EntropyDevice(#[from] EntropyDeviceError),
66+
/// Memory hotplug config error: {0}
67+
MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
6568
}
6669

6770
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
@@ -89,6 +92,7 @@ pub struct VmmConfig {
8992
entropy: Option<EntropyDeviceConfig>,
9093
#[serde(skip)]
9194
serial_config: Option<SerialConfig>,
95+
memory_hotplug: Option<MemoryHotplugConfig>,
9296
}
9397

9498
/// A data structure that encapsulates the device configurations
@@ -109,6 +113,8 @@ pub struct VmResources {
109113
pub net_builder: NetBuilder,
110114
/// The entropy device builder.
111115
pub entropy: EntropyDeviceBuilder,
116+
/// The memory hotplug configuration.
117+
pub memory_hotplug: Option<MemoryHotplugConfig>,
112118
/// The optional Mmds data store.
113119
// This is initialised on demand (if ever used), so that we don't allocate it unless it's
114120
// actually used.
@@ -202,6 +208,10 @@ impl VmResources {
202208
resources.serial_out_path = serial_cfg.serial_out_path;
203209
}
204210

211+
if let Some(memory_hotplug_config) = vmm_config.memory_hotplug {
212+
resources.set_memory_hotplug_config(memory_hotplug_config)?;
213+
}
214+
205215
Ok(resources)
206216
}
207217

@@ -389,6 +399,16 @@ impl VmResources {
389399
self.entropy.insert(body)
390400
}
391401

402+
/// Sets the memory hotplug configuration.
403+
pub fn set_memory_hotplug_config(
404+
&mut self,
405+
config: MemoryHotplugConfig,
406+
) -> Result<(), MemoryHotplugConfigError> {
407+
config.validate()?;
408+
self.memory_hotplug = Some(config);
409+
Ok(())
410+
}
411+
392412
/// Setter for mmds config.
393413
pub fn set_mmds_config(
394414
&mut self,
@@ -517,6 +537,7 @@ impl From<&VmResources> for VmmConfig {
517537
entropy: resources.entropy.config(),
518538
// serial_config is marked serde(skip) so that it doesnt end up in snapshots.
519539
serial_config: None,
540+
memory_hotplug: resources.memory_hotplug.clone(),
520541
}
521542
}
522543
}
@@ -629,6 +650,7 @@ mod tests {
629650
entropy: Default::default(),
630651
pci_enabled: false,
631652
serial_out_path: None,
653+
memory_hotplug: Default::default(),
632654
}
633655
}
634656

src/vmm/src/rpc_interface.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::vmm_config::drive::{BlockDeviceConfig, BlockDeviceUpdateConfig, Drive
2828
use crate::vmm_config::entropy::{EntropyDeviceConfig, EntropyDeviceError};
2929
use crate::vmm_config::instance_info::InstanceInfo;
3030
use crate::vmm_config::machine_config::{MachineConfig, MachineConfigError, MachineConfigUpdate};
31+
use crate::vmm_config::memory_hotplug::{MemoryHotplugConfig, MemoryHotplugConfigError};
3132
use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError};
3233
use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
3334
use crate::vmm_config::net::{
@@ -106,6 +107,9 @@ pub enum VmmAction {
106107
/// Set the entropy device using `EntropyDeviceConfig` as input. This action can only be called
107108
/// before the microVM has booted.
108109
SetEntropyDevice(EntropyDeviceConfig),
110+
/// Set the memory hotplug device using `MemoryHotplugConfig` as input. This action can only be
111+
/// called before the microVM has booted.
112+
SetMemoryHotplugDevice(MemoryHotplugConfig),
109113
/// Launch the microVM. This action can only be called before the microVM has booted.
110114
StartMicroVm,
111115
/// Send CTRL+ALT+DEL to the microVM, using the i8042 keyboard function. If an AT-keyboard
@@ -143,6 +147,8 @@ pub enum VmmActionError {
143147
DriveConfig(#[from] DriveError),
144148
/// Entropy device error: {0}
145149
EntropyDevice(#[from] EntropyDeviceError),
150+
/// Memory hotplug config error: {0}
151+
MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
146152
/// Internal VMM error: {0}
147153
InternalVmm(#[from] VmmError),
148154
/// Load snapshot error: {0}
@@ -447,6 +453,7 @@ impl<'a> PrebootApiController<'a> {
447453
StartMicroVm => self.start_microvm(),
448454
UpdateMachineConfiguration(config) => self.update_machine_config(config),
449455
SetEntropyDevice(config) => self.set_entropy_device(config),
456+
SetMemoryHotplugDevice(config) => self.set_memory_hotplug_device(config),
450457
// Operations not allowed pre-boot.
451458
CreateSnapshot(_)
452459
| FlushMetrics
@@ -546,6 +553,15 @@ impl<'a> PrebootApiController<'a> {
546553
Ok(VmmData::Empty)
547554
}
548555

556+
fn set_memory_hotplug_device(
557+
&mut self,
558+
cfg: MemoryHotplugConfig,
559+
) -> Result<VmmData, VmmActionError> {
560+
self.boot_path = true;
561+
self.vm_resources.set_memory_hotplug_config(cfg)?;
562+
Ok(VmmData::Empty)
563+
}
564+
549565
// On success, this command will end the pre-boot stage and this controller
550566
// will be replaced by a runtime controller.
551567
fn start_microvm(&mut self) -> Result<VmmData, VmmActionError> {
@@ -694,6 +710,7 @@ impl RuntimeApiController {
694710
| SetVsockDevice(_)
695711
| SetMmdsConfiguration(_)
696712
| SetEntropyDevice(_)
713+
| SetMemoryHotplugDevice(_)
697714
| StartMicroVm
698715
| UpdateMachineConfiguration(_) => Err(VmmActionError::OperationNotSupportedPostBoot),
699716
}
@@ -1272,5 +1289,8 @@ mod tests {
12721289
check_unsupported(runtime_request(VmmAction::SetEntropyDevice(
12731290
EntropyDeviceConfig::default(),
12741291
)));
1292+
check_unsupported(runtime_request(VmmAction::SetMemoryHotplugDevice(
1293+
MemoryHotplugConfig::default(),
1294+
)));
12751295
}
12761296
}

src/vmm/src/utils/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ pub const fn mib_to_bytes(mib: usize) -> usize {
5454
mib << MIB_TO_BYTES_SHIFT
5555
}
5656

57+
/// Converts Bytes to MiB, truncating any remainder
58+
pub const fn bytes_to_mib(bytes: usize) -> usize {
59+
bytes >> MIB_TO_BYTES_SHIFT
60+
}
61+
5762
/// Align address up to the aligment.
5863
pub const fn align_up(addr: u64, align: u64) -> u64 {
5964
debug_assert!(align != 0);

0 commit comments

Comments
 (0)