From 8c71a0d8214c96aa8d5fd21f0dcac785f2d61168 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Thu, 25 Jun 2026 14:34:50 +0200 Subject: [PATCH] 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 On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster --- vmm/src/lib.rs | 122 ++++++++++++++++----------------- vmm/src/memory_manager.rs | 36 ++++++---- vmm/src/migration/mod.rs | 26 ++++--- vmm/src/migration/transport.rs | 26 +++---- vmm/src/vm.rs | 37 +++++----- 5 files changed, 130 insertions(+), 117 deletions(-) diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 8575b75e1..844621952 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -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 = 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( diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index f0faafa8e..44b0ccda3 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -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 { 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) => { diff --git a/vmm/src/migration/mod.rs b/vmm/src/migration/mod.rs index 2d94a3136..1e52f8420 100644 --- a/vmm/src/migration/mod.rs +++ b/vmm/src/migration/mod.rs @@ -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 result::Result { @@ -72,14 +76,18 @@ pub fn recv_vm_state(source_url: &str) -> result::Result result::Result { diff --git a/vmm/src/migration/transport.rs b/vmm/src/migration/transport.rs index 3dfc380bd..226b51bd3 100644 --- a/vmm/src/migration/transport.rs +++ b/vmm/src/migration/transport.rs @@ -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 { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 84ae1418e..afbb135b9 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -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) {