Skip to content

Commit 1ed0aa2

Browse files
committed
Add an "upgrade from previous" test
We have tests that do upgrades, but they start from the *new* bootc. Add a `just`+CI workflow that starts from a stable shipped image (we just need to inject tmt deps + nu). The readonly test then gains a helper which optionally performs an upgrade to the new target. IOW the flow is - deploy stock image with unmodified bootc etc - upgrade - run readonly tests It could of course make sense to run *all* of the tests this way as an optional thing, nothing blocks that, but it would be *another* entry in our matrix and we're going to need to figure out how to wrangle that matrix size. Also do to that we'd need to abstract over TMT_REBOOT_COUNT. Assisted-by: OpenCode (Claude claude-opus-4-6) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 7063887 commit 1ed0aa2

5 files changed

Lines changed: 132 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,50 @@ jobs:
261261
${{ env.ARCH }}"
262262
path: /var/tmp/tmt
263263

264+
# Test the upgrade path: boot from published base image, upgrade to locally-built image,
265+
# then run readonly tests to verify the upgrade worked.
266+
# Excluded: centos-9 (lacks systemd.extra-unit.* support needed for --bind-storage-ro)
267+
test-upgrade:
268+
needs: package
269+
strategy:
270+
fail-fast: false
271+
matrix:
272+
test_os: [fedora-43, centos-10]
273+
274+
runs-on: ubuntu-24.04
275+
276+
steps:
277+
- uses: actions/checkout@v6
278+
- name: Bootc Ubuntu Setup
279+
uses: bootc-dev/actions/bootc-ubuntu-setup@main
280+
with:
281+
libvirt: true
282+
- name: Install tmt
283+
run: pip install --user "tmt[provision-virtual]"
284+
285+
- name: Setup env
286+
run: |
287+
BASE=$(just pullspec-for-os base ${{ matrix.test_os }})
288+
echo "BOOTC_base=${BASE}" >> $GITHUB_ENV
289+
echo "BOOTC_SKIP_PACKAGE=1" >> $GITHUB_ENV
290+
echo "RUST_BACKTRACE=full" >> $GITHUB_ENV
291+
292+
- name: Download package artifacts
293+
uses: actions/download-artifact@v8
294+
with:
295+
name: packages-${{ matrix.test_os }}
296+
path: target/packages/
297+
298+
- name: Run upgrade test
299+
run: just test-upgrade
300+
301+
- name: Archive TMT logs
302+
if: always()
303+
uses: actions/upload-artifact@v7
304+
with:
305+
name: "tmt-log-PR-${{ github.event.number }}-${{ matrix.test_os }}-upgrade-${{ env.ARCH }}"
306+
path: /var/tmp/tmt
307+
264308
# Test bootc install on Fedora CoreOS (separate job to avoid disk space issues
265309
# when run in the same job as test-integration).
266310
# Uses fedora-43 as it's the current stable Fedora release matching CoreOS.
@@ -341,7 +385,7 @@ jobs:
341385
# Sentinel job for required checks - configure this job name in repository settings
342386
required-checks:
343387
if: always()
344-
needs: [cargo-deny, validate, package, test-integration, test-coreos, test-container-export]
388+
needs: [cargo-deny, validate, package, test-integration, test-upgrade, test-coreos, test-container-export]
345389
runs-on: ubuntu-latest
346390
steps:
347391
- run: exit 1
@@ -350,5 +394,6 @@ jobs:
350394
needs.validate.result != 'success' ||
351395
needs.package.result != 'success' ||
352396
needs.test-integration.result != 'success' ||
397+
needs.test-upgrade.result != 'success' ||
353398
needs.test-coreos.result != 'success' ||
354399
needs.test-container-export.result != 'success'

Justfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
base_img := "localhost/bootc"
1818
# Synthetic upgrade image for testing
1919
upgrade_img := base_img + "-upgrade"
20+
# Base image with tmt dependencies added, used as the boot source for upgrade tests
21+
upgrade_source_img := base_img + "-upgrade-source"
2022

2123
# Build variant: ostree (default) or composefs
2224
variant := env("BOOTC_variant", "ostree")
@@ -141,6 +143,14 @@ test-composefs bootloader filesystem boot_type seal_state *ARGS:
141143
{{ARGS}} \
142144
$(if [ "{{boot_type}}" = "uki" ]; then echo "readonly"; else echo "integration"; fi)
143145

146+
# Run upgrade test: boot VM from published base image (with tmt deps added),
147+
# upgrade to locally-built image, reboot, then run readonly tests to verify.
148+
# The --upgrade-image flag triggers --bind-storage-ro in bcvk, making the
149+
# locally-built image available inside the VM via containers-storage transport.
150+
[group('core')]
151+
test-upgrade *ARGS: build _build-upgrade-source-image
152+
cargo xtask run-tmt --env=BOOTC_variant={{variant}} --env=BOOTC_test_upgrade_image={{base_img}} --upgrade-image={{base_img}} {{upgrade_source_img}} {{ARGS}} readonly
153+
144154
# Run cargo fmt and clippy checks in container
145155
[group('core')]
146156
validate:
@@ -339,6 +349,10 @@ _keygen:
339349
_build-upgrade-image:
340350
cat tmt/tests/Dockerfile.upgrade | podman build -t {{upgrade_img}} --from={{base_img}} -
341351

352+
# Build the upgrade source image: base image + tmt dependencies (rsync, nu, cloud-init)
353+
_build-upgrade-source-image:
354+
podman build --build-arg=base={{base}} -t {{upgrade_source_img}} -f tmt/tests/Dockerfile.upgrade-source .
355+
342356
# Copy an image from user podman storage to root's podman storage
343357
# This allows building as regular user then running privileged tests
344358
[group('testing')]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Build an image suitable for upgrade testing: takes the published base image
2+
# and adds the minimal dependencies needed by tmt (rsync, nu, etc.)
3+
# so we can boot it in a VM, then upgrade into the locally-built image.
4+
#
5+
# Note: we do NOT pass `cloudinit` to provision-derived.sh because bcvk
6+
# handles SSH key injection itself; cloud-init interferes with that.
7+
ARG base
8+
FROM ${base}
9+
COPY hack/provision-derived.sh hack/packages.txt /run/provision/
10+
RUN --mount=type=tmpfs,target=/tmp <<EORUN
11+
set -xeuo pipefail
12+
cd /run/provision
13+
# Install nu and tmt dependencies using the same script as the main build,
14+
# but with SKIP_CONFIGS=1 since we don't need LBIs or test kargs in the
15+
# upgrade source image (those come from the image we upgrade into).
16+
SKIP_CONFIGS=1 ./provision-derived.sh
17+
rm -rf /run/provision
18+
EORUN

tmt/tests/booted/bootc_testlib.nu

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# A simple nushell "library" for the
1+
# A simple nushell "library" for bootc test helpers
22

33
# This is a workaround for what must be a systemd bug
44
# that seems to have appeared in C10S
@@ -23,3 +23,50 @@ export def have_hostexports [] {
2323
export def parse_cmdline [] {
2424
open /proc/cmdline | str trim | split row " "
2525
}
26+
27+
# If the BOOTC_test_upgrade_image environment variable is set, performs
28+
# an upgrade to that image and reboots on the first boot. On the second
29+
# boot (after the upgrade), verifies we're running the upgraded image
30+
# and returns so the caller can proceed with its tests.
31+
#
32+
# This enables an "upgrade test" flow: boot from a published base image,
33+
# upgrade to the image under test, reboot, then run verification tests.
34+
#
35+
# Note: This uses BOOTC_test_upgrade_image (the image to upgrade *into*),
36+
# which is distinct from BOOTC_upgrade_image (the synthetic upgrade image
37+
# used by existing upgrade tests like test-image-upgrade-reboot).
38+
#
39+
# Returns without doing anything if BOOTC_test_upgrade_image is not set.
40+
export def maybe_upgrade [] {
41+
use std assert
42+
43+
let upgrade_image = $env.BOOTC_test_upgrade_image? | default ""
44+
if $upgrade_image == "" {
45+
return
46+
}
47+
48+
match $env.TMT_REBOOT_COUNT? {
49+
null | "0" => {
50+
if not (have_hostexports) {
51+
error make { msg: "BOOTC_test_upgrade_image is set but host exports (--bind-storage-ro) are not available" }
52+
}
53+
print $"Upgrade image specified: ($upgrade_image)"
54+
print "Performing upgrade switch..."
55+
bootc switch --transport containers-storage $upgrade_image
56+
print "Switch complete, rebooting..."
57+
tmt-reboot
58+
},
59+
"1" => {
60+
print $"Second boot after upgrade to ($upgrade_image)"
61+
let st = bootc status --json | from json
62+
let booted = $st.status.booted.image
63+
assert equal $booted.image.transport "containers-storage"
64+
assert equal $booted.image.image $upgrade_image
65+
print "Upgrade verified, continuing with tests..."
66+
},
67+
$o => {
68+
# For higher reboot counts, just continue - the caller
69+
# may have its own reboot logic
70+
},
71+
}
72+
}

tmt/tests/booted/test-01-readonly.nu

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
#
88
# Run all readonly tests in sequence
99
use tap.nu
10+
use bootc_testlib.nu
1011

1112
tap begin "readonly tests"
1213

14+
# If an upgrade image is specified (via BOOTC_test_upgrade_image env var),
15+
# perform the upgrade and reboot first. On the second boot after upgrade,
16+
# this returns and we continue with the readonly tests below.
17+
bootc_testlib maybe_upgrade
18+
1319
# Get all readonly test files and run them in order
1420
let tests = (ls booted/readonly/*-test-*.nu | get name | sort)
1521

0 commit comments

Comments
 (0)