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<u64> 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<u64> 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<String>,
/// 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<Arc<Domain>>,
}
impl PartialEq for Planet {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl TryFrom<Domain> for Planet {
type Error = Error;
fn try_from(d: Domain) -> Result<Self, Self::Error> {
let c = d.get_connect()?;
// This... feels wrong
//
// I know it probably works
//
// Based on code by Cadey in waifud
let addr: Option<String> = if d.is_active()? {
let mut addr: Vec<String> = 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<Self, Self::Error> {
let c = d.get_connect()?;
// This... feels wrong
//
// I know it probably works
//
// Based on code by Cadey in waifud
let addr: Option<String> = if d.is_active()? {
let mut addr: Vec<String> = 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<u32> for Health {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
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<Health, Error> {
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);
}
}