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" edition = "2024"
[features] [features]
# Enable libvirt backend on Linux hosts # Enable libvirt backend on Linux hosts (uses virt crate on Linux)
libvirt = ["dep:libvirt"] libvirt = []
[dependencies] [dependencies]
common = { path = "../common" } common = { path = "../common" }
@ -22,8 +22,6 @@ bytes = "1"
path-absolutize = "3" path-absolutize = "3"
# Compression/decompression # Compression/decompression
zstd = "0.13" zstd = "0.13"
# Linux-only optional libvirt bindings (feature-gated)
libvirt = { version = "0.1", optional = true }
# DB (optional basic persistence) # 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" ] } sea-orm = { version = "0.12", default-features = false, features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls", "macros", "with-uuid", "with-chrono" ] }
migration = { path = "../migration" } migration = { path = "../migration" }
@ -34,3 +32,6 @@ once_cell = "1"
dashmap = "6" dashmap = "6"
async-trait = "0.1" async-trait = "0.1"
uuid = { version = "1", features = ["v4", "serde"] } 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] #[async_trait]
impl Hypervisor for LibvirtHypervisor { impl Hypervisor for LibvirtHypervisor {
async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result<VmHandle> { async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result<VmHandle> {
use libvirt::{Connect, Network, Domain};
use std::process::Command; use std::process::Command;
let id = format!("job-{}", ctx.request_id); let id = format!("job-{}", ctx.request_id);
let work_dir = self.mk_work_dir(&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 uri = self.uri.clone();
let net_name = self.network.clone(); let net_name = self.network.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> { tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?; use virt::{connect::Connect, network::Network};
let net: Network = conn.network_lookup_by_name(&net_name) let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
.map_err(|e| miette::miette!("libvirt network '{}' not found: {e}", net_name))?; if let Ok(net) = Network::lookup_by_name(&conn, &net_name) {
if !net.is_active().unwrap_or(false) { // If not active, try to create (activate). Then set autostart.
net.create().map_err(|e| miette::miette!("failed to activate network '{}': {e}", net_name))?; let active = net.is_active().unwrap_or(false);
} if !active {
if !net.get_autostart().unwrap_or(true) { let _ = net.create();
net.set_autostart(true).ok(); }
let _ = net.set_autostart(true);
} }
Ok(()) Ok(())
}).await.into_diagnostic()??; }).await.into_diagnostic()??;
@ -280,16 +280,14 @@ impl Hypervisor for LibvirtHypervisor {
id, mem, vcpus, overlay_str, cdrom, net) id, mem, vcpus, overlay_str, cdrom, net)
}; };
// Define domain // Define via virt crate
let uri2 = self.uri.clone(); let uri2 = self.uri.clone();
tokio::task::spawn_blocking({ let xml_clone = xml.clone();
let xml = xml.clone(); tokio::task::spawn_blocking(move || -> miette::Result<()> {
move || -> miette::Result<()> { use virt::{connect::Connect, domain::Domain};
let conn = Connect::open(&uri2).map_err(|e| miette::miette!("libvirt connect error: {e}"))?; let conn = Connect::open(Some(&uri2)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
let _dom: Domain = conn.domain_define_xml(&xml) let _dom = Domain::define_xml(&conn, &xml_clone).map_err(|e| miette::miette!("define domain failed: {e}"))?;
.map_err(|e| miette::miette!("domain define failed: {e}"))?; Ok(())
Ok(())
}
}).await.into_diagnostic()??; }).await.into_diagnostic()??;
info!(domain = %id, image = ?spec.image_path, cpu = spec.cpu, ram_mb = spec.ram_mb, "libvirt prepared"); 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<()> { async fn start(&self, vm: &VmHandle) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone(); let id = vm.id.clone();
let uri = self.uri.clone(); let uri = self.uri.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> { tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?; use virt::{connect::Connect, domain::Domain};
let dom = conn.domain_lookup_by_name(&id).map_err(|e| miette::miette!("lookup domain {}: {e}", id))?; let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
dom.create().map_err(|e| miette::miette!("start domain {} failed: {e}", id))?; // 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(()) Ok(())
}).await.into_diagnostic()??; }).await.into_diagnostic()??;
info!(domain = %vm.id, "libvirt started"); info!(domain = %vm.id, "libvirt started");
@ -311,22 +310,23 @@ impl Hypervisor for LibvirtHypervisor {
} }
async fn stop(&self, vm: &VmHandle, t: Duration) -> Result<()> { async fn stop(&self, vm: &VmHandle, t: Duration) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone(); let id = vm.id.clone();
let uri = self.uri.clone(); let uri = self.uri.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> { tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?; use virt::{connect::Connect, domain::Domain};
let dom = conn.domain_lookup_by_name(&id).map_err(|e| miette::miette!("lookup domain {}: {e}", id))?; let conn = Connect::open(Some(&uri)).map_err(|e| miette::miette!("libvirt connect failed: {e}"))?;
dom.shutdown().ok(); let dom = Domain::lookup_by_name(&conn, &id).map_err(|e| miette::miette!("lookup domain failed: {e}"))?;
// Poll for inactive up to timeout let _ = dom.shutdown();
let start = std::time::Instant::now(); let start = std::time::Instant::now();
while start.elapsed() < t { 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)); std::thread::sleep(std::time::Duration::from_millis(500));
} }
if dom.is_active().unwrap_or(false) { // Force destroy if still active
dom.destroy().ok(); let _ = dom.destroy();
}
Ok(()) Ok(())
}).await.into_diagnostic()??; }).await.into_diagnostic()??;
info!(domain = %vm.id, "libvirt stopped"); info!(domain = %vm.id, "libvirt stopped");
@ -334,12 +334,13 @@ impl Hypervisor for LibvirtHypervisor {
} }
async fn destroy(&self, vm: VmHandle) -> Result<()> { async fn destroy(&self, vm: VmHandle) -> Result<()> {
use libvirt::Connect;
let id = vm.id.clone(); let id = vm.id.clone();
let uri = self.uri.clone(); let uri = self.uri.clone();
let id_for_task = id.clone();
tokio::task::spawn_blocking(move || -> miette::Result<()> { tokio::task::spawn_blocking(move || -> miette::Result<()> {
let conn = Connect::open(&uri).map_err(|e| miette::miette!("libvirt connect error: {e}"))?; use virt::{connect::Connect, domain::Domain};
if let Ok(dom) = conn.domain_lookup_by_name(&id) { 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(); let _ = dom.undefine();
} }
Ok(()) Ok(())

View file

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