Switch orchestrator from libvirt crate to virt crate for Linux hypervisor backend

This commit replaces the `libvirt` crate with the `virt` crate for managing the libvirt backend on Linux. Key changes include:

- Updated `Cargo.toml` dependencies and feature configuration.
- Refactored hypervisor implementation to align with `virt` crate API.
- Improved error handling and lifecycle management for VMs and networks.
This commit is contained in:
Till Wegmueller 2025-10-26 16:08:36 +01:00
parent 6568183d86
commit d05121b378
No known key found for this signature in database
3 changed files with 42 additions and 40 deletions

View file

@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2024"
[features]
# Enable libvirt backend on Linux hosts
libvirt = ["dep:libvirt"]
# Enable libvirt backend on Linux hosts (uses virt crate on Linux)
libvirt = []
[dependencies]
common = { path = "../common" }
@ -22,8 +22,6 @@ bytes = "1"
path-absolutize = "3"
# Compression/decompression
zstd = "0.13"
# Linux-only optional libvirt bindings (feature-gated)
libvirt = { version = "0.1", optional = true }
# DB (optional basic persistence)
sea-orm = { version = "0.12", default-features = false, features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls", "macros", "with-uuid", "with-chrono" ] }
migration = { path = "../migration" }
@ -34,3 +32,6 @@ once_cell = "1"
dashmap = "6"
async-trait = "0.1"
uuid = { version = "1", features = ["v4", "serde"] }
[target.'cfg(target_os = "linux")'.dependencies]
virt = { version = "0.3" }

View file

@ -193,24 +193,24 @@ impl LibvirtHypervisor {
#[async_trait]
impl Hypervisor for LibvirtHypervisor {
async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result<VmHandle> {
use libvirt::{Connect, Network, Domain};
use std::process::Command;
let id = format!("job-{}", ctx.request_id);
let work_dir = self.mk_work_dir(&id);
// Connect and ensure network is active
// Ensure network is active via virt crate; best-effort
let uri = self.uri.clone();
let net_name = self.network.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?;
let net: Network = conn.network_lookup_by_name(&net_name)
.map_err(|e| miette::miette!("libvirt network '{}' not found: {e}", net_name))?;
if !net.is_active().unwrap_or(false) {
net.create().map_err(|e| miette::miette!("failed to activate network '{}': {e}", net_name))?;
}
if !net.get_autostart().unwrap_or(true) {
net.set_autostart(true).ok();
use virt::{connect::Connect, network::Network};
let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
if let Ok(net) = Network::lookup_by_name(&conn, &net_name) {
// If not active, try to create (activate). Then set autostart.
let active = net.is_active().unwrap_or(false);
if !active {
let _ = net.create();
}
let _ = net.set_autostart(true);
}
Ok(())
}).await.into_diagnostic()??;
@ -280,16 +280,14 @@ impl Hypervisor for LibvirtHypervisor {
id, mem, vcpus, overlay_str, cdrom, net)
};
// Define domain
// Define via virt crate
let uri2 = self.uri.clone();
tokio::task::spawn_blocking({
let xml = xml.clone();
move || -> miette::Result<()> {
let conn = Connect::open(&uri2).map_err(|e| miette::miette!("libvirt connect error: {e}"))?;
let _dom: Domain = conn.domain_define_xml(&xml)
.map_err(|e| miette::miette!("domain define failed: {e}"))?;
Ok(())
}
let xml_clone = xml.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> {
use virt::{connect::Connect, domain::Domain};
let conn = Connect::open(Some(&uri2)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
let _dom = Domain::define_xml(&conn, &xml_clone).map_err(|e| miette::miette!("define domain failed: {e}"))?;
Ok(())
}).await.into_diagnostic()??;
info!(domain = %id, image = ?spec.image_path, cpu = spec.cpu, ram_mb = spec.ram_mb, "libvirt prepared");
@ -297,13 +295,14 @@ impl Hypervisor for LibvirtHypervisor {
}
async fn start(&self, vm: &VmHandle) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone();
let uri = self.uri.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?;
let dom = conn.domain_lookup_by_name(&id).map_err(|e| miette::miette!("lookup domain {}: {e}", id))?;
dom.create().map_err(|e| miette::miette!("start domain {} failed: {e}", id))?;
use virt::{connect::Connect, domain::Domain};
let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
// Lookup domain by name and start
let dom = Domain::lookup_by_name(&conn, &id).map_err(|e| miette::miette!("lookup domain failed: {e}"))?;
dom.create().map_err(|e| miette::miette!("domain start failed: {e}"))?;
Ok(())
}).await.into_diagnostic()??;
info!(domain = %vm.id, "libvirt started");
@ -311,22 +310,23 @@ impl Hypervisor for LibvirtHypervisor {
}
async fn stop(&self, vm: &VmHandle, t: Duration) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone();
let uri = self.uri.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?;
let dom = conn.domain_lookup_by_name(&id).map_err(|e| miette::miette!("lookup domain {}: {e}", id))?;
dom.shutdown().ok();
// Poll for inactive up to timeout
use virt::{connect::Connect, domain::Domain};
let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
let dom = Domain::lookup_by_name(&conn, &id).map_err(|e| miette::miette!("lookup domain failed: {e}"))?;
let _ = dom.shutdown();
let start = std::time::Instant::now();
while start.elapsed() < t {
if !dom.is_active().unwrap_or(true) { break; }
match dom.is_active() {
Ok(false) => break,
_ => {}
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
if dom.is_active().unwrap_or(false) {
dom.destroy().ok();
}
// Force destroy if still active
let _ = dom.destroy();
Ok(())
}).await.into_diagnostic()??;
info!(domain = %vm.id, "libvirt stopped");
@ -334,12 +334,13 @@ impl Hypervisor for LibvirtHypervisor {
}
async fn destroy(&self, vm: VmHandle) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone();
let uri = self.uri.clone();
let id_for_task = id.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?;
if let Ok(dom) = conn.domain_lookup_by_name(&id) {
use virt::{connect::Connect, domain::Domain};
let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
if let Ok(dom) = Domain::lookup_by_name(&conn, &id_for_task) {
let _ = dom.undefine();
}
Ok(())

View file

@ -3,7 +3,7 @@ mod hypervisor;
mod scheduler;
mod persist;
use std::{collections::HashMap, path::PathBuf, time::Duration};
use std::{collections::HashMap, path::PathBuf};
use clap::Parser;
use miette::{IntoDiagnostic as _, Result};