hypha: implement console support

This commit is contained in:
Alex Zenla 2024-01-21 04:49:31 -08:00
parent ece88e16cc
commit d9629f46d0
No known key found for this signature in database
GPG Key ID: 067B238899B51269
5 changed files with 103 additions and 21 deletions

View File

@ -24,6 +24,7 @@ sha256 = "1.5.0"
url = "2.5.0" url = "2.5.0"
ureq = "2.9.1" ureq = "2.9.1"
path-clean = "1.0.1" path-clean = "1.0.1"
termion = "3.0.0"
[dependencies.xenstore] [dependencies.xenstore]
path = "../xenstore" path = "../xenstore"

View File

@ -6,12 +6,6 @@ use std::path::PathBuf;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about)] #[command(version, about)]
struct ControllerArgs { struct ControllerArgs {
#[arg(short, long)]
kernel: String,
#[arg(short = 'r', long)]
initrd: String,
#[arg(short, long, default_value = "auto")] #[arg(short, long, default_value = "auto")]
store: String, store: String,
@ -22,6 +16,10 @@ struct ControllerArgs {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum Commands { enum Commands {
Launch { Launch {
#[arg(short, long)]
kernel: String,
#[arg(short = 'r', long)]
initrd: String,
#[arg(short, long)] #[arg(short, long)]
image: String, image: String,
#[arg(short, long, default_value_t = 1)] #[arg(short, long, default_value_t = 1)]
@ -33,6 +31,10 @@ enum Commands {
#[arg(short, long)] #[arg(short, long)]
domain: u32, domain: u32,
}, },
Console {
#[arg(short, long)]
domain: u32,
},
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -51,16 +53,27 @@ fn main() -> Result<()> {
.map(|x| x.to_string()) .map(|x| x.to_string())
.ok_or_else(|| HyphaError::new("unable to convert store path to string"))?; .ok_or_else(|| HyphaError::new("unable to convert store path to string"))?;
let mut controller = Controller::new(store_path, args.kernel, args.initrd)?; let mut controller = Controller::new(store_path)?;
match args.command { match args.command {
Commands::Launch { image, cpus, mem } => { Commands::Launch {
let domid = controller.launch(image.as_str(), cpus, mem)?; kernel,
initrd,
image,
cpus,
mem,
} => {
let domid = controller.launch(&kernel, &initrd, &image, cpus, mem)?;
println!("launched domain: {}", domid); println!("launched domain: {}", domid);
} }
Commands::Destroy { domain } => { Commands::Destroy { domain } => {
controller.destroy(domain)?; controller.destroy(domain)?;
} }
Commands::Console { domain } => {
controller.console(domain)?;
}
} }
Ok(()) Ok(())
} }

View File

@ -7,8 +7,11 @@ use crate::image::cache::ImageCache;
use crate::image::name::ImageName; use crate::image::name::ImageName;
use crate::image::{ImageCompiler, ImageInfo}; use crate::image::{ImageCompiler, ImageInfo};
use loopdev::LoopControl; use loopdev::LoopControl;
use std::fs; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit;
use std::{fs, io, thread};
use termion::raw::IntoRawMode;
use uuid::Uuid; use uuid::Uuid;
use xenclient::{DomainConfig, DomainDisk, XenClient}; use xenclient::{DomainConfig, DomainDisk, XenClient};
use xenstore::client::{XsdClient, XsdInterface}; use xenstore::client::{XsdClient, XsdInterface};
@ -17,12 +20,10 @@ pub struct Controller {
image_cache: ImageCache, image_cache: ImageCache,
autoloop: AutoLoop, autoloop: AutoLoop,
client: XenClient, client: XenClient,
kernel_path: String,
initrd_path: String,
} }
impl Controller { impl Controller {
pub fn new(store_path: String, kernel_path: String, initrd_path: String) -> Result<Controller> { pub fn new(store_path: String) -> Result<Controller> {
let mut image_cache_path = PathBuf::from(store_path); let mut image_cache_path = PathBuf::from(store_path);
image_cache_path.push("cache"); image_cache_path.push("cache");
fs::create_dir_all(&image_cache_path)?; fs::create_dir_all(&image_cache_path)?;
@ -35,8 +36,6 @@ impl Controller {
image_cache, image_cache,
autoloop: AutoLoop::new(LoopControl::open()?), autoloop: AutoLoop::new(LoopControl::open()?),
client, client,
kernel_path,
initrd_path,
}) })
} }
@ -46,7 +45,14 @@ impl Controller {
compiler.compile(&image) compiler.compile(&image)
} }
pub fn launch(&mut self, image: &str, vcpus: u32, mem: u64) -> Result<u32> { pub fn launch(
&mut self,
kernel_path: &str,
initrd_path: &str,
image: &str,
vcpus: u32,
mem: u64,
) -> Result<u32> {
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let name = format!("hypha-{uuid}"); let name = format!("hypha-{uuid}");
let image_info = self.compile(image)?; let image_info = self.compile(image)?;
@ -64,8 +70,8 @@ impl Controller {
name: &name, name: &name,
max_vcpus: vcpus, max_vcpus: vcpus,
mem_mb: mem, mem_mb: mem,
kernel_path: self.kernel_path.as_str(), kernel_path,
initrd_path: self.initrd_path.as_str(), initrd_path,
cmdline: "quiet elevator=noop", cmdline: "quiet elevator=noop",
disks: vec![ disks: vec![
DomainDisk { DomainDisk {
@ -130,4 +136,45 @@ impl Controller {
} }
Ok(uuid) Ok(uuid)
} }
pub fn console(&mut self, domid: u32) -> Result<()> {
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()?;
}
}
}
} }

View File

@ -52,16 +52,19 @@ impl RegistryClient {
.url .url
.join(&format!("/v2/{}/manifests/{}", name, reference))?; .join(&format!("/v2/{}/manifests/{}", name, reference))?;
let accept = format!( let accept = format!(
"{}, {}, {}", "{}, {}, {}, {}",
MediaType::ImageManifest.to_docker_v2s2()?, MediaType::ImageManifest.to_docker_v2s2()?,
MediaType::ImageManifest, MediaType::ImageManifest,
MediaType::ImageIndex, MediaType::ImageIndex,
MediaType::ImageIndex.to_docker_v2s2()?,
); );
let response = self.call(self.agent.get(url.as_str()).set("Accept", &accept))?; let response = self.call(self.agent.get(url.as_str()).set("Accept", &accept))?;
let content_type = response.header("Content-Type").ok_or_else(|| { let content_type = response.header("Content-Type").ok_or_else(|| {
HyphaError::new("registry response did not have a Content-Type header") HyphaError::new("registry response did not have a Content-Type header")
})?; })?;
if content_type == MediaType::ImageIndex.to_string() { if content_type == MediaType::ImageIndex.to_string()
|| content_type == MediaType::ImageIndex.to_docker_v2s2()?
{
let index = ImageIndex::from_reader(response.into_reader())?; let index = ImageIndex::from_reader(response.into_reader())?;
let descriptor = self let descriptor = self
.pick_manifest(index) .pick_manifest(index)

View File

@ -10,7 +10,7 @@ use crate::x86::X86BootSetup;
use log::warn; use log::warn;
use std::error::Error; use std::error::Error;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::fs::read; use std::fs::{read, File, OpenOptions};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
@ -561,4 +561,22 @@ impl XenClient {
tx.commit()?; tx.commit()?;
Ok(()) Ok(())
} }
pub fn open_console(&mut self, domid: u32) -> Result<(File, File), XenClientError> {
let dom_path = self.store.get_domain_path(domid)?;
let console_tty_path = format!("{}/console/tty", dom_path);
let tty = self
.store
.read_string_optional(&console_tty_path)?
.unwrap_or("".to_string());
if tty.is_empty() {
return Err(XenClientError::new(&format!(
"domain {} does not have a tty",
domid
)));
}
let read = OpenOptions::new().read(true).write(false).open(&tty)?;
let write = OpenOptions::new().read(false).write(true).open(&tty)?;
Ok((read, write))
}
} }