From 585c7d350690259d2fb1cf9b86a5ea6ca41215c5 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Tue, 4 Feb 2025 12:54:27 -0500 Subject: [PATCH] Add kexec support When --kexec is passed, an upgrade or switch will load the new deployment into the kernel's kexec buffer I don't know how to get a bootc dev environment quickly, so it's very possible this doesn't actually work. Please feel free to take this as a base and make changes :) Signed-off-by: Mary Strodl --- lib/src/cli.rs | 50 +++++++++++++++++++++++++++--- lib/src/deploy.rs | 78 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 15b56bdb..6c9e6a07 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -24,7 +24,7 @@ use ostree_ext::ostree; use schemars::schema_for; use serde::{Deserialize, Serialize}; -use crate::deploy::RequiredHostSpec; +use crate::deploy::{RequiredHostSpec, StageOptions}; use crate::lints; use crate::progress_jsonl::{ProgressWriter, RawProgressFd}; use crate::spec::Host; @@ -77,6 +77,12 @@ pub(crate) struct UpgradeOpts { #[clap(long, conflicts_with = "check")] pub(crate) apply: bool, + /// Load the new deployment into kexec. + /// + /// Next time the system is rebooted, it will kexec instead of doing a full reboot + #[clap(long, conflicts_with = "check")] + pub(crate) kexec: bool, + #[clap(flatten)] pub(crate) progress: ProgressOptions, } @@ -96,6 +102,12 @@ pub(crate) struct SwitchOpts { #[clap(long)] pub(crate) apply: bool, + /// Load the new deployment into kexec. + /// + /// Next time the system is rebooted, it will kexec instead of doing a full reboot + #[clap(long)] + pub(crate) kexec: bool, + /// The transport; e.g. oci, oci-archive, containers-storage. Defaults to `registry`. #[clap(long, default_value = "registry")] pub(crate) transport: String, @@ -792,7 +804,18 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { println!("No update available.") } else { let osname = booted_deployment.osname(); - crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?; + crate::deploy::stage( + sysroot, + &osname, + &fetched, + &spec, + prog.clone(), + StageOptions { + deploy_kexec: opts.kexec, + ..Default::default() + }, + ) + .await?; changed = true; if let Some(prev) = booted_image.as_ref() { if let Some(fetched_manifest) = fetched.get_manifest(repo)? { @@ -877,7 +900,18 @@ async fn switch(opts: SwitchOpts) -> Result<()> { } let stateroot = booted_deployment.osname(); - crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, prog.clone()).await?; + crate::deploy::stage( + sysroot, + &stateroot, + &fetched, + &new_spec, + prog.clone(), + StageOptions { + deploy_kexec: opts.kexec, + ..Default::default() + }, + ) + .await?; sysroot.update_mtime()?; @@ -934,7 +968,15 @@ async fn edit(opts: EditOpts) -> Result<()> { // TODO gc old layers here let stateroot = booted_deployment.osname(); - crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, prog.clone()).await?; + crate::deploy::stage( + sysroot, + &stateroot, + &fetched, + &new_spec, + prog.clone(), + Default::default(), + ) + .await?; sysroot.update_mtime()?; diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index 5d932b8b..64c42e56 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -478,6 +478,27 @@ pub(crate) fn get_base_commit(repo: &ostree::Repo, commit: &str) -> Result Result<()> { + // Clone all the things to move to worker thread + let sysroot = sysroot.sysroot.clone(); + // ostree::Deployment is incorrently !Send 😢 so convert it to an integer + let deployment_index = deployment.index() as usize; + + async_task_with_spinner( + "Deploying", + spawn_blocking_cancellable_flatten(move |cancellable| -> Result<()> { + let deployments = sysroot.deployments(); + let deployment = &deployments[deployment_index]; + + sysroot.deployment_kexec_load(&deployment, Some(cancellable))?; + Ok(()) + }), + ) + .await?; + Ok(()) +} + #[context("Writing deployment")] async fn deploy( sysroot: &Storage, @@ -552,6 +573,12 @@ fn origin_from_imageref(imgref: &ImageReference) -> Result { Ok(origin) } +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub(crate) struct StageOptions { + pub(crate) deploy_kexec: bool, +} + /// Stage (queue deployment of) a fetched container image. #[context("Staging")] pub(crate) async fn stage( @@ -560,7 +587,11 @@ pub(crate) async fn stage( image: &ImageState, spec: &RequiredHostSpec<'_>, prog: ProgressWriter, + StageOptions { deploy_kexec }: StageOptions, ) -> Result<()> { + let steps_total = 4 + (deploy_kexec as u64); + let mut steps = 0; + let mut subtask = SubTaskStep { subtask: "merging".into(), description: "Merging Image".into(), @@ -574,7 +605,7 @@ pub(crate) async fn stage( id: image.manifest_digest.clone().as_ref().into(), steps_cached: 0, steps: 0, - steps_total: 3, + steps_total, subtasks: subtasks .clone() .into_iter() @@ -590,13 +621,14 @@ pub(crate) async fn stage( subtask.id = "deploying".into(); subtask.description = "Deploying Image".into(); subtask.completed = false; + steps += 1; prog.send(Event::ProgressSteps { task: "staging".into(), description: "Deploying Image".into(), id: image.manifest_digest.clone().as_ref().into(), steps_cached: 0, - steps: 1, - steps_total: 3, + steps, + steps_total, subtasks: subtasks .clone() .into_iter() @@ -620,13 +652,14 @@ pub(crate) async fn stage( subtask.id = "bound_images".into(); subtask.description = "Pulling Bound Images".into(); subtask.completed = false; + steps += 1; prog.send(Event::ProgressSteps { task: "staging".into(), description: "Deploying Image".into(), id: image.manifest_digest.clone().as_ref().into(), steps_cached: 0, - steps: 1, - steps_total: 3, + steps, + steps_total, subtasks: subtasks .clone() .into_iter() @@ -642,13 +675,14 @@ pub(crate) async fn stage( subtask.id = "cleanup".into(); subtask.description = "Removing old images".into(); subtask.completed = false; + steps += 1; prog.send(Event::ProgressSteps { task: "staging".into(), description: "Deploying Image".into(), id: image.manifest_digest.clone().as_ref().into(), steps_cached: 0, - steps: 2, - steps_total: 3, + steps, + steps_total, subtasks: subtasks .clone() .into_iter() @@ -663,15 +697,41 @@ pub(crate) async fn stage( } println!(" Digest: {}", image.manifest_digest); + if deploy_kexec { + subtask.completed = true; + subtasks.push(subtask.clone()); + subtask.subtask = "kexec".into(); + subtask.id = "kexec".into(); + subtask.description = "Loading image into kexec".into(); + subtask.completed = false; + steps += 1; + prog.send(Event::ProgressSteps { + task: "staging".into(), + description: "Deploying Image".into(), + id: image.manifest_digest.clone().as_ref().into(), + steps_cached: 0, + steps, + steps_total, + subtasks: subtasks + .clone() + .into_iter() + .chain([subtask.clone()]) + .collect(), + }) + .await; + crate::deploy::kexec_load(sysroot, &deployment).await?; + } + subtask.completed = true; subtasks.push(subtask.clone()); + steps += 1; prog.send(Event::ProgressSteps { task: "staging".into(), description: "Deploying Image".into(), id: image.manifest_digest.clone().as_ref().into(), steps_cached: 0, - steps: 3, - steps_total: 3, + steps, + steps_total, subtasks: subtasks .clone() .into_iter()