diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0081dfa11..cd695e328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,14 +152,26 @@ jobs: strategy: fail-fast: false matrix: - # No fedora-44 due to https://bugzilla.redhat.com/show_bug.cgi?id=2429501 - test_os: [fedora-42, fedora-43, centos-9, centos-10] - variant: [ostree, composefs-sealeduki-sdboot] + test_os: [fedora-42] + variant: [ostree] + gating: [true] exclude: # centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812) - test_os: centos-9 variant: composefs-sealeduki-sdboot - + - test_os: fedora-44 + gating: true + # include: + # # fedora-44 non-gating due to grub2 regression + # # https://bugzilla.redhat.com/show_bug.cgi?id=2429501 + # - test_os: fedora-44 + # gating: false + # variant: ostree + # - test_os: fedora-44 + # gating: false + # variant: composefs-sealeduki-sdboot + # Non-gating jobs are allowed to fail without blocking the PR + continue-on-error: ${{ !matrix.gating }} runs-on: ubuntu-24.04 steps: @@ -200,11 +212,12 @@ jobs: - name: Run TMT integration tests run: | - if [ "${{ matrix.variant }}" = "composefs-sealeduki-sdboot" ]; then - just test-composefs + if [ "${{ matrix.variant }}" = "composefs" ]; then + just test-tmt else just test-tmt integration fi + just clean-local-images - name: Archive TMT logs diff --git a/Justfile b/Justfile index 09e84c263..f362ce27b 100644 --- a/Justfile +++ b/Justfile @@ -124,7 +124,7 @@ package: # Build+test using the `composefs-sealeduki-sdboot` variant. test-composefs: - just variant=composefs-sealeduki-sdboot test-tmt readonly local-upgrade-reboot + just variant=composefs-sealeduki-sdboot test-tmt readonly soft-reboot # Only used by ci.yml right now build-install-test-image: build @@ -162,6 +162,7 @@ _build-upgrade-image: # Assume the localhost/bootc image is up to date, and just run tests. # Useful for iterating on tests quickly. test-tmt-nobuild *ARGS: + echo {{ARGS}} cargo xtask run-tmt --env=BOOTC_variant={{variant}} --upgrade-image={{upgrade_img}} {{base_img}} {{ARGS}} # Build test container image for testing on coreos with SKIP_CONFIGS=1, diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 1a295d071..801a019bd 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -1277,7 +1277,7 @@ pub(crate) async fn setup_composefs_boot( &root_setup.physical_root_path, &id, &crate::spec::ImageReference::from(state.target_imgref.clone()), - false, + None, boot_type, boot_digest, &get_container_manifest_and_config(&get_imgref( diff --git a/crates/lib/src/bootc_composefs/export.rs b/crates/lib/src/bootc_composefs/export.rs index b8abd4b4c..f86392421 100644 --- a/crates/lib/src/bootc_composefs/export.rs +++ b/crates/lib/src/bootc_composefs/export.rs @@ -50,7 +50,8 @@ pub async fn export_repo_to_image( let imginfo = get_imginfo(storage, &depl_verity, None).await?; - let config_digest = imginfo.manifest.config().digest().digest(); + // We want the digest in the form of "sha256:abc123" + let config_digest = format!("{}", imginfo.manifest.config().digest()); let var_tmp = Dir::open_ambient_dir("/var/tmp", ambient_authority()).context("Opening /var/tmp")?; @@ -60,7 +61,7 @@ pub async fn export_repo_to_image( // Use composefs_oci::open_config to get the config and layer map let (config, layer_map) = - open_config(&*booted_cfs.repo, config_digest, None).context("Opening config")?; + open_config(&*booted_cfs.repo, &config_digest, None).context("Opening config")?; // We can't guarantee that we'll get the same tar stream as the container image // So we create new config and manifest diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index 027ffb5ee..68c302033 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -52,6 +52,11 @@ pub(crate) async fn composefs_backend_finalize( return Ok(()); }; + if staged_depl.download_only { + tracing::debug!("Staged deployment is marked download only. Won't finalize"); + return Ok(()); + } + let staged_composefs = staged_depl.composefs.as_ref().ok_or(anyhow::anyhow!( "Staged deployment is not a composefs deployment" ))?; diff --git a/crates/lib/src/bootc_composefs/soft_reboot.rs b/crates/lib/src/bootc_composefs/soft_reboot.rs index 3e4509da1..9ec3d6157 100644 --- a/crates/lib/src/bootc_composefs/soft_reboot.rs +++ b/crates/lib/src/bootc_composefs/soft_reboot.rs @@ -2,6 +2,7 @@ use crate::{ bootc_composefs::{ service::start_finalize_stated_svc, status::composefs_deployment_status_from, }, + cli::SoftRebootMode, composefs_consts::COMPOSEFS_CMDLINE, store::{BootedComposefs, Storage}, }; @@ -10,25 +11,66 @@ use bootc_initramfs_setup::setup_root; use bootc_kernel_cmdline::utf8::Cmdline; use bootc_mount::{PID1, bind_mount_from_pidns}; use camino::Utf8Path; +use cap_std_ext::cap_std::ambient_authority; +use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::dirext::CapStdExtDirExt; use fn_error_context::context; use ostree_ext::systemd_has_soft_reboot; +use rustix::mount::{UnmountFlags, unmount}; use std::{fs::create_dir_all, os::unix::process::CommandExt, path::PathBuf, process::Command}; const NEXTROOT: &str = "/run/nextroot"; +#[context("Resetting soft reboot state")] +pub(crate) fn reset_soft_reboot() -> Result<()> { + let run = Utf8Path::new("/run"); + bind_mount_from_pidns(PID1, &run, &run, true).context("Bind mounting /run")?; + + let run_dir = Dir::open_ambient_dir("/run", ambient_authority()).context("Opening run")?; + + let nextroot = run_dir + .open_dir_optional("nextroot") + .context("Opening nextroot")?; + + let Some(nextroot) = nextroot else { + tracing::debug!("Nextroot is not a directory"); + println!("No deployment staged for soft rebooting"); + return Ok(()); + }; + + let nextroot_mounted = nextroot + .is_mountpoint(".")? + .ok_or_else(|| anyhow::anyhow!("Failed to get mount info"))?; + + if !nextroot_mounted { + tracing::debug!("Nextroot is not a mountpoint"); + println!("No deployment staged for soft rebooting"); + return Ok(()); + } + + unmount(NEXTROOT, UnmountFlags::DETACH).context("Unmounting nextroot")?; + + println!("Soft reboot state cleared successfully"); + + Ok(()) +} + /// Checks if the provided deployment is soft reboot capable, and soft reboots the system if /// argument `reboot` is true #[context("Soft rebooting")] pub(crate) async fn prepare_soft_reboot_composefs( storage: &Storage, booted_cfs: &BootedComposefs, - deployment_id: &String, + deployment_id: Option<&String>, + soft_reboot_mode: SoftRebootMode, reboot: bool, ) -> Result<()> { if !systemd_has_soft_reboot() { anyhow::bail!("System does not support soft reboots") } + let deployment_id = deployment_id.ok_or_else(|| anyhow::anyhow!("Expected deployment id"))?; + if *deployment_id == *booted_cfs.cmdline.digest { anyhow::bail!("Cannot soft-reboot to currently booted deployment"); } @@ -44,7 +86,13 @@ pub(crate) async fn prepare_soft_reboot_composefs( .ok_or_else(|| anyhow::anyhow!("Deployment '{deployment_id}' not found"))?; if !requred_deployment.soft_reboot_capable { - anyhow::bail!("Cannot soft-reboot to deployment with a different kernel state"); + match soft_reboot_mode { + SoftRebootMode::Required => { + anyhow::bail!("Cannot soft-reboot to deployment with a different kernel state") + } + + SoftRebootMode::Auto => return Ok(()), + } } start_finalize_stated_svc()?; diff --git a/crates/lib/src/bootc_composefs/state.rs b/crates/lib/src/bootc_composefs/state.rs index 0dc20f8be..517281be0 100644 --- a/crates/lib/src/bootc_composefs/state.rs +++ b/crates/lib/src/bootc_composefs/state.rs @@ -9,6 +9,7 @@ use bootc_kernel_cmdline::utf8::Cmdline; use bootc_mount::tempmount::TempMount; use bootc_utils::CommandRunExt; use camino::Utf8PathBuf; +use canon_json::CanonJsonSerialize; use cap_std_ext::cap_std::ambient_authority; use cap_std_ext::cap_std::fs::{Dir, Permissions, PermissionsExt}; use cap_std_ext::dirext::CapStdExtDirExt; @@ -23,7 +24,9 @@ use rustix::{ use crate::bootc_composefs::boot::BootType; use crate::bootc_composefs::repo::get_imgref; -use crate::bootc_composefs::status::{ImgConfigManifest, get_sorted_type1_boot_entries}; +use crate::bootc_composefs::status::{ + ImgConfigManifest, StagedDeployment, get_sorted_type1_boot_entries, +}; use crate::parsers::bls_config::BLSConfigType; use crate::store::{BootedComposefs, Storage}; use crate::{ @@ -227,7 +230,7 @@ pub(crate) async fn write_composefs_state( root_path: &Utf8PathBuf, deployment_id: &Sha512HashValue, target_imgref: &ImageReference, - staged: bool, + staged: Option, boot_type: BootType, boot_digest: String, container_details: &ImgConfigManifest, @@ -248,7 +251,12 @@ pub(crate) async fn write_composefs_state( ) .context("Failed to create symlink for /var")?; - initialize_state(&root_path, &deployment_id.to_hex(), &state_path, !staged)?; + initialize_state( + &root_path, + &deployment_id.to_hex(), + &state_path, + staged.is_none(), + )?; let ImageReference { image: image_name, @@ -291,7 +299,7 @@ pub(crate) async fn write_composefs_state( ) .context("Failed to write to .origin file")?; - if staged { + if let Some(staged) = staged { std::fs::create_dir_all(COMPOSEFS_TRANSIENT_STATE_DIR) .with_context(|| format!("Creating {COMPOSEFS_TRANSIENT_STATE_DIR}"))?; @@ -302,7 +310,9 @@ pub(crate) async fn write_composefs_state( staged_depl_dir .atomic_write( COMPOSEFS_STAGED_DEPLOYMENT_FNAME, - deployment_id.to_hex().as_bytes(), + staged + .to_canon_json_vec() + .context("Failed to serialize staged deployment JSON")?, ) .with_context(|| format!("Writing to {COMPOSEFS_STAGED_DEPLOYMENT_FNAME}"))?; } diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 386e2470a..50a277823 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -79,6 +79,12 @@ impl std::fmt::Display for ComposefsCmdline { } } +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct StagedDeployment { + pub(crate) depl_id: String, + pub(crate) finalization_locked: bool, +} + /// Detect if we have composefs= in /proc/cmdline pub(crate) fn composefs_booted() -> Result> { static CACHED_DIGEST_VALUE: OnceLock> = OnceLock::new(); @@ -448,8 +454,10 @@ fn set_reboot_capable_type1_deployments( .chain(host.status.rollback.iter_mut()) .chain(host.status.other_deployments.iter_mut()) { - let entry = find_bls_entry(&depl.require_composefs()?.verity, &bls_entries)? - .ok_or_else(|| anyhow::anyhow!("Entry not found"))?; + let v = &depl.require_composefs()?.verity; + + let entry = find_bls_entry(v, &bls_entries)? + .ok_or_else(|| anyhow::anyhow!("Entry {v} not found"))?; let depl_cmdline = entry.get_cmdline()?; @@ -554,7 +562,7 @@ pub(crate) async fn composefs_deployment_status_from( let mut host = Host::new(host_spec); - let staged_deployment_id = match std::fs::File::open(format!( + let staged_deployment = match std::fs::File::open(format!( "{COMPOSEFS_TRANSIENT_STATE_DIR}/{COMPOSEFS_STAGED_DEPLOYMENT_FNAME}" )) { Ok(mut f) => { @@ -590,7 +598,7 @@ pub(crate) async fn composefs_deployment_status_from( let ini = tini::Ini::from_string(&config) .with_context(|| format!("Failed to parse file {depl_file_name}.origin as ini"))?; - let boot_entry = + let mut boot_entry = boot_entry_from_composefs_deployment(storage, ini, depl_file_name.to_string()).await?; // SAFETY: boot_entry.composefs will always be present @@ -614,8 +622,11 @@ pub(crate) async fn composefs_deployment_status_from( continue; } - if let Some(staged_deployment_id) = &staged_deployment_id { - if depl_file_name == staged_deployment_id.trim() { + if let Some(staged_deployment) = &staged_deployment { + let staged_depl = serde_json::from_str::(&staged_deployment)?; + + if depl_file_name == staged_depl.depl_id { + boot_entry.download_only = staged_depl.finalization_locked; host.status.staged = Some(boot_entry); continue; } diff --git a/crates/lib/src/bootc_composefs/switch.rs b/crates/lib/src/bootc_composefs/switch.rs index 4f12b4790..944e166c3 100644 --- a/crates/lib/src/bootc_composefs/switch.rs +++ b/crates/lib/src/bootc_composefs/switch.rs @@ -45,6 +45,7 @@ pub(crate) async fn switch_composefs( let do_upgrade_opts = DoUpgradeOpts { soft_reboot: opts.soft_reboot, apply: opts.apply, + download_only: false, }; if let Some(cfg_verity) = image { diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index f6252a65f..8b74f4c03 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -1,10 +1,14 @@ +use std::io::Write; + use anyhow::{Context, Result}; use camino::Utf8PathBuf; -use cap_std_ext::cap_std::fs::Dir; +use canon_json::CanonJsonSerialize; +use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt}; use composefs::fsverity::{FsVerityHashValue, Sha512HashValue}; use composefs_boot::BootOps; use composefs_oci::image::create_filesystem; use fn_error_context::context; +use ocidir::cap_std::ambient_authority; use ostree_ext::container::ManifestDiff; use crate::{ @@ -15,12 +19,15 @@ use crate::{ soft_reboot::prepare_soft_reboot_composefs, state::write_composefs_state, status::{ - ImgConfigManifest, get_bootloader, get_composefs_status, + ImgConfigManifest, StagedDeployment, get_bootloader, get_composefs_status, get_container_manifest_and_config, get_imginfo, }, }, cli::{SoftRebootMode, UpgradeOpts}, - composefs_consts::{STATE_DIR_RELATIVE, TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED}, + composefs_consts::{ + COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, STATE_DIR_RELATIVE, + TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED, + }, spec::{Bootloader, Host, ImageReference}, store::{BootedComposefs, ComposefsRepository, Storage}, }; @@ -206,6 +213,31 @@ pub(crate) fn validate_update( pub(crate) struct DoUpgradeOpts { pub(crate) apply: bool, pub(crate) soft_reboot: Option, + pub(crate) download_only: bool, +} + +async fn apply_upgrade( + storage: &Storage, + booted_cfs: &BootedComposefs, + depl_id: &String, + opts: &DoUpgradeOpts, +) -> Result<()> { + if let Some(soft_reboot_mode) = opts.soft_reboot { + return prepare_soft_reboot_composefs( + storage, + booted_cfs, + Some(depl_id), + soft_reboot_mode, + opts.apply, + ) + .await; + }; + + if opts.apply { + return crate::reboot::reboot(); + } + + Ok(()) } /// Performs the Update or Switch operation @@ -255,22 +287,17 @@ pub(crate) async fn do_upgrade( &Utf8PathBuf::from("/sysroot"), &id, imgref, - true, + Some(StagedDeployment { + depl_id: id.to_hex(), + finalization_locked: opts.download_only, + }), boot_type, boot_digest, img_manifest_config, ) .await?; - if opts.apply { - return crate::reboot::reboot(); - } - - if opts.soft_reboot.is_some() { - prepare_soft_reboot_composefs(storage, booted_cfs, &id.to_hex(), true).await?; - } - - Ok(()) + apply_upgrade(storage, booted_cfs, &id.to_hex(), opts).await } #[context("Upgrading composefs")] @@ -279,18 +306,60 @@ pub(crate) async fn upgrade_composefs( storage: &Storage, composefs: &BootedComposefs, ) -> Result<()> { - // Download-only mode is not yet supported for composefs backend - if opts.download_only { - anyhow::bail!("--download-only is not yet supported for composefs backend"); - } - if opts.from_downloaded { - anyhow::bail!("--from-downloaded is not yet supported for composefs backend"); - } - let host = get_composefs_status(storage, composefs) .await .context("Getting composefs deployment status")?; + let do_upgrade_opts = DoUpgradeOpts { + soft_reboot: opts.soft_reboot, + apply: opts.apply, + download_only: opts.download_only, + }; + + if opts.from_downloaded { + let staged = host + .status + .staged + .as_ref() + .ok_or_else(|| anyhow::anyhow!("No staged deployment found"))?; + + // Staged deployment exists, but it will be finalized + if !staged.download_only { + println!("Staged deployment is present and not in download only mode."); + println!("Use `bootc update --apply` to apply the update."); + return Ok(()); + } + + start_finalize_stated_svc()?; + + // Make the staged deployment not download_only + let new_staged = StagedDeployment { + depl_id: staged.require_composefs()?.verity.clone(), + finalization_locked: false, + }; + + let staged_depl_dir = + Dir::open_ambient_dir(COMPOSEFS_TRANSIENT_STATE_DIR, ambient_authority()) + .context("Opening transient state directory")?; + + staged_depl_dir + .atomic_replace_with( + COMPOSEFS_STAGED_DEPLOYMENT_FNAME, + |f| -> std::io::Result<()> { + f.write_all(new_staged.to_canon_json_string()?.as_bytes()) + }, + ) + .context("Writing staged file")?; + + return apply_upgrade( + storage, + composefs, + &staged.require_composefs()?.verity, + &do_upgrade_opts, + ) + .await; + } + let mut booted_imgref = host .spec .image @@ -306,11 +375,6 @@ pub(crate) async fn upgrade_composefs( // Or if we have another staged deployment with a different image let staged_image = host.status.staged.as_ref().and_then(|i| i.image.as_ref()); - let do_upgrade_opts = DoUpgradeOpts { - soft_reboot: opts.soft_reboot, - apply: opts.apply, - }; - if let Some(staged_image) = staged_image { // We have a staged image and it has the same digest as the currently booted image's latest // digest diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index b4887f619..5d26ef4a9 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -34,7 +34,7 @@ use schemars::schema_for; use serde::{Deserialize, Serialize}; use crate::bootc_composefs::delete::delete_composefs_deployment; -use crate::bootc_composefs::soft_reboot::prepare_soft_reboot_composefs; +use crate::bootc_composefs::soft_reboot::{prepare_soft_reboot_composefs, reset_soft_reboot}; use crate::bootc_composefs::{ digest::{compute_composefs_digest, new_temp_composefs_repo}, finalize::{composefs_backend_finalize, get_etc_diff}, @@ -618,9 +618,12 @@ pub(crate) enum InternalsOpts { /// Dump CLI structure as JSON for documentation generation DumpCliJson, PrepSoftReboot { - deployment: String, - #[clap(long)] + #[clap(required_unless_present = "reset")] + deployment: Option, + #[clap(long, conflicts_with = "reset")] reboot: bool, + #[clap(long, conflicts_with = "reboot")] + reset: bool, }, } @@ -1834,7 +1837,11 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Ok(()) } - InternalsOpts::PrepSoftReboot { deployment, reboot } => { + InternalsOpts::PrepSoftReboot { + deployment, + reboot, + reset, + } => { let storage = &get_storage().await?; match storage.kind()? { @@ -1842,9 +1849,20 @@ async fn run_from_opt(opt: Opt) -> Result<()> { // TODO: Call ostree implementation? anyhow::bail!("soft-reboot only implemented for composefs") } + BootedStorageKind::Composefs(booted_cfs) => { - prepare_soft_reboot_composefs(&storage, &booted_cfs, &deployment, reboot) - .await + if reset { + return reset_soft_reboot(); + } + + prepare_soft_reboot_composefs( + &storage, + &booted_cfs, + deployment.as_ref(), + SoftRebootMode::Required, + reboot, + ) + .await } } } diff --git a/crates/lib/src/store/mod.rs b/crates/lib/src/store/mod.rs index b001e7abb..f38edd4bb 100644 --- a/crates/lib/src/store/mod.rs +++ b/crates/lib/src/store/mod.rs @@ -272,7 +272,7 @@ pub(crate) struct Storage { pub esp: Option, /// Our runtime state - run: Dir, + pub run: Dir, /// The OSTree storage ostree: OnceCell, diff --git a/crates/xtask/src/tmt.rs b/crates/xtask/src/tmt.rs index 0dd4634aa..a3ab3ef5c 100644 --- a/crates/xtask/src/tmt.rs +++ b/crates/xtask/src/tmt.rs @@ -29,6 +29,8 @@ const ENV_BOOTC_UPGRADE_IMAGE: &str = "BOOTC_upgrade_image"; // Distro identifiers const DISTRO_CENTOS_9: &str = "centos-9"; +const KARGS: [&str; 2] = ["--karg=enforcing=0", "--karg=console=ttyS0,115000n8"]; + // Import the argument types from xtask.rs use crate::{RunTmtArgs, TmtProvisionArgs}; @@ -280,6 +282,8 @@ fn parse_plan_metadata(plans_file: &Utf8Path) -> Result Result<()> { + println!("RunTmtArgs: {args:#?}"); + // Check dependencies first check_dependencies(sh)?; @@ -437,7 +441,8 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { let firmware_args_slice = firmware_args.as_slice(); let launch_result = cmd!( sh, - "bcvk libvirt run --name {vm_name} --detach {firmware_args_slice...} {COMMON_INST_ARGS...} {plan_bcvk_opts...} {image}" + "bcvk libvirt run --name {vm_name} --detach {firmware_args_slice...} + --filesystem ext4 --composefs-backend {KARGS...} {COMMON_INST_ARGS...} {plan_bcvk_opts...} {image}" ) .run() .context("Launching VM with bcvk"); @@ -686,12 +691,15 @@ pub(crate) fn tmt_provision(sh: &Shell, args: &TmtProvisionArgs) -> Result<()> { let firmware_args = build_firmware_args(sh, image)?; + println!("firmware_args: {firmware_args:#?}"); + // Launch VM with bcvk // Use ds=iid-datasource-none to disable cloud-init for faster boot let firmware_args_slice = firmware_args.as_slice(); + cmd!( sh, - "bcvk libvirt run --name {vm_name} --detach {firmware_args_slice...} {COMMON_INST_ARGS...} {image}" + "bcvk libvirt run --name {vm_name} --filesystem ext4 --composefs-backend {KARGS...} --detach {firmware_args_slice...} {COMMON_INST_ARGS...} {image}" ) .run() .context("Launching VM with bcvk")?; diff --git a/hack/build-sealed b/hack/build-sealed index 22b668312..2b467a631 100755 --- a/hack/build-sealed +++ b/hack/build-sealed @@ -19,7 +19,7 @@ runv() { } case $variant in - ostree) + ostree|composefs) # Nothing to do echo "Not building a sealed image; forwarding tag" runv podman tag $input_image $output_image diff --git a/podman-cmd b/podman-cmd new file mode 100644 index 000000000..277ee0587 --- /dev/null +++ b/podman-cmd @@ -0,0 +1,65 @@ +DEBUG Podman command: + + +podman run +--pull=never +--label=bcvk.ephemeral=1 +--mount=type=tmpfs,target=/run +--rm -t -d +--cap-add=all +--security-opt=label=disable +--security-opt=seccomp=unconfined +--security-opt=unmask=/proc/* +-v /sys:/sys:ro +-v /var/tmp:/var/tmp +--device=/dev/kvm +--device=/dev/vhost-vsock +-v /usr:/run/tmproot/usr:ro +-v /tmp/.tmpebjLzO/entrypoint:/var/lib/bcvk/entrypoint +-v /home/pragyan/.cargo/bin/bcvk:/run/selfexe:ro +--stop-signal=SIGKILL +--mount=type=image,source=localhost/bootc,target=/run/source-image,rw=true +-v /home/pragyan/.local/share/containers/storage:/run/host-mounts/hoststorage:ro +-v /home/pragyan/.local/share/libvirt/images/bootc-base-4b963f65acbfab31.WzUM7g.tmp.qcow2:/run/disk-files/output:rw +--dns 192.168.0.1 +-e BCK_CONFIG={ + "image": "localhost/bootc", + "common": { + "itype": null, + "memory": { + "memory": "4G" + }, + "vcpus": null, + "console": false, + "debug": false, + "virtio_serial_out": [], + "execute": [], + "ssh_keygen": true + }, + "podman": { + "tty": true, + "interactive": false, + "detach": true, + "rm": true, + "name": null, + "network": null, + "label": [], + "env": [] + }, + "debug_entrypoint": null, + "bind_mounts": [], + "ro_bind_mounts": [], + "systemd_units_dir": null, + "bind_storage_ro": true, + "add_swap": "21474836480", + "mount_disk_files": [ + "/home/pragyan/.local/share/libvirt/images/bootc-base-4b963f65acbfab31.WzUM7g.tmp.qcow2:output:qcow2" + ], + "kernel_args": [], + "host_dns_servers": [ + "192.168.0.1" + ] +} +-e BOOTC_DISK_FILES=/run/disk-files/output:output:qcow2 +localhost/bootc +/var/lib/bcvk/entrypoint diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index 365e6618c..9c4c3ec4a 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -125,7 +125,7 @@ execute: test: - /tmt/tests/tests/test-28-factory-reset -/plan-29-soft-reboot-selinux-policy: +/plan-29-soft-selinux-policy: summary: Test soft reboot with SELinux policy changes discover: how: fmf diff --git a/tmt/tests/booted/test-image-pushpull-upgrade.nu b/tmt/tests/booted/test-image-pushpull-upgrade.nu index 39f757b56..dd1a1c0f9 100644 --- a/tmt/tests/booted/test-image-pushpull-upgrade.nu +++ b/tmt/tests/booted/test-image-pushpull-upgrade.nu @@ -54,7 +54,11 @@ RUN echo test content > /usr/share/blah.txt let v = podman run --rm localhost/bootc-derived cat /usr/share/blah.txt | str trim assert equal $v "test content" - let orig_root_mtime = ls -Dl /ostree/bootc | get modified + mut orig_root_mtime = null; + + if not $is_composefs { + $orig_root_mtime = ls -Dl /ostree/bootc | get modified + } # Now, fetch it back into the bootc storage! # We also test the progress API here @@ -68,24 +72,25 @@ RUN echo test content > /usr/share/blah.txt systemd-run -u test-cat-progress -- /bin/bash -c $"exec cat ($progress_fifo) > ($progress_json)" # nushell doesn't do fd passing right now either, so run via bash bash -c $"bootc switch --progress-fd 3 --transport containers-storage localhost/bootc-derived 3>($progress_fifo)" - # Now, let's do some checking of the progress json - let progress = open --raw $progress_json | from json -o - sanity_check_switch_progress_json $progress - # Check that /run/reboot-required exists and is not empty - let rr_meta = (ls /run/reboot-required | first) - assert ($rr_meta.size > 0b) + if not $is_composefs { + # Now, let's do some checking of the progress json + let progress = open --raw $progress_json | from json -o + sanity_check_switch_progress_json $progress - # Verify that we logged to the journal - journalctl _MESSAGE_ID=3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7 + # Check that /run/reboot-required exists and is not empty + let rr_meta = (ls /run/reboot-required | first) + assert ($rr_meta.size > 0b) - # The mtime should change on modification - let new_root_mtime = ls -Dl /ostree/bootc | get modified - assert ($new_root_mtime > $orig_root_mtime) + # Verify that we logged to the journal + journalctl _MESSAGE_ID=3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7 - # Test for https://github.com/ostreedev/ostree/issues/3544 - # Add a quoted karg using rpm-ostree if available - if not $is_composefs { + # The mtime should change on modification + let new_root_mtime = ls -Dl /ostree/bootc | get modified + assert ($new_root_mtime > $orig_root_mtime) + + # Test for https://github.com/ostreedev/ostree/issues/3544 + # Add a quoted karg using rpm-ostree if available # Check rpm-ostree and rpm-ostreed service status before run rpm-ostree # And collect info for flaky error "error: System transaction in progress" rpm-ostree status diff --git a/tmt/tests/booted/test-image-upgrade-reboot.nu b/tmt/tests/booted/test-image-upgrade-reboot.nu index 676605658..d20ee12df 100644 --- a/tmt/tests/booted/test-image-upgrade-reboot.nu +++ b/tmt/tests/booted/test-image-upgrade-reboot.nu @@ -38,10 +38,15 @@ def initial_build [] { tap begin "local image push + pull + upgrade" let imgsrc = imgsrc + + print $"USING IMAGE: ($imgsrc)" + # For the packit case, we build locally right now if ($imgsrc | str ends-with "-local") { bootc image copy-to-storage + print $"After copy-to-storage" + # A simple derived container that adds a file "FROM localhost/bootc RUN touch /usr/share/testing-bootc-upgrade-apply @@ -53,6 +58,7 @@ RUN touch /usr/share/testing-bootc-upgrade-apply # Now, switch into the new image print $"Applying ($imgsrc)" bootc switch --transport containers-storage ($imgsrc) + print $"Switch to ($imgsrc) COMPLETE" tmt-reboot } diff --git a/tmt/tests/booted/test-soft-reboot.nu b/tmt/tests/booted/test-soft-reboot.nu index dd3374e13..8a93988d3 100644 --- a/tmt/tests/booted/test-soft-reboot.nu +++ b/tmt/tests/booted/test-soft-reboot.nu @@ -17,6 +17,9 @@ if not $soft_reboot_capable { # Here we just capture information. bootc status +let st = bootc status --json | from json +let is_composefs = ($st.status.booted.composefs? != null) + # Run on the first boot def initial_build [] { tap begin "local image push + pull + upgrade" @@ -41,8 +44,13 @@ RUN echo test content > /usr/share/testfile-for-soft-reboot.txt assert ("/run/nextroot" | path exists) - # See ../bug-soft-reboot.md - TMT cannot handle systemd soft-reboots - ostree admin prepare-soft-reboot --reset + if not $is_composefs { + # See ../bug-soft-reboot.md - TMT cannot handle systemd soft-reboots + ostree admin prepare-soft-reboot --reset + } else { + bootc internals prep-soft-reboot --reset + } + # https://tmt.readthedocs.io/en/stable/stories/features.html#reboot-during-test tmt-reboot } diff --git a/tmt/tests/booted/test-soft-reboot-selinux-policy.nu b/tmt/tests/booted/test-soft-selinux-policy.nu similarity index 100% rename from tmt/tests/booted/test-soft-reboot-selinux-policy.nu rename to tmt/tests/booted/test-soft-selinux-policy.nu