vmm: Allow receiving information about changing MemoryZones

It is possible to migrate a VM to a host that might have a different
but compatible NUMA configuration. For example, it should be possible
to move a `MemoryZone` from NUMA node 2 and 3 of the sender to nodes
0 and 1 on the receiver, given sender and receiver nodes are compatible
with respect to available memory.

In such a case, we need to adjust the mapping of guest memory to host
nodes. We need this information for live migration as well as for
snapshot (when performing cold migration). We consume this information
in the following commit.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
This commit is contained in:
Pascal Scholz
2026-07-20 11:21:07 +02:00
committed by Rob Bradford
parent febe70ae36
commit c2da8fed8e
3 changed files with 36 additions and 3 deletions

View File

@@ -54,7 +54,9 @@ use vmm_sys_util::eventfd::EventFd;
pub use self::dbus::start_dbus_thread;
pub use self::http::{start_http_fd_thread, start_http_path_thread};
use crate::Error as VmmError;
use crate::config::{RestoreConfig, RestoredVfioConfig, deserialize_restored_fd};
use crate::config::{
RestoreConfig, RestoredVfioConfig, VmMemoryZoneUpdateData, deserialize_restored_fd,
};
use crate::device_tree::DeviceTree;
use crate::migration::transport::{
MAX_MIGRATION_CONNECTIONS, TcpAddressParseError, tcp_address_to_server_name,
@@ -319,6 +321,9 @@ pub struct VmReceiveMigrationData {
// FDs are not serialized and any deserialized value is invalid; see NetConfig::fds.
#[serde(default, deserialize_with = "deserialize_restored_fd")]
pub iommufd_fd: Option<i32>,
/// Optional memory zone update data
#[serde(default)]
pub zone_updates: Vec<VmMemoryZoneUpdateData>,
}
#[derive(Debug, Error)]
@@ -381,6 +386,7 @@ impl VmReceiveMigrationData {
memory_mode,
vfio_fds,
iommufd_fd,
zone_updates: vec![],
};
data.validate()?;
@@ -2089,6 +2095,7 @@ mod unit_tests {
memory_mode: MigrationMode::Precopy,
vfio_fds: None,
iommufd_fd: None,
zone_updates: vec![],
}
);
@@ -2118,6 +2125,7 @@ mod unit_tests {
memory_mode: MigrationMode::Precopy,
vfio_fds: None,
iommufd_fd: None,
zone_updates: vec![],
}
);
@@ -2143,6 +2151,7 @@ mod unit_tests {
memory_mode: MigrationMode::Precopy,
vfio_fds: None,
iommufd_fd: None,
..Default::default()
}
);
@@ -2158,6 +2167,7 @@ mod unit_tests {
memory_mode: MigrationMode::Postcopy,
vfio_fds: None,
iommufd_fd: None,
..Default::default()
}
);

View File

@@ -2808,6 +2808,15 @@ impl FromStr for MemoryRestoreMode {
}
}
#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq)]
/// Data required for updating memory zone <-> host NUMA node mappings.
pub struct VmMemoryZoneUpdateData {
/// Id of the MemoryZone to update
pub id: String,
/// Host NUMA node to relocate the MemoryZone to
pub host_numa_node: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct RestoredVfioConfig {
pub id: String,
@@ -2844,6 +2853,8 @@ pub struct RestoreConfig {
pub iommufd_fd: Option<i32>,
#[serde(default)]
pub resume: bool,
#[serde(default)]
pub zone_updates: Vec<VmMemoryZoneUpdateData>,
}
impl RestoreConfig {
@@ -2928,6 +2939,7 @@ impl RestoreConfig {
vfio_fds,
iommufd_fd,
resume,
zone_updates: vec![],
})
}
@@ -5196,6 +5208,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
}
);
assert_eq!(
@@ -5221,6 +5234,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
}
);
assert_eq!(
@@ -5233,6 +5247,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
}
);
assert_eq!(
@@ -5245,6 +5260,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: true,
zone_updates: vec![],
}
);
assert_eq!(
@@ -5268,6 +5284,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
]),
iommufd_fd: Some(7),
resume: false,
zone_updates: vec![],
}
);
// Parsing should fail as source_url is a required field
@@ -5382,6 +5399,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
};
valid_config.validate(&snapshot_vm_config).unwrap();
@@ -5449,6 +5467,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
};
snapshot_vm_config.net = Some(vec![NetConfig {
pci_common: PciDeviceCommonConfig {
@@ -5468,6 +5487,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
vfio_fds: None,
iommufd_fd: None,
resume: false,
zone_updates: vec![],
};
assert_eq!(
invalid_restore_mode.validate(&snapshot_vm_config),
@@ -5541,6 +5561,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
}]),
iommufd_fd: Some(6),
resume: false,
zone_updates: vec![],
};
valid_config.validate(&snapshot_vm_config).unwrap();

View File

@@ -3082,9 +3082,10 @@ impl RequestHandler for Vmm {
.map_err(MigratableError::MigrateReceive)?;
info!(
"Receiving migration: receiver_url={},tls={}",
"Receiving migration: receiver_url={},tls={},zone_updates={:?}",
receive_data_migration.receiver_url,
receive_data_migration.tls_dir.is_some()
receive_data_migration.tls_dir.is_some(),
receive_data_migration.zone_updates,
);
let mut listener = transport::receive_migration_listener(
@@ -3927,6 +3928,7 @@ mod unit_tests {
memory_mode: MigrationMode::default(),
vfio_fds,
iommufd_fd,
zone_updates: vec![],
}
}