aboutsummaryrefslogblamecommitdiff
path: root/src/planet.rs
blob: 8775f159466a1255269ed37893c49151d66760ec (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                     
                                          



                                    
   
                                            
   
                                                     
                           





                             
 
   
                                           
   
                                                     
                             





                             



                                                             




                                                                                                  
   
                                               
                   



                                                 



                     




                                                             
                    

                                                    
                            
 


                                                  
                  
                                

 
                           




                                        
                                 


































                                                                                                
                                     
                                      




          
                                  

                       
                                                          




                                   

                                           

















                                                                                                

                                                                              



                                    


                                                 
                                     
                                          


          
 
                                                   










                      





                                                                        

















                                                                       
             
                                                       
                                    







                                                                                     


                               
                                                 











                                                                                     
                                              











                                                                                     
                                              









                                                                                     



















































                                                                                     













                                                                                     


















































                                                                      

     
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.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);
    }
}