diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index 744d93b..95f8e43 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -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" } diff --git a/crates/orchestrator/src/hypervisor.rs b/crates/orchestrator/src/hypervisor.rs index dd2fdd3..7695e98 100644 --- a/crates/orchestrator/src/hypervisor.rs +++ b/crates/orchestrator/src/hypervisor.rs @@ -193,24 +193,24 @@ impl LibvirtHypervisor { #[async_trait] impl Hypervisor for LibvirtHypervisor { async fn prepare(&self, spec: &VmSpec, ctx: &JobContext) -> Result { - 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(()) diff --git a/crates/orchestrator/src/main.rs b/crates/orchestrator/src/main.rs index a321124..7b4c63a 100644 --- a/crates/orchestrator/src/main.rs +++ b/crates/orchestrator/src/main.rs @@ -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};