diff options
| author | Cara Salter <cara@devcara.com> | 2022-04-20 13:08:28 -0400 | 
|---|---|---|
| committer | Cara Salter <cara@devcara.com> | 2022-04-20 13:08:28 -0400 | 
| commit | 6b995785c780dd47cb0e02821001f446cf4ec211 (patch) | |
| tree | 2bbe9147151056527468c1dcf70b6b98140ccb40 /src | |
| parent | 5978befd317189f1f18dddbab3db7ddd0061c236 (diff) | |
| download | solarlib-6b995785c780dd47cb0e02821001f446cf4ec211.tar.gz solarlib-6b995785c780dd47cb0e02821001f446cf4ec211.zip  | |
house: Allow for the introduction of new VMs
Templates the XML and creates the disk images
Diffstat (limited to 'src')
| -rw-r--r-- | src/build.rs | 5 | ||||
| -rw-r--r-- | src/errors.rs | 7 | ||||
| -rw-r--r-- | src/house.rs | 115 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/waifu.rs | 52 | 
5 files changed, 177 insertions, 4 deletions
diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..9a6d3c3 --- /dev/null +++ b/src/build.rs @@ -0,0 +1,5 @@ +use ructe::{Result, Ructe}; + +fn main() -> Result<()> { +    Ructe::from_env()?.compile_templates("templates") +} diff --git a/src/errors.rs b/src/errors.rs index 3a0eedd..29f7f4f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,4 +8,11 @@ pub enum Error {      Other(String),      #[error("Missing connection: {0}")]      Connection(String), +    #[error("Missing image: {0}")] +    MissingImage(String), +    #[error("Could not allocate VM storage: {0}")] +    Allocation(String), +    #[error("I/O: {0}")] +    Io(#[from] std::io::Error),  } + diff --git a/src/house.rs b/src/house.rs index 06dd62f..ad6e055 100644 --- a/src/house.rs +++ b/src/house.rs @@ -5,7 +5,12 @@  use crate::waifu::*;  use crate::errors::Error;  use serde::{Serialize, Deserialize}; -use virt::connect::Connect; +use virt::{connect::Connect, domain::Domain}; +use std::process::{ExitStatus}; +use std::os::unix::process::ExitStatusExt; +use rand::Rng; + +use tokio::{process::Command, task::spawn_blocking};  #[derive(Serialize, Deserialize, Debug)]  pub enum Address { @@ -36,6 +41,13 @@ pub struct House {  }  impl House {  +    /// Creates a new House based on a libvirtd connect URL +    /// +    /// Example: +    /// ``` +    /// use waifulib::house::House; +    /// let mut h: House = House::new("test:///default".to_string()).unwrap(); +    /// ```      pub fn new(url: String) -> Result<Self, Error> {          let mut c = Connect::open(&url.clone())?; @@ -47,6 +59,14 @@ impl House {          })      } +    /// Lists the "inhabitants" of the House (the [Waifu]s on the machine +    /// +    /// ``` +    /// use waifulib::house::House; +    /// let mut h: House = House::new("test:///default".to_string()).unwrap(); +    /// +    /// assert_eq!(h.inhabitants().unwrap().len(), 1); +    /// ```      pub fn inhabitants(&mut self) -> Result<Vec<Waifu>, Error> {          match &self.con {              Some(c) => { @@ -65,10 +85,97 @@ impl House {          }      } -    /// TODO: Implement and figure out what the hell I need to define one of these -    pub fn introduce(&mut self, name: String, max_mem: Memory, max_cpus: CpuCount) -> Result<&Waifu, Error> { -        unimplemented!(); +    /// Introduces a new Waifu into the House, 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]. +    /// +    /// ``` +    /// use waifulib::house::House +    /// +    /// let mut h: House = House::new("test:///default".to_string()).unwrap(); +    /// +    /// h.introduce("test-2", 1024, 1, 20000, "test.iso").unwrap(); +    /// ``` +    pub async fn introduce(&mut self, name: String, max_mem: Memory, max_cpus: CpuCount, disk_size_mb: u64, image_name: String) -> Result<Waifu, Error> { +        // Check for image on host machine +         +        let mut output = Command::new("ssh") +            .args([ +                  "-oStrictHostKeyChecking=accept-new", +                  &self.address.clone(), +                  "stat", +                  &format!("/var/lib/libvirt/images/{}", image_name.clone()) +            ]) +            .output() +            .await?; + +        if output.status != ExitStatus::from_raw(0) { +           return Err(Error::MissingImage(image_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())); +        } + +        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.count(), +            max_cpus.count(), +            "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)] @@ -3,3 +3,5 @@ pub mod errors;  pub mod waifu;  pub mod house; + +include!(concat!(env!("OUT_DIR"), "/templates.rs")); diff --git a/src/waifu.rs b/src/waifu.rs index 55ff4ed..da9653f 100644 --- a/src/waifu.rs +++ b/src/waifu.rs @@ -15,6 +15,12 @@ impl From<u64> for Memory {          Self(u)      }  } + +impl Memory { +    pub fn count(&self) -> u64 { +        self.0 +    } +}  /**   * Defines the number of vCPUs a waifu has   */ @@ -27,6 +33,11 @@ impl From<u64> for CpuCount {      }  } +impl CpuCount { +    pub fn count(&self) -> u64 { +        self.0 +    } +}  /**   * Represents a virtual machine, that's active on some server   * @@ -59,6 +70,47 @@ impl PartialEq for Waifu {      }  } +impl TryFrom<Domain> for Waifu { +    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(), +        }) +    } +} + +  impl TryFrom<&Domain> for Waifu {      type Error = Error;  | 
