mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-02 21:00:55 +00:00
krata: implement guest tab for automatic guest startup
This commit is contained in:
parent
a8b5ed1138
commit
f23d84c415
@ -48,6 +48,7 @@ rand = "0.8.5"
|
|||||||
rtnetlink = "0.14.1"
|
rtnetlink = "0.14.1"
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.113"
|
||||||
sha256 = "1.5.0"
|
sha256 = "1.5.0"
|
||||||
|
serde_yaml = "0.9"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
slice-copy = "0.3.0"
|
slice-copy = "0.3.0"
|
||||||
smoltcp = "0.11.0"
|
smoltcp = "0.11.0"
|
||||||
|
@ -23,16 +23,18 @@ message GuestNetworkInfo {
|
|||||||
|
|
||||||
message GuestInfo {
|
message GuestInfo {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
GuestImageSpec image = 2;
|
string name = 2;
|
||||||
GuestNetworkInfo network = 3;
|
GuestImageSpec image = 3;
|
||||||
|
GuestNetworkInfo network = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LaunchGuestRequest {
|
message LaunchGuestRequest {
|
||||||
GuestImageSpec image = 1;
|
string name = 1;
|
||||||
uint32 vcpus = 2;
|
GuestImageSpec image = 2;
|
||||||
uint64 mem = 3;
|
uint32 vcpus = 3;
|
||||||
repeated string env = 4;
|
uint64 mem = 4;
|
||||||
repeated string run = 5;
|
repeated string env = 5;
|
||||||
|
repeated string run = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LaunchGuestReply {
|
message LaunchGuestReply {
|
||||||
|
@ -22,6 +22,8 @@ struct ControllerArgs {
|
|||||||
enum Commands {
|
enum Commands {
|
||||||
List {},
|
List {},
|
||||||
Launch {
|
Launch {
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: Option<String>,
|
||||||
#[arg(short, long, default_value_t = 1)]
|
#[arg(short, long, default_value_t = 1)]
|
||||||
cpus: u32,
|
cpus: u32,
|
||||||
#[arg(short, long, default_value_t = 512)]
|
#[arg(short, long, default_value_t = 512)]
|
||||||
@ -59,6 +61,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Launch {
|
Commands::Launch {
|
||||||
|
name,
|
||||||
oci,
|
oci,
|
||||||
cpus,
|
cpus,
|
||||||
mem,
|
mem,
|
||||||
@ -67,6 +70,7 @@ async fn main() -> Result<()> {
|
|||||||
run,
|
run,
|
||||||
} => {
|
} => {
|
||||||
let request = LaunchGuestRequest {
|
let request = LaunchGuestRequest {
|
||||||
|
name: name.unwrap_or_default(),
|
||||||
image: Some(GuestImageSpec {
|
image: Some(GuestImageSpec {
|
||||||
image: Some(Image::Oci(GuestOciImageSpec { image: oci })),
|
image: Some(Image::Oci(GuestOciImageSpec { image: oci })),
|
||||||
}),
|
}),
|
||||||
@ -119,7 +123,7 @@ async fn main() -> Result<()> {
|
|||||||
.await?
|
.await?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
let mut table = cli_tables::Table::new();
|
let mut table = cli_tables::Table::new();
|
||||||
let header = vec!["uuid", "ipv4", "ipv6", "image"];
|
let header = vec!["name", "uuid", "ipv4", "ipv6", "image"];
|
||||||
table.push_row(&header)?;
|
table.push_row(&header)?;
|
||||||
for guest in response.guests {
|
for guest in response.guests {
|
||||||
let ipv4 = guest
|
let ipv4 = guest
|
||||||
@ -143,6 +147,7 @@ async fn main() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.unwrap_or("unknown".to_string());
|
.unwrap_or("unknown".to_string());
|
||||||
table.push_row_string(&vec![
|
table.push_row_string(&vec![
|
||||||
|
guest.name,
|
||||||
guest.id,
|
guest.id,
|
||||||
ipv4.to_string(),
|
ipv4.to_string(),
|
||||||
ipv6.to_string(),
|
ipv6.to_string(),
|
||||||
|
@ -14,6 +14,8 @@ futures = { workspace = true }
|
|||||||
krata = { path = "../krata" }
|
krata = { path = "../krata" }
|
||||||
kratart = { path = "../kratart" }
|
kratart = { path = "../kratart" }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_yaml = { workspace = true }
|
||||||
signal-hook = { workspace = true }
|
signal-hook = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
|
@ -4,6 +4,7 @@ use env_logger::Env;
|
|||||||
use krata::dial::ControlDialAddress;
|
use krata::dial::ControlDialAddress;
|
||||||
use kratad::Daemon;
|
use kratad::Daemon;
|
||||||
use kratart::Runtime;
|
use kratart::Runtime;
|
||||||
|
use log::error;
|
||||||
use std::{
|
use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
@ -15,6 +16,8 @@ struct Args {
|
|||||||
listen: String,
|
listen: String,
|
||||||
#[arg(short, long, default_value = "/var/lib/krata")]
|
#[arg(short, long, default_value = "/var/lib/krata")]
|
||||||
store: String,
|
store: String,
|
||||||
|
#[arg(long, default_value = "false")]
|
||||||
|
no_load_guest_tab: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||||
@ -26,6 +29,11 @@ async fn main() -> Result<()> {
|
|||||||
let addr = ControlDialAddress::from_str(&args.listen)?;
|
let addr = ControlDialAddress::from_str(&args.listen)?;
|
||||||
let runtime = Runtime::new(args.store.clone()).await?;
|
let runtime = Runtime::new(args.store.clone()).await?;
|
||||||
let mut daemon = Daemon::new(args.store.clone(), runtime).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?;
|
daemon.listen(addr).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,11 @@ impl ControlService for RuntimeControlService {
|
|||||||
let guest: GuestInfo = convert_guest_info(
|
let guest: GuestInfo = convert_guest_info(
|
||||||
self.runtime
|
self.runtime
|
||||||
.launch(GuestLaunchRequest {
|
.launch(GuestLaunchRequest {
|
||||||
|
name: if request.name.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&request.name)
|
||||||
|
},
|
||||||
image: &oci.image,
|
image: &oci.image,
|
||||||
vcpus: request.vcpus,
|
vcpus: request.vcpus,
|
||||||
mem: request.mem,
|
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 {
|
fn convert_guest_info(value: kratart::GuestInfo) -> GuestInfo {
|
||||||
GuestInfo {
|
GuestInfo {
|
||||||
|
name: value.name.unwrap_or_default(),
|
||||||
id: value.uuid.to_string(),
|
id: value.uuid.to_string(),
|
||||||
image: Some(GuestImageSpec {
|
image: Some(GuestImageSpec {
|
||||||
image: Some(Image::Oci(GuestOciImageSpec { image: value.image })),
|
image: Some(Image::Oci(GuestOciImageSpec { image: value.image })),
|
||||||
|
@ -4,14 +4,16 @@ use anyhow::Result;
|
|||||||
use control::RuntimeControlService;
|
use control::RuntimeControlService;
|
||||||
use event::{DaemonEventContext, DaemonEventGenerator};
|
use event::{DaemonEventContext, DaemonEventGenerator};
|
||||||
use krata::{control::control_service_server::ControlServiceServer, dial::ControlDialAddress};
|
use krata::{control::control_service_server::ControlServiceServer, dial::ControlDialAddress};
|
||||||
use kratart::Runtime;
|
use kratart::{launch::GuestLaunchRequest, Runtime};
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use tokio::{net::UnixListener, task::JoinHandle};
|
use tab::Tab;
|
||||||
|
use tokio::{fs, net::UnixListener, task::JoinHandle};
|
||||||
use tokio_stream::wrappers::UnixListenerStream;
|
use tokio_stream::wrappers::UnixListenerStream;
|
||||||
use tonic::transport::{Identity, Server, ServerTlsConfig};
|
use tonic::transport::{Identity, Server, ServerTlsConfig};
|
||||||
|
|
||||||
pub mod control;
|
pub mod control;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod tab;
|
||||||
|
|
||||||
pub struct Daemon {
|
pub struct Daemon {
|
||||||
store: String,
|
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<()> {
|
pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> {
|
||||||
let control_service = RuntimeControlService::new(self.events.clone(), self.runtime.clone());
|
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>,
|
||||||
|
}
|
@ -22,6 +22,7 @@ use crate::RuntimeContext;
|
|||||||
use super::{GuestInfo, GuestState};
|
use super::{GuestInfo, GuestState};
|
||||||
|
|
||||||
pub struct GuestLaunchRequest<'a> {
|
pub struct GuestLaunchRequest<'a> {
|
||||||
|
pub name: Option<&'a str>,
|
||||||
pub image: &'a str,
|
pub image: &'a str,
|
||||||
pub vcpus: u32,
|
pub vcpus: u32,
|
||||||
pub mem: u64,
|
pub mem: u64,
|
||||||
@ -112,6 +113,51 @@ impl GuestLauncher {
|
|||||||
|
|
||||||
let container_mac_string = container_mac.to_string().replace('-', ":");
|
let container_mac_string = container_mac.to_string().replace('-', ":");
|
||||||
let gateway_mac_string = gateway_mac.to_string().replace('-', ":");
|
let gateway_mac_string = gateway_mac.to_string().replace('-', ":");
|
||||||
|
|
||||||
|
let mut extra_keys = vec![
|
||||||
|
("krata/uuid".to_string(), uuid.to_string()),
|
||||||
|
(
|
||||||
|
"krata/loops".to_string(),
|
||||||
|
format!(
|
||||||
|
"{}:{}:none,{}:{}:{}",
|
||||||
|
&image_squashfs_loop.path,
|
||||||
|
image_squashfs_path,
|
||||||
|
&cfgblk_squashfs_loop.path,
|
||||||
|
cfgblk_squashfs_path,
|
||||||
|
cfgblk_dir_path,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("krata/image".to_string(), request.image.to_string()),
|
||||||
|
(
|
||||||
|
"krata/network/guest/ipv4".to_string(),
|
||||||
|
format!("{}/{}", guest_ipv4, ipv4_network_mask),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"krata/network/guest/ipv6".to_string(),
|
||||||
|
format!("{}/{}", guest_ipv6, ipv6_network_mask),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"krata/network/guest/mac".to_string(),
|
||||||
|
container_mac_string.clone(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"krata/network/gateway/ipv4".to_string(),
|
||||||
|
format!("{}/{}", gateway_ipv4, ipv4_network_mask),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"krata/network/gateway/ipv6".to_string(),
|
||||||
|
format!("{}/{}", gateway_ipv6, ipv6_network_mask),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"krata/network/gateway/mac".to_string(),
|
||||||
|
gateway_mac_string.clone(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(name) = request.name {
|
||||||
|
extra_keys.push(("krata/name".to_string(), name.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let config = DomainConfig {
|
let config = DomainConfig {
|
||||||
backend_domid: 0,
|
backend_domid: 0,
|
||||||
name: &name,
|
name: &name,
|
||||||
@ -141,49 +187,12 @@ impl GuestLauncher {
|
|||||||
}],
|
}],
|
||||||
filesystems: vec![],
|
filesystems: vec![],
|
||||||
event_channels: vec![],
|
event_channels: vec![],
|
||||||
extra_keys: vec![
|
extra_keys,
|
||||||
("krata/uuid".to_string(), uuid.to_string()),
|
|
||||||
(
|
|
||||||
"krata/loops".to_string(),
|
|
||||||
format!(
|
|
||||||
"{}:{}:none,{}:{}:{}",
|
|
||||||
&image_squashfs_loop.path,
|
|
||||||
image_squashfs_path,
|
|
||||||
&cfgblk_squashfs_loop.path,
|
|
||||||
cfgblk_squashfs_path,
|
|
||||||
cfgblk_dir_path,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("krata/image".to_string(), request.image.to_string()),
|
|
||||||
(
|
|
||||||
"krata/network/guest/ipv4".to_string(),
|
|
||||||
format!("{}/{}", guest_ipv4, ipv4_network_mask),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"krata/network/guest/ipv6".to_string(),
|
|
||||||
format!("{}/{}", guest_ipv6, ipv6_network_mask),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"krata/network/guest/mac".to_string(),
|
|
||||||
container_mac_string.clone(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"krata/network/gateway/ipv4".to_string(),
|
|
||||||
format!("{}/{}", gateway_ipv4, ipv4_network_mask),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"krata/network/gateway/ipv6".to_string(),
|
|
||||||
format!("{}/{}", gateway_ipv6, ipv6_network_mask),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"krata/network/gateway/mac".to_string(),
|
|
||||||
gateway_mac_string.clone(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
extra_rw_paths: vec!["krata/guest".to_string()],
|
extra_rw_paths: vec!["krata/guest".to_string()],
|
||||||
};
|
};
|
||||||
match context.xen.create(&config).await {
|
match context.xen.create(&config).await {
|
||||||
Ok(domid) => Ok(GuestInfo {
|
Ok(domid) => Ok(GuestInfo {
|
||||||
|
name: request.name.map(|x| x.to_string()),
|
||||||
uuid,
|
uuid,
|
||||||
domid,
|
domid,
|
||||||
image: request.image.to_string(),
|
image: request.image.to_string(),
|
||||||
|
@ -37,6 +37,7 @@ pub struct GuestState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct GuestInfo {
|
pub struct GuestInfo {
|
||||||
|
pub name: Option<String>,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub domid: u32,
|
pub domid: u32,
|
||||||
pub image: String,
|
pub image: String,
|
||||||
@ -92,6 +93,9 @@ impl RuntimeContext {
|
|||||||
pub async fn list(&mut self) -> Result<Vec<GuestInfo>> {
|
pub async fn list(&mut self) -> Result<Vec<GuestInfo>> {
|
||||||
let mut guests: Vec<GuestInfo> = Vec::new();
|
let mut guests: Vec<GuestInfo> = Vec::new();
|
||||||
for domid_candidate in self.xen.store.list("/local/domain").await? {
|
for domid_candidate in self.xen.store.list("/local/domain").await? {
|
||||||
|
if domid_candidate == "0" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let dom_path = format!("/local/domain/{}", domid_candidate);
|
let dom_path = format!("/local/domain/{}", domid_candidate);
|
||||||
let uuid_string = match self
|
let uuid_string = match self
|
||||||
.xen
|
.xen
|
||||||
@ -105,6 +109,13 @@ impl RuntimeContext {
|
|||||||
let domid =
|
let domid =
|
||||||
u32::from_str(&domid_candidate).map_err(|_| anyhow!("failed to parse domid"))?;
|
u32::from_str(&domid_candidate).map_err(|_| anyhow!("failed to parse domid"))?;
|
||||||
let uuid = Uuid::from_str(&uuid_string)?;
|
let uuid = Uuid::from_str(&uuid_string)?;
|
||||||
|
|
||||||
|
let name = self
|
||||||
|
.xen
|
||||||
|
.store
|
||||||
|
.read_string(&format!("{}/krata/name", &dom_path))
|
||||||
|
.await?;
|
||||||
|
|
||||||
let image = self
|
let image = self
|
||||||
.xen
|
.xen
|
||||||
.store
|
.store
|
||||||
@ -154,6 +165,7 @@ impl RuntimeContext {
|
|||||||
|
|
||||||
let loops = RuntimeContext::parse_loop_set(&loops);
|
let loops = RuntimeContext::parse_loop_set(&loops);
|
||||||
guests.push(GuestInfo {
|
guests.push(GuestInfo {
|
||||||
|
name,
|
||||||
uuid,
|
uuid,
|
||||||
domid,
|
domid,
|
||||||
image,
|
image,
|
||||||
@ -170,6 +182,13 @@ impl RuntimeContext {
|
|||||||
for guest in self.list().await? {
|
for guest in self.list().await? {
|
||||||
let uuid_string = guest.uuid.to_string();
|
let uuid_string = guest.uuid.to_string();
|
||||||
let domid_string = guest.domid.to_string();
|
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) {
|
if uuid_string == id || domid_string == id || id == format!("krata-{}", uuid_string) {
|
||||||
return Ok(Some(guest));
|
return Ok(Some(guest));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user