Skip to content

Commit

Permalink
feat: implement kernel / initrd oci image support (#103)
Browse files Browse the repository at this point in the history
* feat: implement kernel / initrd oci image support

* fix: implement image urls more faithfully
  • Loading branch information
azenla authored Apr 22, 2024
1 parent 1b90eed commit 82576df
Show file tree
Hide file tree
Showing 18 changed files with 470 additions and 262 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 55 additions & 15 deletions crates/ctl/src/cli/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ pub struct LauchCommand {
help = "Wait for the guest to start, implied by --attach"
)]
wait: bool,
#[arg(short = 'k', long, help = "OCI kernel image for guest to use")]
kernel: Option<String>,
#[arg(short = 'I', long, help = "OCI initrd image for guest to use")]
initrd: Option<String>,
#[arg(help = "Container image for guest to use")]
oci: String,
#[arg(
Expand All @@ -80,27 +84,41 @@ impl LauchCommand {
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let response = client
.pull_image(PullImageRequest {
image: self.oci.clone(),
format: match self.image_format {
LaunchImageFormat::Squashfs => OciImageFormat::Squashfs.into(),
LaunchImageFormat::Erofs => OciImageFormat::Erofs.into(),
let image = self
.pull_image(
&mut client,
&self.oci,
match self.image_format {
LaunchImageFormat::Squashfs => OciImageFormat::Squashfs,
LaunchImageFormat::Erofs => OciImageFormat::Erofs,
},
overwrite_cache: self.pull_overwrite_cache,
})
)
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;

let kernel = if let Some(ref kernel) = self.kernel {
let kernel_image = self
.pull_image(&mut client, kernel, OciImageFormat::Tar)
.await?;
Some(kernel_image)
} else {
None
};

let initrd = if let Some(ref initrd) = self.initrd {
let kernel_image = self
.pull_image(&mut client, initrd, OciImageFormat::Tar)
.await?;
Some(kernel_image)
} else {
None
};

let request = CreateGuestRequest {
spec: Some(GuestSpec {
name: self.name.unwrap_or_default(),
image: Some(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec {
digest: reply.digest,
format: reply.format,
})),
}),
image: Some(image),
kernel,
initrd,
vcpus: self.cpus,
mem: self.mem,
task: Some(GuestTaskSpec {
Expand Down Expand Up @@ -146,6 +164,28 @@ impl LauchCommand {
StdioConsoleStream::restore_terminal_mode();
std::process::exit(code.unwrap_or(0));
}

async fn pull_image(
&self,
client: &mut ControlServiceClient<Channel>,
image: &str,
format: OciImageFormat,
) -> Result<GuestImageSpec> {
let response = client
.pull_image(PullImageRequest {
image: image.to_string(),
format: format.into(),
overwrite_cache: self.pull_overwrite_cache,
})
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;
Ok(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec {
digest: reply.digest,
format: reply.format,
})),
})
}
}

async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
Expand Down
1 change: 1 addition & 0 deletions crates/daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ scopeguard = { workspace = true }
signal-hook = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
krata-tokio-tar = { workspace = true }
tonic = { workspace = true, features = ["tls"] }
uuid = { workspace = true }

Expand Down
2 changes: 1 addition & 1 deletion crates/daemon/src/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ impl ControlService for DaemonControlService {
format: match packed.format {
OciPackedFormat::Squashfs => OciImageFormat::Squashfs.into(),
OciPackedFormat::Erofs => OciImageFormat::Erofs.into(),
_ => OciImageFormat::Unknown.into(),
OciPackedFormat::Tar => OciImageFormat::Tar.into(),
},
};
yield reply;
Expand Down
23 changes: 20 additions & 3 deletions crates/daemon/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::Result;
use anyhow::{anyhow, Result};
use console::{DaemonConsole, DaemonConsoleHandle};
use control::DaemonControlService;
use db::GuestStore;
Expand Down Expand Up @@ -74,9 +74,11 @@ impl Daemon {
generated
};

let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
let initrd_path = detect_guest_file(&store, "initrd")?;
let kernel_path = detect_guest_file(&store, "kernel")?;

let runtime = Runtime::new(store.clone()).await?;
let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
let runtime = Runtime::new().await?;
let glt = GuestLookupTable::new(0, host_uuid);
let guests_db_path = format!("{}/guests.db", store);
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
Expand All @@ -97,6 +99,8 @@ impl Daemon {
runtime_for_reconciler,
packer.clone(),
guest_reconciler_notify.clone(),
kernel_path,
initrd_path,
)?;

let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
Expand Down Expand Up @@ -181,3 +185,16 @@ impl Drop for Daemon {
self.generator_task.abort();
}
}

fn detect_guest_file(store: &str, name: &str) -> Result<PathBuf> {
let mut path = PathBuf::from(format!("{}/guest/{}", store, name));
if path.is_file() {
return Ok(path);
}

path = PathBuf::from(format!("/usr/share/krata/guest/{}", name));
if path.is_file() {
return Ok(path);
}
Err(anyhow!("unable to find required guest file: {}", name))
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use std::{
collections::{hash_map::Entry, HashMap},
path::PathBuf,
sync::Arc,
time::Duration,
};

use anyhow::{anyhow, Result};
use krata::launchcfg::LaunchPackedFormat;
use anyhow::Result;
use krata::v1::{
common::{
guest_image_spec::Image, Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState,
GuestState, GuestStatus, OciImageFormat,
},
common::{Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState, GuestState, GuestStatus},
control::GuestChangedEvent,
};
use krataoci::packer::{service::OciPackerService, OciPackedFormat};
use kratart::{launch::GuestLaunchRequest, GuestInfo, Runtime};
use krataoci::packer::service::OciPackerService;
use kratart::{GuestInfo, Runtime};
use log::{error, info, trace, warn};
use tokio::{
select,
Expand All @@ -33,6 +30,10 @@ use crate::{
glt::GuestLookupTable,
};

use self::start::GuestStarter;

mod start;

const PARALLEL_LIMIT: u32 = 5;

#[derive(Debug)]
Expand All @@ -59,26 +60,33 @@ pub struct GuestReconciler {
events: DaemonEventContext,
runtime: Runtime,
packer: OciPackerService,
kernel_path: PathBuf,
initrd_path: PathBuf,
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
guest_reconciler_notify: Sender<Uuid>,
reconcile_lock: Arc<RwLock<()>>,
}

impl GuestReconciler {
#[allow(clippy::too_many_arguments)]
pub fn new(
glt: GuestLookupTable,
guests: GuestStore,
events: DaemonEventContext,
runtime: Runtime,
packer: OciPackerService,
guest_reconciler_notify: Sender<Uuid>,
kernel_path: PathBuf,
initrd_path: PathBuf,
) -> Result<Self> {
Ok(Self {
glt,
guests,
events,
runtime,
packer,
kernel_path,
initrd_path,
tasks: Arc::new(Mutex::new(HashMap::new())),
guest_reconciler_notify,
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
Expand Down Expand Up @@ -246,76 +254,14 @@ impl GuestReconciler {
}

async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
let Some(ref spec) = guest.spec else {
return Err(anyhow!("guest spec not specified"));
};

let Some(ref image) = spec.image else {
return Err(anyhow!("image spec not provided"));
let starter = GuestStarter {
kernel_path: &self.kernel_path,
initrd_path: &self.initrd_path,
packer: &self.packer,
glt: &self.glt,
runtime: &self.runtime,
};
let oci = match image.image {
Some(Image::Oci(ref oci)) => oci,
None => {
return Err(anyhow!("oci spec not specified"));
}
};
let task = spec.task.as_ref().cloned().unwrap_or_default();

let image = self
.packer
.recall(
&oci.digest,
match oci.format() {
OciImageFormat::Unknown => OciPackedFormat::Squashfs,
OciImageFormat::Squashfs => OciPackedFormat::Squashfs,
OciImageFormat::Erofs => OciPackedFormat::Erofs,
OciImageFormat::Tar => {
return Err(anyhow!("tar image format is not supported for guests"));
}
},
)
.await?;

let Some(image) = image else {
return Err(anyhow!(
"image {} in the requested format did not exist",
oci.digest
));
};

let info = self
.runtime
.launch(GuestLaunchRequest {
format: LaunchPackedFormat::Squashfs,
uuid: Some(uuid),
name: if spec.name.is_empty() {
None
} else {
Some(spec.name.clone())
},
image,
vcpus: spec.vcpus,
mem: spec.mem,
env: task
.environment
.iter()
.map(|x| (x.key.clone(), x.value.clone()))
.collect::<HashMap<_, _>>(),
run: empty_vec_optional(task.command.clone()),
debug: false,
})
.await?;
self.glt.associate(uuid, info.domid).await;
info!("started guest {}", uuid);
guest.state = Some(GuestState {
status: GuestStatus::Started.into(),
network: Some(guestinfo_to_networkstate(&info)),
exit_info: None,
error_info: None,
host: self.glt.host_uuid().to_string(),
domid: info.domid,
});
Ok(GuestReconcilerResult::Changed { rerun: false })
starter.start(uuid, guest).await
}

async fn exited(&self, guest: &mut Guest) -> Result<GuestReconcilerResult> {
Expand Down Expand Up @@ -390,15 +336,7 @@ impl GuestReconciler {
}
}

fn empty_vec_optional<T>(value: Vec<T>) -> Option<Vec<T>> {
if value.is_empty() {
None
} else {
Some(value)
}
}

fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
pub fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
GuestNetworkState {
guest_ipv4: info.guest_ipv4.map(|x| x.to_string()).unwrap_or_default(),
guest_ipv6: info.guest_ipv6.map(|x| x.to_string()).unwrap_or_default(),
Expand Down
Loading

0 comments on commit 82576df

Please sign in to comment.