feature(krata): rename guest to zone (#266)

This commit is contained in:
Alex Zenla
2024-07-18 20:47:18 -07:00
committed by GitHub
parent 9bd8d1bb1d
commit 5ee1035896
58 changed files with 854 additions and 879 deletions

View File

@ -7,13 +7,13 @@ use tonic::transport::Channel;
use crate::console::StdioConsoleStream;
use super::resolve_guest;
use super::resolve_zone;
#[derive(Parser)]
#[command(about = "Attach to the guest console")]
#[command(about = "Attach to the zone console")]
pub struct AttachCommand {
#[arg(help = "Guest to attach to, either the name or the uuid")]
guest: String,
#[arg(help = "Zone to attach to, either the name or the uuid")]
zone: String,
}
impl AttachCommand {
@ -22,12 +22,12 @@ impl AttachCommand {
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let input = StdioConsoleStream::stdin_stream(guest_id.clone()).await;
let output = client.console_data(input).await?.into_inner();
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let input = StdioConsoleStream::stdin_stream(zone_id.clone()).await;
let output = client.attach_zone_console(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
let code = select! {
x = stdout_handle => {
x??;

View File

@ -3,10 +3,10 @@ use clap::Parser;
use krata::{
events::EventStream,
v1::{
common::GuestStatus,
common::ZoneStatus,
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
DestroyGuestRequest,
DestroyZoneRequest,
},
},
};
@ -14,19 +14,19 @@ use krata::{
use log::error;
use tonic::{transport::Channel, Request};
use crate::cli::resolve_guest;
use crate::cli::resolve_zone;
#[derive(Parser)]
#[command(about = "Destroy a guest")]
#[command(about = "Destroy a zone")]
pub struct DestroyCommand {
#[arg(
short = 'W',
long,
help = "Wait for the destruction of the guest to complete"
help = "Wait for the destruction of the zone to complete"
)]
wait: bool,
#[arg(help = "Guest to destroy, either the name or the uuid")]
guest: String,
#[arg(help = "Zone to destroy, either the name or the uuid")]
zone: String,
}
impl DestroyCommand {
@ -35,46 +35,46 @@ impl DestroyCommand {
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let _ = client
.destroy_guest(Request::new(DestroyGuestRequest {
guest_id: guest_id.clone(),
.destroy_zone(Request::new(DestroyZoneRequest {
zone_id: zone_id.clone(),
}))
.await?
.into_inner();
if self.wait {
wait_guest_destroyed(&guest_id, events).await?;
wait_zone_destroyed(&zone_id, events).await?;
}
Ok(())
}
}
async fn wait_guest_destroyed(id: &str, events: EventStream) -> Result<()> {
async fn wait_zone_destroyed(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
let Event::ZoneChanged(changed) = event;
let Some(zone) = changed.zone else {
continue;
};
if guest.id != id {
if zone.id != id {
continue;
}
let Some(state) = guest.state else {
let Some(state) = zone.state else {
continue;
};
if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
if state.status() == ZoneStatus::Failed {
error!("destroy failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
error!("zone error: {}", error.message);
}
}
if state.status() == GuestStatus::Destroyed {
if state.status() == ZoneStatus::Destroyed {
std::process::exit(0);
}
}

View File

@ -4,42 +4,42 @@ use anyhow::Result;
use clap::Parser;
use krata::v1::{
common::{GuestTaskSpec, GuestTaskSpecEnvVar},
control::{control_service_client::ControlServiceClient, ExecGuestRequest},
common::{ZoneTaskSpec, ZoneTaskSpecEnvVar},
control::{control_service_client::ControlServiceClient, ExecZoneRequest},
};
use tonic::{transport::Channel, Request};
use crate::console::StdioConsoleStream;
use super::resolve_guest;
use super::resolve_zone;
#[derive(Parser)]
#[command(about = "Execute a command inside the guest")]
#[command(about = "Execute a command inside the zone")]
pub struct ExecCommand {
#[arg[short, long, help = "Environment variables"]]
env: Option<Vec<String>>,
#[arg(short = 'w', long, help = "Working directory")]
working_directory: Option<String>,
#[arg(help = "Guest to exec inside, either the name or the uuid")]
guest: String,
#[arg(help = "Zone to exec inside, either the name or the uuid")]
zone: String,
#[arg(
allow_hyphen_values = true,
trailing_var_arg = true,
help = "Command to run inside the guest"
help = "Command to run inside the zone"
)]
command: Vec<String>,
}
impl ExecCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let initial = ExecGuestRequest {
guest_id,
task: Some(GuestTaskSpec {
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let initial = ExecZoneRequest {
zone_id,
task: Some(ZoneTaskSpec {
environment: env_map(&self.env.unwrap_or_default())
.iter()
.map(|(key, value)| GuestTaskSpecEnvVar {
.map(|(key, value)| ZoneTaskSpecEnvVar {
key: key.clone(),
value: value.clone(),
})
@ -52,7 +52,7 @@ impl ExecCommand {
let stream = StdioConsoleStream::stdin_stream_exec(initial).await;
let response = client.exec_guest(Request::new(stream)).await?.into_inner();
let response = client.exec_zone(Request::new(stream)).await?.into_inner();
let code = StdioConsoleStream::exec_output(response).await?;
std::process::exit(code);

View File

@ -6,12 +6,12 @@ use krata::{
events::EventStream,
v1::{
common::{
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestSpecDevice,
GuestStatus, GuestTaskSpec, GuestTaskSpecEnvVar, OciImageFormat,
zone_image_spec::Image, OciImageFormat, ZoneImageSpec, ZoneOciImageSpec, ZoneSpec,
ZoneSpecDevice, ZoneStatus, ZoneTaskSpec, ZoneTaskSpecEnvVar,
},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
CreateGuestRequest, PullImageRequest,
CreateZoneRequest, PullImageRequest,
},
},
};
@ -28,56 +28,51 @@ pub enum LaunchImageFormat {
}
#[derive(Parser)]
#[command(about = "Launch a new guest")]
#[command(about = "Launch a new zone")]
pub struct LaunchCommand {
#[arg(long, default_value = "squashfs", help = "Image format")]
image_format: LaunchImageFormat,
#[arg(long, help = "Overwrite image cache on pull")]
pull_overwrite_cache: bool,
#[arg(short, long, help = "Name of the guest")]
#[arg(short, long, help = "Name of the zone")]
name: Option<String>,
#[arg(
short,
long,
default_value_t = 1,
help = "vCPUs available to the guest"
)]
#[arg(short, long, default_value_t = 1, help = "vCPUs available to the zone")]
cpus: u32,
#[arg(
short,
long,
default_value_t = 512,
help = "Memory available to the guest, in megabytes"
help = "Memory available to the zone, in megabytes"
)]
mem: u64,
#[arg[short = 'D', long = "device", help = "Devices to request for the guest"]]
#[arg[short = 'D', long = "device", help = "Devices to request for the zone"]]
device: Vec<String>,
#[arg[short, long, help = "Environment variables set in the guest"]]
#[arg[short, long, help = "Environment variables set in the zone"]]
env: Option<Vec<String>>,
#[arg(
short,
long,
help = "Attach to the guest after guest starts, implies --wait"
help = "Attach to the zone after zone starts, implies --wait"
)]
attach: bool,
#[arg(
short = 'W',
long,
help = "Wait for the guest to start, implied by --attach"
help = "Wait for the zone to start, implied by --attach"
)]
wait: bool,
#[arg(short = 'k', long, help = "OCI kernel image for guest to use")]
#[arg(short = 'k', long, help = "OCI kernel image for zone to use")]
kernel: Option<String>,
#[arg(short = 'I', long, help = "OCI initrd image for guest to use")]
#[arg(short = 'I', long, help = "OCI initrd image for zone to use")]
initrd: Option<String>,
#[arg(short = 'w', long, help = "Working directory")]
working_directory: Option<String>,
#[arg(help = "Container image for guest to use")]
#[arg(help = "Container image for zone to use")]
oci: String,
#[arg(
allow_hyphen_values = true,
trailing_var_arg = true,
help = "Command to run inside the guest"
help = "Command to run inside the zone"
)]
command: Vec<String>,
}
@ -117,18 +112,18 @@ impl LaunchCommand {
None
};
let request = CreateGuestRequest {
spec: Some(GuestSpec {
let request = CreateZoneRequest {
spec: Some(ZoneSpec {
name: self.name.unwrap_or_default(),
image: Some(image),
kernel,
initrd,
vcpus: self.cpus,
mem: self.mem,
task: Some(GuestTaskSpec {
task: Some(ZoneTaskSpec {
environment: env_map(&self.env.unwrap_or_default())
.iter()
.map(|(key, value)| GuestTaskSpecEnvVar {
.map(|(key, value)| ZoneTaskSpecEnvVar {
key: key.clone(),
value: value.clone(),
})
@ -140,26 +135,26 @@ impl LaunchCommand {
devices: self
.device
.iter()
.map(|name| GuestSpecDevice { name: name.clone() })
.map(|name| ZoneSpecDevice { name: name.clone() })
.collect(),
}),
};
let response = client
.create_guest(Request::new(request))
.create_zone(Request::new(request))
.await?
.into_inner();
let id = response.guest_id;
let id = response.zone_id;
if self.wait || self.attach {
wait_guest_started(&id, events.clone()).await?;
wait_zone_started(&id, events.clone()).await?;
}
let code = if self.attach {
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
let output = client.console_data(input).await?.into_inner();
let output = client.attach_zone_console(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
let exit_hook_task = StdioConsoleStream::zone_exit_hook(id.clone(), events).await?;
select! {
x = stdout_handle => {
x??;
@ -180,7 +175,7 @@ impl LaunchCommand {
client: &mut ControlServiceClient<Channel>,
image: &str,
format: OciImageFormat,
) -> Result<GuestImageSpec> {
) -> Result<ZoneImageSpec> {
let response = client
.pull_image(PullImageRequest {
image: image.to_string(),
@ -189,8 +184,8 @@ impl LaunchCommand {
})
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;
Ok(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec {
Ok(ZoneImageSpec {
image: Some(Image::Oci(ZoneOciImageSpec {
digest: reply.digest,
format: reply.format,
})),
@ -198,38 +193,38 @@ impl LaunchCommand {
}
}
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
async fn wait_zone_started(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
match event {
Event::GuestChanged(changed) => {
let Some(guest) = changed.guest else {
Event::ZoneChanged(changed) => {
let Some(zone) = changed.zone else {
continue;
};
if guest.id != id {
if zone.id != id {
continue;
}
let Some(state) = guest.state else {
let Some(state) = zone.state else {
continue;
};
if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
if state.status() == ZoneStatus::Failed {
error!("launch failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
error!("zone error: {}", error.message);
}
}
if state.status() == GuestStatus::Destroyed {
error!("guest destroyed");
if state.status() == ZoneStatus::Destroyed {
error!("zone destroyed");
std::process::exit(1);
}
if state.status() == GuestStatus::Started {
if state.status() == ZoneStatus::Started {
break;
}
}

View File

@ -4,9 +4,9 @@ use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
use krata::{
events::EventStream,
v1::{
common::{Guest, GuestStatus},
common::{Zone, ZoneStatus},
control::{
control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest,
control_service_client::ControlServiceClient, ListZonesRequest, ResolveZoneRequest,
},
},
};
@ -14,7 +14,7 @@ use krata::{
use serde_json::Value;
use tonic::{transport::Channel, Request};
use crate::format::{guest_simple_line, guest_status_text, kv2line, proto2dynamic, proto2kv};
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line, zone_status_text};
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum ListFormat {
@ -28,12 +28,12 @@ enum ListFormat {
}
#[derive(Parser)]
#[command(about = "List the guests on the isolation engine")]
#[command(about = "List the zones on the isolation engine")]
pub struct ListCommand {
#[arg(short, long, default_value = "table", help = "Output format")]
format: ListFormat,
#[arg(help = "Limit to a single guest, either the name or the uuid")]
guest: Option<String>,
#[arg(help = "Limit to a single zone, either the name or the uuid")]
zone: Option<String>,
}
impl ListCommand {
@ -42,27 +42,25 @@ impl ListCommand {
mut client: ControlServiceClient<Channel>,
_events: EventStream,
) -> Result<()> {
let mut guests = if let Some(ref guest) = self.guest {
let mut zones = if let Some(ref zone) = self.zone {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: guest.clone(),
}))
.resolve_zone(Request::new(ResolveZoneRequest { name: zone.clone() }))
.await?
.into_inner();
if let Some(guest) = reply.guest {
vec![guest]
if let Some(zone) = reply.zone {
vec![zone]
} else {
return Err(anyhow!("unable to resolve guest '{}'", guest));
return Err(anyhow!("unable to resolve zone '{}'", zone));
}
} else {
client
.list_guests(Request::new(ListGuestsRequest {}))
.list_zones(Request::new(ListZonesRequest {}))
.await?
.into_inner()
.guests
.zones
};
guests.sort_by(|a, b| {
zones.sort_by(|a, b| {
a.spec
.as_ref()
.map(|x| x.name.as_str())
@ -72,19 +70,19 @@ impl ListCommand {
match self.format {
ListFormat::Table => {
self.print_guest_table(guests)?;
self.print_zone_table(zones)?;
}
ListFormat::Simple => {
for guest in guests {
println!("{}", guest_simple_line(&guest));
for zone in zones {
println!("{}", zone_simple_line(&zone));
}
}
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
let mut values = Vec::new();
for guest in guests {
let message = proto2dynamic(guest)?;
for zone in zones {
let message = proto2dynamic(zone)?;
values.push(serde_json::to_value(message)?);
}
let value = Value::Array(values);
@ -99,64 +97,62 @@ impl ListCommand {
}
ListFormat::Jsonl => {
for guest in guests {
let message = proto2dynamic(guest)?;
for zone in zones {
let message = proto2dynamic(zone)?;
println!("{}", serde_json::to_string(&message)?);
}
}
ListFormat::KeyValue => {
self.print_key_value(guests)?;
self.print_key_value(zones)?;
}
}
Ok(())
}
fn print_guest_table(&self, guests: Vec<Guest>) -> Result<()> {
fn print_zone_table(&self, zones: Vec<Zone>) -> Result<()> {
let mut table = Table::new();
table.load_preset(UTF8_FULL_CONDENSED);
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["name", "uuid", "status", "ipv4", "ipv6"]);
for guest in guests {
let ipv4 = guest
for zone in zones {
let ipv4 = zone
.state
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv4.as_str())
.map(|x| x.zone_ipv4.as_str())
.unwrap_or("n/a");
let ipv6 = guest
let ipv6 = zone
.state
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv6.as_str())
.map(|x| x.zone_ipv6.as_str())
.unwrap_or("n/a");
let Some(spec) = guest.spec else {
let Some(spec) = zone.spec else {
continue;
};
let status = guest.state.as_ref().cloned().unwrap_or_default().status();
let status_text = guest_status_text(status);
let status = zone.state.as_ref().cloned().unwrap_or_default().status();
let status_text = zone_status_text(status);
let status_color = match status {
GuestStatus::Destroyed | GuestStatus::Failed => Color::Red,
GuestStatus::Destroying | GuestStatus::Exited | GuestStatus::Starting => {
Color::Yellow
}
GuestStatus::Started => Color::Green,
ZoneStatus::Destroyed | ZoneStatus::Failed => Color::Red,
ZoneStatus::Destroying | ZoneStatus::Exited | ZoneStatus::Starting => Color::Yellow,
ZoneStatus::Started => Color::Green,
_ => Color::Reset,
};
table.add_row(vec![
Cell::new(spec.name),
Cell::new(guest.id),
Cell::new(zone.id),
Cell::new(status_text).fg(status_color),
Cell::new(ipv4.to_string()),
Cell::new(ipv6.to_string()),
]);
}
if table.is_empty() {
if self.guest.is_none() {
println!("no guests have been launched");
if self.zone.is_none() {
println!("no zones have been launched");
}
} else {
println!("{}", table);
@ -164,9 +160,9 @@ impl ListCommand {
Ok(())
}
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
for guest in guests {
let kvs = proto2kv(guest)?;
fn print_key_value(&self, zones: Vec<Zone>) -> Result<()> {
for zone in zones {
let kvs = proto2kv(zone)?;
println!("{}", kv2line(kvs),);
}
Ok(())

View File

@ -3,7 +3,7 @@ use async_stream::stream;
use clap::Parser;
use krata::{
events::EventStream,
v1::control::{control_service_client::ControlServiceClient, ConsoleDataRequest},
v1::control::{control_service_client::ControlServiceClient, ZoneConsoleRequest},
};
use tokio::select;
@ -12,15 +12,15 @@ use tonic::transport::Channel;
use crate::console::StdioConsoleStream;
use super::resolve_guest;
use super::resolve_zone;
#[derive(Parser)]
#[command(about = "View the logs of a guest")]
#[command(about = "View the logs of a zone")]
pub struct LogsCommand {
#[arg(short, long, help = "Follow output from the guest")]
#[arg(short, long, help = "Follow output from the zone")]
follow: bool,
#[arg(help = "Guest to show logs for, either the name or the uuid")]
guest: String,
#[arg(help = "Zone to show logs for, either the name or the uuid")]
zone: String,
}
impl LogsCommand {
@ -29,22 +29,22 @@ impl LogsCommand {
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let guest_id_stream = guest_id.clone();
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let zone_id_stream = zone_id.clone();
let follow = self.follow;
let input = stream! {
yield ConsoleDataRequest { guest_id: guest_id_stream, data: Vec::new() };
yield ZoneConsoleRequest { zone_id: zone_id_stream, data: Vec::new() };
if follow {
let mut pending = pending::<ConsoleDataRequest>();
let mut pending = pending::<ZoneConsoleRequest>();
while let Some(x) = pending.next().await {
yield x;
}
}
};
let output = client.console_data(input).await?.into_inner();
let output = client.attach_zone_console(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
let code = select! {
x = stdout_handle => {
x??;

View File

@ -3,8 +3,8 @@ use clap::{Parser, ValueEnum};
use krata::{
events::EventStream,
v1::{
common::GuestMetricNode,
control::{control_service_client::ControlServiceClient, ReadGuestMetricsRequest},
common::ZoneMetricNode,
control::{control_service_client::ControlServiceClient, ReadZoneMetricsRequest},
},
};
@ -12,7 +12,7 @@ use tonic::transport::Channel;
use crate::format::{kv2line, metrics_flat, metrics_tree, proto2dynamic};
use super::resolve_guest;
use super::resolve_zone;
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum MetricsFormat {
@ -24,12 +24,12 @@ enum MetricsFormat {
}
#[derive(Parser)]
#[command(about = "Read metrics from the guest")]
#[command(about = "Read metrics from the zone")]
pub struct MetricsCommand {
#[arg(short, long, default_value = "tree", help = "Output format")]
format: MetricsFormat,
#[arg(help = "Guest to read metrics for, either the name or the uuid")]
guest: String,
#[arg(help = "Zone to read metrics for, either the name or the uuid")]
zone: String,
}
impl MetricsCommand {
@ -38,9 +38,9 @@ impl MetricsCommand {
mut client: ControlServiceClient<Channel>,
_events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
let root = client
.read_guest_metrics(ReadGuestMetricsRequest { guest_id })
.read_zone_metrics(ReadZoneMetricsRequest { zone_id })
.await?
.into_inner()
.root
@ -70,12 +70,12 @@ impl MetricsCommand {
Ok(())
}
fn print_metrics_tree(&self, root: GuestMetricNode) -> Result<()> {
fn print_metrics_tree(&self, root: ZoneMetricNode) -> Result<()> {
print!("{}", metrics_tree(root));
Ok(())
}
fn print_key_value(&self, metrics: GuestMetricNode) -> Result<()> {
fn print_key_value(&self, metrics: ZoneMetricNode) -> Result<()> {
let kvs = metrics_flat(metrics);
println!("{}", kv2line(kvs));
Ok(())

View File

@ -19,7 +19,7 @@ use clap::{Parser, Subcommand};
use krata::{
client::ControlClientProvider,
events::EventStream,
v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest},
v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest},
};
use tonic::{transport::Channel, Request};
@ -135,20 +135,20 @@ impl ControlCommand {
}
}
pub async fn resolve_guest(
pub async fn resolve_zone(
client: &mut ControlServiceClient<Channel>,
name: &str,
) -> Result<String> {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
.resolve_zone(Request::new(ResolveZoneRequest {
name: name.to_string(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
Ok(guest.id)
if let Some(zone) = reply.zone {
Ok(zone.id)
} else {
Err(anyhow!("unable to resolve guest '{}'", name))
Err(anyhow!("unable to resolve zone '{}'", name))
}
}

View File

@ -1,26 +1,26 @@
use anyhow::Result;
use clap::Parser;
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest};
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest};
use tonic::{transport::Channel, Request};
#[derive(Parser)]
#[command(about = "Resolve a guest name to a uuid")]
#[command(about = "Resolve a zone name to a uuid")]
pub struct ResolveCommand {
#[arg(help = "Guest name")]
guest: String,
#[arg(help = "Zone name")]
zone: String,
}
impl ResolveCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: self.guest.clone(),
.resolve_zone(Request::new(ResolveZoneRequest {
name: self.zone.clone(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
println!("{}", guest.id);
if let Some(zone) = reply.zone {
println!("{}", zone.id);
} else {
std::process::exit(1);
}

View File

@ -24,14 +24,14 @@ use ratatui::{
};
use crate::{
format::guest_status_text,
format::zone_status_text,
metrics::{
lookup_metric_value, MultiMetricCollector, MultiMetricCollectorHandle, MultiMetricState,
},
};
#[derive(Parser)]
#[command(about = "Dashboard for running guests")]
#[command(about = "Dashboard for running zones")]
pub struct TopCommand {}
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
@ -46,7 +46,7 @@ impl TopCommand {
let collector = collector.launch().await?;
let mut tui = TopCommand::init()?;
let mut app = TopApp {
metrics: MultiMetricState { guests: vec![] },
metrics: MultiMetricState { zones: vec![] },
exit: false,
table: TableState::new(),
};
@ -152,12 +152,12 @@ impl Widget for &mut TopApp {
let mut rows = vec![];
for ms in &self.metrics.guests {
let Some(ref spec) = ms.guest.spec else {
for ms in &self.metrics.zones {
let Some(ref spec) = ms.zone.spec else {
continue;
};
let Some(ref state) = ms.guest.state else {
let Some(ref state) = ms.zone.state else {
continue;
};
@ -176,8 +176,8 @@ impl Widget for &mut TopApp {
let row = Row::new(vec![
spec.name.clone(),
ms.guest.id.clone(),
guest_status_text(state.status()),
ms.zone.id.clone(),
zone_status_text(state.status()),
memory_total.unwrap_or_default(),
memory_used.unwrap_or_default(),
memory_free.unwrap_or_default(),

View File

@ -2,12 +2,12 @@ use anyhow::Result;
use clap::{Parser, ValueEnum};
use krata::{
events::EventStream,
v1::{common::Guest, control::watch_events_reply::Event},
v1::{common::Zone, control::watch_events_reply::Event},
};
use prost_reflect::ReflectMessage;
use serde_json::Value;
use crate::format::{guest_simple_line, kv2line, proto2dynamic, proto2kv};
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line};
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum WatchFormat {
@ -17,7 +17,7 @@ enum WatchFormat {
}
#[derive(Parser)]
#[command(about = "Watch for guest changes")]
#[command(about = "Watch for zone changes")]
pub struct WatchCommand {
#[arg(short, long, default_value = "simple", help = "Output format")]
format: WatchFormat,
@ -29,22 +29,17 @@ impl WatchCommand {
loop {
let event = stream.recv().await?;
let Event::GuestChanged(changed) = event;
let guest = changed.guest.clone();
self.print_event("guest.changed", changed, guest)?;
let Event::ZoneChanged(changed) = event;
let zone = changed.zone.clone();
self.print_event("zone.changed", changed, zone)?;
}
}
fn print_event(
&self,
typ: &str,
event: impl ReflectMessage,
guest: Option<Guest>,
) -> Result<()> {
fn print_event(&self, typ: &str, event: impl ReflectMessage, zone: Option<Zone>) -> Result<()> {
match self.format {
WatchFormat::Simple => {
if let Some(guest) = guest {
println!("{}", guest_simple_line(&guest));
if let Some(zone) = zone {
println!("{}", zone_simple_line(&zone));
}
}

View File

@ -7,10 +7,10 @@ use crossterm::{
use krata::{
events::EventStream,
v1::{
common::GuestStatus,
common::ZoneStatus,
control::{
watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, ExecGuestReply,
ExecGuestRequest,
watch_events_reply::Event, ExecZoneReply, ExecZoneRequest, ZoneConsoleReply,
ZoneConsoleRequest,
},
},
};
@ -25,10 +25,10 @@ use tonic::Streaming;
pub struct StdioConsoleStream;
impl StdioConsoleStream {
pub async fn stdin_stream(guest: String) -> impl Stream<Item = ConsoleDataRequest> {
pub async fn stdin_stream(zone: String) -> impl Stream<Item = ZoneConsoleRequest> {
let mut stdin = stdin();
stream! {
yield ConsoleDataRequest { guest_id: guest, data: vec![] };
yield ZoneConsoleRequest { zone_id: zone, data: vec![] };
let mut buffer = vec![0u8; 60];
loop {
@ -43,14 +43,14 @@ impl StdioConsoleStream {
if size == 1 && buffer[0] == 0x1d {
break;
}
yield ConsoleDataRequest { guest_id: String::default(), data };
yield ZoneConsoleRequest { zone_id: String::default(), data };
}
}
}
pub async fn stdin_stream_exec(
initial: ExecGuestRequest,
) -> impl Stream<Item = ExecGuestRequest> {
initial: ExecZoneRequest,
) -> impl Stream<Item = ExecZoneRequest> {
let mut stdin = stdin();
stream! {
yield initial;
@ -68,12 +68,12 @@ impl StdioConsoleStream {
if size == 1 && buffer[0] == 0x1d {
break;
}
yield ExecGuestRequest { guest_id: String::default(), task: None, data };
yield ExecZoneRequest { zone_id: String::default(), task: None, data };
}
}
}
pub async fn stdout(mut stream: Streaming<ConsoleDataReply>) -> Result<()> {
pub async fn stdout(mut stream: Streaming<ZoneConsoleReply>) -> Result<()> {
if stdin().is_tty() {
enable_raw_mode()?;
StdioConsoleStream::register_terminal_restore_hook()?;
@ -90,7 +90,7 @@ impl StdioConsoleStream {
Ok(())
}
pub async fn exec_output(mut stream: Streaming<ExecGuestReply>) -> Result<i32> {
pub async fn exec_output(mut stream: Streaming<ExecZoneReply>) -> Result<i32> {
let mut stdout = stdout();
let mut stderr = stderr();
while let Some(reply) = stream.next().await {
@ -106,33 +106,33 @@ impl StdioConsoleStream {
}
if reply.exited {
if reply.error.is_empty() {
return Ok(reply.exit_code);
return if reply.error.is_empty() {
Ok(reply.exit_code)
} else {
return Err(anyhow!("exec failed: {}", reply.error));
}
Err(anyhow!("exec failed: {}", reply.error))
};
}
}
Ok(-1)
}
pub async fn guest_exit_hook(
pub async fn zone_exit_hook(
id: String,
events: EventStream,
) -> Result<JoinHandle<Option<i32>>> {
Ok(tokio::task::spawn(async move {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
let Event::ZoneChanged(changed) = event;
let Some(zone) = changed.zone else {
continue;
};
let Some(state) = guest.state else {
let Some(state) = zone.state else {
continue;
};
if guest.id != id {
if zone.id != id {
continue;
}
@ -141,7 +141,7 @@ impl StdioConsoleStream {
}
let status = state.status();
if status == GuestStatus::Destroying || status == GuestStatus::Destroyed {
if status == ZoneStatus::Destroying || status == ZoneStatus::Destroyed {
return Some(10);
}
}

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Duration};
use anyhow::Result;
use fancy_duration::FancyDuration;
use human_bytes::human_bytes;
use krata::v1::common::{Guest, GuestMetricFormat, GuestMetricNode, GuestStatus};
use krata::v1::common::{Zone, ZoneMetricFormat, ZoneMetricNode, ZoneStatus};
use prost_reflect::{DynamicMessage, ReflectMessage};
use prost_types::Value;
use termtree::Tree;
@ -75,32 +75,31 @@ pub fn kv2line(map: HashMap<String, String>) -> String {
.join(" ")
}
pub fn guest_status_text(status: GuestStatus) -> String {
pub fn zone_status_text(status: ZoneStatus) -> String {
match status {
GuestStatus::Starting => "starting",
GuestStatus::Started => "started",
GuestStatus::Destroying => "destroying",
GuestStatus::Destroyed => "destroyed",
GuestStatus::Exited => "exited",
GuestStatus::Failed => "failed",
ZoneStatus::Starting => "starting",
ZoneStatus::Started => "started",
ZoneStatus::Destroying => "destroying",
ZoneStatus::Destroyed => "destroyed",
ZoneStatus::Exited => "exited",
ZoneStatus::Failed => "failed",
_ => "unknown",
}
.to_string()
}
pub fn guest_simple_line(guest: &Guest) -> String {
let state = guest_status_text(
guest
.state
pub fn zone_simple_line(zone: &Zone) -> String {
let state = zone_status_text(
zone.state
.as_ref()
.map(|x| x.status())
.unwrap_or(GuestStatus::Unknown),
.unwrap_or(ZoneStatus::Unknown),
);
let name = guest.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
let network = guest.state.as_ref().and_then(|x| x.network.as_ref());
let ipv4 = network.map(|x| x.guest_ipv4.as_str()).unwrap_or("");
let ipv6 = network.map(|x| x.guest_ipv6.as_str()).unwrap_or("");
format!("{}\t{}\t{}\t{}\t{}", guest.id, state, name, ipv4, ipv6)
let name = zone.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
let network = zone.state.as_ref().and_then(|x| x.network.as_ref());
let ipv4 = network.map(|x| x.zone_ipv4.as_str()).unwrap_or("");
let ipv6 = network.map(|x| x.zone_ipv6.as_str()).unwrap_or("");
format!("{}\t{}\t{}\t{}\t{}", zone.id, state, name, ipv4, ipv6)
}
fn metrics_value_string(value: Value) -> String {
@ -116,18 +115,18 @@ fn metrics_value_numeric(value: Value) -> f64 {
string.parse::<f64>().ok().unwrap_or(f64::NAN)
}
pub fn metrics_value_pretty(value: Value, format: GuestMetricFormat) -> String {
pub fn metrics_value_pretty(value: Value, format: ZoneMetricFormat) -> String {
match format {
GuestMetricFormat::Bytes => human_bytes(metrics_value_numeric(value)),
GuestMetricFormat::Integer => (metrics_value_numeric(value) as u64).to_string(),
GuestMetricFormat::DurationSeconds => {
ZoneMetricFormat::Bytes => human_bytes(metrics_value_numeric(value)),
ZoneMetricFormat::Integer => (metrics_value_numeric(value) as u64).to_string(),
ZoneMetricFormat::DurationSeconds => {
FancyDuration(Duration::from_secs_f64(metrics_value_numeric(value))).to_string()
}
_ => metrics_value_string(value),
}
}
fn metrics_flat_internal(prefix: &str, node: GuestMetricNode, map: &mut HashMap<String, String>) {
fn metrics_flat_internal(prefix: &str, node: ZoneMetricNode, map: &mut HashMap<String, String>) {
if let Some(value) = node.value {
map.insert(prefix.to_string(), metrics_value_string(value));
}
@ -142,13 +141,13 @@ fn metrics_flat_internal(prefix: &str, node: GuestMetricNode, map: &mut HashMap<
}
}
pub fn metrics_flat(root: GuestMetricNode) -> HashMap<String, String> {
pub fn metrics_flat(root: ZoneMetricNode) -> HashMap<String, String> {
let mut map = HashMap::new();
metrics_flat_internal("", root, &mut map);
map
}
pub fn metrics_tree(node: GuestMetricNode) -> Tree<String> {
pub fn metrics_tree(node: ZoneMetricNode) -> Tree<String> {
let mut name = node.name.to_string();
let format = node.format();
if let Some(value) = node.value {

View File

@ -2,10 +2,10 @@ use anyhow::Result;
use krata::{
events::EventStream,
v1::{
common::{Guest, GuestMetricNode, GuestStatus},
common::{Zone, ZoneMetricNode, ZoneStatus},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
ListGuestsRequest, ReadGuestMetricsRequest,
ListZonesRequest, ReadZoneMetricsRequest,
},
},
};
@ -22,12 +22,12 @@ use tonic::transport::Channel;
use crate::format::metrics_value_pretty;
pub struct MetricState {
pub guest: Guest,
pub root: Option<GuestMetricNode>,
pub zone: Zone,
pub root: Option<ZoneMetricNode>,
}
pub struct MultiMetricState {
pub guests: Vec<MetricState>,
pub zones: Vec<MetricState>,
}
pub struct MultiMetricCollector {
@ -72,26 +72,26 @@ impl MultiMetricCollector {
pub async fn process(&mut self, sender: Sender<MultiMetricState>) -> Result<()> {
let mut events = self.events.subscribe();
let mut guests: Vec<Guest> = self
let mut zones: Vec<Zone> = self
.client
.list_guests(ListGuestsRequest {})
.list_zones(ListZonesRequest {})
.await?
.into_inner()
.guests;
.zones;
loop {
let collect = select! {
x = events.recv() => match x {
Ok(event) => {
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
let Event::ZoneChanged(changed) = event;
let Some(zone) = changed.zone else {
continue;
};
let Some(ref state) = guest.state else {
let Some(ref state) = zone.state else {
continue;
};
guests.retain(|x| x.id != guest.id);
if state.status() != GuestStatus::Destroying {
guests.push(guest);
zones.retain(|x| x.id != zone.id);
if state.status() != ZoneStatus::Destroying {
zones.push(zone);
}
false
},
@ -111,19 +111,19 @@ impl MultiMetricCollector {
}
let mut metrics = Vec::new();
for guest in &guests {
let Some(ref state) = guest.state else {
for zone in &zones {
let Some(ref state) = zone.state else {
continue;
};
if state.status() != GuestStatus::Started {
if state.status() != ZoneStatus::Started {
continue;
}
let root = timeout(
Duration::from_secs(5),
self.client.read_guest_metrics(ReadGuestMetricsRequest {
guest_id: guest.id.clone(),
self.client.read_zone_metrics(ReadZoneMetricsRequest {
zone_id: zone.id.clone(),
}),
)
.await
@ -132,16 +132,16 @@ impl MultiMetricCollector {
.map(|x| x.into_inner())
.and_then(|x| x.root);
metrics.push(MetricState {
guest: guest.clone(),
zone: zone.clone(),
root,
});
}
sender.send(MultiMetricState { guests: metrics }).await?;
sender.send(MultiMetricState { zones: metrics }).await?;
}
}
}
pub fn lookup<'a>(node: &'a GuestMetricNode, path: &str) -> Option<&'a GuestMetricNode> {
pub fn lookup<'a>(node: &'a ZoneMetricNode, path: &str) -> Option<&'a ZoneMetricNode> {
let Some((what, b)) = path.split_once('/') else {
return node.children.iter().find(|x| x.name == path);
};
@ -149,7 +149,7 @@ pub fn lookup<'a>(node: &'a GuestMetricNode, path: &str) -> Option<&'a GuestMetr
return lookup(next, b);
}
pub fn lookup_metric_value(node: &GuestMetricNode, path: &str) -> Option<String> {
pub fn lookup_metric_value(node: &ZoneMetricNode, path: &str) -> Option<String> {
lookup(node, path).and_then(|x| {
x.value
.as_ref()