From f029c336e9dc6c01780cce9cc41162f8a34ebad5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 29 Jul 2026 14:32:26 +0100 Subject: [PATCH 1/2] mkosi: stage volatile deb packages for upgrade test Prepare scripts run before volatile packages are installed, so parse the list from the config to ensure they are all included to avoid failures due to some packages missing from the list. Follow-up for 28e1f84d6a2721000b0d781220b154f0bed50cc8 --- mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare index 3a4415c547..8a166cd166 100755 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare @@ -40,6 +40,7 @@ if [[ $deltaver -le 0 ]]; then exit 0 fi +declare -A packages=() distro_packages=() devel_packages=() @@ -48,7 +49,18 @@ while IFS=$'\t' read -r package source package_architecture; do [[ $package_architecture == "$DISTRIBUTION_ARCHITECTURE" || $package_architecture == all ]] || continue [[ $package != *-dbgsym* ]] || continue - devel_packages+=("$package=$(dpkg-query --root="$BUILDROOT" --showformat='${Version}' --show "$package")") + packages["$package"]= +done < <(dpkg-query --root="$BUILDROOT" --show --showformat='${Package}\t${source:Package}\t${Architecture}\n') + +# Prepare scripts run before VolatilePackages are installed, so include them explicitly. +while read -r package; do + [[ $package != *-dbgsym* ]] || continue + + packages["$package"]= +done < <(jq --raw-output '.VolatilePackages[]' <"$MKOSI_CONFIG") + +for package in "${!packages[@]}"; do + devel_packages+=("$package=$develver") package_versions=$(apt-cache madison "$package") if awk -F '|' -v version="$distrover" ' @@ -63,7 +75,7 @@ while IFS=$'\t' read -r package source package_architecture; do ' <<<"$package_versions"; then distro_packages+=("$package=$distrover") fi -done < <(dpkg-query --root="$BUILDROOT" --show --showformat='${binary:Package}\t${source:Package}\t${Architecture}\n') +done if (( ${#distro_packages[@]} == 0 )); then echo "Skipping upgrade test, no distro packages found" From cdc3fd019b87b2055d253d99c6fa50db4b530de4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 29 Jul 2026 17:02:46 +0100 Subject: [PATCH 2/2] test: retry units failed during package replacement in TEST-88-UPGRADE Retry units that still exist and clear stale state for units removed by a downgrade, as packages might be old and not have new units that were added in the latest version. --- test/units/TEST-88-UPGRADE.sh | 188 ++++++++++++++++++++++++++++++++-- 1 file changed, 182 insertions(+), 6 deletions(-) diff --git a/test/units/TEST-88-UPGRADE.sh b/test/units/TEST-88-UPGRADE.sh index 90443dd09c..4f0b620db5 100755 --- a/test/units/TEST-88-UPGRADE.sh +++ b/test/units/TEST-88-UPGRADE.sh @@ -44,20 +44,181 @@ if command -v udevadm >/dev/null && systemctl is-active --quiet systemd-udevd.se udev=1 fi +: >/tmp/failed-units if [[ $($unitscmd --output json | jq length) -gt 0 ]]; then echo 'Systemd failed units found before the test:' $unitscmd $unitscmd --output json | jq -r '.[].unit' >/tmp/failed-units fi +save_unit_state() { + local snapshot_failed="" trigger_list unit + local unit_files=/tmp/unit-files-before-package-operation.json + local units=/tmp/units-before-package-operation.json + local -a state_files=(/tmp/active-units /tmp/preexisting-units /tmp/triggered-units) + local -a triggers + + rm -f "${state_files[@]}" "$unit_files" "$units" + if ! systemctl list-units --all '*systemd*' --output json >"$units" || + ! systemctl list-unit-files '*systemd*' --output json >"$unit_files" || + ! jq -r '.[].unit' "$units" >/tmp/preexisting-units || + ! jq -r '.[].unit_file' "$unit_files" >>/tmp/preexisting-units || + ! jq -r ' + .[] + | select(.active == "active" or .active == "activating" or .active == "reloading") + | .unit + ' "$units" >/tmp/active-units; then + echo 'Failed to save unit state before package operation, unit retries will be skipped.' + snapshot_failed=1 + fi + + if [[ -z $snapshot_failed ]]; then + : >/tmp/triggered-units + while read -r unit; do + if ! trigger_list=$(systemctl show -P Triggers "$unit"); then + echo 'Failed to save unit triggers before package operation, unit retries will be skipped.' + snapshot_failed=1 + break + fi + + if [[ -n $trigger_list ]]; then + read -ra triggers <<<"$trigger_list" + printf '%s\n' "${triggers[@]}" >>/tmp/triggered-units + fi + done >"$paths"; then + echo "Failed to inspect $package_set packages, removed units will be treated as failures." + rm -f "$paths" + return 0 + fi + elif ! rpm -qlp "$package" >>"$paths"; then + echo "Failed to inspect $package_set packages, removed units will be treated as failures." + rm -f "$paths" + return 0 + fi + done + + if [[ -z $packages_found ]]; then + echo "No $package_set packages found, removed units will be treated as failures." + return 0 + fi + + sed -n -E 's#^(\./|/)?(usr/)?lib/systemd/system/([^/]+)$#\3#p' "$paths" | + sort -u >"$units" + rm -f "$paths" +} + +unit_removal_expected() { + local operation=$1 unit=$2 + local template units + + if [[ $operation == downgrade ]]; then + units=/tmp/distro-units + else + units=/tmp/devel-units + fi + + [[ -s $units ]] || return 1 + grep -sxqF "$unit" "$units" && return 1 + + if [[ $unit == *@*.* ]]; then + template="${unit%%@*}@.${unit##*.}" + grep -sxqF "$template" "$units" && return 1 + fi + + return 0 +} + check_sd() { - local unit fail=0 timer1_new timer2_new + local operation=$1 load_state unit fail=0 timer1_new timer2_new if ! systemctl daemon-reload; then echo 'System manager reload failed after the test!' fail=1 fi + # Reset units activated during package replacement, then restart only units + # that were previously running or are newly introduced. + rm -f /tmp/restart-units /tmp/retry-units + if [[ -e /tmp/active-units && -e /tmp/preexisting-units && -e /tmp/triggered-units ]]; then + : >/tmp/restart-units + : >/tmp/retry-units + for unit in $($unitscmd --output json | jq -r '.[].unit'); do + if grep -sxqF "$unit" /tmp/failed-units; then + continue + fi + + if ! grep -sxqF "$unit" /tmp/active-units && + ! grep -sxqF "$unit" /tmp/triggered-units && + grep -sxqF "$unit" /tmp/preexisting-units; then + continue + fi + + if ! load_state=$(systemctl show -P LoadState "$unit"); then + fail=1 + continue + fi + + if [[ $load_state == not-found ]]; then + if unit_removal_expected "$operation" "$unit"; then + systemctl reset-failed "$unit" || true + else + fail=1 + systemctl status "$unit" || true + fi + continue + fi + + if ! systemctl reset-failed "$unit"; then + fail=1 + continue + fi + + if grep -sxqF "$unit" /tmp/active-units || ! grep -sxqF "$unit" /tmp/preexisting-units; then + echo "$unit" >>/tmp/restart-units + else + echo "$unit" >>/tmp/retry-units + fi + done + + while read -r unit; do + if ! systemctl start "$unit"; then + fail=1 + systemctl status "$unit" || true + fi + done