Skip to content

Commit

Permalink
Add kexec support
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Mstrodl committed Feb 7, 2025
1 parent 0487bb9 commit 585c7d3
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 13 deletions.
50 changes: 46 additions & 4 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}
Expand All @@ -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,
Expand Down Expand Up @@ -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)? {
Expand Down Expand Up @@ -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()?;

Expand Down Expand Up @@ -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()?;

Expand Down
78 changes: 69 additions & 9 deletions lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,27 @@ pub(crate) fn get_base_commit(repo: &ostree::Repo, commit: &str) -> Result<Optio
Ok(r)
}

#[context("Loading deployment into kexec")]
async fn kexec_load(sysroot: &Storage, deployment: &Deployment) -> 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,
Expand Down Expand Up @@ -552,6 +573,12 @@ fn origin_from_imageref(imgref: &ImageReference) -> Result<glib::KeyFile> {
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(
Expand All @@ -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(),
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down

0 comments on commit 585c7d3

Please sign in to comment.