mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
krata: improvements to event handling during reconciliation
This commit is contained in:
@ -11,9 +11,8 @@ use krata::{
|
|||||||
WatchEventsRequest,
|
WatchEventsRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use kratactl::{client::ControlClientProvider, console::StdioConsoleStream};
|
use kratactl::{client::ControlClientProvider, console::StdioConsoleStream, events::EventStream};
|
||||||
use log::error;
|
use log::error;
|
||||||
use tokio_stream::StreamExt;
|
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@ -62,10 +61,13 @@ 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 mut events = client
|
let events = EventStream::open(
|
||||||
.watch_events(WatchEventsRequest {})
|
client
|
||||||
.await?
|
.watch_events(WatchEventsRequest {})
|
||||||
.into_inner();
|
.await?
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Launch {
|
Commands::Launch {
|
||||||
@ -95,41 +97,7 @@ async fn main() -> Result<()> {
|
|||||||
.into_inner();
|
.into_inner();
|
||||||
let id = response.guest_id;
|
let id = response.guest_id;
|
||||||
if attach {
|
if attach {
|
||||||
while let Some(event) = events.next().await {
|
wait_guest_started(&id, events.clone()).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 input = StdioConsoleStream::stdin_stream(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 =
|
let exit_hook_task =
|
||||||
@ -208,19 +176,17 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Commands::Watch {} => {
|
Commands::Watch {} => {
|
||||||
let response = client
|
let mut stream = events.subscribe();
|
||||||
.watch_events(Request::new(WatchEventsRequest {}))
|
loop {
|
||||||
.await?;
|
let event = stream.recv().await?;
|
||||||
let mut stream = response.into_inner();
|
|
||||||
while let Some(reply) = stream.message().await? {
|
|
||||||
let Some(event) = reply.event else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::GuestChanged(changed) => {
|
Event::GuestChanged(changed) => {
|
||||||
if let Some(guest) = changed.guest {
|
if let Some(guest) = changed.guest {
|
||||||
println!("event=guest.changed guest={}", guest.id);
|
println!(
|
||||||
|
"event=guest.changed guest={} status={}",
|
||||||
|
guest.id,
|
||||||
|
guest_status_text(guest.state.unwrap_or_default().status())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,7 +198,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
fn guest_status_text(status: GuestStatus) -> String {
|
fn guest_status_text(status: GuestStatus) -> String {
|
||||||
match status {
|
match status {
|
||||||
GuestStatus::Unknown => "unknown",
|
GuestStatus::Destroy => "destroying",
|
||||||
GuestStatus::Destroyed => "destroyed",
|
GuestStatus::Destroyed => "destroyed",
|
||||||
GuestStatus::Start => "starting",
|
GuestStatus::Start => "starting",
|
||||||
GuestStatus::Exited => "exited",
|
GuestStatus::Exited => "exited",
|
||||||
@ -254,3 +220,38 @@ fn guest_state_text(state: GuestState) -> String {
|
|||||||
}
|
}
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_guest_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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -7,9 +7,9 @@ use anyhow::Result;
|
|||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use krata::{
|
use krata::{
|
||||||
common::GuestStatus,
|
common::GuestStatus,
|
||||||
control::{watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, WatchEventsReply},
|
control::{watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest},
|
||||||
};
|
};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, warn};
|
||||||
use termion::raw::IntoRawMode;
|
use termion::raw::IntoRawMode;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -19,6 +19,8 @@ use tokio::{
|
|||||||
use tokio_stream::{Stream, StreamExt};
|
use tokio_stream::{Stream, StreamExt};
|
||||||
use tonic::Streaming;
|
use tonic::Streaming;
|
||||||
|
|
||||||
|
use crate::events::EventStream;
|
||||||
|
|
||||||
pub struct StdioConsoleStream;
|
pub struct StdioConsoleStream;
|
||||||
|
|
||||||
impl StdioConsoleStream {
|
impl StdioConsoleStream {
|
||||||
@ -59,46 +61,31 @@ impl StdioConsoleStream {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn guest_exit_hook(
|
pub async fn guest_exit_hook(id: String, events: EventStream) -> Result<JoinHandle<()>> {
|
||||||
id: String,
|
|
||||||
mut events: Streaming<WatchEventsReply>,
|
|
||||||
) -> Result<JoinHandle<()>> {
|
|
||||||
Ok(tokio::task::spawn(async move {
|
Ok(tokio::task::spawn(async move {
|
||||||
while let Some(result) = events.next().await {
|
let mut stream = events.subscribe();
|
||||||
match result {
|
while let Ok(event) = stream.recv().await {
|
||||||
Err(error) => {
|
match event {
|
||||||
error!("failed to handle events for exit hook: {}", error);
|
Event::GuestChanged(changed) => {
|
||||||
break;
|
let Some(guest) = changed.guest else {
|
||||||
}
|
|
||||||
|
|
||||||
Ok(reply) => {
|
|
||||||
let Some(event) = reply.event else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
let Some(state) = guest.state else {
|
||||||
Event::GuestChanged(changed) => {
|
continue;
|
||||||
let Some(guest) = changed.guest else {
|
};
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(state) = guest.state else {
|
if guest.id != id {
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
if guest.id != id {
|
if let Some(exit_info) = state.exit_info {
|
||||||
continue;
|
std::process::exit(exit_info.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(exit_info) = state.exit_info {
|
if state.status() == GuestStatus::Destroy {
|
||||||
std::process::exit(exit_info.code);
|
warn!("attached guest was destroyed");
|
||||||
}
|
std::process::exit(1);
|
||||||
|
|
||||||
if state.status() == GuestStatus::Destroyed {
|
|
||||||
warn!("attached guest was destroyed");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
crates/kratactl/src/events.rs
Normal file
57
crates/kratactl/src/events.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use krata::control::{watch_events_reply::Event, WatchEventsReply};
|
||||||
|
use log::trace;
|
||||||
|
use tokio::{sync::broadcast, task::JoinHandle};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tonic::Streaming;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EventStream {
|
||||||
|
sender: Arc<broadcast::Sender<Event>>,
|
||||||
|
task: Arc<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventStream {
|
||||||
|
pub async fn open(mut events: Streaming<WatchEventsReply>) -> Result<Self> {
|
||||||
|
let (sender, _) = broadcast::channel(1000);
|
||||||
|
let emit = sender.clone();
|
||||||
|
let task = tokio::task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let Some(result) = events.next().await else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let reply = match result {
|
||||||
|
Ok(reply) => reply,
|
||||||
|
Err(error) => {
|
||||||
|
trace!("event stream processing failed: {}", error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(event) = reply.event else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let _ = emit.send(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(Self {
|
||||||
|
sender: Arc::new(sender),
|
||||||
|
task: Arc::new(task),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
||||||
|
self.sender.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EventStream {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if Arc::strong_count(&self.task) <= 1 {
|
||||||
|
self.task.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod console;
|
pub mod console;
|
||||||
|
pub mod events;
|
||||||
|
@ -191,11 +191,10 @@ impl ControlService for RuntimeControlService {
|
|||||||
.into());
|
.into());
|
||||||
};
|
};
|
||||||
let request = request?;
|
let request = request?;
|
||||||
let mut console = self
|
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
|
||||||
.runtime
|
message: error.to_string(),
|
||||||
.console(&request.guest_id)
|
})?;
|
||||||
.await
|
let mut console = self.runtime.console(uuid).await.map_err(ApiError::from)?;
|
||||||
.map_err(ApiError::from)?;
|
|
||||||
|
|
||||||
let output = try_stream! {
|
let output = try_stream! {
|
||||||
let mut buffer: Vec<u8> = vec![0u8; 256];
|
let mut buffer: Vec<u8> = vec![0u8; 256];
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use krata::{
|
use krata::common::{GuestExitInfo, GuestState, GuestStatus};
|
||||||
common::{GuestExitInfo, GuestState, GuestStatus},
|
|
||||||
control::watch_events_reply::Event,
|
|
||||||
};
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{broadcast, mpsc::Sender},
|
sync::{broadcast, mpsc::Sender},
|
||||||
@ -42,7 +39,7 @@ pub struct DaemonEventGenerator {
|
|||||||
guests: GuestStore,
|
guests: GuestStore,
|
||||||
guest_reconciler_notify: Sender<Uuid>,
|
guest_reconciler_notify: Sender<Uuid>,
|
||||||
last: HashMap<Uuid, GuestInfo>,
|
last: HashMap<Uuid, GuestInfo>,
|
||||||
_sender: broadcast::Sender<Event>,
|
_sender: broadcast::Sender<DaemonEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonEventGenerator {
|
impl DaemonEventGenerator {
|
||||||
|
@ -107,12 +107,8 @@ impl GuestReconciler {
|
|||||||
}))?;
|
}))?;
|
||||||
|
|
||||||
let result = match guest.state.as_ref().map(|x| x.status()).unwrap_or_default() {
|
let result = match guest.state.as_ref().map(|x| x.status()).unwrap_or_default() {
|
||||||
GuestStatus::Start => self.start(uuid, guest).await.map(|_| true),
|
GuestStatus::Start => self.start(uuid, guest).await,
|
||||||
|
GuestStatus::Destroy | GuestStatus::Exited => self.destroy(uuid, guest).await,
|
||||||
GuestStatus::Destroy | GuestStatus::Exited => {
|
|
||||||
self.destroy(uuid, guest).await.map(|_| true)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Ok(false),
|
_ => Ok(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,7 +145,7 @@ impl GuestReconciler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<()> {
|
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<bool> {
|
||||||
let Some(ref spec) = guest.spec else {
|
let Some(ref spec) = guest.spec else {
|
||||||
return Err(anyhow!("guest spec not specified"));
|
return Err(anyhow!("guest spec not specified"));
|
||||||
};
|
};
|
||||||
@ -191,11 +187,11 @@ impl GuestReconciler {
|
|||||||
exit_info: None,
|
exit_info: None,
|
||||||
error_info: None,
|
error_info: None,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn destroy(&self, uuid: Uuid, guest: &mut Guest) -> Result<()> {
|
async fn destroy(&self, uuid: Uuid, guest: &mut Guest) -> Result<bool> {
|
||||||
self.runtime.destroy(&uuid.to_string()).await?;
|
self.runtime.destroy(uuid).await?;
|
||||||
info!("destroyed guest {}", uuid);
|
info!("destroyed guest {}", uuid);
|
||||||
guest.network = None;
|
guest.network = None;
|
||||||
guest.state = Some(GuestState {
|
guest.state = Some(GuestState {
|
||||||
@ -203,7 +199,7 @@ impl GuestReconciler {
|
|||||||
exit_info: None,
|
exit_info: None,
|
||||||
error_info: None,
|
error_info: None,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ impl GuestLauncher {
|
|||||||
request: GuestLaunchRequest<'r>,
|
request: GuestLaunchRequest<'r>,
|
||||||
) -> Result<GuestInfo> {
|
) -> Result<GuestInfo> {
|
||||||
let uuid = request.uuid.unwrap_or_else(Uuid::new_v4);
|
let uuid = request.uuid.unwrap_or_else(Uuid::new_v4);
|
||||||
let name = format!("krata-{uuid}");
|
let xen_name = format!("krata-{uuid}");
|
||||||
let image_info = self.compile(request.image, &context.image_cache).await?;
|
let image_info = self.compile(request.image, &context.image_cache).await?;
|
||||||
|
|
||||||
let mut gateway_mac = MacAddr6::random();
|
let mut gateway_mac = MacAddr6::random();
|
||||||
@ -161,7 +161,7 @@ impl GuestLauncher {
|
|||||||
|
|
||||||
let config = DomainConfig {
|
let config = DomainConfig {
|
||||||
backend_domid: 0,
|
backend_domid: 0,
|
||||||
name: &name,
|
name: &xen_name,
|
||||||
max_vcpus: request.vcpus,
|
max_vcpus: request.vcpus,
|
||||||
mem_mb: request.mem,
|
mem_mb: request.mem,
|
||||||
kernel_path: &context.kernel,
|
kernel_path: &context.kernel,
|
||||||
|
@ -178,18 +178,9 @@ impl RuntimeContext {
|
|||||||
Ok(guests)
|
Ok(guests)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve(&mut self, id: &str) -> Result<Option<GuestInfo>> {
|
pub async fn resolve(&mut self, uuid: Uuid) -> Result<Option<GuestInfo>> {
|
||||||
for guest in self.list().await? {
|
for guest in self.list().await? {
|
||||||
let uuid_string = guest.uuid.to_string();
|
if guest.uuid == uuid {
|
||||||
let domid_string = guest.domid.to_string();
|
|
||||||
|
|
||||||
if let Some(ref name) = guest.name {
|
|
||||||
if name == id {
|
|
||||||
return Ok(Some(guest));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if uuid_string == id || domid_string == id || id == format!("krata-{}", uuid_string) {
|
|
||||||
return Ok(Some(guest));
|
return Ok(Some(guest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,12 +232,12 @@ impl Runtime {
|
|||||||
launcher.launch(&mut context, request).await
|
launcher.launch(&mut context, request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn destroy(&self, id: &str) -> Result<Uuid> {
|
pub async fn destroy(&self, uuid: Uuid) -> Result<Uuid> {
|
||||||
let mut context = self.context.lock().await;
|
let mut context = self.context.lock().await;
|
||||||
let info = context
|
let info = context
|
||||||
.resolve(id)
|
.resolve(uuid)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("unable to resolve guest: {}", id))?;
|
.ok_or_else(|| anyhow!("unable to resolve guest: {}", uuid))?;
|
||||||
let domid = info.domid;
|
let domid = info.domid;
|
||||||
let mut store = XsdClient::open().await?;
|
let mut store = XsdClient::open().await?;
|
||||||
let dom_path = store.get_domain_path(domid).await?;
|
let dom_path = store.get_domain_path(domid).await?;
|
||||||
@ -288,12 +279,12 @@ impl Runtime {
|
|||||||
Ok(uuid)
|
Ok(uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn console(&self, id: &str) -> Result<XenConsole> {
|
pub async fn console(&self, uuid: Uuid) -> Result<XenConsole> {
|
||||||
let mut context = self.context.lock().await;
|
let mut context = self.context.lock().await;
|
||||||
let info = context
|
let info = context
|
||||||
.resolve(id)
|
.resolve(uuid)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("unable to resolve guest: {}", id))?;
|
.ok_or_else(|| anyhow!("unable to resolve guest: {}", uuid))?;
|
||||||
let domid = info.domid;
|
let domid = info.domid;
|
||||||
let tty = context.xen.get_console_path(domid).await?;
|
let tty = context.xen.get_console_path(domid).await?;
|
||||||
XenConsole::new(&tty).await
|
XenConsole::new(&tty).await
|
||||||
|
Reference in New Issue
Block a user