diff --git a/.github/workflows/scripts/qemu-xfstests-bake-vm.sh b/.github/workflows/scripts/qemu-xfstests-bake-vm.sh new file mode 100755 index 000000000000..177acc5bbfa6 --- /dev/null +++ b/.github/workflows/scripts/qemu-xfstests-bake-vm.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +###################################################################### +# Bake xfstests into the build VM image. +# +# Runs on the build machine (vm0) AFTER ZFS has been built and installed +# (qemu-4-build.sh, without --poweroff) +# Called on the runner as: +# ssh zfs@vm0 '$HOME/zfs/.github/workflows/scripts/qemu-xfstests-bake-vm.sh' $OS +# +# The script powers the VM off at the end (like qemu-4-build-vm.sh --poweroff) +###################################################################### + +set -eu + +OS="$1" + +# TODO: move the sources under openzfs +XFSTESTS_REPO="${XFSTESTS_REPO:-https://github.com/implr/xfstests}" +XFSTESTS_BRANCH="${XFSTESTS_BRANCH:-zfs}" + +echo "##[group]Install xfstests dependencies" +case "$OS" in + debian*|ubuntu*) + sudo apt-get update + # Build toolchain + xfstests build/runtime deps + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ + git build-essential autoconf automake libtool pkg-config gettext \ + uuid-dev libattr1-dev libacl1-dev libaio-dev libgdbm-dev libssl-dev \ + xfsprogs e2fsprogs attr acl quota gdisk parted + # optional extras for a couple tests + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y fio dbench || true + ;; + *) + echo "xfstests bake is only implemented for debian/ubuntu so far" >&2 + exit 1 + ;; +esac +echo "##[endgroup]" + +echo "##[group]Create xfstests service users" +# xfstests insists on these accounts. Digit-leading name +# needs --badnames on modern shadow-utils. +sudo groupadd -f fsgqa +for u in fsgqa fsgqa2 123456-fsgqa; do + if ! id "$u" &>/dev/null; then + sudo useradd --badnames -g fsgqa -m "$u" 2>/dev/null \ + || sudo useradd -g fsgqa -m "$u" 2>/dev/null || true + fi +done + +# xfstests runs as root and drops to fsgqa via `su` (common/rc _user_do/_su). +# Installing OpenZFS pulls in libpam-zfs, which registers pam_zfs_key.so in the +# PAM stack to unlock users' encrypted home datasets at login. Its *session* +# hook prompts "Password:" when root su's to fsgqa (to derive the key for the +# nonexistent rpool/home/fsgqa), and that prompt lands in test output and fails +# every _user_do-based test (generic/123, 128, 314, ...). We don't use encrypted +# homes here, so strip pam_zfs_key from the PAM config. +sudo pam-auth-update --package --remove zfs_key 2>/dev/null || true +sudo sed -i '/pam_zfs_key/d' \ + /etc/pam.d/common-auth /etc/pam.d/common-session \ + /etc/pam.d/common-password /etc/pam.d/common-account 2>/dev/null || true +echo "##[endgroup]" + +echo "##[group]Clone + build xfstests ($XFSTESTS_BRANCH)" +rm -rf "$HOME/xfstests" +git clone --depth 1 -b "$XFSTESTS_BRANCH" "$XFSTESTS_REPO" "$HOME/xfstests" +cd "$HOME/xfstests" +make -j"$(nproc)" +echo "xfstests baked at $HOME/xfstests ($(git rev-parse --short HEAD))" +echo "##[endgroup]" + +# reset cloud-init and power off +sudo cloud-init clean --logs +sync && sleep 2 && sudo poweroff & +exit 0 diff --git a/.github/workflows/scripts/qemu-xfstests-prepare.sh b/.github/workflows/scripts/qemu-xfstests-prepare.sh new file mode 100755 index 000000000000..33d5993c121a --- /dev/null +++ b/.github/workflows/scripts/qemu-xfstests-prepare.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +###################################################################### +# Collect xfstests results off the test VM, tar them for upload, and +# write a short GitHub job summary. Runs on the runner; always() in the +# workflow so we still grab logs (and the console) on a guest crash. +###################################################################### + +set -eu + +source /var/tmp/env.txt +RES="$RESPATH" # /var/tmp/test_results +mkdir -p "$RES" + +# Pull results + logs off vm1 (best-effort; the VM may have crashed). +rsync -arL zfs@vm1:xfstests/results "$RES/" 2>/dev/null || true +scp zfs@vm1:xfstests/local.config "$RES/" 2>/dev/null || true +scp 'zfs@vm1:/var/tmp/dmesg-*.txt' "$RES/" 2>/dev/null || true +scp 'zfs@vm1:/var/tmp/tests-exitcode.txt' "$RES/" 2>/dev/null || true +cp -f /var/tmp/xfstests-run.log "$RES/" 2>/dev/null || true +# qemu-5-setup.sh already streams the VM serial console to $RES/vm1/console.txt. + +TARNAME="xfstests-$OS" +TAR="/tmp/$TARNAME.tar.bz2" +mv "$RES" "$(dirname "$RES")/$TARNAME" +tar cjf "$TAR" -C "$(dirname "$RES")" -h "$TARNAME" || true +mv "$(dirname "$RES")/$TARNAME" "$RES" + +# --- Job summary ---------------------------------------------------- +SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/stdout}" +{ + echo "## xfstests on ${OSNAME:-$OS}" + echo + if [ -f "$RES/tests-exitcode.txt" ]; then + rv=$(cat "$RES/tests-exitcode.txt") + if [ "$rv" = "0" ]; then + echo ":thumbsup: \`./check\` exited 0 — all selected tests passed." + else + echo ":warning: \`./check\` exited $rv — failures (or a notrun treated as error)." + fi + else + echo ":interrobang: no exit code recorded — the VM may have crashed mid-run." + fi + echo + + # xfstests prints a "Failures:" / "Passed all N tests" line near the end of + # results/check.log. Surface the tail. + if [ -f "$RES/results/check.log" ]; then + echo '
check.log (tail)' + echo + echo '```' + tail -n 40 "$RES/results/check.log" + echo '```' + echo '
' + fi +} >> "$SUMMARY" 2>/dev/null || true + +exit 0 diff --git a/.github/workflows/scripts/qemu-xfstests-run.sh b/.github/workflows/scripts/qemu-xfstests-run.sh new file mode 100755 index 000000000000..ca423ace75b9 --- /dev/null +++ b/.github/workflows/scripts/qemu-xfstests-run.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +###################################################################### +# Run xfstests +# +# - called with 'guest' -> runs inside the test VM (vm1) +# called on runner: qemu-xfstests-run.sh +# called on qemu-vm: qemu-xfstests-run.sh guest +# +###################################################################### + +set -eu + +############################ +# Runner side (no args) +############################ +if [ -z "${1:-}" ]; then + source /var/tmp/env.txt + SCRIPT='$HOME/zfs/.github/workflows/scripts/qemu-xfstests-run.sh' + # Pass the user ./check arguments through to the guest. + ssh zfs@vm1 "OS='$OS' XFSTESTS_OPTIONS='${XFSTESTS_OPTIONS:-}' $SCRIPT guest" \ + 2>&1 | stdbuf -oL tee /var/tmp/xfstests-run.log + exit "${PIPESTATUS[0]}" +fi + +############################ +# VM side +############################ +OS="${OS:?}" +OPTS="${XFSTESTS_OPTIONS:--g quick}" +export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin" + +sudo -E modprobe zfs + +# Longer RCU timeouts (as in qemu-6-tests.sh) +rcu="/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout" +test -f "$rcu" && echo 120 | sudo tee "$rcu" >/dev/null || true + +# Carve the 64 GiB tests disk (attached by qemu-5-setup.sh as the 2nd virtio +# disk) into a TEST_DEV partition + three SCRATCH pool members. +DEV=/dev/vdb +sudo wipefs -a "$DEV" || true +sudo sgdisk -Z "$DEV" +sudo sgdisk -n1:0:+20G -n2:0:+14G -n3:0:+14G -n4:0:0 "$DEV" +sudo partprobe "$DEV" +sleep 2 + +# Persistent test pool/dataset. +# (acltype=posix + xattr=sa are required for several generic/ tests). +sudo zpool create -f \ + -O mountpoint=legacy -O acltype=posix -O xattr=sa \ + -O compression=off -O relatime=off \ + testpool "${DEV}1" +sudo zfs create -o mountpoint=legacy -o recordsize=64K testpool/testfs + +sudo mkdir -p /mnt/test /mnt/scratch +sudo mount -t zfs testpool/testfs /mnt/test + +cd "$HOME/xfstests" +cat > local.config < /var/tmp/dmesg-prerun.txt || true + +RV=0 +# shellcheck disable=SC2086 # word-splitting of OPTS/EXCLUDE is intentional +sudo HOST_OPTIONS="$PWD/local.config" ./check $EXCLUDE $OPTS || RV=$? +echo "$RV" | sudo tee /var/tmp/tests-exitcode.txt >/dev/null + +sudo dmesg > /var/tmp/dmesg-postrun.txt || true +sync +exit "$RV" diff --git a/.github/workflows/zfs-qemu-xfstests.yml b/.github/workflows/zfs-qemu-xfstests.yml new file mode 100644 index 000000000000..4f7196df75ea --- /dev/null +++ b/.github/workflows/zfs-qemu-xfstests.yml @@ -0,0 +1,77 @@ +name: zfs-qemu-xfstests + +on: + workflow_dispatch: + inputs: + os: + type: string + required: false + default: "ubuntu24" + description: "Distribution to test on (e.g. ubuntu24, debian13)" + xfstests_options: + type: string + required: false + default: "-g quick" + description: "Arguments for xfstests' ./check (e.g. '-g quick', '-g auto', 'generic/467')" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + xfstests: + name: xfstests-${{ inputs.os }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup QEMU + timeout-minutes: 60 + run: .github/workflows/scripts/qemu-1-setup.sh + + - name: Start build machine + timeout-minutes: 10 + run: | + .github/workflows/scripts/qemu-2-start.sh ${{ inputs.os }} + # force single vm for now + sed -i 's/^VMs=.*/VMs="1"/' /var/tmp/env.txt + + - name: Install dependencies + timeout-minutes: 60 + run: .github/workflows/scripts/qemu-3-deps.sh --poweroff ${{ inputs.os }} + + # NOTE: no --poweroff here, unlike zfs-qemu.yml — we keep vm0 up so the + # bake step can install xfstests before the image is snapshotted. + - name: Build modules + timeout-minutes: 30 + run: .github/workflows/scripts/qemu-4-build.sh --enable-debug ${{ inputs.os }} + + - name: Bake xfstests into image + timeout-minutes: 30 + run: ssh zfs@vm0 '$HOME/zfs/.github/workflows/scripts/qemu-xfstests-bake-vm.sh' ${{ inputs.os }} + + - name: Setup testing machines + timeout-minutes: 5 + run: .github/workflows/scripts/qemu-5-setup.sh + + - name: Run xfstests + timeout-minutes: 270 + run: .github/workflows/scripts/qemu-xfstests-run.sh + env: + XFSTESTS_OPTIONS: ${{ inputs.xfstests_options }} + + - name: Prepare artifacts + if: always() + timeout-minutes: 10 + run: .github/workflows/scripts/qemu-xfstests-prepare.sh + + - uses: actions/upload-artifact@v7 + id: artifact-upload + if: always() + with: + name: xfstests-${{ inputs.os }} + path: /tmp/xfstests-${{ inputs.os }}.tar.bz2 + archive: false + if-no-files-found: ignore