//! An abstraction on top of the libvirt bindings. use log::debug; use std::path::Path; use std::thread; use std::time::Duration; use virt::connect::Connect; use virt::domain::Domain; /// Errors from this module. #[derive(Debug, thiserror::Error)] pub enum VirtError { /// Error creating virtual machine. #[error(transparent)] VirtError(#[from] virt::error::Error), /// Error doing I/O. #[error(transparent)] IoError(#[from] std::io::Error), } /// Access libvirt for all the things this program needs. pub struct Libvirt { conn: Connect, } impl Libvirt { pub fn connect(url: &str) -> Result { debug!("connecting to libvirtd {}", url); let conn = Connect::open(url)?; Ok(Self { conn }) } fn get_domains(&self) -> Result, VirtError> { debug!("listing all domains"); Ok(self.conn.list_all_domains(0)?) } fn get_domain(&self, name: &str) -> Result, VirtError> { for domain in self.get_domains()? { if domain.get_name()? == name { return Ok(Some(domain)); } } Ok(None) } pub fn names(&self) -> Result, VirtError> { let mut ret = vec![]; for domain in self.get_domains()? { ret.push(domain.get_name()?); } Ok(ret) } pub fn is_active(&self, name: &str) -> Result { if let Some(domain) = self.get_domain(name)? { Ok(domain.is_active()?) } else { Ok(false) } } pub fn start(&self, name: &str) -> Result<(), VirtError> { if let Some(domain) = self.get_domain(name)? { domain.create()?; } Ok(()) } pub fn shutdown(&self, name: &str) -> Result<(), VirtError> { if let Some(domain) = self.get_domain(name)? { domain.shutdown()?; } Ok(()) } pub fn delete(&self, name: &str, image: &Path) -> Result<(), VirtError> { if let Some(domain) = self.get_domain(name)? { debug!("shutting down {}", name); domain.shutdown()?; wait_until_inactive(&domain, name)?; debug!("undefine {}", name); domain.undefine()?; debug!("removing image file {}", image.display()); std::fs::remove_file(image)?; } Ok(()) } } fn wait_until_inactive(domain: &Domain, name: &str) -> Result<(), VirtError> { debug!("waiting for domain {} to become inactive", name); let briefly = Duration::from_millis(1000); loop { thread::sleep(briefly); match domain.is_active() { Ok(true) => (), Ok(false) => break, Err(err) => { debug!("is_active: {}", err); } } debug!("domain {} is still running", name); } Ok(()) }