mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
krata: implement guest tab for automatic guest startup
This commit is contained in:
@ -14,6 +14,8 @@ futures = { workspace = true }
|
||||
krata = { path = "../krata" }
|
||||
kratart = { path = "../kratart" }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
signal-hook = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
@ -4,6 +4,7 @@ use env_logger::Env;
|
||||
use krata::dial::ControlDialAddress;
|
||||
use kratad::Daemon;
|
||||
use kratart::Runtime;
|
||||
use log::error;
|
||||
use std::{
|
||||
str::FromStr,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@ -15,6 +16,8 @@ struct Args {
|
||||
listen: String,
|
||||
#[arg(short, long, default_value = "/var/lib/krata")]
|
||||
store: String,
|
||||
#[arg(long, default_value = "false")]
|
||||
no_load_guest_tab: bool,
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||
@ -26,6 +29,11 @@ async fn main() -> Result<()> {
|
||||
let addr = ControlDialAddress::from_str(&args.listen)?;
|
||||
let runtime = Runtime::new(args.store.clone()).await?;
|
||||
let mut daemon = Daemon::new(args.store.clone(), runtime).await?;
|
||||
if !args.no_load_guest_tab {
|
||||
if let Err(error) = daemon.load_guest_tab().await {
|
||||
error!("failed to load guest tab: {}", error);
|
||||
}
|
||||
}
|
||||
daemon.listen(addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -84,6 +84,11 @@ impl ControlService for RuntimeControlService {
|
||||
let guest: GuestInfo = convert_guest_info(
|
||||
self.runtime
|
||||
.launch(GuestLaunchRequest {
|
||||
name: if request.name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&request.name)
|
||||
},
|
||||
image: &oci.image,
|
||||
vcpus: request.vcpus,
|
||||
mem: request.mem,
|
||||
@ -197,6 +202,7 @@ fn empty_vec_optional<T>(value: Vec<T>) -> Option<Vec<T>> {
|
||||
|
||||
fn convert_guest_info(value: kratart::GuestInfo) -> GuestInfo {
|
||||
GuestInfo {
|
||||
name: value.name.unwrap_or_default(),
|
||||
id: value.uuid.to_string(),
|
||||
image: Some(GuestImageSpec {
|
||||
image: Some(Image::Oci(GuestOciImageSpec { image: value.image })),
|
||||
|
@ -4,14 +4,16 @@ use anyhow::Result;
|
||||
use control::RuntimeControlService;
|
||||
use event::{DaemonEventContext, DaemonEventGenerator};
|
||||
use krata::{control::control_service_server::ControlServiceServer, dial::ControlDialAddress};
|
||||
use kratart::Runtime;
|
||||
use log::info;
|
||||
use tokio::{net::UnixListener, task::JoinHandle};
|
||||
use kratart::{launch::GuestLaunchRequest, Runtime};
|
||||
use log::{info, warn};
|
||||
use tab::Tab;
|
||||
use tokio::{fs, net::UnixListener, task::JoinHandle};
|
||||
use tokio_stream::wrappers::UnixListenerStream;
|
||||
use tonic::transport::{Identity, Server, ServerTlsConfig};
|
||||
|
||||
pub mod control;
|
||||
pub mod event;
|
||||
pub mod tab;
|
||||
|
||||
pub struct Daemon {
|
||||
store: String,
|
||||
@ -32,6 +34,66 @@ impl Daemon {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_guest_tab(&mut self) -> Result<()> {
|
||||
let tab_path = PathBuf::from(format!("{}/guests.yml", self.store));
|
||||
|
||||
if !tab_path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("loading guest tab");
|
||||
|
||||
let tab_content = fs::read_to_string(tab_path).await?;
|
||||
let tab: Tab = serde_yaml::from_str(&tab_content)?;
|
||||
let running = self.runtime.list().await?;
|
||||
for (name, guest) in tab.guests {
|
||||
let existing = running
|
||||
.iter()
|
||||
.filter(|x| x.name.is_some())
|
||||
.find(|run| *run.name.as_ref().unwrap() == name);
|
||||
|
||||
if let Some(existing) = existing {
|
||||
info!("guest {} is already running: {}", name, existing.uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
let request = GuestLaunchRequest {
|
||||
name: Some(&name),
|
||||
image: &guest.image,
|
||||
vcpus: guest.cpus,
|
||||
mem: guest.mem,
|
||||
env: if guest.env.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
guest
|
||||
.env
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{}={}", key, value))
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
},
|
||||
run: if guest.run.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(guest.run)
|
||||
},
|
||||
debug: false,
|
||||
};
|
||||
match self.runtime.launch(request).await {
|
||||
Err(error) => {
|
||||
warn!("failed to launch guest {}: {}", name, error);
|
||||
}
|
||||
|
||||
Ok(info) => {
|
||||
info!("launched guest {}: {}", name, info.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("loaded guest tab");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> {
|
||||
let control_service = RuntimeControlService::new(self.events.clone(), self.runtime.clone());
|
||||
|
||||
|
20
crates/kratad/src/tab.rs
Normal file
20
crates/kratad/src/tab.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Tab {
|
||||
#[serde(default)]
|
||||
pub guests: HashMap<String, TabGuest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TabGuest {
|
||||
pub image: String,
|
||||
pub mem: u64,
|
||||
pub cpus: u32,
|
||||
#[serde(default)]
|
||||
pub env: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub run: Vec<String>,
|
||||
}
|
Reference in New Issue
Block a user