From 226faae8799b23c1fded716abf9f0f37bcb4377e Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Thu, 18 Jul 2024 21:43:52 -0700 Subject: [PATCH] feature(kratactl): rework cli to use subcommands --- DEV.md | 35 +++--- FAQ.md | 2 +- README.md | 12 +- crates/ctl/src/cli/cpu_topology.rs | 46 ------- .../cli/{list_devices.rs => device/list.rs} | 22 ++-- crates/ctl/src/cli/device/mod.rs | 44 +++++++ crates/ctl/src/cli/host/cpu_topology.rs | 60 +++++++++ .../{identify_host.rs => host/identify.rs} | 4 +- crates/ctl/src/cli/{ => host}/idm_snoop.rs | 14 +-- crates/ctl/src/cli/host/mod.rs | 54 ++++++++ crates/ctl/src/cli/image/mod.rs | 44 +++++++ crates/ctl/src/cli/{ => image}/pull.rs | 14 +-- crates/ctl/src/cli/mod.rs | 119 +++--------------- crates/ctl/src/cli/{ => zone}/attach.rs | 6 +- crates/ctl/src/cli/{ => zone}/destroy.rs | 4 +- crates/ctl/src/cli/{ => zone}/exec.rs | 6 +- crates/ctl/src/cli/{ => zone}/launch.rs | 4 +- crates/ctl/src/cli/{ => zone}/list.rs | 22 ++-- crates/ctl/src/cli/{ => zone}/logs.rs | 6 +- crates/ctl/src/cli/{ => zone}/metrics.rs | 20 +-- crates/ctl/src/cli/zone/mod.rs | 89 +++++++++++++ crates/ctl/src/cli/{ => zone}/resolve.rs | 4 +- crates/ctl/src/cli/{ => zone}/top.rs | 16 +-- crates/ctl/src/cli/{ => zone}/watch.rs | 14 +-- crates/krata/proto/krata/v1/control.proto | 6 +- 25 files changed, 418 insertions(+), 249 deletions(-) delete mode 100644 crates/ctl/src/cli/cpu_topology.rs rename crates/ctl/src/cli/{list_devices.rs => device/list.rs} (86%) create mode 100644 crates/ctl/src/cli/device/mod.rs create mode 100644 crates/ctl/src/cli/host/cpu_topology.rs rename crates/ctl/src/cli/{identify_host.rs => host/identify.rs} (91%) rename crates/ctl/src/cli/{ => host}/idm_snoop.rs (94%) create mode 100644 crates/ctl/src/cli/host/mod.rs create mode 100644 crates/ctl/src/cli/image/mod.rs rename crates/ctl/src/cli/{ => image}/pull.rs (75%) rename crates/ctl/src/cli/{ => zone}/attach.rs (93%) rename crates/ctl/src/cli/{ => zone}/destroy.rs (97%) rename crates/ctl/src/cli/{ => zone}/exec.rs (96%) rename crates/ctl/src/cli/{ => zone}/launch.rs (99%) rename crates/ctl/src/cli/{ => zone}/list.rs (90%) rename crates/ctl/src/cli/{ => zone}/logs.rs (95%) rename crates/ctl/src/cli/{ => zone}/metrics.rs (80%) create mode 100644 crates/ctl/src/cli/zone/mod.rs rename crates/ctl/src/cli/{ => zone}/resolve.rs (92%) rename crates/ctl/src/cli/{ => zone}/top.rs (95%) rename crates/ctl/src/cli/{ => zone}/watch.rs (88%) diff --git a/DEV.md b/DEV.md index a051d38..f56c75f 100644 --- a/DEV.md +++ b/DEV.md @@ -4,25 +4,25 @@ krata is composed of four major executables: -| Executable | Runs On | User Interaction | Dev Runner | Code Path | -| ---------- | ------- | ---------------- | ------------------------ | ----------------- | -| kratad | host | backend daemon | ./hack/debug/kratad.sh | crates/daemon | -| kratanet | host | backend daemon | ./hack/debug/kratanet.sh | crates/network | -| kratactl | host | CLI tool | ./hack/debug/kratactl.sh | crates/ctl | -| kratazone | zone | none, zone init | N/A | crates/zone | +| Executable | Runs On | User Interaction | Dev Runner | Code Path | +|------------|---------|------------------|--------------------------|----------------| +| kratad | host | backend daemon | ./hack/debug/kratad.sh | crates/daemon | +| kratanet | host | backend daemon | ./hack/debug/kratanet.sh | crates/network | +| kratactl | host | CLI tool | ./hack/debug/kratactl.sh | crates/ctl | +| kratazone | zone | none, zone init | N/A | crates/zone | You will find the code to each executable available in the bin/ and src/ directories inside it's corresponding code path from the above table. ## Environment -| Component | Specification | Notes | -| ------------- | ------------- | --------------------------------------------------------------------------------- | -| Architecture | x86_64 | aarch64 support is still in development | -| Memory | At least 6GB | dom0 will need to be configured with lower memory limit to give krata zones room | -| Xen | 4.17 | Temporary due to hardcoded interface version constants | -| Debian | stable / sid | Debian is recommended due to the ease of Xen setup | -| rustup | any | Install Rustup from https://rustup.rs | +| Component | Specification | Notes | +|--------------|---------------|----------------------------------------------------------------------------------| +| Architecture | x86_64 | aarch64 support is still in development | +| Memory | At least 6GB | dom0 will need to be configured with lower memory limit to give krata zones room | +| Xen | 4.17+ | | +| Debian | stable / sid | Debian is recommended due to the ease of Xen setup | +| rustup | any | Install Rustup from https://rustup.rs | ## Setup Guide @@ -31,8 +31,7 @@ it's corresponding code path from the above table. 2. Install required packages: ```sh -$ apt install git xen-system-amd64 build-essential \ - libclang-dev musl-tools flex bison libelf-dev libssl-dev bc \ +$ apt install git xen-system-amd64 build-essential musl-tools \ protobuf-compiler libprotobuf-dev squashfs-tools erofs-utils ``` @@ -83,17 +82,17 @@ $ cp target/kernel/addons-x86_64.squashfs /var/lib/krata/zone/addons.squashfs 10. Run `kratactl` to launch a zone: ```sh -$ ./hack/debug/kratactl.sh launch --attach alpine:latest +$ ./hack/debug/kratactl.sh zone launch --attach alpine:latest ``` To detach from the zone console, use `Ctrl + ]` on your keyboard. To list the running zones, run: ```sh -$ ./hack/debug/kratactl.sh list +$ ./hack/debug/kratactl.sh zone list ``` To destroy a running zone, copy it's UUID from either the launch command or the zone list and run: ```sh -$ ./hack/debug/kratactl.sh destroy ZONE_UUID +$ ./hack/debug/kratactl.sh zone destroy ZONE_UUID ``` diff --git a/FAQ.md b/FAQ.md index 2430e09..21ba8d6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,4 +12,4 @@ Xen is a very interesting technology, and Edera believes that type-1 hypervisors ## Why not utilize pvcalls to provide access to the host network? -pvcalls is extremely interesting, and although it is certainly possible to utilize pvcalls to get the job done, we chose to utilize userspace networking technology in order to enhance security. Our goal is to drop the use of all xen networking backend drivers within the kernel and have the guest talk directly to a userspace daemon, bypassing the vif (xen-netback) driver. Currently, in order to develop the networking layer, we utilize xen-netback and then use raw sockets to provide the userspace networking layer on the host. +pvcalls is fascinating, and although it is certainly possible to utilize pvcalls to get the job done, we chose to utilize userspace networking technology in order to enhance security. Our goal is to drop the use of all xen networking backend drivers within the kernel and have the guest talk directly to a userspace daemon, bypassing the vif (xen-netback) driver. Currently, in order to develop the networking layer, we utilize xen-netback and then use raw sockets to provide the userspace networking layer on the host. diff --git a/README.md b/README.md index 2652187..3da7350 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ An isolation engine for securing compute workloads. +```bash +$ kratactl zone launch -a alpine:latest +``` + ![license](https://img.shields.io/github/license/edera-dev/krata) ![discord](https://img.shields.io/discord/1207447453083766814?label=discord) [![check](https://github.com/edera-dev/krata/actions/workflows/check.yml/badge.svg)](https://github.com/edera-dev/krata/actions/workflows/check.yml) @@ -22,7 +26,7 @@ krata utilizes the core of the Xen hypervisor with a fully memory-safe Rust cont ## Hardware Support -| Architecture | Completion Level | Hardware Virtualization | -| ------------ | ---------------- | ------------------------------- | -| x86_64 | 100% Completed | None, Intel VT-x, AMD-V | -| aarch64 | 10% Completed | AArch64 virtualization | +| Architecture | Completion Level | Hardware Virtualization | +|--------------|------------------|-------------------------| +| x86_64 | 100% Completed | None, Intel VT-x, AMD-V | +| aarch64 | 10% Completed | AArch64 virtualization | diff --git a/crates/ctl/src/cli/cpu_topology.rs b/crates/ctl/src/cli/cpu_topology.rs deleted file mode 100644 index 12b77fd..0000000 --- a/crates/ctl/src/cli/cpu_topology.rs +++ /dev/null @@ -1,46 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::v1::control::{control_service_client::ControlServiceClient, HostCpuTopologyRequest}; - -use tonic::{transport::Channel, Request}; - -fn class_to_str(input: i32) -> String { - match input { - 0 => "Standard".to_string(), - 1 => "Performance".to_string(), - 2 => "Efficiency".to_string(), - _ => "???".to_string(), - } -} - -#[derive(Parser)] -#[command(about = "Display information about a host's CPU topology")] -pub struct CpuTopologyCommand {} - -impl CpuTopologyCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - println!( - "{0:<10} {1:<10} {2:<10} {3:<10} {4:<10} {5:<10}", - "CPUID", "Node", "Socket", "Core", "Thread", "Class" - ); - - let response = client - .get_host_cpu_topology(Request::new(HostCpuTopologyRequest {})) - .await? - .into_inner(); - - for (i, cpu) in response.cpus.iter().enumerate() { - println!( - "{0:<10} {1:<10} {2:<10} {3:<10} {4:<10} {5:<10}", - i, - cpu.node, - cpu.socket, - cpu.core, - cpu.thread, - class_to_str(cpu.class) - ); - } - - Ok(()) - } -} diff --git a/crates/ctl/src/cli/list_devices.rs b/crates/ctl/src/cli/device/list.rs similarity index 86% rename from crates/ctl/src/cli/list_devices.rs rename to crates/ctl/src/cli/device/list.rs index c289f36..1280593 100644 --- a/crates/ctl/src/cli/list_devices.rs +++ b/crates/ctl/src/cli/device/list.rs @@ -12,7 +12,7 @@ use tonic::transport::Channel; use crate::format::{kv2line, proto2dynamic, proto2kv}; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum ListDevicesFormat { +enum DeviceListFormat { Table, Json, JsonPretty, @@ -24,12 +24,12 @@ enum ListDevicesFormat { #[derive(Parser)] #[command(about = "List the devices on the isolation engine")] -pub struct ListDevicesCommand { +pub struct DeviceListCommand { #[arg(short, long, default_value = "table", help = "Output format")] - format: ListDevicesFormat, + format: DeviceListFormat, } -impl ListDevicesCommand { +impl DeviceListCommand { pub async fn run( self, mut client: ControlServiceClient, @@ -44,26 +44,26 @@ impl ListDevicesCommand { devices.sort_by(|a, b| a.name.cmp(&b.name)); match self.format { - ListDevicesFormat::Table => { + DeviceListFormat::Table => { self.print_devices_table(devices)?; } - ListDevicesFormat::Simple => { + DeviceListFormat::Simple => { for device in devices { println!("{}\t{}\t{}", device.name, device.claimed, device.owner); } } - ListDevicesFormat::Json | ListDevicesFormat::JsonPretty | ListDevicesFormat::Yaml => { + DeviceListFormat::Json | DeviceListFormat::JsonPretty | DeviceListFormat::Yaml => { let mut values = Vec::new(); for device in devices { let message = proto2dynamic(device)?; values.push(serde_json::to_value(message)?); } let value = Value::Array(values); - let encoded = if self.format == ListDevicesFormat::JsonPretty { + let encoded = if self.format == DeviceListFormat::JsonPretty { serde_json::to_string_pretty(&value)? - } else if self.format == ListDevicesFormat::Yaml { + } else if self.format == DeviceListFormat::Yaml { serde_yaml::to_string(&value)? } else { serde_json::to_string(&value)? @@ -71,14 +71,14 @@ impl ListDevicesCommand { println!("{}", encoded.trim()); } - ListDevicesFormat::Jsonl => { + DeviceListFormat::Jsonl => { for device in devices { let message = proto2dynamic(device)?; println!("{}", serde_json::to_string(&message)?); } } - ListDevicesFormat::KeyValue => { + DeviceListFormat::KeyValue => { self.print_key_value(devices)?; } } diff --git a/crates/ctl/src/cli/device/mod.rs b/crates/ctl/src/cli/device/mod.rs new file mode 100644 index 0000000..58d9e2a --- /dev/null +++ b/crates/ctl/src/cli/device/mod.rs @@ -0,0 +1,44 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use tonic::transport::Channel; + +use krata::events::EventStream; +use krata::v1::control::control_service_client::ControlServiceClient; + +use crate::cli::device::list::DeviceListCommand; + +pub mod list; + +#[derive(Parser)] +#[command(about = "Manage the devices on the isolation engine")] +pub struct DeviceCommand { + #[command(subcommand)] + subcommand: DeviceCommands, +} + +impl DeviceCommand { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + self.subcommand.run(client, events).await + } +} + +#[derive(Subcommand)] +pub enum DeviceCommands { + List(DeviceListCommand), +} + +impl DeviceCommands { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + match self { + DeviceCommands::List(list) => list.run(client, events).await, + } + } +} diff --git a/crates/ctl/src/cli/host/cpu_topology.rs b/crates/ctl/src/cli/host/cpu_topology.rs new file mode 100644 index 0000000..253ad2f --- /dev/null +++ b/crates/ctl/src/cli/host/cpu_topology.rs @@ -0,0 +1,60 @@ +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use comfy_table::presets::UTF8_FULL_CONDENSED; +use comfy_table::{Cell, Table}; +use krata::v1::control::{ + control_service_client::ControlServiceClient, HostCpuTopologyClass, HostCpuTopologyRequest, +}; + +use tonic::{transport::Channel, Request}; + +fn class_to_str(input: HostCpuTopologyClass) -> String { + match input { + HostCpuTopologyClass::Standard => "Standard".to_string(), + HostCpuTopologyClass::Performance => "Performance".to_string(), + HostCpuTopologyClass::Efficiency => "Efficiency".to_string(), + } +} + +#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] +enum HostCpuTopologyFormat { + Table, +} + +#[derive(Parser)] +#[command(about = "Display information about the host CPU topology")] +pub struct HostCpuTopologyCommand { + #[arg(short, long, default_value = "table", help = "Output format")] + format: HostCpuTopologyFormat, +} + +impl HostCpuTopologyCommand { + pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { + let response = client + .get_host_cpu_topology(Request::new(HostCpuTopologyRequest {})) + .await? + .into_inner(); + + let mut table = Table::new(); + table.load_preset(UTF8_FULL_CONDENSED); + table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); + table.set_header(vec!["id", "node", "socket", "core", "thread", "class"]); + + for (i, cpu) in response.cpus.iter().enumerate() { + table.add_row(vec![ + Cell::new(i), + Cell::new(cpu.node), + Cell::new(cpu.socket), + Cell::new(cpu.core), + Cell::new(cpu.thread), + Cell::new(class_to_str(cpu.class())), + ]); + } + + if !table.is_empty() { + println!("{}", table); + } + + Ok(()) + } +} diff --git a/crates/ctl/src/cli/identify_host.rs b/crates/ctl/src/cli/host/identify.rs similarity index 91% rename from crates/ctl/src/cli/identify_host.rs rename to crates/ctl/src/cli/host/identify.rs index de96fc4..caf60f5 100644 --- a/crates/ctl/src/cli/identify_host.rs +++ b/crates/ctl/src/cli/host/identify.rs @@ -6,9 +6,9 @@ use tonic::{transport::Channel, Request}; #[derive(Parser)] #[command(about = "Identify information about the host")] -pub struct IdentifyHostCommand {} +pub struct HostIdentifyCommand {} -impl IdentifyHostCommand { +impl HostIdentifyCommand { pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { let response = client .identify_host(Request::new(IdentifyHostRequest {})) diff --git a/crates/ctl/src/cli/idm_snoop.rs b/crates/ctl/src/cli/host/idm_snoop.rs similarity index 94% rename from crates/ctl/src/cli/idm_snoop.rs rename to crates/ctl/src/cli/host/idm_snoop.rs index b66c7c2..e5c0f76 100644 --- a/crates/ctl/src/cli/idm_snoop.rs +++ b/crates/ctl/src/cli/host/idm_snoop.rs @@ -15,7 +15,7 @@ use tonic::transport::Channel; use crate::format::{kv2line, proto2dynamic, value2kv}; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum IdmSnoopFormat { +enum HostIdmSnoopFormat { Simple, Jsonl, KeyValue, @@ -23,12 +23,12 @@ enum IdmSnoopFormat { #[derive(Parser)] #[command(about = "Snoop on the IDM bus")] -pub struct IdmSnoopCommand { +pub struct HostIdmSnoopCommand { #[arg(short, long, default_value = "simple", help = "Output format")] - format: IdmSnoopFormat, + format: HostIdmSnoopFormat, } -impl IdmSnoopCommand { +impl HostIdmSnoopCommand { pub async fn run( self, mut client: ControlServiceClient, @@ -43,16 +43,16 @@ impl IdmSnoopCommand { }; match self.format { - IdmSnoopFormat::Simple => { + HostIdmSnoopFormat::Simple => { self.print_simple(line)?; } - IdmSnoopFormat::Jsonl => { + HostIdmSnoopFormat::Jsonl => { let encoded = serde_json::to_string(&line)?; println!("{}", encoded.trim()); } - IdmSnoopFormat::KeyValue => { + HostIdmSnoopFormat::KeyValue => { self.print_key_value(line)?; } } diff --git a/crates/ctl/src/cli/host/mod.rs b/crates/ctl/src/cli/host/mod.rs new file mode 100644 index 0000000..ca6d853 --- /dev/null +++ b/crates/ctl/src/cli/host/mod.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use tonic::transport::Channel; + +use krata::events::EventStream; +use krata::v1::control::control_service_client::ControlServiceClient; + +use crate::cli::host::cpu_topology::HostCpuTopologyCommand; +use crate::cli::host::identify::HostIdentifyCommand; +use crate::cli::host::idm_snoop::HostIdmSnoopCommand; + +pub mod cpu_topology; +pub mod identify; +pub mod idm_snoop; + +#[derive(Parser)] +#[command(about = "Manage the host of the isolation engine")] +pub struct HostCommand { + #[command(subcommand)] + subcommand: HostCommands, +} + +impl HostCommand { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + self.subcommand.run(client, events).await + } +} + +#[derive(Subcommand)] +pub enum HostCommands { + CpuTopology(HostCpuTopologyCommand), + Identify(HostIdentifyCommand), + IdmSnoop(HostIdmSnoopCommand), +} + +impl HostCommands { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + match self { + HostCommands::CpuTopology(cpu_topology) => cpu_topology.run(client).await, + + HostCommands::Identify(identify) => identify.run(client).await, + + HostCommands::IdmSnoop(snoop) => snoop.run(client, events).await, + } + } +} diff --git a/crates/ctl/src/cli/image/mod.rs b/crates/ctl/src/cli/image/mod.rs new file mode 100644 index 0000000..905fd4e --- /dev/null +++ b/crates/ctl/src/cli/image/mod.rs @@ -0,0 +1,44 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use tonic::transport::Channel; + +use krata::events::EventStream; +use krata::v1::control::control_service_client::ControlServiceClient; + +use crate::cli::image::pull::ImagePullCommand; + +pub mod pull; + +#[derive(Parser)] +#[command(about = "Manage the images on the isolation engine")] +pub struct ImageCommand { + #[command(subcommand)] + subcommand: ImageCommands, +} + +impl ImageCommand { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + self.subcommand.run(client, events).await + } +} + +#[derive(Subcommand)] +pub enum ImageCommands { + Pull(ImagePullCommand), +} + +impl ImageCommands { + pub async fn run( + self, + client: ControlServiceClient, + _events: EventStream, + ) -> Result<()> { + match self { + ImageCommands::Pull(pull) => pull.run(client).await, + } + } +} diff --git a/crates/ctl/src/cli/pull.rs b/crates/ctl/src/cli/image/pull.rs similarity index 75% rename from crates/ctl/src/cli/pull.rs rename to crates/ctl/src/cli/image/pull.rs index 3a63fe1..0a05008 100644 --- a/crates/ctl/src/cli/pull.rs +++ b/crates/ctl/src/cli/image/pull.rs @@ -10,7 +10,7 @@ use tonic::transport::Channel; use crate::pull::pull_interactive_progress; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -pub enum PullImageFormat { +pub enum ImagePullImageFormat { Squashfs, Erofs, Tar, @@ -18,24 +18,24 @@ pub enum PullImageFormat { #[derive(Parser)] #[command(about = "Pull an image into the cache")] -pub struct PullCommand { +pub struct ImagePullCommand { #[arg(help = "Image name")] image: String, #[arg(short = 's', long, default_value = "squashfs", help = "Image format")] - image_format: PullImageFormat, + image_format: ImagePullImageFormat, #[arg(short = 'o', long, help = "Overwrite image cache")] overwrite_cache: bool, } -impl PullCommand { +impl ImagePullCommand { pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { let response = client .pull_image(PullImageRequest { image: self.image.clone(), format: match self.image_format { - PullImageFormat::Squashfs => OciImageFormat::Squashfs.into(), - PullImageFormat::Erofs => OciImageFormat::Erofs.into(), - PullImageFormat::Tar => OciImageFormat::Tar.into(), + ImagePullImageFormat::Squashfs => OciImageFormat::Squashfs.into(), + ImagePullImageFormat::Erofs => OciImageFormat::Erofs.into(), + ImagePullImageFormat::Tar => OciImageFormat::Tar.into(), }, overwrite_cache: self.overwrite_cache, }) diff --git a/crates/ctl/src/cli/mod.rs b/crates/ctl/src/cli/mod.rs index e0e42f6..76e0533 100644 --- a/crates/ctl/src/cli/mod.rs +++ b/crates/ctl/src/cli/mod.rs @@ -1,21 +1,14 @@ -pub mod attach; -pub mod cpu_topology; -pub mod destroy; -pub mod exec; -pub mod identify_host; -pub mod idm_snoop; -pub mod launch; -pub mod list; -pub mod list_devices; -pub mod logs; -pub mod metrics; -pub mod pull; -pub mod resolve; -pub mod top; -pub mod watch; +pub mod device; +pub mod host; +pub mod image; +pub mod zone; +use crate::cli::device::DeviceCommand; +use crate::cli::host::HostCommand; +use crate::cli::image::ImageCommand; +use crate::cli::zone::ZoneCommand; use anyhow::{anyhow, Result}; -use clap::{Parser, Subcommand}; +use clap::Parser; use krata::{ client::ControlClientProvider, events::EventStream, @@ -23,14 +16,6 @@ use krata::{ }; use tonic::{transport::Channel, Request}; -use self::{ - attach::AttachCommand, cpu_topology::CpuTopologyCommand, destroy::DestroyCommand, - exec::ExecCommand, identify_host::IdentifyHostCommand, idm_snoop::IdmSnoopCommand, - launch::LaunchCommand, list::ListCommand, list_devices::ListDevicesCommand, logs::LogsCommand, - metrics::MetricsCommand, pull::PullCommand, resolve::ResolveCommand, top::TopCommand, - watch::WatchCommand, -}; - #[derive(Parser)] #[command(version, about = "Control the krata isolation engine")] pub struct ControlCommand { @@ -43,26 +28,15 @@ pub struct ControlCommand { connection: String, #[command(subcommand)] - command: Commands, + command: ControlCommands, } -#[derive(Subcommand)] -pub enum Commands { - Launch(LaunchCommand), - Destroy(DestroyCommand), - List(ListCommand), - ListDevices(ListDevicesCommand), - Attach(AttachCommand), - Pull(PullCommand), - Logs(LogsCommand), - Watch(WatchCommand), - Resolve(ResolveCommand), - Metrics(MetricsCommand), - IdmSnoop(IdmSnoopCommand), - Top(TopCommand), - IdentifyHost(IdentifyHostCommand), - Exec(ExecCommand), - CpuTopology(CpuTopologyCommand), +#[derive(Parser)] +pub enum ControlCommands { + Zone(ZoneCommand), + Image(ImageCommand), + Device(DeviceCommand), + Host(HostCommand), } impl ControlCommand { @@ -71,67 +45,14 @@ impl ControlCommand { let events = EventStream::open(client.clone()).await?; match self.command { - Commands::Launch(launch) => { - launch.run(client, events).await?; - } + ControlCommands::Zone(zone) => zone.run(client, events).await, - Commands::Destroy(destroy) => { - destroy.run(client, events).await?; - } + ControlCommands::Image(image) => image.run(client, events).await, - Commands::Attach(attach) => { - attach.run(client, events).await?; - } + ControlCommands::Device(device) => device.run(client, events).await, - Commands::Logs(logs) => { - logs.run(client, events).await?; - } - - Commands::List(list) => { - list.run(client, events).await?; - } - - Commands::Watch(watch) => { - watch.run(events).await?; - } - - Commands::Resolve(resolve) => { - resolve.run(client).await?; - } - - Commands::Metrics(metrics) => { - metrics.run(client, events).await?; - } - - Commands::IdmSnoop(snoop) => { - snoop.run(client, events).await?; - } - - Commands::Top(top) => { - top.run(client, events).await?; - } - - Commands::Pull(pull) => { - pull.run(client).await?; - } - - Commands::IdentifyHost(identify) => { - identify.run(client).await?; - } - - Commands::Exec(exec) => { - exec.run(client).await?; - } - - Commands::ListDevices(list) => { - list.run(client, events).await?; - } - - Commands::CpuTopology(cpu_topology) => { - cpu_topology.run(client).await?; - } + ControlCommands::Host(snoop) => snoop.run(client, events).await, } - Ok(()) } } diff --git a/crates/ctl/src/cli/attach.rs b/crates/ctl/src/cli/zone/attach.rs similarity index 93% rename from crates/ctl/src/cli/attach.rs rename to crates/ctl/src/cli/zone/attach.rs index 82796da..990e74f 100644 --- a/crates/ctl/src/cli/attach.rs +++ b/crates/ctl/src/cli/zone/attach.rs @@ -7,16 +7,16 @@ use tonic::transport::Channel; use crate::console::StdioConsoleStream; -use super::resolve_zone; +use crate::cli::resolve_zone; #[derive(Parser)] #[command(about = "Attach to the zone console")] -pub struct AttachCommand { +pub struct ZoneAttachCommand { #[arg(help = "Zone to attach to, either the name or the uuid")] zone: String, } -impl AttachCommand { +impl ZoneAttachCommand { pub async fn run( self, mut client: ControlServiceClient, diff --git a/crates/ctl/src/cli/destroy.rs b/crates/ctl/src/cli/zone/destroy.rs similarity index 97% rename from crates/ctl/src/cli/destroy.rs rename to crates/ctl/src/cli/zone/destroy.rs index 38eae76..de5d59f 100644 --- a/crates/ctl/src/cli/destroy.rs +++ b/crates/ctl/src/cli/zone/destroy.rs @@ -18,7 +18,7 @@ use crate::cli::resolve_zone; #[derive(Parser)] #[command(about = "Destroy a zone")] -pub struct DestroyCommand { +pub struct ZoneDestroyCommand { #[arg( short = 'W', long, @@ -29,7 +29,7 @@ pub struct DestroyCommand { zone: String, } -impl DestroyCommand { +impl ZoneDestroyCommand { pub async fn run( self, mut client: ControlServiceClient, diff --git a/crates/ctl/src/cli/exec.rs b/crates/ctl/src/cli/zone/exec.rs similarity index 96% rename from crates/ctl/src/cli/exec.rs rename to crates/ctl/src/cli/zone/exec.rs index d2c124a..0cd4b63 100644 --- a/crates/ctl/src/cli/exec.rs +++ b/crates/ctl/src/cli/zone/exec.rs @@ -12,11 +12,11 @@ use tonic::{transport::Channel, Request}; use crate::console::StdioConsoleStream; -use super::resolve_zone; +use crate::cli::resolve_zone; #[derive(Parser)] #[command(about = "Execute a command inside the zone")] -pub struct ExecCommand { +pub struct ZoneExecCommand { #[arg[short, long, help = "Environment variables"]] env: Option>, #[arg(short = 'w', long, help = "Working directory")] @@ -31,7 +31,7 @@ pub struct ExecCommand { command: Vec, } -impl ExecCommand { +impl ZoneExecCommand { pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { let zone_id: String = resolve_zone(&mut client, &self.zone).await?; let initial = ExecZoneRequest { diff --git a/crates/ctl/src/cli/launch.rs b/crates/ctl/src/cli/zone/launch.rs similarity index 99% rename from crates/ctl/src/cli/launch.rs rename to crates/ctl/src/cli/zone/launch.rs index fb4a251..452d700 100644 --- a/crates/ctl/src/cli/launch.rs +++ b/crates/ctl/src/cli/zone/launch.rs @@ -29,7 +29,7 @@ pub enum LaunchImageFormat { #[derive(Parser)] #[command(about = "Launch a new zone")] -pub struct LaunchCommand { +pub struct ZoneLaunchCommand { #[arg(long, default_value = "squashfs", help = "Image format")] image_format: LaunchImageFormat, #[arg(long, help = "Overwrite image cache on pull")] @@ -77,7 +77,7 @@ pub struct LaunchCommand { command: Vec, } -impl LaunchCommand { +impl ZoneLaunchCommand { pub async fn run( self, mut client: ControlServiceClient, diff --git a/crates/ctl/src/cli/list.rs b/crates/ctl/src/cli/zone/list.rs similarity index 90% rename from crates/ctl/src/cli/list.rs rename to crates/ctl/src/cli/zone/list.rs index 65589ff..eef38c5 100644 --- a/crates/ctl/src/cli/list.rs +++ b/crates/ctl/src/cli/zone/list.rs @@ -17,7 +17,7 @@ use tonic::{transport::Channel, Request}; use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line, zone_status_text}; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum ListFormat { +enum ZoneListFormat { Table, Json, JsonPretty, @@ -29,14 +29,14 @@ enum ListFormat { #[derive(Parser)] #[command(about = "List the zones on the isolation engine")] -pub struct ListCommand { +pub struct ZoneListCommand { #[arg(short, long, default_value = "table", help = "Output format")] - format: ListFormat, + format: ZoneListFormat, #[arg(help = "Limit to a single zone, either the name or the uuid")] zone: Option, } -impl ListCommand { +impl ZoneListCommand { pub async fn run( self, mut client: ControlServiceClient, @@ -69,26 +69,26 @@ impl ListCommand { }); match self.format { - ListFormat::Table => { + ZoneListFormat::Table => { self.print_zone_table(zones)?; } - ListFormat::Simple => { + ZoneListFormat::Simple => { for zone in zones { println!("{}", zone_simple_line(&zone)); } } - ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => { + ZoneListFormat::Json | ZoneListFormat::JsonPretty | ZoneListFormat::Yaml => { let mut values = Vec::new(); for zone in zones { let message = proto2dynamic(zone)?; values.push(serde_json::to_value(message)?); } let value = Value::Array(values); - let encoded = if self.format == ListFormat::JsonPretty { + let encoded = if self.format == ZoneListFormat::JsonPretty { serde_json::to_string_pretty(&value)? - } else if self.format == ListFormat::Yaml { + } else if self.format == ZoneListFormat::Yaml { serde_yaml::to_string(&value)? } else { serde_json::to_string(&value)? @@ -96,14 +96,14 @@ impl ListCommand { println!("{}", encoded.trim()); } - ListFormat::Jsonl => { + ZoneListFormat::Jsonl => { for zone in zones { let message = proto2dynamic(zone)?; println!("{}", serde_json::to_string(&message)?); } } - ListFormat::KeyValue => { + ZoneListFormat::KeyValue => { self.print_key_value(zones)?; } } diff --git a/crates/ctl/src/cli/logs.rs b/crates/ctl/src/cli/zone/logs.rs similarity index 95% rename from crates/ctl/src/cli/logs.rs rename to crates/ctl/src/cli/zone/logs.rs index 2d12ea1..820183b 100644 --- a/crates/ctl/src/cli/logs.rs +++ b/crates/ctl/src/cli/zone/logs.rs @@ -12,18 +12,18 @@ use tonic::transport::Channel; use crate::console::StdioConsoleStream; -use super::resolve_zone; +use crate::cli::resolve_zone; #[derive(Parser)] #[command(about = "View the logs of a zone")] -pub struct LogsCommand { +pub struct ZoneLogsCommand { #[arg(short, long, help = "Follow output from the zone")] follow: bool, #[arg(help = "Zone to show logs for, either the name or the uuid")] zone: String, } -impl LogsCommand { +impl ZoneLogsCommand { pub async fn run( self, mut client: ControlServiceClient, diff --git a/crates/ctl/src/cli/metrics.rs b/crates/ctl/src/cli/zone/metrics.rs similarity index 80% rename from crates/ctl/src/cli/metrics.rs rename to crates/ctl/src/cli/zone/metrics.rs index 31a8737..e97eedd 100644 --- a/crates/ctl/src/cli/metrics.rs +++ b/crates/ctl/src/cli/zone/metrics.rs @@ -12,10 +12,10 @@ use tonic::transport::Channel; use crate::format::{kv2line, metrics_flat, metrics_tree, proto2dynamic}; -use super::resolve_zone; +use crate::cli::resolve_zone; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum MetricsFormat { +enum ZoneMetricsFormat { Tree, Json, JsonPretty, @@ -25,14 +25,14 @@ enum MetricsFormat { #[derive(Parser)] #[command(about = "Read metrics from the zone")] -pub struct MetricsCommand { +pub struct ZoneMetricsCommand { #[arg(short, long, default_value = "tree", help = "Output format")] - format: MetricsFormat, + format: ZoneMetricsFormat, #[arg(help = "Zone to read metrics for, either the name or the uuid")] zone: String, } -impl MetricsCommand { +impl ZoneMetricsCommand { pub async fn run( self, mut client: ControlServiceClient, @@ -46,15 +46,15 @@ impl MetricsCommand { .root .unwrap_or_default(); match self.format { - MetricsFormat::Tree => { + ZoneMetricsFormat::Tree => { self.print_metrics_tree(root)?; } - MetricsFormat::Json | MetricsFormat::JsonPretty | MetricsFormat::Yaml => { + ZoneMetricsFormat::Json | ZoneMetricsFormat::JsonPretty | ZoneMetricsFormat::Yaml => { let value = serde_json::to_value(proto2dynamic(root)?)?; - let encoded = if self.format == MetricsFormat::JsonPretty { + let encoded = if self.format == ZoneMetricsFormat::JsonPretty { serde_json::to_string_pretty(&value)? - } else if self.format == MetricsFormat::Yaml { + } else if self.format == ZoneMetricsFormat::Yaml { serde_yaml::to_string(&value)? } else { serde_json::to_string(&value)? @@ -62,7 +62,7 @@ impl MetricsCommand { println!("{}", encoded.trim()); } - MetricsFormat::KeyValue => { + ZoneMetricsFormat::KeyValue => { self.print_key_value(root)?; } } diff --git a/crates/ctl/src/cli/zone/mod.rs b/crates/ctl/src/cli/zone/mod.rs new file mode 100644 index 0000000..1fef6e3 --- /dev/null +++ b/crates/ctl/src/cli/zone/mod.rs @@ -0,0 +1,89 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use tonic::transport::Channel; + +use krata::events::EventStream; +use krata::v1::control::control_service_client::ControlServiceClient; + +use crate::cli::zone::attach::ZoneAttachCommand; +use crate::cli::zone::destroy::ZoneDestroyCommand; +use crate::cli::zone::exec::ZoneExecCommand; +use crate::cli::zone::launch::ZoneLaunchCommand; +use crate::cli::zone::list::ZoneListCommand; +use crate::cli::zone::logs::ZoneLogsCommand; +use crate::cli::zone::metrics::ZoneMetricsCommand; +use crate::cli::zone::resolve::ZoneResolveCommand; +use crate::cli::zone::top::ZoneTopCommand; +use crate::cli::zone::watch::ZoneWatchCommand; + +pub mod attach; +pub mod destroy; +pub mod exec; +pub mod launch; +pub mod list; +pub mod logs; +pub mod metrics; +pub mod resolve; +pub mod top; +pub mod watch; + +#[derive(Parser)] +#[command(about = "Manage the zones on the isolation engine")] +pub struct ZoneCommand { + #[command(subcommand)] + subcommand: ZoneCommands, +} + +impl ZoneCommand { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + self.subcommand.run(client, events).await + } +} + +#[derive(Subcommand)] +pub enum ZoneCommands { + Attach(ZoneAttachCommand), + List(ZoneListCommand), + Launch(ZoneLaunchCommand), + Destroy(ZoneDestroyCommand), + Exec(ZoneExecCommand), + Logs(ZoneLogsCommand), + Metrics(ZoneMetricsCommand), + Resolve(ZoneResolveCommand), + Top(ZoneTopCommand), + Watch(ZoneWatchCommand), +} + +impl ZoneCommands { + pub async fn run( + self, + client: ControlServiceClient, + events: EventStream, + ) -> Result<()> { + match self { + ZoneCommands::Launch(launch) => launch.run(client, events).await, + + ZoneCommands::Destroy(destroy) => destroy.run(client, events).await, + + ZoneCommands::Attach(attach) => attach.run(client, events).await, + + ZoneCommands::Logs(logs) => logs.run(client, events).await, + + ZoneCommands::List(list) => list.run(client, events).await, + + ZoneCommands::Watch(watch) => watch.run(events).await, + + ZoneCommands::Resolve(resolve) => resolve.run(client).await, + + ZoneCommands::Metrics(metrics) => metrics.run(client, events).await, + + ZoneCommands::Top(top) => top.run(client, events).await, + + ZoneCommands::Exec(exec) => exec.run(client).await, + } + } +} diff --git a/crates/ctl/src/cli/resolve.rs b/crates/ctl/src/cli/zone/resolve.rs similarity index 92% rename from crates/ctl/src/cli/resolve.rs rename to crates/ctl/src/cli/zone/resolve.rs index b7ed19e..34251d6 100644 --- a/crates/ctl/src/cli/resolve.rs +++ b/crates/ctl/src/cli/zone/resolve.rs @@ -6,12 +6,12 @@ use tonic::{transport::Channel, Request}; #[derive(Parser)] #[command(about = "Resolve a zone name to a uuid")] -pub struct ResolveCommand { +pub struct ZoneResolveCommand { #[arg(help = "Zone name")] zone: String, } -impl ResolveCommand { +impl ZoneResolveCommand { pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { let reply = client .resolve_zone(Request::new(ResolveZoneRequest { diff --git a/crates/ctl/src/cli/top.rs b/crates/ctl/src/cli/zone/top.rs similarity index 95% rename from crates/ctl/src/cli/top.rs rename to crates/ctl/src/cli/zone/top.rs index 90fdc97..843192c 100644 --- a/crates/ctl/src/cli/top.rs +++ b/crates/ctl/src/cli/zone/top.rs @@ -32,11 +32,11 @@ use crate::{ #[derive(Parser)] #[command(about = "Dashboard for running zones")] -pub struct TopCommand {} +pub struct ZoneTopCommand {} pub type Tui = Terminal>; -impl TopCommand { +impl ZoneTopCommand { pub async fn run( self, client: ControlServiceClient, @@ -44,14 +44,14 @@ impl TopCommand { ) -> Result<()> { let collector = MultiMetricCollector::new(client, events, Duration::from_millis(200))?; let collector = collector.launch().await?; - let mut tui = TopCommand::init()?; - let mut app = TopApp { + let mut tui = ZoneTopCommand::init()?; + let mut app = ZoneTopApp { metrics: MultiMetricState { zones: vec![] }, exit: false, table: TableState::new(), }; app.run(collector, &mut tui).await?; - TopCommand::restore()?; + ZoneTopCommand::restore()?; Ok(()) } @@ -68,13 +68,13 @@ impl TopCommand { } } -pub struct TopApp { +pub struct ZoneTopApp { table: TableState, metrics: MultiMetricState, exit: bool, } -impl TopApp { +impl ZoneTopApp { pub async fn run( &mut self, mut collector: MultiMetricCollectorHandle, @@ -136,7 +136,7 @@ impl TopApp { } } -impl Widget for &mut TopApp { +impl Widget for &mut ZoneTopApp { fn render(self, area: Rect, buf: &mut Buffer) { let title = Title::from(" krata isolation engine ".bold()); let instructions = Title::from(vec![" Quit ".into(), " ".blue().bold()]); diff --git a/crates/ctl/src/cli/watch.rs b/crates/ctl/src/cli/zone/watch.rs similarity index 88% rename from crates/ctl/src/cli/watch.rs rename to crates/ctl/src/cli/zone/watch.rs index 9cbc1ac..ed23cab 100644 --- a/crates/ctl/src/cli/watch.rs +++ b/crates/ctl/src/cli/zone/watch.rs @@ -10,7 +10,7 @@ use serde_json::Value; use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line}; #[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum WatchFormat { +enum ZoneWatchFormat { Simple, Json, KeyValue, @@ -18,12 +18,12 @@ enum WatchFormat { #[derive(Parser)] #[command(about = "Watch for zone changes")] -pub struct WatchCommand { +pub struct ZoneWatchCommand { #[arg(short, long, default_value = "simple", help = "Output format")] - format: WatchFormat, + format: ZoneWatchFormat, } -impl WatchCommand { +impl ZoneWatchCommand { pub async fn run(self, events: EventStream) -> Result<()> { let mut stream = events.subscribe(); loop { @@ -37,13 +37,13 @@ impl WatchCommand { fn print_event(&self, typ: &str, event: impl ReflectMessage, zone: Option) -> Result<()> { match self.format { - WatchFormat::Simple => { + ZoneWatchFormat::Simple => { if let Some(zone) = zone { println!("{}", zone_simple_line(&zone)); } } - WatchFormat::Json => { + ZoneWatchFormat::Json => { let message = proto2dynamic(event)?; let mut value = serde_json::to_value(&message)?; if let Value::Object(ref mut map) = value { @@ -52,7 +52,7 @@ impl WatchCommand { println!("{}", serde_json::to_string(&value)?); } - WatchFormat::KeyValue => { + ZoneWatchFormat::KeyValue => { let mut map = proto2kv(event)?; map.insert("event.type".to_string(), typ.to_string()); println!("{}", kv2line(map),); diff --git a/crates/krata/proto/krata/v1/control.proto b/crates/krata/proto/krata/v1/control.proto index 4362777..5cfccff 100644 --- a/crates/krata/proto/krata/v1/control.proto +++ b/crates/krata/proto/krata/v1/control.proto @@ -205,9 +205,9 @@ message ListDevicesReply { } enum HostCpuTopologyClass { - CPU_CLASS_STANDARD = 0; - CPU_CLASS_PERFORMANCE = 1; - CPU_CLASS_EFFICIENCY = 2; + HOST_CPU_TOPOLOGY_CLASS_STANDARD = 0; + HOST_CPU_TOPOLOGY_CLASS_PERFORMANCE = 1; + HOST_CPU_TOPOLOGY_CLASS_EFFICIENCY = 2; } message HostCpuTopologyInfo {