From d9395b9773839e44654b2b2f4b1f39fcca96baf7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sat, 25 Apr 2026 18:43:25 +0100 Subject: [PATCH] tests: Move live migration helpers to common::utils Move the helper methods used for live migration to the common utils (like many other tests use). Assisted-by: Claude:Opus-4.7 Signed-off-by: Rob Bradford --- cloud-hypervisor/tests/common/utils.rs | 137 ++++++++++++++++++++++- cloud-hypervisor/tests/integration.rs | 145 +------------------------ 2 files changed, 139 insertions(+), 143 deletions(-) diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index 1821575d4..a1bbb743e 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use std::process::{Child, Command}; +use std::process::{Child, Command, Stdio}; use std::string::String; use std::sync::mpsc; use std::sync::mpsc::Receiver; @@ -14,6 +14,7 @@ use std::{cmp, fs, io, thread}; use test_infra::*; use vmm_sys_util::tempdir::TempDir; +use wait_timeout::ChildExt; const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; // 10MB is our maximum accepted overhead. @@ -1059,3 +1060,137 @@ pub(crate) fn bdf_from_hotplug_response( (segment_id, bus_id, device_id, function_id) } + +pub(crate) fn start_live_migration( + migration_socket: &str, + src_api_socket: &str, + dest_api_socket: &str, + local: bool, +) -> bool { + // Start to receive migration from the destination VM + let mut receive_migration = Command::new(clh_command("ch-remote")) + .args([ + &format!("--api-socket={dest_api_socket}"), + "receive-migration", + &format! {"unix:{migration_socket}"}, + ]) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + // Give it '1s' to make sure the 'migration_socket' file is properly created + thread::sleep(std::time::Duration::new(1, 0)); + // Start to send migration from the source VM + + let args = [ + format!("--api-socket={}", &src_api_socket), + "send-migration".to_string(), + format!( + "destination_url=unix:{migration_socket},local={}", + if local { "on" } else { "off" } + ), + ] + .to_vec(); + + let mut send_migration = Command::new(clh_command("ch-remote")) + .args(&args) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + // The 'send-migration' command should be executed successfully within the given timeout + let send_success = if let Some(status) = send_migration + .wait_timeout(std::time::Duration::from_secs(30)) + .unwrap() + { + status.success() + } else { + false + }; + + if !send_success { + let _ = send_migration.kill(); + let output = send_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'send_migration' output ==== \ + \n\n---stdout---\n{}\n\n---stderr---\n{} \ + \n\n==== End 'send_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + // The 'receive-migration' command should be executed successfully within the given timeout + let receive_success = if let Some(status) = receive_migration + .wait_timeout(std::time::Duration::from_secs(30)) + .unwrap() + { + status.success() + } else { + false + }; + + if !receive_success { + let _ = receive_migration.kill(); + let output = receive_migration.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'receive_migration' output ==== \ + \n\n---stdout---\n{}\n\n---stderr---\n{} \ + \n\n==== End 'receive_migration' output ====\n\n", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + send_success && receive_success +} + +pub(crate) fn print_and_panic( + src_vm: Child, + dest_vm: Child, + ovs_vm: Option, + message: &str, +) -> ! { + let mut src_vm = src_vm; + let mut dest_vm = dest_vm; + + let _ = src_vm.kill(); + let src_output = src_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'source_vm' stdout ====\n\n{}\n\n==== End 'source_vm' stdout ====", + String::from_utf8_lossy(&src_output.stdout) + ); + eprintln!( + "\n\n==== Start 'source_vm' stderr ====\n\n{}\n\n==== End 'source_vm' stderr ====", + String::from_utf8_lossy(&src_output.stderr) + ); + let _ = dest_vm.kill(); + let dest_output = dest_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", + String::from_utf8_lossy(&dest_output.stdout) + ); + eprintln!( + "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", + String::from_utf8_lossy(&dest_output.stderr) + ); + + if let Some(ovs_vm) = ovs_vm { + let mut ovs_vm = ovs_vm; + let _ = ovs_vm.kill(); + let ovs_output = ovs_vm.wait_with_output().unwrap(); + eprintln!( + "\n\n==== Start 'ovs_vm' stdout ====\n\n{}\n\n==== End 'ovs_vm' stdout ====", + String::from_utf8_lossy(&ovs_output.stdout) + ); + eprintln!( + "\n\n==== Start 'ovs_vm' stderr ====\n\n{}\n\n==== End 'ovs_vm' stderr ====", + String::from_utf8_lossy(&ovs_output.stderr) + ); + + cleanup_ovs_dpdk(); + } + + panic!("Test failed: {message}") +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d0a7b49c8..e860af7d4 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -6351,19 +6351,14 @@ mod ivshmem { ); assert!( - live_migration::start_live_migration( - &migration_socket, - &src_api_socket, - &dest_api_socket, - local - ), + start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local), "Unsuccessful command: 'send-migration' or 'receive-migration'." ); }); // Check and report any errors occurred during the live-migration if r.is_err() { - live_migration::print_and_panic( + print_and_panic( src_child, dest_child, None, @@ -6374,7 +6369,7 @@ mod ivshmem { // Check the source vm has been terminated successful (give it '3s' to settle) thread::sleep(std::time::Duration::new(3, 0)); if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) { - live_migration::print_and_panic( + print_and_panic( src_child, dest_child, None, @@ -9170,140 +9165,6 @@ mod live_migration { use crate::*; - pub fn start_live_migration( - migration_socket: &str, - src_api_socket: &str, - dest_api_socket: &str, - local: bool, - ) -> bool { - // Start to receive migration from the destination VM - let mut receive_migration = Command::new(clh_command("ch-remote")) - .args([ - &format!("--api-socket={dest_api_socket}"), - "receive-migration", - &format! {"unix:{migration_socket}"}, - ]) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - // Give it '1s' to make sure the 'migration_socket' file is properly created - thread::sleep(std::time::Duration::new(1, 0)); - // Start to send migration from the source VM - - let args = [ - format!("--api-socket={}", &src_api_socket), - "send-migration".to_string(), - format!( - "destination_url=unix:{migration_socket},local={}", - if local { "on" } else { "off" } - ), - ] - .to_vec(); - - let mut send_migration = Command::new(clh_command("ch-remote")) - .args(&args) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - - // The 'send-migration' command should be executed successfully within the given timeout - let send_success = if let Some(status) = send_migration - .wait_timeout(std::time::Duration::from_secs(30)) - .unwrap() - { - status.success() - } else { - false - }; - - if !send_success { - let _ = send_migration.kill(); - let output = send_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'send_migration' output ==== \ - \n\n---stdout---\n{}\n\n---stderr---\n{} \ - \n\n==== End 'send_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } - - // The 'receive-migration' command should be executed successfully within the given timeout - let receive_success = if let Some(status) = receive_migration - .wait_timeout(std::time::Duration::from_secs(30)) - .unwrap() - { - status.success() - } else { - false - }; - - if !receive_success { - let _ = receive_migration.kill(); - let output = receive_migration.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'receive_migration' output ==== \ - \n\n---stdout---\n{}\n\n---stderr---\n{} \ - \n\n==== End 'receive_migration' output ====\n\n", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } - - send_success && receive_success - } - - pub fn print_and_panic( - src_vm: Child, - dest_vm: Child, - ovs_vm: Option, - message: &str, - ) -> ! { - let mut src_vm = src_vm; - let mut dest_vm = dest_vm; - - let _ = src_vm.kill(); - let src_output = src_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'source_vm' stdout ====\n\n{}\n\n==== End 'source_vm' stdout ====", - String::from_utf8_lossy(&src_output.stdout) - ); - eprintln!( - "\n\n==== Start 'source_vm' stderr ====\n\n{}\n\n==== End 'source_vm' stderr ====", - String::from_utf8_lossy(&src_output.stderr) - ); - let _ = dest_vm.kill(); - let dest_output = dest_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====", - String::from_utf8_lossy(&dest_output.stdout) - ); - eprintln!( - "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====", - String::from_utf8_lossy(&dest_output.stderr) - ); - - if let Some(ovs_vm) = ovs_vm { - let mut ovs_vm = ovs_vm; - let _ = ovs_vm.kill(); - let ovs_output = ovs_vm.wait_with_output().unwrap(); - eprintln!( - "\n\n==== Start 'ovs_vm' stdout ====\n\n{}\n\n==== End 'ovs_vm' stdout ====", - String::from_utf8_lossy(&ovs_output.stdout) - ); - eprintln!( - "\n\n==== Start 'ovs_vm' stderr ====\n\n{}\n\n==== End 'ovs_vm' stderr ====", - String::from_utf8_lossy(&ovs_output.stderr) - ); - - cleanup_ovs_dpdk(); - } - - panic!("Test failed: {message}") - } - // This test exercises the local live-migration between two Cloud Hypervisor VMs on the // same host. It ensures the following behaviors: // 1. The source VM is up and functional (including various virtio-devices are working properly);