mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 21:30:41 +00:00
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:
parent
f3831dac4a
commit
7918db3468
3 changed files with 82 additions and 5 deletions
|
|
@ -15,6 +15,7 @@ thiserror = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal", "fs", "io-util"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal", "fs", "io-util"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
config = { version = "0.14", default-features = false, features = ["yaml"] }
|
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"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "gzip", "brotli", "zstd"] }
|
||||||
|
|
|
||||||
|
|
@ -222,14 +222,37 @@ impl Hypervisor for LibvirtHypervisor {
|
||||||
let base = spec.image_path.clone();
|
let base = spec.image_path.clone();
|
||||||
let overlay = overlay.clone();
|
let overlay = overlay.clone();
|
||||||
move || -> miette::Result<()> {
|
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")
|
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(&base)
|
||||||
.arg(&overlay)
|
.arg(&overlay)
|
||||||
.arg(&size_arg)
|
.arg(&size_arg)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| miette::miette!("qemu-img not found or failed: {e}"))?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}).await.into_diagnostic()??;
|
}).await.into_diagnostic()??;
|
||||||
|
|
@ -361,9 +384,62 @@ pub struct ZonesHypervisor;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Hypervisor for ZonesHypervisor {
|
impl Hypervisor for ZonesHypervisor {
|
||||||
async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result<VmHandle> {
|
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);
|
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 start(&self, _vm: &VmHandle) -> Result<()> { Ok(()) }
|
||||||
async fn stop(&self, _vm: &VmHandle, _t: Duration) -> Result<()> { Ok(()) }
|
async fn stop(&self, _vm: &VmHandle, _t: Duration) -> Result<()> { Ok(()) }
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ images:
|
||||||
# Example Ubuntu image for libvirt/KVM on Linux hosts (commented by default)
|
# Example Ubuntu image for libvirt/KVM on Linux hosts (commented by default)
|
||||||
ubuntu-22.04:
|
ubuntu-22.04:
|
||||||
source: https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
|
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
|
decompress: none
|
||||||
nocloud: true
|
nocloud: true
|
||||||
defaults:
|
defaults:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue