diff options
Diffstat (limited to 'crates/hule-vmm/tests/qemu_lifecycle.rs')
| -rw-r--r-- | crates/hule-vmm/tests/qemu_lifecycle.rs | 136 | +136 −0 |
1 files changed, 136 insertions, 0 deletions
diff --git a/crates/hule-vmm/tests/qemu_lifecycle.rs b/crates/hule-vmm/tests/qemu_lifecycle.rs new file mode 100644 --- /dev/null +++ b/crates/hule-vmm/tests/qemu_lifecycle.rs @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2026 Nikolay Govorov +// SPDX-License-Identifier: Apache-2.0 + +//! Boots a real qemu VM against the repo's alpine fixture and drives it +//! through lifecycle, QGA exec, and serial-console methods. Needs qemu-system-x86_64 and +//! `images/alpine/3.24/amd64` on disk; `#[ignore]`d so a normal `cargo +//! test` run doesn't depend on either. + +use std::path::Path; +use std::time::Duration; + +use hule_image::MachineImage; +use hule_vmm::backend::qemu::QemuHypervisor; +use hule_vmm::{Hypervisor, MachineId, Settings, State}; +use uuid::Uuid; + +#[tokio::test] +#[ignore = "needs qemu-system-x86_64 and the alpine fixture on disk"] +async fn pause_unpause_restart_and_kill_track_a_real_qemu() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../images/alpine/3.24/amd64"); + let config = std::fs::read_to_string(fixture.join("config.json")) + .expect("read fixture config.json") + .replace( + r#"{ "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }"#, + r#"{ "type": "qga" }"#, + ); + let image = MachineImage::from_json(config.as_bytes()).expect("valid fixture config.json"); + + let scratch = std::env::temp_dir().join(format!("hule-vmm-test-{}", std::process::id())); + std::fs::create_dir_all(&scratch).unwrap(); + for disk in &image.disks { + let src = fixture.join(&disk.path); + let dest = scratch.join(&disk.path); + std::fs::hard_link(&src, &dest) + .or_else(|_| std::fs::copy(&src, &dest).map(|_| ())) + .expect("stage disk into scratch dir"); + } + + let hv = QemuHypervisor; + let boot = image + .machine + .boot + .iter() + .find(|b| hv.supports(b)) + .expect("fixture declares a boot protocol qemu supports"); + let settings = Settings { + cpu: 1, + ram: 512, + port_forwards: vec![], + }; + + let mut machine = hv + .create( + MachineId(Uuid::now_v7()), + None, + &image, + &scratch, + boot, + &settings, + ) + .await + .expect("create"); + + machine.start().await.expect("start"); + + let output = machine + .exec(&["/bin/echo".into(), "hello from qga".into()]) + .await + .expect("qga exec"); + assert!(output.status.success()); + assert_eq!(output.stdout, b"hello from qga\n"); + assert!(!machine.logs().await.expect("serial logs").is_empty()); + drop(machine.attach().await.expect("attach serial console")); + + assert_eq!(machine.state().await, State::Running); + + machine.pause().await.expect("pause"); + assert_eq!(machine.state().await, State::Paused); + + machine.unpause().await.expect("unpause"); + assert_eq!(machine.state().await, State::Running); + + machine.restart().await.expect("restart"); + tokio::time::sleep(Duration::from_millis(500)).await; + assert_eq!(machine.state().await, State::Running); + + let stats = machine.stats().await.expect("stats"); + assert!(stats.cpu_time_ns > 0); + assert!(stats.memory_bytes > 0); + + let mut new_settings = machine.settings(); + new_settings.ram = 384; + machine + .update(new_settings) + .await + .expect("update (balloon)"); + assert_eq!(machine.settings().ram, 384); + + machine.kill().await.expect("kill"); + assert!(matches!(machine.state().await, State::Exited(_))); + + let _ = std::fs::remove_dir_all(&scratch); +} + +/// QMP is internal to the qemu backend, so a black-box image is valid. +#[tokio::test] +async fn create_accepts_an_image_without_access() { + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../images/alpine/3.24/amd64"); + let config = std::fs::read(fixture.join("config.json")).expect("read fixture config.json"); + let mut image = MachineImage::from_json(&config).expect("valid fixture config.json"); + image.machine.access.clear(); + + let hv = QemuHypervisor; + let boot = image + .machine + .boot + .iter() + .find(|b| hv.supports(b)) + .expect("fixture declares a boot protocol qemu supports"); + let settings = Settings { + cpu: 1, + ram: 512, + port_forwards: vec![], + }; + + hv.create( + MachineId(Uuid::now_v7()), + None, + &image, + &fixture, + boot, + &settings, + ) + .await + .expect("black-box images don't need to declare qemu's QMP channel"); +} |
