From 3c52d1638eef69226afccd5c502eeb7846dfa3b8 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 23 Sep 2026 07:07:52 -0400 Subject: [PATCH] tmt: Add test switching to a zstd:chunked image The composefs backend used to fail to pull zstd:chunked images, because a zstd:chunked layer is a multi-frame zstd stream (with skippable frames for the table of contents) and the decoder stopped after the first frame. That was fixed in composefs-rs, but nothing here would have caught it or would catch a regression, since all our tests pull from containers-storage, which hands out uncompressed layers. Push a derived image with `--compression-format zstd:chunked` to an OCI directory and switch to it, which avoids needing a registry. composefs-rs reads OCI directories directly rather than via skopeo, but the layers go through the same decompressor as a registry pull. Layers already in the repository (i.e. all the base image ones) are skipped, so the one new layer must itself be multi-frame for this to test anything; it gets a bunch of small files, which zstd:chunked compresses into separate frames, and checksums to catch silent truncation. The test also checks the pushed manifest, so that it can't silently degrade into testing plain zstd or gzip. It runs for both backends, since it's cheap coverage for ostree too. Also drop the known-issue note about zstd:chunked pulls failing from the composefs docs, since the composefs-rs bump in 3024fa2b fixed that. Closes: https://github.com/bootc-dev/bootc/issues/2408 Generated-by: AI Signed-off-by: Colin Walters --- docs/src/experimental-composefs.md | 4 - tmt/plans/integration.fmf | 7 ++ tmt/tests/booted/test-switch-zstd-chunked.nu | 89 ++++++++++++++++++++ tmt/tests/tests.fmf | 5 ++ 4 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 tmt/tests/booted/test-switch-zstd-chunked.nu diff --git a/docs/src/experimental-composefs.md b/docs/src/experimental-composefs.md index 4218d016f..a891f5e9d 100644 --- a/docs/src/experimental-composefs.md +++ b/docs/src/experimental-composefs.md @@ -290,10 +290,6 @@ The composefs backend is experimental; on-disk formats are subject to change. - How container signature enforcement carries over from installation into the installed system is not settled yet. - Extended install APIs: Ability to cleanly implement anaconda %post and osbuild post mutations and general post-install pre-reboot; right now some tools just mount the deployment directory (note this one also relates to [APIs in general](https://github.com/bootc-dev/bootc/issues/522)) -- [zstd:chunked pull failures](https://github.com/bootc-dev/bootc/issues/2408): - images pushed with `--compression-format zstd:chunked` currently fail to - pull on the composefs backend ("unexpected EOF reading tar entry"). Until a - composefs-rs decode fix is incorporated, publish with plain zstd or gzip. ## Related issues diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index 06dadb2cf..e0cb65ec4 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -331,4 +331,11 @@ execute: enabled: true extra-try_bind_storage: true extra-skip_if_ostree: true + +/plan-50-switch-zstd-chunked: + summary: Switch to an image with zstd:chunked compressed layers + discover: + how: fmf + test: + - /tmt/tests/tests/test-50-switch-zstd-chunked # END GENERATED PLANS diff --git a/tmt/tests/booted/test-switch-zstd-chunked.nu b/tmt/tests/booted/test-switch-zstd-chunked.nu new file mode 100644 index 000000000..0d6e8edb9 --- /dev/null +++ b/tmt/tests/booted/test-switch-zstd-chunked.nu @@ -0,0 +1,89 @@ +# number: 50 +# tmt: +# summary: Switch to an image with zstd:chunked compressed layers +# duration: 30m +# +# zstd:chunked layers are multi-frame zstd streams with skippable frames +# holding a table of contents, which a naive zstd decoder truncates; see +# https://github.com/bootc-dev/bootc/issues/2408 +# +# This test does: +# podman build +# podman push --compression-format zstd:chunked +# bootc switch +# Verify we boot into the new image +# +# An OCI directory is used rather than a registry to avoid a network +# dependency. The layers still go through the same decompression code +# as a registry pull on both backends. +use std assert +use tap.nu + +const image_dir = "/var/tmp/bootc-zstd-chunked" +const data_dir = "/usr/share/testing-bootc-zstd-chunked" +const zstd_media_type = "application/vnd.oci.image.layer.v1.tar+zstd" +# Annotation that c/image adds to each zstd:chunked layer +const chunked_annotation = "io.github.containers.zstd-chunked.manifest-checksum" + +# This code runs on *each* boot. +bootc status +let st = bootc status --json | from json +let booted = $st.status.booted.image + +def initial_build [] { + tap begin "switch to zstd:chunked image" + + let td = mktemp -d + cd $td + + bootc image copy-to-storage + # Layers we already have (i.e. all the base image ones) are skipped when + # pulling, so only the layer added here is actually decompressed. Put a + # number of files in it: zstd:chunked compresses each into its own + # frame(s), so this layer is multi-frame too. The checksums verify + # nothing got silently truncated. + let gen_data = 'for i in $(seq 64); do head -c 65536 /dev/urandom > data$i; done && sha256sum data* > SHA256SUMS' + ( + tap make_uki_containerfile $" + FROM localhost/bootc as base + RUN mkdir -p ($data_dir) && cd ($data_dir) && ($gen_data) + ") | save Dockerfile + podman build -t localhost/bootc-zstd-chunked . + + rm -rf $image_dir + podman push --compression-format zstd:chunked --force-compression localhost/bootc-zstd-chunked $"oci:($image_dir)" + # Free up space; we only need the OCI directory from here on + podman rmi localhost/bootc-zstd-chunked localhost/bootc + + # Make sure we're actually testing what we think we are + let manifest = skopeo inspect --raw $"oci:($image_dir)" | from json + assert (($manifest.layers | length) > 0) + for layer in $manifest.layers { + assert equal $layer.mediaType $zstd_media_type + assert ($chunked_annotation in ($layer.annotations? | default {} | columns)) $"layer ($layer.digest) is not zstd:chunked" + } + + bootc switch --transport oci $image_dir + let st = bootc status --json | from json + assert equal $st.status.staged.image.image.transport oci + tmt-reboot +} + +def second_boot [] { + print "verifying second boot" + assert equal $booted.image.transport oci + assert equal $booted.image.image $image_dir + cd $data_dir + sha256sum --check --quiet SHA256SUMS + bootc internals fsck + tap ok +} + +def main [] { + # See https://tmt.readthedocs.io/en/stable/stories/features.html#reboot-during-test + match $env.TMT_REBOOT_COUNT? { + null | "0" => initial_build, + "1" => second_boot, + $o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } }, + } +} diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf index f27e9addd..f9f73990d 100644 --- a/tmt/tests/tests.fmf +++ b/tmt/tests/tests.fmf @@ -206,3 +206,8 @@ check: - when: composefs_bridge == true enabled: true test: nu booted/test-49-composefs-1-16-bridge.nu + +/test-50-switch-zstd-chunked: + summary: Switch to an image with zstd:chunked compressed layers + duration: 30m + test: nu booted/test-switch-zstd-chunked.nu