mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
krata: implement guest reconciliation
This commit is contained in:
@ -1,11 +1,19 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use env_logger::Env;
|
||||
use krata::control::{
|
||||
guest_image_spec::Image, watch_events_reply::Event, DestroyGuestRequest, GuestImageSpec,
|
||||
GuestOciImageSpec, LaunchGuestRequest, ListGuestsRequest, WatchEventsRequest,
|
||||
use krata::{
|
||||
common::{
|
||||
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestState,
|
||||
GuestStatus,
|
||||
},
|
||||
control::{
|
||||
watch_events_reply::Event, CreateGuestRequest, DestroyGuestRequest, ListGuestsRequest,
|
||||
WatchEventsRequest,
|
||||
},
|
||||
};
|
||||
use kratactl::{client::ControlClientProvider, console::StdioConsoleStream};
|
||||
use log::error;
|
||||
use tokio_stream::StreamExt;
|
||||
use tonic::Request;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@ -54,7 +62,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
let args = ControllerArgs::parse();
|
||||
let mut client = ControlClientProvider::dial(args.connection.parse()?).await?;
|
||||
let events = client
|
||||
let mut events = client
|
||||
.watch_events(WatchEventsRequest {})
|
||||
.await?
|
||||
.into_inner();
|
||||
@ -69,33 +77,67 @@ async fn main() -> Result<()> {
|
||||
env,
|
||||
run,
|
||||
} => {
|
||||
let request = LaunchGuestRequest {
|
||||
name: name.unwrap_or_default(),
|
||||
image: Some(GuestImageSpec {
|
||||
image: Some(Image::Oci(GuestOciImageSpec { image: oci })),
|
||||
let request = CreateGuestRequest {
|
||||
spec: Some(GuestSpec {
|
||||
name: name.unwrap_or_default(),
|
||||
image: Some(GuestImageSpec {
|
||||
image: Some(Image::Oci(GuestOciImageSpec { image: oci })),
|
||||
}),
|
||||
vcpus: cpus,
|
||||
mem,
|
||||
env: env.unwrap_or_default(),
|
||||
run,
|
||||
}),
|
||||
vcpus: cpus,
|
||||
mem,
|
||||
env: env.unwrap_or_default(),
|
||||
run,
|
||||
};
|
||||
let response = client
|
||||
.launch_guest(Request::new(request))
|
||||
.create_guest(Request::new(request))
|
||||
.await?
|
||||
.into_inner();
|
||||
let Some(guest) = response.guest else {
|
||||
return Err(anyhow!(
|
||||
"control service did not return a guest in the response"
|
||||
));
|
||||
};
|
||||
println!("launched guest: {}", guest.id);
|
||||
let id = response.guest_id;
|
||||
if attach {
|
||||
let input = StdioConsoleStream::stdin_stream(guest.id.clone()).await;
|
||||
while let Some(event) = events.next().await {
|
||||
let reply = event?;
|
||||
match reply.event {
|
||||
Some(Event::GuestChanged(changed)) => {
|
||||
let Some(guest) = changed.guest else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(ref error) = state.error_info {
|
||||
error!("guest error: {}", error.message);
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
error!("guest destroyed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Started {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let exit_hook_task =
|
||||
StdioConsoleStream::guest_exit_hook(guest.id.clone(), events).await?;
|
||||
StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
|
||||
StdioConsoleStream::stdout(output).await?;
|
||||
exit_hook_task.abort();
|
||||
} else {
|
||||
println!("created guest: {}", id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +165,7 @@ async fn main() -> Result<()> {
|
||||
.await?
|
||||
.into_inner();
|
||||
let mut table = cli_tables::Table::new();
|
||||
let header = vec!["name", "uuid", "ipv4", "ipv6", "image"];
|
||||
let header = vec!["name", "uuid", "state", "ipv4", "ipv6", "image"];
|
||||
table.push_row(&header)?;
|
||||
for guest in response.guests {
|
||||
let ipv4 = guest
|
||||
@ -136,7 +178,10 @@ async fn main() -> Result<()> {
|
||||
.as_ref()
|
||||
.map(|x| x.ipv6.as_str())
|
||||
.unwrap_or("unknown");
|
||||
let image = guest
|
||||
let Some(spec) = guest.spec else {
|
||||
continue;
|
||||
};
|
||||
let image = spec
|
||||
.image
|
||||
.map(|x| {
|
||||
x.image
|
||||
@ -147,8 +192,9 @@ async fn main() -> Result<()> {
|
||||
})
|
||||
.unwrap_or("unknown".to_string());
|
||||
table.push_row_string(&vec![
|
||||
guest.name,
|
||||
spec.name,
|
||||
guest.id,
|
||||
format!("{}", guest_state_text(guest.state.unwrap_or_default())),
|
||||
ipv4.to_string(),
|
||||
ipv6.to_string(),
|
||||
image,
|
||||
@ -172,19 +218,10 @@ async fn main() -> Result<()> {
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::GuestLaunched(launched) => {
|
||||
println!("event=guest.launched guest={}", launched.guest_id);
|
||||
}
|
||||
|
||||
Event::GuestDestroyed(destroyed) => {
|
||||
println!("event=guest.destroyed guest={}", destroyed.guest_id);
|
||||
}
|
||||
|
||||
Event::GuestExited(exited) => {
|
||||
println!(
|
||||
"event=guest.exited guest={} code={}",
|
||||
exited.guest_id, exited.code
|
||||
);
|
||||
Event::GuestChanged(changed) => {
|
||||
if let Some(guest) = changed.guest {
|
||||
println!("event=guest.changed guest={}", guest.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,3 +229,28 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn guest_status_text(status: GuestStatus) -> String {
|
||||
match status {
|
||||
GuestStatus::Unknown => "unknown",
|
||||
GuestStatus::Destroyed => "destroyed",
|
||||
GuestStatus::Start => "starting",
|
||||
GuestStatus::Exited => "exited",
|
||||
GuestStatus::Started => "started",
|
||||
_ => "unknown",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn guest_state_text(state: GuestState) -> String {
|
||||
let mut text = guest_status_text(state.status());
|
||||
|
||||
if let Some(exit) = state.exit_info {
|
||||
text.push_str(&format!(" (exit code: {})", exit.code));
|
||||
}
|
||||
|
||||
if let Some(error) = state.error_info {
|
||||
text.push_str(&format!(" (error: {})", error.message));
|
||||
}
|
||||
text
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
use async_stream::stream;
|
||||
use krata::control::{
|
||||
watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, WatchEventsReply,
|
||||
use krata::{
|
||||
common::GuestStatus,
|
||||
control::{watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, WatchEventsReply},
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use termion::raw::IntoRawMode;
|
||||
@ -76,22 +77,28 @@ impl StdioConsoleStream {
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::GuestExited(exit) => {
|
||||
if exit.guest_id == id {
|
||||
std::process::exit(exit.code);
|
||||
}
|
||||
}
|
||||
Event::GuestChanged(changed) => {
|
||||
let Some(guest) = changed.guest else {
|
||||
continue;
|
||||
};
|
||||
|
||||
Event::GuestDestroyed(destroy) => {
|
||||
if destroy.guest_id == id {
|
||||
warn!("attached guest destroyed");
|
||||
let Some(state) = guest.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(exit_info) = state.exit_info {
|
||||
std::process::exit(exit_info.code);
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
warn!("attached guest was destroyed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user