Skip to content
4 changes: 4 additions & 0 deletions src/firecracker/src/api_server/parsed_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use super::request::net::{parse_patch_net, parse_put_net};
use super::request::snapshot::{parse_patch_vm_state, parse_put_snapshot};
use super::request::version::parse_get_version;
use super::request::vsock::parse_put_vsock;
use crate::api_server::request::hotplug::memory::parse_put_memory_hotplug;
use crate::api_server::request::serial::parse_put_serial;

#[derive(Debug)]
Expand Down Expand Up @@ -101,6 +102,9 @@ impl TryFrom<&Request> for ParsedRequest {
(Method::Put, "snapshot", Some(body)) => parse_put_snapshot(body, path_tokens.next()),
(Method::Put, "vsock", Some(body)) => parse_put_vsock(body),
(Method::Put, "entropy", Some(body)) => parse_put_entropy(body),
(Method::Put, "hotplug", Some(body)) if path_tokens.next() == Some("memory") => {
parse_put_memory_hotplug(body)
}
(Method::Put, _, None) => method_to_error(Method::Put),
(Method::Patch, "balloon", Some(body)) => parse_patch_balloon(body, path_tokens.next()),
(Method::Patch, "drives", Some(body)) => parse_patch_drive(body, path_tokens.next()),
Expand Down
69 changes: 69 additions & 0 deletions src/firecracker/src/api_server/request/hotplug/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use micro_http::Body;
use vmm::logger::{IncMetric, METRICS};
use vmm::rpc_interface::VmmAction;
use vmm::vmm_config::memory_hotplug::MemoryHotplugConfig;

use crate::api_server::parsed_request::{ParsedRequest, RequestError};

pub(crate) fn parse_put_memory_hotplug(body: &Body) -> Result<ParsedRequest, RequestError> {
METRICS.put_api_requests.hotplug_memory_count.inc();
let config = serde_json::from_slice::<MemoryHotplugConfig>(body.raw()).inspect_err(|_| {
METRICS.put_api_requests.hotplug_memory_fails.inc();
})?;
Ok(ParsedRequest::new_sync(VmmAction::SetMemoryHotplugDevice(
config,
)))
}

#[cfg(test)]
mod tests {
use vmm::devices::virtio::mem::{VIRTIO_MEM_DEFAULT_BLOCK_SIZE, VIRTIO_MEM_DEFAULT_SLOT_SIZE};
use vmm::utils::bytes_to_mib;

use super::*;
use crate::api_server::parsed_request::tests::vmm_action_from_request;

#[test]
fn test_parse_put_memory_hotplug_request() {
parse_put_memory_hotplug(&Body::new("invalid_payload")).unwrap_err();

// PUT with invalid fields.
let body = r#"{
"total_size_mib": "bar"
}"#;
parse_put_memory_hotplug(&Body::new(body)).unwrap_err();

// PUT with valid input fields with defaults.
let body = r#"{
"total_size_mib": 2048
}"#;
let expected_config = MemoryHotplugConfig {
total_size_mib: 2048,
block_size_mib: bytes_to_mib(VIRTIO_MEM_DEFAULT_BLOCK_SIZE),
slot_size_mib: bytes_to_mib(VIRTIO_MEM_DEFAULT_SLOT_SIZE),
};
assert_eq!(
vmm_action_from_request(parse_put_memory_hotplug(&Body::new(body)).unwrap()),
VmmAction::SetMemoryHotplugDevice(expected_config)
);

// PUT with valid input fields.
let body = r#"{
"total_size_mib": 2048,
"block_size_mib": 64,
"slot_size_mib": 64
}"#;
let expected_config = MemoryHotplugConfig {
total_size_mib: 2048,
block_size_mib: 64,
slot_size_mib: 64,
};
assert_eq!(
vmm_action_from_request(parse_put_memory_hotplug(&Body::new(body)).unwrap()),
VmmAction::SetMemoryHotplugDevice(expected_config)
);
}
}
4 changes: 4 additions & 0 deletions src/firecracker/src/api_server/request/hotplug/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

pub mod memory;
1 change: 1 addition & 0 deletions src/firecracker/src/api_server/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod boot_source;
pub mod cpu_configuration;
pub mod drive;
pub mod entropy;
pub mod hotplug;
pub mod instance_info;
pub mod logger;
pub mod machine_configuration;
Expand Down
4 changes: 3 additions & 1 deletion src/vmm/src/device_manager/pci_mngr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ mod tests {
let _restored_dev_manager =
PciDevices::restore(restore_args, &device_manager_state.pci_state).unwrap();

// TODO(virtio-mem): add memory-hotplug device when snapshot-restore is implemented
let expected_vm_resources = format!(
r#"{{
"balloon": {{
Expand Down Expand Up @@ -730,7 +731,8 @@ mod tests {
}},
"entropy": {{
"rate_limiter": null
}}
}},
"memory-hotplug": null
}}"#,
_block_files.last().unwrap().as_path().to_str().unwrap(),
tmp_sock_file.as_path().to_str().unwrap()
Expand Down
4 changes: 3 additions & 1 deletion src/vmm/src/device_manager/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ mod tests {
let _restored_dev_manager =
MMIODeviceManager::restore(restore_args, &device_manager_state.mmio_state).unwrap();

// TODO(virtio-mem): add memory-hotplug device when snapshot-restore is implemented
let expected_vm_resources = format!(
r#"{{
"balloon": {{
Expand Down Expand Up @@ -751,7 +752,8 @@ mod tests {
}},
"entropy": {{
"rate_limiter": null
}}
}},
"memory-hotplug": null
}}"#,
_block_files.last().unwrap().as_path().to_str().unwrap(),
tmp_sock_file.as_path().to_str().unwrap()
Expand Down
6 changes: 6 additions & 0 deletions src/vmm/src/logger/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ pub struct PutRequestsMetrics {
pub serial_count: SharedIncMetric,
/// Number of failed PUTs to /serial
pub serial_fails: SharedIncMetric,
/// Number of PUTs to /hotplug/memory
pub hotplug_memory_count: SharedIncMetric,
/// Number of failed PUTs to /hotplug/memory
pub hotplug_memory_fails: SharedIncMetric,
}
impl PutRequestsMetrics {
/// Const default construction.
Expand Down Expand Up @@ -452,6 +456,8 @@ impl PutRequestsMetrics {
vsock_fails: SharedIncMetric::new(),
serial_count: SharedIncMetric::new(),
serial_fails: SharedIncMetric::new(),
hotplug_memory_count: SharedIncMetric::new(),
hotplug_memory_fails: SharedIncMetric::new(),
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/vmm/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::vmm_config::instance_info::InstanceInfo;
use crate::vmm_config::machine_config::{
HugePageConfig, MachineConfig, MachineConfigError, MachineConfigUpdate,
};
use crate::vmm_config::memory_hotplug::{MemoryHotplugConfig, MemoryHotplugConfigError};
use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError, init_metrics};
use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
use crate::vmm_config::net::*;
Expand Down Expand Up @@ -62,6 +63,8 @@ pub enum ResourcesError {
VsockDevice(#[from] VsockConfigError),
/// Entropy device error: {0}
EntropyDevice(#[from] EntropyDeviceError),
/// Memory hotplug config error: {0}
MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -89,6 +92,7 @@ pub struct VmmConfig {
entropy: Option<EntropyDeviceConfig>,
#[serde(skip)]
serial_config: Option<SerialConfig>,
memory_hotplug: Option<MemoryHotplugConfig>,
}

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

if let Some(memory_hotplug_config) = vmm_config.memory_hotplug {
resources.set_memory_hotplug_config(memory_hotplug_config)?;
}

Ok(resources)
}

Expand Down Expand Up @@ -389,6 +399,16 @@ impl VmResources {
self.entropy.insert(body)
}

/// Sets the memory hotplug configuration.
pub fn set_memory_hotplug_config(
&mut self,
config: MemoryHotplugConfig,
) -> Result<(), MemoryHotplugConfigError> {
config.validate()?;
self.memory_hotplug = Some(config);
Ok(())
}

/// Setter for mmds config.
pub fn set_mmds_config(
&mut self,
Expand Down Expand Up @@ -517,6 +537,7 @@ impl From<&VmResources> for VmmConfig {
entropy: resources.entropy.config(),
// serial_config is marked serde(skip) so that it doesnt end up in snapshots.
serial_config: None,
memory_hotplug: resources.memory_hotplug.clone(),
}
}
}
Expand Down Expand Up @@ -629,6 +650,7 @@ mod tests {
entropy: Default::default(),
pci_enabled: false,
serial_out_path: None,
memory_hotplug: Default::default(),
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/vmm/src/rpc_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::vmm_config::drive::{BlockDeviceConfig, BlockDeviceUpdateConfig, Drive
use crate::vmm_config::entropy::{EntropyDeviceConfig, EntropyDeviceError};
use crate::vmm_config::instance_info::InstanceInfo;
use crate::vmm_config::machine_config::{MachineConfig, MachineConfigError, MachineConfigUpdate};
use crate::vmm_config::memory_hotplug::{MemoryHotplugConfig, MemoryHotplugConfigError};
use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError};
use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
use crate::vmm_config::net::{
Expand Down Expand Up @@ -106,6 +107,9 @@ pub enum VmmAction {
/// Set the entropy device using `EntropyDeviceConfig` as input. This action can only be called
/// before the microVM has booted.
SetEntropyDevice(EntropyDeviceConfig),
/// Set the memory hotplug device using `MemoryHotplugConfig` as input. This action can only be
/// called before the microVM has booted.
SetMemoryHotplugDevice(MemoryHotplugConfig),
/// Launch the microVM. This action can only be called before the microVM has booted.
StartMicroVm,
/// Send CTRL+ALT+DEL to the microVM, using the i8042 keyboard function. If an AT-keyboard
Expand Down Expand Up @@ -143,6 +147,8 @@ pub enum VmmActionError {
DriveConfig(#[from] DriveError),
/// Entropy device error: {0}
EntropyDevice(#[from] EntropyDeviceError),
/// Memory hotplug config error: {0}
MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
/// Internal VMM error: {0}
InternalVmm(#[from] VmmError),
/// Load snapshot error: {0}
Expand Down Expand Up @@ -447,6 +453,7 @@ impl<'a> PrebootApiController<'a> {
StartMicroVm => self.start_microvm(),
UpdateMachineConfiguration(config) => self.update_machine_config(config),
SetEntropyDevice(config) => self.set_entropy_device(config),
SetMemoryHotplugDevice(config) => self.set_memory_hotplug_device(config),
// Operations not allowed pre-boot.
CreateSnapshot(_)
| FlushMetrics
Expand Down Expand Up @@ -546,6 +553,15 @@ impl<'a> PrebootApiController<'a> {
Ok(VmmData::Empty)
}

fn set_memory_hotplug_device(
&mut self,
cfg: MemoryHotplugConfig,
) -> Result<VmmData, VmmActionError> {
self.boot_path = true;
self.vm_resources.set_memory_hotplug_config(cfg)?;
Ok(VmmData::Empty)
}

// On success, this command will end the pre-boot stage and this controller
// will be replaced by a runtime controller.
fn start_microvm(&mut self) -> Result<VmmData, VmmActionError> {
Expand Down Expand Up @@ -694,6 +710,7 @@ impl RuntimeApiController {
| SetVsockDevice(_)
| SetMmdsConfiguration(_)
| SetEntropyDevice(_)
| SetMemoryHotplugDevice(_)
| StartMicroVm
| UpdateMachineConfiguration(_) => Err(VmmActionError::OperationNotSupportedPostBoot),
}
Expand Down Expand Up @@ -1272,5 +1289,8 @@ mod tests {
check_unsupported(runtime_request(VmmAction::SetEntropyDevice(
EntropyDeviceConfig::default(),
)));
check_unsupported(runtime_request(VmmAction::SetMemoryHotplugDevice(
MemoryHotplugConfig::default(),
)));
}
}
5 changes: 5 additions & 0 deletions src/vmm/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ pub const fn mib_to_bytes(mib: usize) -> usize {
mib << MIB_TO_BYTES_SHIFT
}

/// Converts Bytes to MiB, truncating any remainder
pub const fn bytes_to_mib(bytes: usize) -> usize {
bytes >> MIB_TO_BYTES_SHIFT
}

/// Align address up to the aligment.
pub const fn align_up(addr: u64, align: u64) -> u64 {
debug_assert!(align != 0);
Expand Down
Loading