From e034567690549a79b3d4c0e5823fba020cde6e07 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 1 Jun 2026 20:36:05 +0200 Subject: [PATCH] vmm: init migration worker module This initializes the module and the thread that will handle (control) the migration. This introduces the new types without the necessary wiring. On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster --- vmm/src/lib.rs | 2 + vmm/src/migration_worker.rs | 162 ++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 vmm/src/migration_worker.rs diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 06549c93e..507648728 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -89,6 +89,8 @@ pub mod landlock; pub mod memory_manager; pub mod migration; pub mod migration_transport; +#[expect(unused)] +mod migration_worker; mod pci_segment; pub mod seccomp_filters; mod serial_manager; diff --git a/vmm/src/migration_worker.rs b/vmm/src/migration_worker.rs new file mode 100644 index 000000000..986615aee --- /dev/null +++ b/vmm/src/migration_worker.rs @@ -0,0 +1,162 @@ +// Copyright © 2026 Cyberus Technology GmbH +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Asynchronous migration worker. +//! +//! The migration worker owns the [`Vm`] while migration is in progress, so the +//! VMM cannot run VM lifecycle operations concurrently. To keep the VM +//! recoverable when thread creation fails, [`MigrationWorker::spawn`] creates +//! the thread before transferring the VM through a zero-capacity +//! (rendezvous-channel). If spawning fails, the VM is returned to the caller in +//! [`MigrationWorkerSpawnError`]. + +use std::fmt::{Debug, Formatter}; +#[cfg(all(feature = "kvm", target_arch = "x86_64"))] +use std::sync::Arc; +use std::sync::mpsc::Receiver; +use std::thread; +use std::thread::JoinHandle; + +use event_monitor::event; +use log::warn; +use vm_migration::MigratableError; +use vmm_sys_util::eventfd::EventFd; + +use crate::Vmm; +use crate::api::VmSendMigrationData; +use crate::vm::{Vm, VmState}; + +#[derive(thiserror::Error)] +#[error("Migration worker could not be spawned: {spawn_error}")] +pub struct MigrationWorkerSpawnError { + pub spawn_error: std::io::Error, + pub vm: Vm, +} + +impl Debug for MigrationWorkerSpawnError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MigrationWorkerSpawnError") + .field("spawn_error", &self.spawn_error) + .field("vm", &"") + .finish() + } +} + +pub struct MigrationWorkerHandle { + handle: Option>, +} + +impl MigrationWorkerHandle { + pub fn join(mut self) -> MigrationWorkerResult { + self.handle + .take() + .expect("should have thread") + .join() + .expect("should join migration worker gracefully") + } +} + +impl Drop for MigrationWorkerHandle { + fn drop(&mut self) { + if let Some(handle) = self.handle.take() { + warn!("Migration worker wasn't cleaned up explicitly via join()"); + handle.join().expect("should not be joined already"); + } + } +} + +pub struct MigrationWorker { + // Keep the VM out of the thread closure until spawning succeeds. + vm_receiver: Receiver, + check_migration_evt: EventFd, + config: VmSendMigrationData, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + hypervisor: Arc, + initial_vm_state: VmState, +} + +impl MigrationWorker { + /// Drives the migration from its start to its end (success, cancellation, + /// failure) + fn run(self) -> MigrationWorkerResult { + let mut vm = self.vm_receiver.recv().expect("VMM should send VM"); + + event!("vm", "migration-started"); + let res = Vmm::send_migration( + &mut vm, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + self.hypervisor.as_ref(), + &self.config, + self.initial_vm_state, + ) + .inspect(|_| event!("vm", "migration-finished")) + .inspect_err(|_| event!("vm", "migration-failed")); + + // Notify VMM thread to check migration result. + self.check_migration_evt.write(1).unwrap(); + + MigrationWorkerResult { + vm, + migration_result: res, + initial_vm_state: self.initial_vm_state, + } + } + + /// Spawns a worker to coordinate the migration. + // All code paths need special care to prevent any panic and thus losing the + // VM in case of failure. + #[expect(clippy::result_large_err)] + pub fn spawn( + vm: Vm, + check_migration_evt: EventFd, + config: VmSendMigrationData, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] hypervisor: Arc< + dyn hypervisor::Hypervisor, + >, + initial_vm_state: VmState, + ) -> Result { + let (vm_sender, vm_receiver) = std::sync::mpsc::sync_channel(0); + let worker = MigrationWorker { + vm_receiver, + check_migration_evt, + config, + #[cfg(all(feature = "kvm", target_arch = "x86_64"))] + hypervisor, + initial_vm_state, + }; + + let inner_handle = match thread::Builder::new() + .name("migration-worker".into()) + .spawn(move || worker.run()) + { + Ok(inner_handle) => { + // The zero-capacity (rendezvous-channel) confirms the worker + // has taken VM ownership. + vm_sender + .send(vm) + .expect("thread should be waiting to receive VM"); + inner_handle + } + Err(e) => return Err(MigrationWorkerSpawnError { spawn_error: e, vm }), + }; + + Ok(MigrationWorkerHandle { + handle: Some(inner_handle), + }) + } +} + +/// Return value of [`MigrationWorker`]. +pub struct MigrationWorkerResult { + /// The VM that was migrated. + /// + /// If `migration_result` is `Ok`, the VM is paused and can be deleted. + /// If `migration_result` is `Err`, the VM can be resumed and given back to + /// the VMM. + pub vm: Vm, + /// The result of [`Vmm::send_migration`]. + pub migration_result: Result<(), MigratableError>, + pub initial_vm_state: VmState, +}