krata/crates/kratad/src/control.rs

246 lines
7.3 KiB
Rust
Raw Normal View History

2024-03-14 14:03:11 +00:00
use std::{io, pin::Pin, str::FromStr};
use async_stream::try_stream;
use futures::Stream;
2024-03-14 14:03:11 +00:00
use krata::{
common::{Guest, GuestState, GuestStatus},
control::{
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
ListGuestsReply, ListGuestsRequest, WatchEventsReply, WatchEventsRequest,
},
};
2024-03-14 14:03:11 +00:00
use kratart::Runtime;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
select,
2024-03-14 14:03:11 +00:00
sync::mpsc::Sender,
};
use tokio_stream::StreamExt;
use tonic::{Request, Response, Status, Streaming};
2024-03-14 14:03:11 +00:00
use uuid::Uuid;
2024-03-14 14:03:11 +00:00
use crate::{
db::{proto::GuestEntry, GuestStore},
event::DaemonEventContext,
};
pub struct ApiError {
message: String,
}
impl From<anyhow::Error> for ApiError {
fn from(value: anyhow::Error) -> Self {
ApiError {
message: value.to_string(),
}
}
}
impl From<ApiError> for Status {
fn from(value: ApiError) -> Self {
Status::unknown(value.message)
}
}
#[derive(Clone)]
pub struct RuntimeControlService {
2024-03-06 15:57:56 +00:00
events: DaemonEventContext,
runtime: Runtime,
2024-03-14 14:03:11 +00:00
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
}
impl RuntimeControlService {
2024-03-14 14:03:11 +00:00
pub fn new(
events: DaemonEventContext,
runtime: Runtime,
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
) -> Self {
Self {
events,
runtime,
guests,
guest_reconciler_notify,
}
}
}
enum ConsoleDataSelect {
Read(io::Result<usize>),
Write(Option<Result<ConsoleDataRequest, tonic::Status>>),
}
#[tonic::async_trait]
impl ControlService for RuntimeControlService {
type ConsoleDataStream =
Pin<Box<dyn Stream<Item = Result<ConsoleDataReply, Status>> + Send + 'static>>;
2024-03-06 15:57:56 +00:00
type WatchEventsStream =
Pin<Box<dyn Stream<Item = Result<WatchEventsReply, Status>> + Send + 'static>>;
2024-03-14 14:03:11 +00:00
async fn create_guest(
&self,
2024-03-14 14:03:11 +00:00
request: Request<CreateGuestRequest>,
) -> Result<Response<CreateGuestReply>, Status> {
let request = request.into_inner();
2024-03-14 14:03:11 +00:00
let Some(spec) = request.spec else {
return Err(ApiError {
2024-03-14 14:03:11 +00:00
message: "guest spec not provided".to_string(),
}
.into());
};
2024-03-14 14:03:11 +00:00
let uuid = Uuid::new_v4();
self.guests
.update(
uuid,
GuestEntry {
id: uuid.to_string(),
guest: Some(Guest {
id: uuid.to_string(),
state: Some(GuestState {
status: GuestStatus::Start.into(),
exit_info: None,
error_info: None,
}),
spec: Some(spec),
network: None,
}),
},
)
.await
.map_err(ApiError::from)?;
self.guest_reconciler_notify
.send(uuid)
.await
.map_err(|x| ApiError {
message: x.to_string(),
})?;
Ok(Response::new(CreateGuestReply {
guest_id: uuid.to_string(),
}))
}
async fn destroy_guest(
&self,
request: Request<DestroyGuestRequest>,
) -> Result<Response<DestroyGuestReply>, Status> {
let request = request.into_inner();
2024-03-14 14:03:11 +00:00
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
message: error.to_string(),
})?;
let Some(mut entry) = self.guests.read(uuid).await.map_err(ApiError::from)? else {
return Err(ApiError {
message: "guest not found".to_string(),
}
.into());
};
let Some(ref mut guest) = entry.guest else {
return Err(ApiError {
message: "guest not found".to_string(),
}
.into());
};
guest.state = Some(guest.state.as_mut().cloned().unwrap_or_default());
if guest.state.as_ref().unwrap().status() == GuestStatus::Destroyed {
return Err(ApiError {
message: "guest already destroyed".to_string(),
}
.into());
}
guest.state.as_mut().unwrap().status = GuestStatus::Destroy.into();
self.guests
.update(uuid, entry)
.await
.map_err(ApiError::from)?;
2024-03-14 14:03:11 +00:00
self.guest_reconciler_notify
.send(uuid)
.await
.map_err(|x| ApiError {
message: x.to_string(),
})?;
Ok(Response::new(DestroyGuestReply {}))
}
async fn list_guests(
&self,
request: Request<ListGuestsRequest>,
) -> Result<Response<ListGuestsReply>, Status> {
let _ = request.into_inner();
2024-03-14 14:03:11 +00:00
let guests = self.guests.list().await.map_err(ApiError::from)?;
let guests = guests
2024-03-14 14:03:11 +00:00
.into_values()
.filter_map(|entry| entry.guest)
.collect::<Vec<Guest>>();
Ok(Response::new(ListGuestsReply { guests }))
}
async fn console_data(
&self,
request: Request<Streaming<ConsoleDataRequest>>,
) -> Result<Response<Self::ConsoleDataStream>, Status> {
let mut input = request.into_inner();
let Some(request) = input.next().await else {
return Err(ApiError {
message: "expected to have at least one request".to_string(),
}
.into());
};
let request = request?;
let mut console = self
.runtime
2024-03-06 15:57:56 +00:00
.console(&request.guest_id)
.await
.map_err(ApiError::from)?;
let output = try_stream! {
let mut buffer: Vec<u8> = vec![0u8; 256];
loop {
let what = select! {
x = console.read_handle.read(&mut buffer) => ConsoleDataSelect::Read(x),
x = input.next() => ConsoleDataSelect::Write(x),
};
match what {
ConsoleDataSelect::Read(result) => {
let size = result?;
let data = buffer[0..size].to_vec();
yield ConsoleDataReply { data, };
},
ConsoleDataSelect::Write(Some(request)) => {
let request = request?;
if !request.data.is_empty() {
console.write_handle.write_all(&request.data).await?;
}
},
ConsoleDataSelect::Write(None) => {
break;
}
}
}
};
Ok(Response::new(Box::pin(output) as Self::ConsoleDataStream))
}
2024-03-06 15:57:56 +00:00
async fn watch_events(
&self,
request: Request<WatchEventsRequest>,
) -> Result<Response<Self::WatchEventsStream>, Status> {
let _ = request.into_inner();
let mut events = self.events.subscribe();
let output = try_stream! {
while let Ok(event) = events.recv().await {
yield WatchEventsReply { event: Some(event), };
}
};
Ok(Response::new(Box::pin(output) as Self::WatchEventsStream))
}
}