]> git.hungrycats.org Git - bees/commitdiff
test/integration: add OpenRC mount-delegation case
authorZygo Blaxell <bees@furryterror.org>
Fri, 26 Jun 2026 17:10:43 +0000 (13:10 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:04:12 +0000 (00:04 -0400)
Add case 30, the first integration case to exercise the service layer
(beesd and the init script) rather than the bees binary directly.  It is
OpenRC-gated: it skips with exit 0 unless TEST_HOST runs OpenRC, since the
rest of the suite targets the systemd host.

On an OpenRC host it installs bees, beesd, and the init script, formats the
reserved scratch device, and drives a dedicated bees.itest service instance
through its lifecycle, asserting that the pidfile pid exec's through
beesd/unshare to the bees binary, that bees runs in a private mount
namespace with the btrfs mount visible inside it and absent from the host,
that beesd created a non-zero beeshash.dat (guarding the zero-length hash
table regression), that the status file lands on the host, that
suspend/resume drive the stop-state, and that stop leaks no mount.  The
installed files are removed on cleanup.

Validated 17/17 on a Gentoo OpenRC host; skips on the systemd host.

Assisted-by: Claude-Code:claude-opus-4-8
test/integration/cases/30-openrc-mount-delegation.sh [new file with mode: 0755]

diff --git a/test/integration/cases/30-openrc-mount-delegation.sh b/test/integration/cases/30-openrc-mount-delegation.sh
new file mode 100755 (executable)
index 0000000..0719425
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/bash
+# Test 30: OpenRC init script delegates the mount to beesd
+#
+# OpenRC-gated.  Skips (exit 0) unless TEST_HOST runs OpenRC, since the rest
+# of the suite targets the systemd host.  On an OpenRC host it installs bees,
+# beesd, and the OpenRC init script, then drives a dedicated bees.itest
+# service instance against the reserved scratch device and asserts:
+#
+#   - the pidfile pid exec's through beesd/unshare to the bees binary
+#   - bees runs in a private mount namespace, with the btrfs mount visible
+#     inside it and absent from the host (the point of delegating to beesd)
+#   - beesd created BEESHOME + a non-zero beeshash.dat (regression guard for
+#     the zero-length hash table that bees refuses to open)
+#   - the status file is written on the host tmpfs
+#   - suspend/resume drive the process stop-state
+#   - stop removes the process and pidfile and leaks no mount into the host
+#
+# Unlike the other cases (which run the bees binary directly), this exercises
+# the service layer: beesd and the init script.  It installs to the canonical
+# paths (/usr/lib/bees/bees, /usr/sbin/beesd, /etc/init.d/bees) and removes
+# them again on cleanup.
+#
+# Prerequisites: testlib.sh, an OpenRC host whose <host>.conf points
+# TEST_SCRATCH_DEVS at a disposable block device (a raw disk is fine; LVM is
+# not required).
+
+set -euo pipefail
+
+SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
+source "$SCRIPT_DIR/../lib/testlib.sh"
+
+IT_TEST_NAME="30-openrc-mount-delegation"
+
+INSTANCE="bees.itest"
+INITD_BASE="/etc/init.d/bees"
+INITD_INST="/etc/init.d/${INSTANCE}"
+CONFD_INST="/etc/conf.d/${INSTANCE}"
+PIDFILE="/run/${INSTANCE}.pid"
+STATUSFILE="/run/bees/${INSTANCE}.status"
+DB_SIZE=2097152          # 2 MiB = 16 * 128 KiB
+POLL=30
+
+it_load_config
+
+# ── OpenRC gate ─────────────────────────────────────────────────────────────────
+# Run before any deploy or scratch use so a systemd host skips cheaply.  The
+# scratch slot reserved by it_load_config is released by the cleanup trap.
+
+if ! it_ssh "command -v rc-service >/dev/null 2>&1 && command -v openrc-run >/dev/null 2>&1"; then
+    echo "=== $IT_TEST_NAME: SKIP (TEST_HOST $TEST_HOST is not an OpenRC host) ==="
+    exit 0
+fi
+
+echo "=== $IT_TEST_NAME (host $TEST_HOST, dev $TEST_SCRATCH_DEV) ==="
+
+# ── Cleanup ─────────────────────────────────────────────────────────────────────
+# Stop the service, remove the instance and installed files, then fall through
+# to the framework cleanup (umount / slot release).  UUID is filled in later.
+
+UUID=""
+MADE_UUID_LINK=0
+openrc_cleanup() {
+    it_ssh "
+        rc-service '$INSTANCE' stop 2>/dev/null || true
+        rm -f '$INITD_INST' '$CONFD_INST' '$STATUSFILE'
+        rm -f '$INITD_BASE' /usr/sbin/beesd /usr/lib/bees/bees
+        [ '$MADE_UUID_LINK' = 1 ] && [ -n '$UUID' ] && rm -f '/dev/disk/by-uuid/$UUID'
+        true
+    " 2>/dev/null || true
+    it_cleanup || true
+}
+trap openrc_cleanup EXIT INT TERM
+
+# ── Deploy bees + beesd + the OpenRC init script ────────────────────────────────
+# Build the binary and the templated scripts locally, then install at the
+# canonical paths beesd hard-codes (/usr/lib/bees/bees, /usr/sbin/beesd).
+
+echo "[deploy] building bees + scripts..."
+flock -x "$(git -C "$BEES_DIR" rev-parse --git-path config)" \
+    make -C "$BEES_DIR" src \
+        scripts/beesd scripts/bees.initd \
+        PREFIX=/usr BINDIR=sbin LIBEXEC_PREFIX=/usr/lib/bees ETC_PREFIX=/etc >&2
+
+echo "[deploy] installing to $TEST_HOST..."
+it_ssh "mkdir -p /usr/lib/bees /usr/sbin /etc/init.d"
+rsync -a "$BEES_DIR/bin/bees"          root@"$TEST_HOST":/usr/lib/bees/bees
+rsync -a "$BEES_DIR/scripts/beesd"     root@"$TEST_HOST":/usr/sbin/beesd
+rsync -a "$BEES_DIR/scripts/bees.initd" root@"$TEST_HOST":"$INITD_BASE"
+it_ssh "chmod 0755 /usr/lib/bees/bees /usr/sbin/beesd '$INITD_BASE'"
+
+# ── Format the scratch device and locate it by UUID ─────────────────────────────
+
+it_assert "mkfs.btrfs on $TEST_SCRATCH_DEV" \
+    it_ssh "umount -R '$TEST_MOUNT' 2>/dev/null; mkfs.btrfs -f '$TEST_SCRATCH_DEV' >/dev/null"
+UUID=$(it_ssh "blkid -s UUID -o value '$TEST_SCRATCH_DEV'")
+it_assert "filesystem has a UUID" test -n "$UUID"
+echo "[info] uuid=$UUID"
+
+# beesd locates the device via /dev/disk/by-uuid/$UUID; create the link if udev
+# did not (e.g. for a raw disk on a minimal host).
+if ! it_ssh "test -b '/dev/disk/by-uuid/$UUID'"; then
+    it_ssh "mkdir -p /dev/disk/by-uuid && ln -sf '$TEST_SCRATCH_DEV' '/dev/disk/by-uuid/$UUID'"
+    MADE_UUID_LINK=1
+fi
+
+# ── Create the dedicated service instance and start it ──────────────────────────
+
+it_ssh "
+    ln -sf '$INITD_BASE' '$INITD_INST'
+    cat > '$CONFD_INST' <<CONF
+fsuuid=\"$UUID\"
+hashsize=\"$DB_SIZE\"
+bees_args=\"--scan-mode 4\"
+loglevel=\"6\"
+statusfile=\"$STATUSFILE\"
+CONF
+"
+
+it_assert "rc-service $INSTANCE start" it_ssh "rc-service '$INSTANCE' start"
+it_assert "pidfile $PIDFILE created" \
+    it_ssh "for i in \$(seq 1 $POLL); do test -s '$PIDFILE' && exit 0; sleep 1; done; exit 1"
+PID=$(it_ssh "cat '$PIDFILE'")
+echo "[info] service pid=$PID"
+
+# ── Assertions ──────────────────────────────────────────────────────────────────
+
+it_assert "process $PID is running" it_ssh "kill -0 '$PID'"
+
+# beesd -> unshare -> beesd -> bees: poll until the pid has exec'd to bees.
+it_assert "pidfile pid exec'd through to the bees binary" \
+    it_ssh "for i in \$(seq 1 $POLL); do case \"\$(readlink -f /proc/$PID/exe 2>/dev/null)\" in */bees) exit 0;; esac; sleep 1; done; exit 1"
+
+it_assert "bees runs in a private mount namespace" \
+    it_ssh "[ \"\$(readlink /proc/$PID/ns/mnt)\" != \"\$(readlink /proc/1/ns/mnt)\" ]"
+
+it_assert "btrfs mount visible inside bees namespace" \
+    it_ssh "nsenter -t '$PID' --mount -- findmnt -n '/run/bees/mnt/$UUID'"
+it_assert "btrfs mount absent from host namespace" \
+    it_ssh "! findmnt -n '/run/bees/mnt/$UUID'"
+
+it_assert "non-zero beeshash.dat created under BEESHOME" \
+    it_ssh "for i in \$(seq 1 $POLL); do nsenter -t '$PID' --mount -- test -s '/run/bees/mnt/$UUID/.beeshome/beeshash.dat' && exit 0; sleep 1; done; exit 1"
+
+it_assert "status file written to host $STATUSFILE" \
+    it_ssh "for i in \$(seq 1 $POLL); do test -e '$STATUSFILE' && exit 0; sleep 1; done; exit 1"
+
+# suspend / resume drive the stop-state (state field after the last ')')
+it_ssh "rc-service '$INSTANCE' suspend" >/dev/null 2>&1 || true
+it_assert "suspend stopped the process (state T)" \
+    it_ssh "for i in \$(seq 1 5); do case \"\$(sed -e 's/^.*) //' /proc/$PID/stat 2>/dev/null | cut -d' ' -f1)\" in T*) exit 0;; esac; sleep 1; done; exit 1"
+it_ssh "rc-service '$INSTANCE' resume" >/dev/null 2>&1 || true
+it_assert "resume continued the process" \
+    it_ssh "for i in \$(seq 1 5); do case \"\$(sed -e 's/^.*) //' /proc/$PID/stat 2>/dev/null | cut -d' ' -f1)\" in T*|\"\") sleep 1;; *) exit 0;; esac; done; exit 1"
+
+# ── Stop: process and pidfile gone, no leaked mount in the host ──────────────────
+
+it_assert "rc-service $INSTANCE stop" it_ssh "rc-service '$INSTANCE' stop"
+it_assert "process exited after stop" \
+    it_ssh "for i in \$(seq 1 $POLL); do kill -0 '$PID' 2>/dev/null || exit 0; sleep 1; done; exit 1"
+it_assert "pidfile removed after stop" it_ssh "! test -e '$PIDFILE'"
+it_assert "no leaked mount in host namespace after stop" it_ssh "! findmnt -n '/run/bees/mnt/$UUID'"
+
+it_summary