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

                        
                     
                         
                      
                                    




                                             
                              















                                            
                                                         
                                        
                 

                     
                                                       

                        
                                                                                       

                     


                                             

 

                                                            


                

                                                                        
           
                                                    






                                                                                                            


                                                     

                                    
                   
                         

          
 


                                                                         

                                                                      


                                                      
                                                                 



                                                     
                                                          

                                         
                                                        
                 
                           





                                                                                        
 
                                                                                                   




                                                                                                 
                                                                                                                                               

                                          





                                                           
                                                                                             




                                                         
                                                                  
             
 





















                                                                                         
                                                                                              




                                                         
                                                                   















                                                                                         












                                        

                       




















                                                                                     
     






                                                       














                                                                                                 


                           
                                                                          





                                                                              
                                                              

     
 
/*!
  * `Star`s are where [crate::planet::Planet]s orbit (the physical hypervisors that
  * libvirtd connects to
  */
use crate::planet::*;
use crate::errors::Error;
use crate::ship::Ship;
use serde::{Serialize, Deserialize};
use virt::{connect::Connect, domain::Domain};
use std::process::{ExitStatus};
use std::os::unix::process::ExitStatusExt;
use rand::Rng;

use tokio::{process::Command};

#[derive(Serialize, Deserialize, Debug)]
pub enum Address {
    IP(String),
    Domain(String)
}

impl ToString for Address {
    fn to_string(&self) -> String {
        match self {
            Address::IP(s) => s.clone(),
            Address::Domain(s) => s.clone(),
        }
    }
}

/// Defines a "star" where [crate::planet::Planet]s orbit
#[derive(Debug, Serialize, Deserialize)]
pub struct Star {
    /// Hostname
    pub name: String,
    /// FQDN or IP address, a way to talk to the planet
    pub address: String,

    /// Whether or not the Planet is local (same machine) or remote (networked machine)
    pub remote: bool,

    /// Connection to the House, if available
    #[serde(skip)]
    con: Option<Connect>,
}

impl Star { 
    /// Creates a new Planet based on a libvirtd connect URL
    ///
    /// Example:
    /// ```
    /// use waifulib::planet::Planet;
    /// let mut h = Planet::new("test:///default".to_string()).unwrap();
    /// ```
    pub fn new(url: String) -> Result<Self, Error> {
        let c = Connect::open(&url.clone())?;

        let remote = if url.contains("qemu:///") || url.contains("localhost") || url.contains("127.0.0.1") {
            true
        } else {
            false
        };

        // If the connection succeeds, we've got one!
        Ok(Self {
            name: c.get_hostname()?,
            address: c.get_uri()?,
            remote,
            con: Some(c),
        })
    }

    /// Lists the "inhabitants" of the House (the [Waifu]s on the machine
    ///
    /// ```
    /// use crate::star::Star;
    /// let mut h = Star::new("test:///default".to_string()).unwrap();
    ///
    /// assert_eq!(h.inhabitants().unwrap().len(), 1);
    /// ```
    pub fn inhabitants(&mut self) -> Result<Vec<Planet>, Error> {
        match &self.con {
            Some(c) => {
                let domains = c.list_all_domains(0)?;

                let mut planets: Vec<Planet> = Vec::new();

                for d in domains.iter() {
                    planets.push(d.clone().try_into()?);
                }
                Ok(planets)
            },
            None => {
                return Err(Error::Connection("Domain connection was None".to_string()));
            }
        }
    }

    /// Creates a new Planet orbiting the Star, taking care of everything needed to make the VM run
    /// fine
    ///
    /// If the installation image doesn't exist in the default libvirtd pool, this will fail with
    /// [`Error::MissingImage`][crate::errors::Error::MissingImage].
    ///
    pub async fn planet(&mut self, name: String, max_mem: Memory, max_cpus: CpuCount, disk_size_mb: u64, ship: Ship) -> Result<Planet, Error> {
        // Check for image on host machine
        
        if self.remote {
            let mut output = Command::new("ssh")
                .args([
                      "-oStrictHostKeyChecking=accept-new",
                      &self.address.clone(),
                      "stat",
                      &format!("/var/lib/libvirt/images/{}", ship.make_pretty_name().clone())
                ])
                .output()
                .await?;

            if output.status != ExitStatus::from_raw(0) {
               return Err(Error::MissingImage(ship.name.clone()));
            }

            // Allocate VM disk
            output = Command::new("ssh")
                .args([
                      "-oStrictHostKeyChecking=accept-new",
                      &self.address.clone(),
                      "qemu-img",
                      "create",
                      "-f",
                      "qcow2",
                      &format!("/var/lib/libvirt/images/{}.qcow2", name.clone()),
                      &format!("{}M", disk_size_mb)
                ])
                .output()
                .await?;

            if output.status != ExitStatus::from_raw(0) {
                return Err(Error::Allocation(String::from_utf8(output.stdout).unwrap()));
            }
        } else {
            // It's local
            let mut output = Command::new("stat")
                .args([
                      &format!("/var/lib/libvirt/images/{}", ship.make_pretty_name().clone()),
                ])
                .output()
                .await?;

            if output.status != ExitStatus::from_raw(0) {
                return Err(Error::MissingImage(ship.name.clone()));
            }

            output = Command::new("qemu-img")
                .args([
                      "create",
                      "-f",
                      "qcow2",
                      &format!("/var/lib/libvirt/images/{}.qcow2", name.clone()),
                      &format!("{}M", disk_size_mb)
                ])
                .output()
                .await?;

            if output.status != ExitStatus::from_raw(0) {
                return Err(Error::Allocation(String::from_utf8(output.stdout).unwrap()));
            }
        }

        let uuid = uuid::Uuid::new_v4();
        
        // Let's get that XML ready
        let mut buf: Vec<u8> = vec![];

        crate::templates::vm_xml(
            &mut buf,
            name.clone(),
            uuid.to_string(),
            random_mac().clone(),
            true,
            max_mem.0,
            max_cpus.0,
            "blank".to_string()
            )?;

        let buf = String::from_utf8(buf).unwrap();

        println!("{}", buf);

        let dom: Domain = match &self.con {
                Some(c) => {
                    let dom = Domain::define_xml(&c, &buf)?;

                    dom.create()?;

                    dom
                },
                None => {
                    return Err(Error::Connection("Connection was None".to_string()));
                }
            };

        dom.try_into()
    }

}

fn random_mac() -> String {
    let mut addr = rand::thread_rng().gen::<[u8; 6]>();
    addr[0] = (addr[0] | 2) & 0xfe;
    mac_address::MacAddress::new(addr).to_string()
}

#[cfg(test)]
mod test {
    use super::*;
    /// Kind of a stupid test, but just a sanity check to make sure that [ToString] impl up above
    /// is still working
    #[test]
    fn addr_to_string() {
        let a: Address = Address::IP("127.0.0.1".to_string());
        let d: Address = Address::Domain("example.com".to_string());

        assert_eq!(String::from("127.0.0.1"), a.to_string());
        assert_eq!("example.com".to_string(), d.to_string());
    }

    #[test]
    fn connect_to_house() {
        let _: House = House::new("test:///default".to_string()).unwrap();
    }

    #[test]
    fn list_inhabitants() {
        let mut h: House = House::new("test:///default".to_string()).unwrap();

        assert_eq!(h.inhabitants().unwrap().len(), 1);        
    }

}