use std::sync::Arc; use crate::{ error::{DomainError, VirtError}, xml, Connection, }; pub struct Domain { inner: xml::Domain, virt: Arc, persist: bool, } impl Domain { pub(crate) async fn define(conn: &Connection, xml: xml::Domain) -> Result { let conn = conn.virtconn.clone(); tokio::task::spawn_blocking(move || { let virt_domain = { let inst_xml = quick_xml::se::to_string(&xml).map_err(DomainError::XmlError)?; virt::domain::Domain::define_xml(&conn, &inst_xml) .map_err(DomainError::VirtError)? }; let built_xml = match virt_domain.get_xml_desc(0) { Ok(xml) => { quick_xml::de::from_str::(&xml).map_err(DomainError::XmlError) } Err(err) => { if let Err(err) = virt_domain.undefine() { tracing::warn!("Couldn't undefine domain after failure: {err}"); } Err(DomainError::VirtError(err)) } }?; Ok(Self { inner: built_xml, virt: Arc::new(virt_domain), persist: false, }) }) .await .unwrap() } #[inline] // Convenience function so I can stop doing exactly this so much async fn spawn_virt(&self, f: F) -> R where F: FnOnce(Arc) -> R + Send + 'static, R: Send + 'static, { let virt = self.virt.clone(); tokio::task::spawn_blocking(move || f(virt)).await.unwrap() } pub(crate) async fn get(conn: &Connection, name: impl AsRef) -> Result { let name = name.as_ref().to_owned(); let virtconn = conn.virtconn.clone(); // Run libvirt calls in a blocking thread tokio::task::spawn_blocking(move || { let dom = match virt::domain::Domain::lookup_by_name(&virtconn, &name) { Ok(inst) => Ok(inst), Err(err) if err.code() == virt::error::ErrorNumber::NoDomain => { Err(DomainError::DomainNotFound) } Err(err) => Err(DomainError::VirtError(err)), }?; let domain_xml: xml::Domain = { let xml_str = dom.get_xml_desc(0).map_err(DomainError::VirtError)?; quick_xml::de::from_str(&xml_str).map_err(DomainError::XmlError)? }; Ok(Self { inner: domain_xml, virt: Arc::new(dom), persist: true, }) }) .await .unwrap() } /// Stops the libvirt domain forcefully. /// /// In libvirt terminology, this is equivalent to `virsh destroy `. pub async fn stop(&mut self) -> Result<(), VirtError> { self.spawn_virt(|virt| virt.destroy()).await } /// Undefines the libvirt domain. /// If `deep` is set to true, all connected volumes are deleted. pub async fn undefine(&mut self, deep: bool) -> Result<(), VirtError> { if deep { let conn: Connection = self.virt.get_connect()?.into(); for disk in self.inner.devices.disks() { if let (Some(pool), Some(vol)) = (&disk.source.pool, &disk.source.volume) { if let Ok(pool) = conn.get_pool(pool).await { if let Ok(vol) = pool.volume(vol).await { vol.delete().await?; } } } } } self.spawn_virt(|virt| virt.undefine()).await } /// Gets a reference to the inner libvirt XML. pub async fn xml(&self) -> &xml::Domain { &self.inner } pub async fn persist(&mut self) { self.persist = true; } /// Sets whether the domain is autostarted. The return value, if successful, /// represents the previous state. pub async fn autostart(&mut self, doit: bool) -> Result { self.spawn_virt(move |virt| virt.set_autostart(doit)).await } /// Starts the domain. pub async fn start(&self) -> Result<(), VirtError> { self.spawn_virt(|virt| virt.create()).await?; Ok(()) } /// Gets the current domain state. pub async fn state(&self) -> Result { self.spawn_virt(|virt| virt.get_state().map(|s| s.0)).await } }