aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2022-04-20 13:08:28 -0400
committerCara Salter <cara@devcara.com>2022-04-20 13:08:28 -0400
commit6b995785c780dd47cb0e02821001f446cf4ec211 (patch)
tree2bbe9147151056527468c1dcf70b6b98140ccb40 /src
parent5978befd317189f1f18dddbab3db7ddd0061c236 (diff)
downloadsolarlib-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.rs5
-rw-r--r--src/errors.rs7
-rw-r--r--src/house.rs115
-rw-r--r--src/lib.rs2
-rw-r--r--src/waifu.rs52
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)]
diff --git a/src/lib.rs b/src/lib.rs
index 6ba4038..2ee2274 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;