use std::{convert::TryFrom, sync::Arc, fmt::Display}; use virt::{domain::{Domain, DomainState}}; use serde::{Serialize, Deserialize}; use crate::errors::Error; /** * Defines the amount of memory a planet has */ #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub struct Memory(pub u64); impl From for Memory { fn from(u: u64) -> Self { Self(u) } } /** * Defines the number of vCPUs a planet has */ #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub struct CpuCount(pub u64); impl From for CpuCount { fn from(u: u64) -> Self { Self(u) } } /** * Represents a virtual machine, that's active on some server * * In keeping with the theme, it's named [Planet] :) * * There is a private `domain` field that contains a reference to the actual domain. This will not * be (de)serialized, and, if needed across a network, should be recreated from the `host` and * `uuid` attributes using [virt::domain::lookup_from_uuid_string] */ #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Planet { /// The reference name of the machine pub name: String, /// The physical machine where this one lives pub host: String, /// The UUID pub uuid: String, /// The network address where this machine can be reached pub addr: Option, /// The amount of RAM (in MB) assigned to this machine pub mem: Memory, /// The amount of vCPUs assigned to this machine pub cpu_count: CpuCount, /// Whether the planet is "orbiting" (running) pub orbiting: bool, #[serde(skip)] domain: Option>, } impl PartialEq for Planet { fn eq(&self, other: &Self) -> bool { self.uuid == other.uuid } } impl TryFrom for Planet { type Error = Error; fn try_from(d: Domain) -> Result { let c = d.get_connect()?; // This... feels wrong // // I know it probably works // // Based on code by Cadey in waifud let addr: Option = if d.is_active()? { let mut addr: Vec = d .interface_addresses(virt::domain::VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)? .into_iter() .map(|iface| iface.addrs.clone()) .filter(|addrs| addrs.get(0).is_some()) .map(|addrs| addrs.get(0).unwrap().clone().addr) .collect(); if addr.get(0).is_none() { Some(String::from("localhost")) } else { Some(addr.swap_remove(0)) } } else { None }; Ok(Self { name: d.get_name()?, host: c.get_hostname()?, addr, uuid: d.get_uuid_string()?, mem: d.get_max_memory()?.into(), cpu_count: d.get_max_vcpus()?.into(), orbiting: d.is_active()?, domain: Some(Arc::new(d)), }) } } impl TryFrom<&Domain> for Planet { type Error = Error; fn try_from(d: &Domain) -> Result { let c = d.get_connect()?; // This... feels wrong // // I know it probably works // // Based on code by Cadey in waifud let addr: Option = if d.is_active()? { let mut addr: Vec = d .interface_addresses(virt::domain::VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)? .into_iter() .map(|iface| iface.addrs.clone()) .filter(|addrs| addrs.get(0).is_some()) .map(|addrs| addrs.get(0).unwrap().clone().addr) .collect(); if addr.get(0).is_none() { Some(String::from("localhost")) } else { Some(addr.swap_remove(0)) } } else { None }; let new_d = Domain::lookup_by_uuid_string(&c, &d.get_uuid_string()?)?; Ok(Self { name: d.get_name()?, host: c.get_hostname()?, addr, uuid: d.get_uuid_string()?, mem: d.get_max_memory()?.into(), cpu_count: d.get_max_vcpus()?.into(), orbiting: d.is_active()?, domain: Some(Arc::new(new_d)), }) } } #[derive(Serialize, Deserialize, Debug, PartialEq)] pub enum Health { Unknown = 0, Running = 1, Blocked = 2, Paused = 3, ShuttingDown = 4, ShutDown = 5, Crashed = 6, GuestSuspended = 7 } impl Display for Health { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl TryFrom for Health { type Error = Error; fn try_from(value: u32) -> Result { match value { 0 => Ok(Health::Unknown), 1 => Ok(Health::Running), 2 => Ok(Health::Blocked), 3 => Ok(Health::Paused), 4 => Ok(Health::ShuttingDown), 5 => Ok(Health::ShutDown), 6 => Ok(Health::Crashed), 7 => Ok(Health::GuestSuspended), _ => { Err(Error::Other(String::from("Invalid Planet state"))) } } } } impl Planet { pub fn get_status(&self) -> Result { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; let state = d.get_state()?; Ok(state.0.try_into()?) } pub fn shutdown(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.shutdown()?; Ok(()) } pub fn start(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.create()?; Ok(()) } pub fn pause(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.suspend()?; Ok(()) } pub fn resume(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.resume()?; Ok(()) } pub fn reboot(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); }, }; d.reboot(0)?; Ok(()) } pub fn hard_shutdown(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.destroy()?; Ok(()) } pub fn hard_reboot(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; d.destroy()?; d.create()?; Ok(()) } pub fn deathstar(&self) -> Result<(), Error> { let d = match &self.domain { Some(d) => d, None => { return Err(Error::Other(String::from("No domain connection found"))); } }; self.hard_shutdown()?; d.undefine()?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::star::Star; #[test] fn domain_status() { let mut s = Star::new("test:///default".to_string()).unwrap(); let p = &s.inhabitants().unwrap()[0]; let p_status = p.get_status().unwrap(); assert_eq!(p_status, Health::Running); } #[test] fn domain_shutdown() { let mut s = Star::new("test:///default".to_string()).unwrap(); let p = &s.inhabitants().unwrap()[0]; p.shutdown().unwrap(); assert_eq!(p.get_status().unwrap(), Health::ShutDown); } #[test] fn domain_startup() { let mut s = Star::new("test:///default".to_string()).unwrap(); let p = &s.inhabitants().unwrap()[0]; p.start().unwrap(); assert_eq!(p.get_status().unwrap(), Health::Running); } #[test] fn domain_pause() { let mut s = Star::new("test:///default".to_string()).unwrap(); let p = &s.inhabitants().unwrap()[0]; p.pause().unwrap(); assert_eq!(p.get_status().unwrap(), Health::Paused); } }