vmm: make migration errors more helpful

Keep the original error sources while adding operation context to the
straightforward migration send and receive paths. This keeps up a
chain of errors that can be printed nicely and in a helpful way.

This commit does that for all MigratableError-occurences where this
change is easily applicable.

Co-authored-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
Philipp Schuster
2026-06-25 14:34:50 +02:00
committed by Bo Chen
parent 05f026440a
commit 8c71a0d821
5 changed files with 130 additions and 117 deletions

View File

@@ -922,9 +922,10 @@ impl Vmm {
) -> result::Result<(u32, File), MigratableError> {
if let SocketStream::Unix(unix_socket) = socket {
let mut buf = [0u8; 4];
let (_, file) = unix_socket.recv_with_fd(&mut buf).map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error receiving slot from socket: {e}"))
})?;
let (_, file) = unix_socket
.recv_with_fd(&mut buf)
.context("Error receiving slot from socket")
.map_err(MigratableError::MigrateReceive)?;
file.ok_or_else(|| MigratableError::MigrateReceive(anyhow!("Failed to receive socket")))
.map(|file| (u32::from_le_bytes(buf), file))
@@ -1148,10 +1149,9 @@ impl Vmm {
.read_exact(&mut data)
.map_err(MigratableError::MigrateSocket)?;
let vm_migration_config: VmMigrationConfig =
serde_json::from_slice(&data).map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error deserialising config: {e}"))
})?;
let vm_migration_config: VmMigrationConfig = serde_json::from_slice(&data)
.context("Error deserialising config")
.map_err(MigratableError::MigrateReceive)?;
// Eager prefault populates memory before UFFD is registered, so those
// pages never fault and are never served. Reject postcopy+prefault
@@ -1179,9 +1179,11 @@ impl Vmm {
let config = vm_migration_config.vm_config.clone();
self.vm_config = Some(vm_migration_config.vm_config);
self.console_info = Some(pre_create_console_devices(self).map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error creating console devices: {e:?}"))
})?);
self.console_info = Some(
pre_create_console_devices(self)
.context("Error creating console devices")
.map_err(MigratableError::MigrateReceive)?,
);
if self
.vm_config
@@ -1192,9 +1194,9 @@ impl Vmm {
.landlock_enable
{
let mut config = self.vm_config.as_ref().unwrap().lock().unwrap();
apply_landlock(&mut config).map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error applying landlock: {e:?}"))
})?;
apply_landlock(&mut config)
.context("Error applying landlock")
.map_err(MigratableError::MigrateReceive)?;
}
let vm = Vm::create_hypervisor_vm(
@@ -1227,11 +1229,8 @@ impl Vmm {
Some(&vm_migration_config.memory_manager_data),
existing_memory_files,
)
.map_err(|e| {
MigratableError::MigrateReceive(anyhow!(
"Error creating MemoryManager from snapshot: {e:?}"
))
})?;
.context("Error creating MemoryManager from snapshot")
.map_err(MigratableError::MigrateReceive)?;
Ok(memory_manager)
}
@@ -1260,27 +1259,37 @@ impl Vmm {
socket
.read_exact(&mut data)
.map_err(MigratableError::MigrateSocket)?;
serde_json::from_slice(&data).map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error deserialising snapshot: {e}"))
})
serde_json::from_slice(&data)
.context("Error deserialising snapshot")
.map_err(MigratableError::MigrateReceive)
})?;
let exit_evt = self.exit_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning exit EventFd: {e}"))
})?;
let reset_evt = self.reset_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning reset EventFd: {e}"))
})?;
let guest_exit_evt = self.guest_exit_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning guest exit EventFd: {e}"))
})?;
let exit_evt = self
.exit_evt
.try_clone()
.context("Error cloning exit EventFd")
.map_err(MigratableError::MigrateReceive)?;
let reset_evt = self
.reset_evt
.try_clone()
.context("Error cloning reset EventFd")
.map_err(MigratableError::MigrateReceive)?;
let guest_exit_evt = self
.guest_exit_evt
.try_clone()
.context("Error cloning guest exit EventFd")
.map_err(MigratableError::MigrateReceive)?;
#[cfg(feature = "guest_debug")]
let debug_evt = self.vm_debug_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning debug EventFd: {e}"))
})?;
let activate_evt = self.activate_evt.try_clone().map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {e}"))
})?;
let debug_evt = self
.vm_debug_evt
.try_clone()
.context("Error cloning debug EventFd")
.map_err(MigratableError::MigrateReceive)?;
let activate_evt = self
.activate_evt
.try_clone()
.context("Error cloning activate EventFd")
.map_err(MigratableError::MigrateReceive)?;
let (vm, restore_duration) = measure_ok(|| {
#[cfg(not(target_arch = "riscv64"))]
@@ -1585,9 +1594,8 @@ impl Vmm {
profile,
},
)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error generating common cpuid': {e:?}"))
})?
.context("Error generating common cpuid")
.map_err(MigratableError::MigrateSend)?
};
if send_data_migration.local {
@@ -1671,20 +1679,15 @@ impl Vmm {
let guest_memory = vm.guest_memory();
// Build the seccomp filter on the parent thread so any failure aborts
// the migration before the serve thread is spawned.
let seccomp_filter = get_seccomp_filter(
seccomp_action,
Thread::MigrateSendPostcopy,
None,
)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!("creating postcopy serve seccomp filter: {e}"))
})?;
let seccomp_filter =
get_seccomp_filter(seccomp_action, Thread::MigrateSendPostcopy, None)
.context("creating postcopy serve seccomp filter")
.map_err(MigratableError::MigrateSend)?;
let handle = thread::Builder::new()
.name("migrate-send-postcopy".to_owned())
.spawn(move || Self::serve_postcopy(seccomp_filter, fault_stream, guest_memory))
.map_err(|e| {
MigratableError::MigrateSend(anyhow!("spawning postcopy serve thread: {e}"))
})?;
.context("spawning postcopy serve thread")
.map_err(MigratableError::MigrateSend)?;
Some(handle)
} else {
None
@@ -1771,9 +1774,9 @@ impl Vmm {
// seccomp is disabled (SeccompAction::Allow), in which case there is
// nothing to apply.
if !seccomp_filter.is_empty() {
apply_filter(&seccomp_filter).map_err(|e| {
MigratableError::MigrateSend(anyhow!("applying postcopy serve seccomp filter: {e}"))
})?;
apply_filter(&seccomp_filter)
.context("applying postcopy serve seccomp filter")
.map_err(MigratableError::MigrateSend)?;
}
let mut buf: Vec<u8> = Vec::new();
@@ -1873,15 +1876,12 @@ impl Vmm {
profile: vm_config.cpus.profile,
},
)
.map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {e:?}"))
})?
.context("Error generating common cpuid")
.map_err(MigratableError::MigrateReceive)?
};
arch::CpuidFeatureEntry::check_cpuid_compatibility(src_vm_cpuid, dest_cpuid).map_err(|e| {
MigratableError::MigrateReceive(anyhow!(
"Error checking cpu feature compatibility': {e:?}"
))
})
arch::CpuidFeatureEntry::check_cpuid_compatibility(src_vm_cpuid, dest_cpuid)
.context("Error checking cpu feature compatibility")
.map_err(MigratableError::MigrateReceive)
}
fn vm_restore(

View File

@@ -20,7 +20,7 @@ use std::sync::{Arc, Barrier, Mutex};
use std::{cmp, ffi, panic, result, thread, time};
use acpi_tables::{Aml, aml};
use anyhow::anyhow;
use anyhow::{Context, anyhow};
use arch::{RegionType, layout};
#[cfg(target_arch = "x86_64")]
use devices::ioapic;
@@ -3196,7 +3196,8 @@ impl Transportable for MemoryManager {
.write(true)
.create_new(true)
.open(&memory_file_path)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.with_context(|| format!("Error creating memory snapshot file {memory_file_path:?}"))
.map_err(MigratableError::MigrateSend)?;
let total_len: u64 = self
.snapshot_memory_ranges
@@ -3237,7 +3238,8 @@ impl Transportable for MemoryManager {
file_cursor,
range.length,
)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error writing sparse memory snapshot region")
.map_err(MigratableError::MigrateSend)?;
}
if !wrote_sparse {
@@ -3247,7 +3249,8 @@ impl Transportable for MemoryManager {
// volatile copy.
memory_file
.seek(SeekFrom::Start(file_cursor))
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error seeking memory snapshot file")
.map_err(MigratableError::MigrateSend)?;
let mut offset: u64 = 0;
// Manual partial-write loop preserves the workaround for
// https://github.com/rust-vmm/vm-memory/issues/174
@@ -3258,7 +3261,8 @@ impl Transportable for MemoryManager {
&mut memory_file,
(range.length - offset) as usize,
)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error writing dense memory snapshot region")
.map_err(MigratableError::MigrateSend)?;
offset += bytes_written as u64;
if offset == range.length {
break;
@@ -3280,9 +3284,10 @@ impl Migratable for MemoryManager {
// Just before we do a bulk copy we want to start/clear the dirty log so that
// pages touched during our bulk copy are tracked.
fn start_dirty_log(&mut self) -> result::Result<(), MigratableError> {
self.vm.start_dirty_log().map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error starting VM dirty log {e}"))
})?;
self.vm
.start_dirty_log()
.context("Error starting VM dirty log")
.map_err(MigratableError::MigrateSend)?;
for r in self.guest_memory.memory().iter() {
(**r).bitmap().reset();
@@ -3292,9 +3297,10 @@ impl Migratable for MemoryManager {
}
fn stop_dirty_log(&mut self) -> result::Result<(), MigratableError> {
self.vm.stop_dirty_log().map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error stopping VM dirty log {e}"))
})?;
self.vm
.stop_dirty_log()
.context("Error stopping VM dirty log")
.map_err(MigratableError::MigrateSend)?;
Ok(())
}
@@ -3304,9 +3310,11 @@ impl Migratable for MemoryManager {
fn dirty_log(&mut self) -> result::Result<MemoryRangeTable, MigratableError> {
let mut table = MemoryRangeTable::default();
for r in &self.guest_ram_mappings {
let vm_dirty_bitmap = self.vm.get_dirty_log(r.slot, r.gpa, r.size).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error getting VM dirty log {e}"))
})?;
let vm_dirty_bitmap = self
.vm
.get_dirty_log(r.slot, r.gpa, r.size)
.context("Error getting VM dirty log")
.map_err(MigratableError::MigrateSend)?;
let vmm_dirty_bitmap = match self.guest_memory.memory().find_region(GuestAddress(r.gpa))
{
Some(region) => {

View File

@@ -7,7 +7,7 @@ use std::io::Read;
use std::path::PathBuf;
use std::result;
use anyhow::anyhow;
use anyhow::{Context, anyhow};
use vm_migration::{MigratableError, Snapshot};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
@@ -56,14 +56,18 @@ pub fn recv_vm_config(source_url: &str) -> result::Result<VmConfig, MigratableEr
vm_config_path.push(SNAPSHOT_CONFIG_FILE);
// Try opening the snapshot file
let mut vm_config_file =
File::open(vm_config_path).map_err(|e| MigratableError::MigrateReceive(e.into()))?;
let mut vm_config_file = File::open(&vm_config_path)
.with_context(|| format!("Error opening VM config snapshot file {vm_config_path:?}"))
.map_err(MigratableError::MigrateReceive)?;
let mut bytes = Vec::new();
vm_config_file
.read_to_end(&mut bytes)
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
.with_context(|| format!("Error reading VM config snapshot file {vm_config_path:?}"))
.map_err(MigratableError::MigrateReceive)?;
serde_json::from_slice(&bytes).map_err(|e| MigratableError::MigrateReceive(e.into()))
serde_json::from_slice(&bytes)
.context("Error deserialising VM config snapshot")
.map_err(MigratableError::MigrateReceive)
}
pub fn recv_vm_state(source_url: &str) -> result::Result<Snapshot, MigratableError> {
@@ -72,14 +76,18 @@ pub fn recv_vm_state(source_url: &str) -> result::Result<Snapshot, MigratableErr
vm_state_path.push(SNAPSHOT_STATE_FILE);
// Try opening the snapshot file
let mut vm_state_file =
File::open(vm_state_path).map_err(|e| MigratableError::MigrateReceive(e.into()))?;
let mut vm_state_file = File::open(&vm_state_path)
.with_context(|| format!("Error opening VM state snapshot file {vm_state_path:?}"))
.map_err(MigratableError::MigrateReceive)?;
let mut bytes = Vec::new();
vm_state_file
.read_to_end(&mut bytes)
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
.with_context(|| format!("Error reading VM state snapshot file {vm_state_path:?}"))
.map_err(MigratableError::MigrateReceive)?;
serde_json::from_slice(&bytes).map_err(|e| MigratableError::MigrateReceive(e.into()))
serde_json::from_slice(&bytes)
.context("Error deserialising VM state snapshot")
.map_err(MigratableError::MigrateReceive)
}
pub fn get_vm_snapshot(snapshot: &Snapshot) -> result::Result<VmSnapshot, MigratableError> {

View File

@@ -928,9 +928,9 @@ pub(crate) fn send_migration_socket(
if let Some(address) = destination_url.strip_prefix("tcp:") {
info!("Connecting to TCP socket at {address}");
let socket = TcpStream::connect(address).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {e}"))
})?;
let socket = TcpStream::connect(address)
.context("Error connecting to TCP socket")
.map_err(MigratableError::MigrateSend)?;
if let Some(tls_dir) = tls_dir {
// The address should have been validated by the API using this exact function.
@@ -949,9 +949,9 @@ pub(crate) fn send_migration_socket(
let path = socket_url_to_path(destination_url).map_err(MigratableError::MigrateSend)?;
info!("Connecting to UNIX socket at {path:?}");
let socket = UnixStream::connect(&path).map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error connecting to UNIX socket: {e}"))
})?;
let socket = UnixStream::connect(&path)
.context("Error connecting to UNIX socket")
.map_err(MigratableError::MigrateSend)?;
Ok(SocketStream::Unix(socket))
}
@@ -1091,11 +1091,8 @@ pub(crate) fn send_memory_ranges(
socket,
(range.length - offset) as usize,
)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!(
"Error transferring memory to socket: {e}"
))
})?;
.context("Error transferring memory to socket")
.map_err(MigratableError::MigrateSend)?;
offset += bytes_written as u64;
if offset == range.length {
@@ -1136,11 +1133,8 @@ pub(crate) fn receive_memory_ranges(
socket,
(range.length - offset) as usize,
)
.map_err(|e| {
MigratableError::MigrateReceive(anyhow!(
"Error receiving memory from socket: {e}"
))
})?;
.context("Error receiving memory from socket")
.map_err(MigratableError::MigrateReceive)?;
offset += bytes_read as u64;
if offset == range.length {

View File

@@ -24,7 +24,7 @@ use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{any, cmp, result, str, thread};
use anyhow::anyhow;
use anyhow::{Context, anyhow};
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
use arch::PciSpaceInfo;
#[cfg(not(target_arch = "x86_64"))]
@@ -3019,14 +3019,12 @@ impl Vm {
{
Request::memory_fd(size_of_val(&slot) as u64)
.write_to(socket)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error sending memory fd request: {e}"))
})?;
.context("Error sending memory fd request")
.map_err(MigratableError::MigrateSend)?;
socket
.send_with_fd(&slot.to_le_bytes()[..], fd)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!("Error sending memory fd: {e}"))
})?;
.context("Error sending memory fd")
.map_err(MigratableError::MigrateSend)?;
Response::read_from(socket)?.ok_or_abandon(
socket,
@@ -3358,9 +3356,8 @@ impl Snapshottable for Vm {
profile,
},
)
.map_err(|e| {
MigratableError::MigrateReceive(anyhow!("Error generating common cpuid: {e:?}"))
})?
.context("Error generating common cpuid")
.map_err(MigratableError::MigrateReceive)?
};
let vm_snapshot_state = VmSnapshot {
@@ -3407,15 +3404,18 @@ impl Transportable for Vm {
.write(true)
.create_new(true)
.open(snapshot_config_path)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error creating VM config snapshot file")
.map_err(MigratableError::MigrateSend)?;
// Serialize and write the snapshot config
let vm_config = serde_json::to_string(self.config.lock().unwrap().deref())
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error serializing VM config snapshot")
.map_err(MigratableError::MigrateSend)?;
snapshot_config_file
.write(vm_config.as_bytes())
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error writing VM config snapshot")
.map_err(MigratableError::MigrateSend)?;
let mut snapshot_state_path = url_to_path(destination_url)?;
snapshot_state_path.push(SNAPSHOT_STATE_FILE);
@@ -3426,15 +3426,18 @@ impl Transportable for Vm {
.write(true)
.create_new(true)
.open(snapshot_state_path)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error creating VM state snapshot file")
.map_err(MigratableError::MigrateSend)?;
// Serialize and write the snapshot state
let vm_state =
serde_json::to_vec(snapshot).map_err(|e| MigratableError::MigrateSend(e.into()))?;
let vm_state = serde_json::to_vec(snapshot)
.context("Error serializing VM state snapshot")
.map_err(MigratableError::MigrateSend)?;
snapshot_state_file
.write(&vm_state)
.map_err(|e| MigratableError::MigrateSend(e.into()))?;
.context("Error writing VM state snapshot")
.map_err(MigratableError::MigrateSend)?;
// Tell the memory manager to also send/write its own snapshot.
if let Some(memory_manager_snapshot) = snapshot.snapshots.get(MEMORY_MANAGER_SNAPSHOT_ID) {