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`.
This commit is contained in:
Till Wegmueller 2025-10-26 18:17:02 +01:00
parent f3831dac4a
commit 7918db3468
No known key found for this signature in database
3 changed files with 82 additions and 5 deletions

View file

@ -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"] }

View file

@ -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<VmHandle> {
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<String> {
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(()) }

View file

@ -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: