From eb1c64e4f01c9dd882e623795b139a129691ccfa Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 11 Jun 2026 15:35:28 -0700 Subject: [PATCH] tests: integration: assert same-host pause/resume keeps aarch64 clock Add an aarch64 test that pauses a running VM, waits out an interval, and resumes it on the same host, then asserts the guest wall clock still matches the host. On aarch64 the architected counter free-runs across the pause, so the guest self-corrects. The downtime and skew tolerance are shared with the snapshot clock test. x86_64 has its own kvmclock path and is covered by the snapshot clock test. Signed-off-by: Atish Patra --- cloud-hypervisor/tests/integration.rs | 64 ++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 8fd4ef644..7e93d1494 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -8020,6 +8020,66 @@ mod ivshmem { ); } + // aarch64 same-host pause/resume must keep the guest clock correct: the + // architected counter free-runs across the pause, so the resume clock path + // must not perturb it. The test network is isolated, so the guest cannot + // NTP-correct itself -- any drift would be the pause path's doing. x86_64 + // has its own kvmclock path and is covered separately. + #[test] + #[cfg(all(not(feature = "mshv"), target_arch = "aarch64"))] + fn test_pause_resume_guest_time() { + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let api_socket = temp_api_path(&guest.tmp_dir); + let kernel_path = direct_kernel_boot_path(); + + let mut child = GuestCommand::new(&guest) + .args(["--api-socket", &api_socket]) + .args(["--cpus", "boot=2"]) + .args(["--memory", "size=1G"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .default_disks() + .args(["--net", guest.default_net_string().as_str()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .capture_output() + .spawn() + .unwrap(); + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + // Pause for an off-host interval, then resume on the same host. + assert!(remote_command(&api_socket, "pause", None)); + thread::sleep(Duration::from_secs( + snapshot_restore_common::CLOCK_DOWNTIME_SECS, + )); + assert!(remote_command(&api_socket, "resume", None)); + + // The counter advanced across the pause, so the guest wall clock must + // still match the host. + let host_secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + let guest_secs = guest + .ssh_command("date -u +%s") + .unwrap() + .trim() + .parse::() + .unwrap(); + let skew = (host_secs - guest_secs).abs(); + assert!( + skew <= snapshot_restore_common::CLOCK_SKEW_TOLERANCE_SECS, + "guest clock is {skew}s from host after pause/resume \ + (host={host_secs}, guest={guest_secs})" + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + handle_child_output(r, &output); + } + #[test] #[cfg(not(feature = "mshv"))] fn test_snapshot_restore_uffd() { @@ -8067,8 +8127,8 @@ mod snapshot_restore_common { // Off-host interval simulated between snapshot and restore, and the maximum // guest-vs-host clock skew tolerated afterwards. The interval must exceed the // tolerance so a guest that fails to advance on restore is caught. - const CLOCK_DOWNTIME_SECS: u64 = 30; - const CLOCK_SKEW_TOLERANCE_SECS: i64 = 15; + pub(crate) const CLOCK_DOWNTIME_SECS: u64 = 30; + pub(crate) const CLOCK_SKEW_TOLERANCE_SECS: i64 = 15; pub(crate) fn snapshot_and_check_events( api_socket: &str,