feature(kratactl): rework cli to use subcommands (#268)

This commit is contained in:
Alex Zenla 2024-07-18 23:13:29 -07:00 committed by GitHub
parent 04665ce690
commit 75901233b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 418 additions and 249 deletions

35
DEV.md
View File

@ -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
```

2
FAQ.md
View File

@ -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.

View File

@ -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 |

View File

@ -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<Channel>) -> 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(())
}
}

View File

@ -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<Channel>,
@ -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)?;
}
}

View File

@ -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<Channel>,
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<Channel>,
events: EventStream,
) -> Result<()> {
match self {
DeviceCommands::List(list) => list.run(client, events).await,
}
}
}

View File

@ -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<Channel>) -> 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(())
}
}

View File

@ -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<Channel>) -> Result<()> {
let response = client
.identify_host(Request::new(IdentifyHostRequest {}))

View File

@ -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<Channel>,
@ -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)?;
}
}

View File

@ -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<Channel>,
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<Channel>,
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,
}
}
}

View File

@ -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<Channel>,
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<Channel>,
_events: EventStream,
) -> Result<()> {
match self {
ImageCommands::Pull(pull) => pull.run(client).await,
}
}
}

View File

@ -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<Channel>) -> 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,
})

View File

@ -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(())
}
}

View File

@ -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<Channel>,

View File

@ -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<Channel>,

View File

@ -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<Vec<String>>,
#[arg(short = 'w', long, help = "Working directory")]
@ -31,7 +31,7 @@ pub struct ExecCommand {
command: Vec<String>,
}
impl ExecCommand {
impl ZoneExecCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let initial = ExecZoneRequest {

View File

@ -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<String>,
}
impl LaunchCommand {
impl ZoneLaunchCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,

View File

@ -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<String>,
}
impl ListCommand {
impl ZoneListCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
@ -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)?;
}
}

View File

@ -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<Channel>,

View File

@ -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<Channel>,
@ -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)?;
}
}

View File

@ -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<Channel>,
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<Channel>,
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,
}
}
}

View File

@ -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<Channel>) -> Result<()> {
let reply = client
.resolve_zone(Request::new(ResolveZoneRequest {

View File

@ -32,11 +32,11 @@ use crate::{
#[derive(Parser)]
#[command(about = "Dashboard for running zones")]
pub struct TopCommand {}
pub struct ZoneTopCommand {}
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
impl TopCommand {
impl ZoneTopCommand {
pub async fn run(
self,
client: ControlServiceClient<Channel>,
@ -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(), "<Q> ".blue().bold()]);

View File

@ -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<Zone>) -> 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),);

View File

@ -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 {