krata: implement auto-exit handling

This commit is contained in:
Alex Zenla
2024-03-13 11:34:52 +00:00
parent 2ec619c0c3
commit 13bea70c0d
3 changed files with 83 additions and 7 deletions

View File

@ -52,6 +52,10 @@ async fn main() -> Result<()> {
let args = ControllerArgs::parse(); let args = ControllerArgs::parse();
let mut client = ControlClientProvider::dial(args.connection.parse()?).await?; let mut client = ControlClientProvider::dial(args.connection.parse()?).await?;
let events = client
.watch_events(WatchEventsRequest {})
.await?
.into_inner();
match args.command { match args.command {
Commands::Launch { Commands::Launch {
@ -82,9 +86,12 @@ async fn main() -> Result<()> {
}; };
println!("launched guest: {}", guest.id); println!("launched guest: {}", guest.id);
if attach { if attach {
let input = StdioConsoleStream::stdin_stream(guest.id).await; let input = StdioConsoleStream::stdin_stream(guest.id.clone()).await;
let output = client.console_data(input).await?.into_inner(); let output = client.console_data(input).await?.into_inner();
let exit_hook_task =
StdioConsoleStream::guest_exit_hook(guest.id.clone(), events).await?;
StdioConsoleStream::stdout(output).await?; StdioConsoleStream::stdout(output).await?;
exit_hook_task.abort();
} }
} }
@ -99,9 +106,11 @@ async fn main() -> Result<()> {
} }
Commands::Console { guest } => { Commands::Console { guest } => {
let input = StdioConsoleStream::stdin_stream(guest).await; let input = StdioConsoleStream::stdin_stream(guest.clone()).await;
let output = client.console_data(input).await?.into_inner(); let output = client.console_data(input).await?.into_inner();
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest.clone(), events).await?;
StdioConsoleStream::stdout(output).await?; StdioConsoleStream::stdout(output).await?;
exit_hook_task.abort();
} }
Commands::List { .. } => { Commands::List { .. } => {

View File

@ -5,12 +5,15 @@ use std::{
use anyhow::Result; use anyhow::Result;
use async_stream::stream; use async_stream::stream;
use krata::control::{ConsoleDataReply, ConsoleDataRequest}; use krata::control::{
use log::debug; watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, WatchEventsReply,
};
use log::{debug, error, warn};
use termion::raw::IntoRawMode; use termion::raw::IntoRawMode;
use tokio::{ use tokio::{
fs::File, fs::File,
io::{stdin, AsyncReadExt, AsyncWriteExt}, io::{stdin, AsyncReadExt, AsyncWriteExt},
task::JoinHandle,
}; };
use tokio_stream::{Stream, StreamExt}; use tokio_stream::{Stream, StreamExt};
use tonic::Streaming; use tonic::Streaming;
@ -54,4 +57,45 @@ impl StdioConsoleStream {
} }
Ok(()) Ok(())
} }
pub async fn guest_exit_hook(
id: String,
mut events: Streaming<WatchEventsReply>,
) -> Result<JoinHandle<()>> {
Ok(tokio::task::spawn(async move {
while let Some(result) = events.next().await {
match result {
Err(error) => {
error!("failed to handle events for exit hook: {}", error);
break;
}
Ok(reply) => {
let Some(event) = reply.event else {
continue;
};
match event {
Event::GuestExited(exit) => {
if exit.guest_id == id {
std::process::exit(exit.code);
}
}
Event::GuestDestroyed(destroy) => {
if destroy.guest_id == id {
warn!("attached guest destroyed");
std::process::exit(1);
}
}
_ => {
continue;
}
}
}
}
}
}))
}
} }

View File

@ -2,7 +2,7 @@ use std::{collections::HashMap, time::Duration};
use anyhow::Result; use anyhow::Result;
use krata::control::{GuestDestroyedEvent, GuestExitedEvent, GuestLaunchedEvent}; use krata::control::{GuestDestroyedEvent, GuestExitedEvent, GuestLaunchedEvent};
use log::error; use log::{error, info, warn};
use tokio::{sync::broadcast, task::JoinHandle, time}; use tokio::{sync::broadcast, task::JoinHandle, time};
use uuid::Uuid; use uuid::Uuid;
@ -52,6 +52,7 @@ impl DaemonEventGenerator {
}; };
let mut events: Vec<DaemonEvent> = Vec::new(); let mut events: Vec<DaemonEvent> = Vec::new();
let mut exits: Vec<GuestExitedEvent> = Vec::new();
for uuid in guests.keys() { for uuid in guests.keys() {
if !self.last.contains_key(uuid) { if !self.last.contains_key(uuid) {
@ -82,10 +83,13 @@ impl DaemonEventGenerator {
continue; continue;
}; };
events.push(DaemonEvent::GuestExited(GuestExitedEvent { let exit = GuestExitedEvent {
guest_id: uuid.to_string(), guest_id: uuid.to_string(),
code, code,
})); };
exits.push(exit.clone());
events.push(DaemonEvent::GuestExited(exit));
} }
self.last = guests; self.last = guests;
@ -94,6 +98,8 @@ impl DaemonEventGenerator {
let _ = self.sender.send(event); let _ = self.sender.send(event);
} }
self.process_exit_auto_destroy(exits).await?;
Ok(()) Ok(())
} }
@ -109,4 +115,21 @@ impl DaemonEventGenerator {
} }
})) }))
} }
async fn process_exit_auto_destroy(&mut self, exits: Vec<GuestExitedEvent>) -> Result<()> {
for exit in exits {
if let Err(error) = self.runtime.destroy(&exit.guest_id).await {
warn!(
"failed to auto-destroy exited guest {}: {}",
exit.guest_id, error
);
} else {
info!(
"auto-destroyed guest {}: exited with status {}",
exit.guest_id, exit.code
);
}
}
Ok(())
}
} }