Skip to content

Commit 585c7d3

Browse files
committed
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 <[email protected]>
1 parent 0487bb9 commit 585c7d3

File tree

2 files changed

+115
-13
lines changed

2 files changed

+115
-13
lines changed

lib/src/cli.rs

+46-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use ostree_ext::ostree;
2424
use schemars::schema_for;
2525
use serde::{Deserialize, Serialize};
2626

27-
use crate::deploy::RequiredHostSpec;
27+
use crate::deploy::{RequiredHostSpec, StageOptions};
2828
use crate::lints;
2929
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
3030
use crate::spec::Host;
@@ -77,6 +77,12 @@ pub(crate) struct UpgradeOpts {
7777
#[clap(long, conflicts_with = "check")]
7878
pub(crate) apply: bool,
7979

80+
/// Load the new deployment into kexec.
81+
///
82+
/// Next time the system is rebooted, it will kexec instead of doing a full reboot
83+
#[clap(long, conflicts_with = "check")]
84+
pub(crate) kexec: bool,
85+
8086
#[clap(flatten)]
8187
pub(crate) progress: ProgressOptions,
8288
}
@@ -96,6 +102,12 @@ pub(crate) struct SwitchOpts {
96102
#[clap(long)]
97103
pub(crate) apply: bool,
98104

105+
/// Load the new deployment into kexec.
106+
///
107+
/// Next time the system is rebooted, it will kexec instead of doing a full reboot
108+
#[clap(long)]
109+
pub(crate) kexec: bool,
110+
99111
/// The transport; e.g. oci, oci-archive, containers-storage. Defaults to `registry`.
100112
#[clap(long, default_value = "registry")]
101113
pub(crate) transport: String,
@@ -792,7 +804,18 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
792804
println!("No update available.")
793805
} else {
794806
let osname = booted_deployment.osname();
795-
crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?;
807+
crate::deploy::stage(
808+
sysroot,
809+
&osname,
810+
&fetched,
811+
&spec,
812+
prog.clone(),
813+
StageOptions {
814+
deploy_kexec: opts.kexec,
815+
..Default::default()
816+
},
817+
)
818+
.await?;
796819
changed = true;
797820
if let Some(prev) = booted_image.as_ref() {
798821
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
@@ -877,7 +900,18 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
877900
}
878901

879902
let stateroot = booted_deployment.osname();
880-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, prog.clone()).await?;
903+
crate::deploy::stage(
904+
sysroot,
905+
&stateroot,
906+
&fetched,
907+
&new_spec,
908+
prog.clone(),
909+
StageOptions {
910+
deploy_kexec: opts.kexec,
911+
..Default::default()
912+
},
913+
)
914+
.await?;
881915

882916
sysroot.update_mtime()?;
883917

@@ -934,7 +968,15 @@ async fn edit(opts: EditOpts) -> Result<()> {
934968
// TODO gc old layers here
935969

936970
let stateroot = booted_deployment.osname();
937-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, prog.clone()).await?;
971+
crate::deploy::stage(
972+
sysroot,
973+
&stateroot,
974+
&fetched,
975+
&new_spec,
976+
prog.clone(),
977+
Default::default(),
978+
)
979+
.await?;
938980

939981
sysroot.update_mtime()?;
940982

lib/src/deploy.rs

+69-9
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,27 @@ pub(crate) fn get_base_commit(repo: &ostree::Repo, commit: &str) -> Result<Optio
478478
Ok(r)
479479
}
480480

481+
#[context("Loading deployment into kexec")]
482+
async fn kexec_load(sysroot: &Storage, deployment: &Deployment) -> Result<()> {
483+
// Clone all the things to move to worker thread
484+
let sysroot = sysroot.sysroot.clone();
485+
// ostree::Deployment is incorrently !Send 😢 so convert it to an integer
486+
let deployment_index = deployment.index() as usize;
487+
488+
async_task_with_spinner(
489+
"Deploying",
490+
spawn_blocking_cancellable_flatten(move |cancellable| -> Result<()> {
491+
let deployments = sysroot.deployments();
492+
let deployment = &deployments[deployment_index];
493+
494+
sysroot.deployment_kexec_load(&deployment, Some(cancellable))?;
495+
Ok(())
496+
}),
497+
)
498+
.await?;
499+
Ok(())
500+
}
501+
481502
#[context("Writing deployment")]
482503
async fn deploy(
483504
sysroot: &Storage,
@@ -552,6 +573,12 @@ fn origin_from_imageref(imgref: &ImageReference) -> Result<glib::KeyFile> {
552573
Ok(origin)
553574
}
554575

576+
#[derive(Debug, Clone, Default)]
577+
#[non_exhaustive]
578+
pub(crate) struct StageOptions {
579+
pub(crate) deploy_kexec: bool,
580+
}
581+
555582
/// Stage (queue deployment of) a fetched container image.
556583
#[context("Staging")]
557584
pub(crate) async fn stage(
@@ -560,7 +587,11 @@ pub(crate) async fn stage(
560587
image: &ImageState,
561588
spec: &RequiredHostSpec<'_>,
562589
prog: ProgressWriter,
590+
StageOptions { deploy_kexec }: StageOptions,
563591
) -> Result<()> {
592+
let steps_total = 4 + (deploy_kexec as u64);
593+
let mut steps = 0;
594+
564595
let mut subtask = SubTaskStep {
565596
subtask: "merging".into(),
566597
description: "Merging Image".into(),
@@ -574,7 +605,7 @@ pub(crate) async fn stage(
574605
id: image.manifest_digest.clone().as_ref().into(),
575606
steps_cached: 0,
576607
steps: 0,
577-
steps_total: 3,
608+
steps_total,
578609
subtasks: subtasks
579610
.clone()
580611
.into_iter()
@@ -590,13 +621,14 @@ pub(crate) async fn stage(
590621
subtask.id = "deploying".into();
591622
subtask.description = "Deploying Image".into();
592623
subtask.completed = false;
624+
steps += 1;
593625
prog.send(Event::ProgressSteps {
594626
task: "staging".into(),
595627
description: "Deploying Image".into(),
596628
id: image.manifest_digest.clone().as_ref().into(),
597629
steps_cached: 0,
598-
steps: 1,
599-
steps_total: 3,
630+
steps,
631+
steps_total,
600632
subtasks: subtasks
601633
.clone()
602634
.into_iter()
@@ -620,13 +652,14 @@ pub(crate) async fn stage(
620652
subtask.id = "bound_images".into();
621653
subtask.description = "Pulling Bound Images".into();
622654
subtask.completed = false;
655+
steps += 1;
623656
prog.send(Event::ProgressSteps {
624657
task: "staging".into(),
625658
description: "Deploying Image".into(),
626659
id: image.manifest_digest.clone().as_ref().into(),
627660
steps_cached: 0,
628-
steps: 1,
629-
steps_total: 3,
661+
steps,
662+
steps_total,
630663
subtasks: subtasks
631664
.clone()
632665
.into_iter()
@@ -642,13 +675,14 @@ pub(crate) async fn stage(
642675
subtask.id = "cleanup".into();
643676
subtask.description = "Removing old images".into();
644677
subtask.completed = false;
678+
steps += 1;
645679
prog.send(Event::ProgressSteps {
646680
task: "staging".into(),
647681
description: "Deploying Image".into(),
648682
id: image.manifest_digest.clone().as_ref().into(),
649683
steps_cached: 0,
650-
steps: 2,
651-
steps_total: 3,
684+
steps,
685+
steps_total,
652686
subtasks: subtasks
653687
.clone()
654688
.into_iter()
@@ -663,15 +697,41 @@ pub(crate) async fn stage(
663697
}
664698
println!(" Digest: {}", image.manifest_digest);
665699

700+
if deploy_kexec {
701+
subtask.completed = true;
702+
subtasks.push(subtask.clone());
703+
subtask.subtask = "kexec".into();
704+
subtask.id = "kexec".into();
705+
subtask.description = "Loading image into kexec".into();
706+
subtask.completed = false;
707+
steps += 1;
708+
prog.send(Event::ProgressSteps {
709+
task: "staging".into(),
710+
description: "Deploying Image".into(),
711+
id: image.manifest_digest.clone().as_ref().into(),
712+
steps_cached: 0,
713+
steps,
714+
steps_total,
715+
subtasks: subtasks
716+
.clone()
717+
.into_iter()
718+
.chain([subtask.clone()])
719+
.collect(),
720+
})
721+
.await;
722+
crate::deploy::kexec_load(sysroot, &deployment).await?;
723+
}
724+
666725
subtask.completed = true;
667726
subtasks.push(subtask.clone());
727+
steps += 1;
668728
prog.send(Event::ProgressSteps {
669729
task: "staging".into(),
670730
description: "Deploying Image".into(),
671731
id: image.manifest_digest.clone().as_ref().into(),
672732
steps_cached: 0,
673-
steps: 3,
674-
steps_total: 3,
733+
steps,
734+
steps_total,
675735
subtasks: subtasks
676736
.clone()
677737
.into_iter()

0 commit comments

Comments
 (0)