mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-02 21:00:55 +00:00
hypha: implement console support
This commit is contained in:
parent
ece88e16cc
commit
d9629f46d0
@ -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"
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user