From 7918db346859ee64690cdb91767dd394c1e07fa1342e25accaa70a8bd6500897 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Sun, 26 Oct 2025 18:17:02 +0100 Subject: [PATCH] Enhance hypervisor image handling with dynamic format detection and raw conversion This commit improves the hypervisor by: - Adding support for detecting base image formats using `qemu-img info`. - Dynamically setting the base image format for overlay creation. - Automatically converting non-raw images to raw format for bhyve compatibility. - Updating `Cargo.toml` to include `serde_json` for JSON parsing. - Modifying default working directory logic for `ZonesHypervisor`. --- crates/orchestrator/Cargo.toml | 1 + crates/orchestrator/src/hypervisor.rs | 84 +++++++++++++++++++++++++-- examples/orchestrator-image-map.yaml | 2 +- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index 95f8e43..02b400c 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -15,6 +15,7 @@ thiserror = "1" tracing = "0.1" tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal", "fs", "io-util"] } serde = { version = "1", features = ["derive"] } +serde_json = "1" serde_yaml = "0.9" config = { version = "0.14", default-features = false, features = ["yaml"] } reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "gzip", "brotli", "zstd"] } diff --git a/crates/orchestrator/src/hypervisor.rs b/crates/orchestrator/src/hypervisor.rs index 7695e98..426900c 100644 --- a/crates/orchestrator/src/hypervisor.rs +++ b/crates/orchestrator/src/hypervisor.rs @@ -222,14 +222,37 @@ impl Hypervisor for LibvirtHypervisor { let base = spec.image_path.clone(); let overlay = overlay.clone(); move || -> miette::Result<()> { + // Detect base image format to set -F accordingly (raw or qcow2) + let base_fmt_out = std::process::Command::new("qemu-img") + .args(["info", "--output=json"]) + .arg(&base) + .output() + .map_err(|e| miette::miette!("qemu-img not found or failed: {e}"))?; + if !base_fmt_out.status.success() { + return Err(miette::miette!( + "qemu-img info failed: {}", + String::from_utf8_lossy(&base_fmt_out.stderr) + )); + } + let base_fmt: String = { + let v: serde_json::Value = serde_json::from_slice(&base_fmt_out.stdout) + .map_err(|e| miette::miette!("parse qemu-img info json failed: {e}"))?; + v.get("format") + .and_then(|f| f.as_str()) + .unwrap_or("raw") + .to_string() + }; + let out = Command::new("qemu-img") - .args(["create","-f","qcow2","-F","qcow2","-b"]) + .args(["create","-f","qcow2","-F"]) + .arg(&base_fmt) + .args(["-b"]) .arg(&base) .arg(&overlay) .arg(&size_arg) .output() .map_err(|e| miette::miette!("qemu-img not found or failed: {e}"))?; - if !out.status.success() { return Err(miette::miette!("qemu-img failed: {}", String::from_utf8_lossy(&out.stderr))); } + if !out.status.success() { return Err(miette::miette!("qemu-img create failed: {}", String::from_utf8_lossy(&out.stderr))); } Ok(()) } }).await.into_diagnostic()??; @@ -361,9 +384,62 @@ pub struct ZonesHypervisor; #[async_trait] impl Hypervisor for ZonesHypervisor { async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result { - warn!(label = %spec.label, "zones hypervisor not yet implemented; returning noop-like handle"); + use std::process::Command; let id = format!("zone-{}", ctx.request_id); - Ok(VmHandle { id, backend: BackendTag::Zones, work_dir: std::env::temp_dir().join("solstice-zones"), overlay_path: None, seed_iso_path: None }) + // Create working directory under /var/lib/solstice-ci if possible + let work_dir = { + let base = std::path::Path::new("/var/lib/solstice-ci"); + let dir = if base.exists() && base.is_dir() && std::fs::metadata(base).is_ok() { + base.join(&id) + } else { + std::env::temp_dir().join("solstice-zones").join(&id) + }; + let _ = std::fs::create_dir_all(&dir); + #[cfg(unix)] + let _ = std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)); + dir + }; + + // Detect base image format + let base = spec.image_path.clone(); + let base_fmt = tokio::task::spawn_blocking(move || -> miette::Result { + let out = Command::new("qemu-img") + .args(["info", "--output=json"]).arg(&base) + .output() + .map_err(|e| miette::miette!("qemu-img not found or failed: {e}"))?; + if !out.status.success() { + return Err(miette::miette!("qemu-img info failed: {}", String::from_utf8_lossy(&out.stderr))); + } + let v: serde_json::Value = serde_json::from_slice(&out.stdout) + .map_err(|e| miette::miette!("parse qemu-img info json failed: {e}"))?; + Ok(v.get("format").and_then(|f| f.as_str()).unwrap_or("raw").to_string()) + }).await.into_diagnostic()??; + + // Ensure raw image for bhyve: convert if needed + let raw_path = if base_fmt != "raw" { + let out_path = work_dir.join("disk.raw"); + let src = spec.image_path.clone(); + let dst = out_path.clone(); + tokio::task::spawn_blocking(move || -> miette::Result<()> { + let out = Command::new("qemu-img") + .args(["convert", "-O", "raw"]) + .arg(&src) + .arg(&dst) + .output() + .map_err(|e| miette::miette!("qemu-img convert failed to start: {e}"))?; + if !out.status.success() { + return Err(miette::miette!("qemu-img convert failed: {}", String::from_utf8_lossy(&out.stderr))); + } + Ok(()) + }).await.into_diagnostic()??; + info!(label = %spec.label, src = ?spec.image_path, out = ?out_path, "converted image to raw for bhyve"); + out_path + } else { + spec.image_path.clone() + }; + + // Seed ISO creation left to future; for now, return handle with path in overlay_path + Ok(VmHandle { id, backend: BackendTag::Zones, work_dir, overlay_path: Some(raw_path), seed_iso_path: None }) } async fn start(&self, _vm: &VmHandle) -> Result<()> { Ok(()) } async fn stop(&self, _vm: &VmHandle, _t: Duration) -> Result<()> { Ok(()) } diff --git a/examples/orchestrator-image-map.yaml b/examples/orchestrator-image-map.yaml index 6117b04..86fde8f 100644 --- a/examples/orchestrator-image-map.yaml +++ b/examples/orchestrator-image-map.yaml @@ -45,7 +45,7 @@ images: # Example Ubuntu image for libvirt/KVM on Linux hosts (commented by default) ubuntu-22.04: source: https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img - local_path: /var/lib/libvirt/images/ubuntu-22.04-base.qcow2 + local_path: /var/lib/solstice/images/ubuntu-22.04-base.qcow2 decompress: none nocloud: true defaults: