diff --git a/controller/bin/control.rs b/controller/bin/control.rs index f6ec6ae..8a51a94 100644 --- a/controller/bin/control.rs +++ b/controller/bin/control.rs @@ -1,7 +1,12 @@ use anyhow::{anyhow, Result}; use clap::{Parser, Subcommand}; use env_logger::Env; -use hyphactrl::ctl::Controller; +use hyphactrl::ctl::{ + console::ControllerConsole, + destroy::ControllerDestroy, + launch::{ControllerLaunch, ControllerLaunchRequest}, + ControllerContext, +}; use std::path::PathBuf; #[derive(Parser, Debug)] @@ -27,8 +32,6 @@ enum Commands { cpus: u32, #[arg(short, long, default_value_t = 512)] mem: u64, - #[arg(long)] - config_bundle: Option, #[arg[short, long]] env: Option>, #[arg(short, long)] @@ -65,7 +68,7 @@ fn main() -> Result<()> { .map(|x| x.to_string()) .ok_or_else(|| anyhow!("unable to convert store path to string"))?; - let mut controller = Controller::new(store_path.clone())?; + let mut context = ControllerContext::new(store_path.clone())?; match args.command { Commands::Launch { @@ -74,7 +77,6 @@ fn main() -> Result<()> { image, cpus, mem, - config_bundle, attach, env, run, @@ -82,33 +84,37 @@ fn main() -> Result<()> { } => { let kernel = map_kernel_path(&store_path, kernel); let initrd = map_initrd_path(&store_path, initrd); - let (uuid, _domid) = controller.launch( - &kernel, - &initrd, - config_bundle.as_deref(), - &image, - cpus, + let mut launch = ControllerLaunch::new(&mut context); + let request = ControllerLaunchRequest { + kernel_path: &kernel, + initrd_path: &initrd, + image: &image, + vcpus: cpus, mem, env, - if run.is_empty() { None } else { Some(run) }, + run: if run.is_empty() { None } else { Some(run) }, debug, - )?; + }; + let (uuid, _domid) = launch.perform(request)?; println!("launched container: {}", uuid); if attach { - controller.console(&uuid.to_string())?; + let mut console = ControllerConsole::new(&mut context); + console.perform(&uuid.to_string())?; } } Commands::Destroy { container } => { - controller.destroy(&container)?; + let mut destroy = ControllerDestroy::new(&mut context); + destroy.perform(&container)?; } Commands::Console { container } => { - controller.console(&container)?; + let mut console = ControllerConsole::new(&mut context); + console.perform(&container)?; } Commands::List { .. } => { - let containers = controller.list()?; + let containers = context.list()?; let mut table = cli_tables::Table::new(); let header = vec!["uuid", "ipv4", "ipv6", "image"]; table.push_row(&header)?; diff --git a/controller/src/autoloop.rs b/controller/src/autoloop.rs index 4dc881d..7b330d9 100644 --- a/controller/src/autoloop.rs +++ b/controller/src/autoloop.rs @@ -7,7 +7,7 @@ pub struct AutoLoop { } impl AutoLoop { - pub(crate) fn new(control: LoopControl) -> AutoLoop { + pub fn new(control: LoopControl) -> AutoLoop { AutoLoop { control } } diff --git a/controller/src/ctl/cfgblk.rs b/controller/src/ctl/cfgblk.rs index c189387..d0f88cb 100644 --- a/controller/src/ctl/cfgblk.rs +++ b/controller/src/ctl/cfgblk.rs @@ -10,17 +10,12 @@ use uuid::Uuid; pub struct ConfigBlock<'a> { pub image_info: &'a ImageInfo, - pub config_bundle: Option<&'a str>, pub file: PathBuf, pub dir: PathBuf, } impl ConfigBlock<'_> { - pub fn new<'a>( - uuid: &Uuid, - image_info: &'a ImageInfo, - config_bundle: Option<&'a str>, - ) -> Result> { + pub fn new<'a>(uuid: &Uuid, image_info: &'a ImageInfo) -> Result> { let mut dir = std::env::temp_dir().clone(); dir.push(format!("hypha-cfg-{}", uuid)); fs::create_dir_all(&dir)?; @@ -28,7 +23,6 @@ impl ConfigBlock<'_> { file.push("config.squashfs"); Ok(ConfigBlock { image_info, - config_bundle, file, dir, }) @@ -36,10 +30,6 @@ impl ConfigBlock<'_> { pub fn build(&self, launch_config: &LaunchInfo) -> Result<()> { trace!("ConfigBlock build launch_config={:?}", launch_config); - let config_bundle_content = match self.config_bundle { - None => None, - Some(path) => Some(fs::read(path)?), - }; let manifest = self.image_info.config.to_string()?; let launch = serde_json::to_string(launch_config)?; let mut writer = FilesystemWriter::default(); @@ -72,18 +62,6 @@ impl ConfigBlock<'_> { mtime: 0, }, )?; - if let Some(config_bundle_content) = config_bundle_content.as_ref() { - writer.push_file( - config_bundle_content.as_slice(), - "/bundle", - NodeHeader { - permissions: 384, - uid: 0, - gid: 0, - mtime: 0, - }, - )?; - } let mut file = File::create(&self.file)?; trace!("ConfigBlock build write sqaushfs"); writer.write(&mut file)?; diff --git a/controller/src/ctl/console.rs b/controller/src/ctl/console.rs new file mode 100644 index 0000000..5bbfaa0 --- /dev/null +++ b/controller/src/ctl/console.rs @@ -0,0 +1,66 @@ +use std::{ + io::{self, Read, Write}, + process::exit, + thread, +}; + +use anyhow::{anyhow, Result}; +use termion::raw::IntoRawMode; + +use super::ControllerContext; + +pub struct ControllerConsole<'a> { + context: &'a mut ControllerContext, +} + +impl ControllerConsole<'_> { + pub fn new(context: &mut ControllerContext) -> ControllerConsole<'_> { + ControllerConsole { context } + } + + pub fn perform(&mut self, id: &str) -> Result<()> { + let info = self + .context + .resolve(id)? + .ok_or_else(|| anyhow!("unable to resolve container: {}", id))?; + let domid = info.domid; + let (mut read, mut write) = self.context.xen.open_console(domid)?; + let mut stdin = io::stdin(); + let is_tty = termion::is_tty(&stdin); + let mut stdout_for_exit = io::stdout().into_raw_mode()?; + thread::spawn(move || { + let mut buffer = vec![0u8; 60]; + loop { + let size = stdin.read(&mut buffer).expect("failed to read stdin"); + if is_tty && size == 1 && buffer[0] == 0x1d { + stdout_for_exit + .suspend_raw_mode() + .expect("failed to disable raw mode"); + stdout_for_exit.flush().expect("failed to flush stdout"); + exit(0); + } + write + .write_all(&buffer[0..size]) + .expect("failed to write to domain console"); + write.flush().expect("failed to flush domain console"); + } + }); + + let mut buffer = vec![0u8; 256]; + if is_tty { + let mut stdout = io::stdout().into_raw_mode()?; + loop { + let size = read.read(&mut buffer)?; + stdout.write_all(&buffer[0..size])?; + stdout.flush()?; + } + } else { + let mut stdout = io::stdout(); + loop { + let size = read.read(&mut buffer)?; + stdout.write_all(&buffer[0..size])?; + stdout.flush()?; + } + } + } +} diff --git a/controller/src/ctl/destroy.rs b/controller/src/ctl/destroy.rs new file mode 100644 index 0000000..2f8e7c3 --- /dev/null +++ b/controller/src/ctl/destroy.rs @@ -0,0 +1,58 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{anyhow, Result}; +use uuid::Uuid; +use xenstore::client::{XsdClient, XsdInterface}; + +use super::ControllerContext; + +pub struct ControllerDestroy<'a> { + context: &'a mut ControllerContext, +} + +impl ControllerDestroy<'_> { + pub fn new(context: &mut ControllerContext) -> ControllerDestroy<'_> { + ControllerDestroy { context } + } + + pub fn perform(&mut self, id: &str) -> Result { + let info = self + .context + .resolve(id)? + .ok_or_else(|| anyhow!("unable to resolve container: {}", id))?; + let domid = info.domid; + let mut store = XsdClient::open()?; + let dom_path = store.get_domain_path(domid)?; + let uuid = match store.read_string_optional(format!("{}/hypha/uuid", dom_path).as_str())? { + None => { + return Err(anyhow!( + "domain {} was not found or not created by hypha", + domid + )) + } + Some(value) => value, + }; + if uuid.is_empty() { + return Err(anyhow!("unable to find hypha uuid based on the domain",)); + } + let uuid = Uuid::parse_str(&uuid)?; + let loops = store.read_string(format!("{}/hypha/loops", dom_path).as_str())?; + let loops = ControllerContext::parse_loop_set(&loops); + self.context.xen.destroy(domid)?; + for info in &loops { + self.context.autoloop.unloop(&info.device)?; + match &info.delete { + None => {} + Some(delete) => { + let delete_path = PathBuf::from(delete); + if delete_path.is_file() || delete_path.is_symlink() { + fs::remove_file(&delete_path)?; + } else if delete_path.is_dir() { + fs::remove_dir_all(&delete_path)?; + } + } + } + } + Ok(uuid) + } +} diff --git a/controller/src/ctl/launch.rs b/controller/src/ctl/launch.rs new file mode 100644 index 0000000..06d96c9 --- /dev/null +++ b/controller/src/ctl/launch.rs @@ -0,0 +1,226 @@ +use std::{fs, net::Ipv4Addr, str::FromStr}; + +use advmac::MacAddr6; +use anyhow::{anyhow, Result}; +use hypha::{ + LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver, +}; +use ipnetwork::Ipv4Network; +use uuid::Uuid; +use xenclient::{DomainConfig, DomainDisk, DomainNetworkInterface}; +use xenstore::client::XsdInterface; + +use crate::image::{name::ImageName, ImageCompiler, ImageInfo}; + +use super::{cfgblk::ConfigBlock, ControllerContext}; + +pub struct ControllerLaunchRequest<'a> { + pub kernel_path: &'a str, + pub initrd_path: &'a str, + pub image: &'a str, + pub vcpus: u32, + pub mem: u64, + pub env: Option>, + pub run: Option>, + pub debug: bool, +} + +pub struct ControllerLaunch<'a> { + context: &'a mut ControllerContext, +} + +impl ControllerLaunch<'_> { + pub fn new(context: &mut ControllerContext) -> ControllerLaunch<'_> { + ControllerLaunch { context } + } + + pub fn perform(&mut self, request: ControllerLaunchRequest) -> Result<(Uuid, u32)> { + let uuid = Uuid::new_v4(); + let name = format!("hypha-{uuid}"); + let image_info = self.compile(request.image)?; + + let mut gateway_mac = MacAddr6::random(); + gateway_mac.set_local(true); + gateway_mac.set_multicast(false); + let mut container_mac = MacAddr6::random(); + container_mac.set_local(true); + container_mac.set_multicast(false); + + let guest_ipv4 = self.allocate_ipv4()?; + let guest_ipv6 = container_mac.to_link_local_ipv6(); + let gateway_ipv4 = "192.168.42.1"; + let gateway_ipv6 = "fe80::1"; + let ipv4_network_mask: u32 = 24; + let ipv6_network_mask: u32 = 10; + + let launch_config = LaunchInfo { + network: Some(LaunchNetwork { + link: "eth0".to_string(), + ipv4: LaunchNetworkIpv4 { + address: format!("{}/{}", guest_ipv4, ipv4_network_mask), + gateway: gateway_ipv4.to_string(), + }, + ipv6: LaunchNetworkIpv6 { + address: format!("{}/{}", guest_ipv6, ipv6_network_mask), + gateway: gateway_ipv6.to_string(), + }, + resolver: LaunchNetworkResolver { + nameservers: vec![ + "1.1.1.1".to_string(), + "1.0.0.1".to_string(), + "2606:4700:4700::1111".to_string(), + "2606:4700:4700::1001".to_string(), + ], + }, + }), + env: request.env, + run: request.run, + }; + + let cfgblk = ConfigBlock::new(&uuid, &image_info)?; + cfgblk.build(&launch_config)?; + + let image_squashfs_path = image_info + .image_squashfs + .to_str() + .ok_or_else(|| anyhow!("failed to convert image squashfs path to string"))?; + + let cfgblk_dir_path = cfgblk + .dir + .to_str() + .ok_or_else(|| anyhow!("failed to convert cfgblk directory path to string"))?; + let cfgblk_squashfs_path = cfgblk + .file + .to_str() + .ok_or_else(|| anyhow!("failed to convert cfgblk squashfs path to string"))?; + + let image_squashfs_loop = self.context.autoloop.loopify(image_squashfs_path)?; + let cfgblk_squashfs_loop = self.context.autoloop.loopify(cfgblk_squashfs_path)?; + + let cmdline_options = [ + if request.debug { "debug" } else { "quiet" }, + "elevator=noop", + ]; + let cmdline = cmdline_options.join(" "); + + let container_mac_string = container_mac.to_string().replace('-', ":"); + let gateway_mac_string = gateway_mac.to_string().replace('-', ":"); + let config = DomainConfig { + backend_domid: 0, + name: &name, + max_vcpus: request.vcpus, + mem_mb: request.mem, + kernel_path: request.kernel_path, + initrd_path: request.initrd_path, + cmdline: &cmdline, + disks: vec![ + DomainDisk { + vdev: "xvda", + block: &image_squashfs_loop, + writable: false, + }, + DomainDisk { + vdev: "xvdb", + block: &cfgblk_squashfs_loop, + writable: false, + }, + ], + consoles: vec![], + vifs: vec![DomainNetworkInterface { + mac: &container_mac_string, + mtu: 1500, + bridge: None, + script: None, + }], + filesystems: vec![], + extra_keys: vec![ + ("hypha/uuid".to_string(), uuid.to_string()), + ( + "hypha/loops".to_string(), + format!( + "{}:{}:none,{}:{}:{}", + &image_squashfs_loop.path, + image_squashfs_path, + &cfgblk_squashfs_loop.path, + cfgblk_squashfs_path, + cfgblk_dir_path, + ), + ), + ("hypha/image".to_string(), request.image.to_string()), + ( + "hypha/network/guest/ipv4".to_string(), + format!("{}/{}", guest_ipv4, ipv4_network_mask), + ), + ( + "hypha/network/guest/ipv6".to_string(), + format!("{}/{}", guest_ipv6, ipv6_network_mask), + ), + ( + "hypha/network/guest/mac".to_string(), + container_mac_string.clone(), + ), + ( + "hypha/network/gateway/ipv4".to_string(), + format!("{}/{}", gateway_ipv4, ipv4_network_mask), + ), + ( + "hypha/network/gateway/ipv6".to_string(), + format!("{}/{}", gateway_ipv6, ipv6_network_mask), + ), + ( + "hypha/network/gateway/mac".to_string(), + gateway_mac_string.clone(), + ), + ], + }; + match self.context.xen.create(&config) { + Ok(domid) => Ok((uuid, domid)), + Err(error) => { + let _ = self.context.autoloop.unloop(&image_squashfs_loop.path); + let _ = self.context.autoloop.unloop(&cfgblk_squashfs_loop.path); + let _ = fs::remove_dir(&cfgblk.dir); + Err(error.into()) + } + } + } + + fn allocate_ipv4(&mut self) -> Result { + let network = Ipv4Network::new(Ipv4Addr::new(192, 168, 42, 0), 24)?; + let mut used: Vec = vec![ + Ipv4Addr::new(192, 168, 42, 0), + Ipv4Addr::new(192, 168, 42, 1), + Ipv4Addr::new(192, 168, 42, 255), + ]; + for domid_candidate in self.context.xen.store.list_any("/local/domain")? { + let dom_path = format!("/local/domain/{}", domid_candidate); + let ip_path = format!("{}/hypha/network/guest/ipv4", dom_path); + let existing_ip = self.context.xen.store.read_string_optional(&ip_path)?; + if let Some(existing_ip) = existing_ip { + let ipv4_network = Ipv4Network::from_str(&existing_ip)?; + used.push(ipv4_network.ip()); + } + } + + let mut found: Option = None; + for ip in network.iter() { + if !used.contains(&ip) { + found = Some(ip); + break; + } + } + + if found.is_none() { + return Err(anyhow!( + "unable to find ipv4 to allocate to container, ipv4 addresses are exhausted" + )); + } + + Ok(found.unwrap()) + } + + fn compile(&self, image: &str) -> Result { + let image = ImageName::parse(image)?; + let compiler = ImageCompiler::new(&self.context.image_cache)?; + compiler.compile(&image) + } +} diff --git a/controller/src/ctl/mod.rs b/controller/src/ctl/mod.rs index 26b5fbc..82656bf 100644 --- a/controller/src/ctl/mod.rs +++ b/controller/src/ctl/mod.rs @@ -1,32 +1,24 @@ pub mod cfgblk; use crate::autoloop::AutoLoop; -use crate::ctl::cfgblk::ConfigBlock; use crate::image::cache::ImageCache; -use crate::image::name::ImageName; -use crate::image::{ImageCompiler, ImageInfo}; -use advmac::MacAddr6; use anyhow::{anyhow, Result}; -use hypha::{ - LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver, -}; -use ipnetwork::Ipv4Network; use loopdev::LoopControl; -use std::io::{Read, Write}; -use std::net::Ipv4Addr; +use std::fs; use std::path::PathBuf; -use std::process::exit; use std::str::FromStr; -use std::{fs, io, thread}; -use termion::raw::IntoRawMode; use uuid::Uuid; -use xenclient::{DomainConfig, DomainDisk, DomainNetworkInterface, XenClient}; -use xenstore::client::{XsdClient, XsdInterface}; +use xenclient::XenClient; +use xenstore::client::XsdInterface; -pub struct Controller { - image_cache: ImageCache, - autoloop: AutoLoop, - client: XenClient, +pub mod console; +pub mod destroy; +pub mod launch; + +pub struct ControllerContext { + pub image_cache: ImageCache, + pub autoloop: AutoLoop, + pub xen: XenClient, } pub struct ContainerLoopInfo { @@ -44,279 +36,29 @@ pub struct ContainerInfo { pub ipv6: String, } -impl Controller { - pub fn new(store_path: String) -> Result { +impl ControllerContext { + pub fn new(store_path: String) -> Result { let mut image_cache_path = PathBuf::from(store_path); image_cache_path.push("cache"); fs::create_dir_all(&image_cache_path)?; - let client = XenClient::open()?; + let xen = XenClient::open()?; image_cache_path.push("image"); fs::create_dir_all(&image_cache_path)?; let image_cache = ImageCache::new(&image_cache_path)?; - Ok(Controller { + Ok(ControllerContext { image_cache, autoloop: AutoLoop::new(LoopControl::open()?), - client, + xen, }) } - fn compile(&mut self, image: &str) -> Result { - let image = ImageName::parse(image)?; - let compiler = ImageCompiler::new(&self.image_cache)?; - compiler.compile(&image) - } - - #[allow(clippy::too_many_arguments)] - pub fn launch( - &mut self, - kernel_path: &str, - initrd_path: &str, - config_bundle_path: Option<&str>, - image: &str, - vcpus: u32, - mem: u64, - env: Option>, - run: Option>, - debug: bool, - ) -> Result<(Uuid, u32)> { - let uuid = Uuid::new_v4(); - let name = format!("hypha-{uuid}"); - let image_info = self.compile(image)?; - - let mut gateway_mac = MacAddr6::random(); - gateway_mac.set_local(true); - gateway_mac.set_multicast(false); - let mut container_mac = MacAddr6::random(); - container_mac.set_local(true); - container_mac.set_multicast(false); - - let guest_ipv4 = self.allocate_ipv4()?; - let guest_ipv6 = container_mac.to_link_local_ipv6(); - let gateway_ipv4 = "192.168.42.1"; - let gateway_ipv6 = "fe80::1"; - let ipv4_network_mask: u32 = 24; - let ipv6_network_mask: u32 = 10; - - let launch_config = LaunchInfo { - network: Some(LaunchNetwork { - link: "eth0".to_string(), - ipv4: LaunchNetworkIpv4 { - address: format!("{}/{}", guest_ipv4, ipv4_network_mask), - gateway: gateway_ipv4.to_string(), - }, - ipv6: LaunchNetworkIpv6 { - address: format!("{}/{}", guest_ipv6, ipv6_network_mask), - gateway: gateway_ipv6.to_string(), - }, - resolver: LaunchNetworkResolver { - nameservers: vec![ - "1.1.1.1".to_string(), - "1.0.0.1".to_string(), - "2606:4700:4700::1111".to_string(), - "2606:4700:4700::1001".to_string(), - ], - }, - }), - env, - run, - }; - - let cfgblk = ConfigBlock::new(&uuid, &image_info, config_bundle_path)?; - cfgblk.build(&launch_config)?; - - let image_squashfs_path = image_info - .image_squashfs - .to_str() - .ok_or_else(|| anyhow!("failed to convert image squashfs path to string"))?; - - let cfgblk_dir_path = cfgblk - .dir - .to_str() - .ok_or_else(|| anyhow!("failed to convert cfgblk directory path to string"))?; - let cfgblk_squashfs_path = cfgblk - .file - .to_str() - .ok_or_else(|| anyhow!("failed to convert cfgblk squashfs path to string"))?; - - let image_squashfs_loop = self.autoloop.loopify(image_squashfs_path)?; - let cfgblk_squashfs_loop = self.autoloop.loopify(cfgblk_squashfs_path)?; - - let cmdline_options = [if debug { "debug" } else { "quiet" }, "elevator=noop"]; - let cmdline = cmdline_options.join(" "); - - let container_mac_string = container_mac.to_string().replace('-', ":"); - let gateway_mac_string = gateway_mac.to_string().replace('-', ":"); - let config = DomainConfig { - backend_domid: 0, - name: &name, - max_vcpus: vcpus, - mem_mb: mem, - kernel_path, - initrd_path, - cmdline: &cmdline, - disks: vec![ - DomainDisk { - vdev: "xvda", - block: &image_squashfs_loop, - writable: false, - }, - DomainDisk { - vdev: "xvdb", - block: &cfgblk_squashfs_loop, - writable: false, - }, - ], - consoles: vec![], - vifs: vec![DomainNetworkInterface { - mac: &container_mac_string, - mtu: 1500, - bridge: None, - script: None, - }], - filesystems: vec![], - extra_keys: vec![ - ("hypha/uuid".to_string(), uuid.to_string()), - ( - "hypha/loops".to_string(), - format!( - "{}:{}:none,{}:{}:{}", - &image_squashfs_loop.path, - image_squashfs_path, - &cfgblk_squashfs_loop.path, - cfgblk_squashfs_path, - cfgblk_dir_path, - ), - ), - ("hypha/image".to_string(), image.to_string()), - ( - "hypha/network/guest/ipv4".to_string(), - format!("{}/{}", guest_ipv4, ipv4_network_mask), - ), - ( - "hypha/network/guest/ipv6".to_string(), - format!("{}/{}", guest_ipv6, ipv6_network_mask), - ), - ( - "hypha/network/guest/mac".to_string(), - container_mac_string.clone(), - ), - ( - "hypha/network/gateway/ipv4".to_string(), - format!("{}/{}", gateway_ipv4, ipv4_network_mask), - ), - ( - "hypha/network/gateway/ipv6".to_string(), - format!("{}/{}", gateway_ipv6, ipv6_network_mask), - ), - ( - "hypha/network/gateway/mac".to_string(), - gateway_mac_string.clone(), - ), - ], - }; - match self.client.create(&config) { - Ok(domid) => Ok((uuid, domid)), - Err(error) => { - let _ = self.autoloop.unloop(&image_squashfs_loop.path); - let _ = self.autoloop.unloop(&cfgblk_squashfs_loop.path); - let _ = fs::remove_dir(&cfgblk.dir); - Err(error.into()) - } - } - } - - pub fn destroy(&mut self, id: &str) -> Result { - let info = self - .resolve(id)? - .ok_or_else(|| anyhow!("unable to resolve container: {}", id))?; - let domid = info.domid; - let mut store = XsdClient::open()?; - let dom_path = store.get_domain_path(domid)?; - let uuid = match store.read_string_optional(format!("{}/hypha/uuid", dom_path).as_str())? { - None => { - return Err(anyhow!( - "domain {} was not found or not created by hypha", - domid - )) - } - Some(value) => value, - }; - if uuid.is_empty() { - return Err(anyhow!("unable to find hypha uuid based on the domain",)); - } - let uuid = Uuid::parse_str(&uuid)?; - let loops = store.read_string(format!("{}/hypha/loops", dom_path).as_str())?; - let loops = Controller::parse_loop_set(&loops); - self.client.destroy(domid)?; - for info in &loops { - self.autoloop.unloop(&info.device)?; - match &info.delete { - None => {} - Some(delete) => { - let delete_path = PathBuf::from(delete); - if delete_path.is_file() || delete_path.is_symlink() { - fs::remove_file(&delete_path)?; - } else if delete_path.is_dir() { - fs::remove_dir_all(&delete_path)?; - } - } - } - } - Ok(uuid) - } - - pub fn console(&mut self, id: &str) -> Result<()> { - let info = self - .resolve(id)? - .ok_or_else(|| anyhow!("unable to resolve container: {}", id))?; - let domid = info.domid; - let (mut read, mut write) = self.client.open_console(domid)?; - let mut stdin = io::stdin(); - let is_tty = termion::is_tty(&stdin); - let mut stdout_for_exit = io::stdout().into_raw_mode()?; - thread::spawn(move || { - let mut buffer = vec![0u8; 60]; - loop { - let size = stdin.read(&mut buffer).expect("failed to read stdin"); - if is_tty && size == 1 && buffer[0] == 0x1d { - stdout_for_exit - .suspend_raw_mode() - .expect("failed to disable raw mode"); - stdout_for_exit.flush().expect("failed to flush stdout"); - exit(0); - } - write - .write_all(&buffer[0..size]) - .expect("failed to write to domain console"); - write.flush().expect("failed to flush domain console"); - } - }); - - let mut buffer = vec![0u8; 256]; - if is_tty { - let mut stdout = io::stdout().into_raw_mode()?; - loop { - let size = read.read(&mut buffer)?; - stdout.write_all(&buffer[0..size])?; - stdout.flush()?; - } - } else { - let mut stdout = io::stdout(); - loop { - let size = read.read(&mut buffer)?; - stdout.write_all(&buffer[0..size])?; - stdout.flush()?; - } - } - } - pub fn list(&mut self) -> Result> { let mut containers: Vec = Vec::new(); - for domid_candidate in self.client.store.list_any("/local/domain")? { + for domid_candidate in self.xen.store.list_any("/local/domain")? { let dom_path = format!("/local/domain/{}", domid_candidate); let uuid_string = match self - .client + .xen .store .read_string_optional(&format!("{}/hypha/uuid", &dom_path))? { @@ -327,26 +69,26 @@ impl Controller { u32::from_str(&domid_candidate).map_err(|_| anyhow!("failed to parse domid"))?; let uuid = Uuid::from_str(&uuid_string)?; let image = self - .client + .xen .store .read_string_optional(&format!("{}/hypha/image", &dom_path))? .unwrap_or("unknown".to_string()); let loops = self - .client + .xen .store .read_string_optional(&format!("{}/hypha/loops", &dom_path))? .unwrap_or("".to_string()); let ipv4 = self - .client + .xen .store .read_string_optional(&format!("{}/hypha/network/guest/ipv4", &dom_path))? .unwrap_or("unknown".to_string()); - let ipv6 = self - .client + let ipv6: String = self + .xen .store .read_string_optional(&format!("{}/hypha/network/guest/ipv6", &dom_path))? .unwrap_or("unknown".to_string()); - let loops = Controller::parse_loop_set(&loops); + let loops = ControllerContext::parse_loop_set(&loops); containers.push(ContainerInfo { uuid, domid, @@ -389,38 +131,4 @@ impl Controller { }) .collect::>() } - - fn allocate_ipv4(&mut self) -> Result { - let network = Ipv4Network::new(Ipv4Addr::new(192, 168, 42, 0), 24)?; - let mut used: Vec = vec![ - Ipv4Addr::new(192, 168, 42, 0), - Ipv4Addr::new(192, 168, 42, 1), - Ipv4Addr::new(192, 168, 42, 255), - ]; - for domid_candidate in self.client.store.list_any("/local/domain")? { - let dom_path = format!("/local/domain/{}", domid_candidate); - let ip_path = format!("{}/hypha/network/guest/ipv4", dom_path); - let existing_ip = self.client.store.read_string_optional(&ip_path)?; - if let Some(existing_ip) = existing_ip { - let ipv4_network = Ipv4Network::from_str(&existing_ip)?; - used.push(ipv4_network.ip()); - } - } - - let mut found: Option = None; - for ip in network.iter() { - if !used.contains(&ip) { - found = Some(ip); - break; - } - } - - if found.is_none() { - return Err(anyhow!( - "unable to find ipv4 to allocate to container, ipv4 addresses are exhausted" - )); - } - - Ok(found.unwrap()) - } }