mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: move migration modules into folder
On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
committed by
Bo Chen
parent
7c7a827ded
commit
a56594324c
92
vmm/src/migration/mod.rs
Normal file
92
vmm/src/migration/mod.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use vm_migration::{MigratableError, Snapshot};
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
||||
use crate::coredump::GuestDebuggableError;
|
||||
use crate::vm::VmSnapshot;
|
||||
use crate::vm_config::VmConfig;
|
||||
|
||||
pub(crate) mod transport;
|
||||
pub(crate) mod worker;
|
||||
|
||||
pub const SNAPSHOT_STATE_FILE: &str = "state.json";
|
||||
pub const SNAPSHOT_CONFIG_FILE: &str = "config.json";
|
||||
|
||||
pub fn url_to_path(url: &str) -> std::result::Result<PathBuf, MigratableError> {
|
||||
let path: PathBuf = url
|
||||
.strip_prefix("file://")
|
||||
.ok_or_else(|| {
|
||||
MigratableError::MigrateSend(anyhow!("Could not extract path from URL: {url}"))
|
||||
})
|
||||
.map(|s| s.into())?;
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(MigratableError::MigrateSend(anyhow!(
|
||||
"Destination is not a directory: {path:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
||||
pub fn url_to_file(url: &str) -> std::result::Result<PathBuf, GuestDebuggableError> {
|
||||
let file: PathBuf = url
|
||||
.strip_prefix("file://")
|
||||
.ok_or_else(|| {
|
||||
GuestDebuggableError::Coredump(anyhow!("Could not extract file from URL: {url}"))
|
||||
})
|
||||
.map(|s| s.into())?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn recv_vm_config(source_url: &str) -> std::result::Result<VmConfig, MigratableError> {
|
||||
let mut vm_config_path = url_to_path(source_url)?;
|
||||
|
||||
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 bytes = Vec::new();
|
||||
vm_config_file
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
|
||||
|
||||
serde_json::from_slice(&bytes).map_err(|e| MigratableError::MigrateReceive(e.into()))
|
||||
}
|
||||
|
||||
pub fn recv_vm_state(source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut vm_state_path = url_to_path(source_url)?;
|
||||
|
||||
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 bytes = Vec::new();
|
||||
vm_state_file
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
|
||||
|
||||
serde_json::from_slice(&bytes).map_err(|e| MigratableError::MigrateReceive(e.into()))
|
||||
}
|
||||
|
||||
pub fn get_vm_snapshot(snapshot: &Snapshot) -> std::result::Result<VmSnapshot, MigratableError> {
|
||||
if let Some(snapshot_data) = snapshot.snapshot_data.as_ref() {
|
||||
return snapshot_data.to_state();
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find VM config snapshot section"
|
||||
)))
|
||||
}
|
||||
1142
vmm/src/migration/transport.rs
Normal file
1142
vmm/src/migration/transport.rs
Normal file
File diff suppressed because it is too large
Load Diff
162
vmm/src/migration/worker.rs
Normal file
162
vmm/src/migration/worker.rs
Normal file
@@ -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", &"<VM>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MigrationWorkerHandle {
|
||||
handle: Option<JoinHandle<MigrationWorkerResult>>,
|
||||
}
|
||||
|
||||
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<Vm>,
|
||||
check_migration_evt: EventFd,
|
||||
config: VmSendMigrationData,
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
hypervisor: Arc<dyn hypervisor::Hypervisor>,
|
||||
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<MigrationWorkerHandle, MigrationWorkerSpawnError> {
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user