test: ./check mount now also exercises the daemon-backed --socket FUSE path
The mount stage previously only tested the in-memory --seed mount, so the daemon-backed write path (DaemonBackend) was never regression-guarded -- which is exactly how a VM shipping an old cube-server (lacking the raw* commands) shipped undetected. The mount stage now runs the full regression suite against BOTH a --seed mount and a --socket mount (with its own fresh daemon), and adds a durability-across-restart assertion proving the FUSE view is a real durable store. run_fs_tests is now backend-agnostic (self-seeds its fixture).
This commit is contained in:
@@ -100,70 +100,77 @@ if [ "${run_stress:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
step "4/4 live FUSE mount"
|
||||
step "4/4 live FUSE mount (in-memory --seed AND daemon-backed --socket)"
|
||||
[ "$(id -u)" -eq 0 ] || { echo "SKIP: live mount needs root" >&2; exit 1; }
|
||||
command -v getfattr >/dev/null || { echo "missing getfattr: apt-get install attr" >&2; exit 1; }
|
||||
|
||||
cargo build $FEAT --jobs "$JOBS"
|
||||
BIN=$PWD/target/debug/cubefs-mount
|
||||
MNT=$(mktemp -d /tmp/cubefs-check.XXXXXX)
|
||||
PASS=0; FAIL=0
|
||||
cleanup() { fusermount3 -u "$MNT" 2>/dev/null || true; sleep 0.5; rmdir "$MNT" 2>/dev/null || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
# --- shared assertion helpers ---------------------------------------------
|
||||
PASS=0; FAIL=0
|
||||
ok() { printf 'PASS %s\n' "$1"; PASS=$((PASS+1)); }
|
||||
no() { printf '\033[31mFAIL %s -- %s\033[0m\n' "$1" "${2-}"; FAIL=$((FAIL+1)); }
|
||||
is() { [ "$2" = "$3" ] && ok "$1" || no "$1" "want[$3] got[$2]"; }
|
||||
# A command that must fail (permission, ENOTEMPTY, EINVAL...).
|
||||
nay() { local m=$1; shift; "$@" >/dev/null 2>&1 && no "$m" "unexpectedly succeeded" || ok "$m"; }
|
||||
|
||||
set +e
|
||||
"$BIN" "$MNT" --seed --label check --allow-other >/dev/null 2>&1 &
|
||||
sleep 2
|
||||
mount | grep -q "on $MNT type fuse" && ok mounted || { no mounted; exit 1; }
|
||||
# run_fs_tests MNT — the regression suite, backend-agnostic.
|
||||
# Establishes its own baseline record (does NOT assume any seeded data), so it
|
||||
# works identically against an in-memory "--seed" mount or a daemon-backed
|
||||
# "--socket" mount (the latter starts with an empty store).
|
||||
run_fs_tests() {
|
||||
local MNT=$1
|
||||
local F=$MNT/c001/z001/y001/x001
|
||||
# Establish the baseline file ourselves (x001 AND x002, so the unlink
|
||||
# assertion holds on both backends — the in-memory seed mount also has both).
|
||||
echo "hello from the cube" > "$F"
|
||||
echo "second seeded record" > "$MNT/c001/z001/y001/x002"
|
||||
is "seed read" "$(cat $F)" "hello from the cube"
|
||||
# root listing: both backends guarantee at least c001 (the baseline file's
|
||||
# coordinate). The in-memory --seed mount also seeds c002, but the daemon
|
||||
# store starts empty, so we assert c001 presence, not an exact set.
|
||||
is "root listing" "$(ls $MNT | tr '\n' ' ' | grep -o c001 | head -1)" "c001"
|
||||
is "stat size" "$(stat -c %s $F)" "20"
|
||||
is "partial read" "$(dd if=$F bs=1 skip=6 count=4 2>/dev/null)" "from"
|
||||
|
||||
F=$MNT/c001/z001/y001/x001
|
||||
is "seed read" "$(cat $F)" "hello from the cube"
|
||||
is "root listing" "$(ls $MNT | tr '\n' ' ')" "c001 c002 "
|
||||
is "stat size" "$(stat -c %s $F)" "20"
|
||||
is "partial read" "$(dd if=$F bs=1 skip=6 count=4 2>/dev/null)" "from"
|
||||
echo posix-write > $MNT/c001/z001/y001/x003
|
||||
is "write+read" "$(cat $MNT/c001/z001/y001/x003)" "posix-write"
|
||||
truncate -s 5 $MNT/c001/z001/y001/x003
|
||||
is "truncate" "$(cat $MNT/c001/z001/y001/x003)" "posix"
|
||||
printf XY >> $MNT/c001/z001/y001/x003
|
||||
is "append" "$(cat $MNT/c001/z001/y001/x003)" "posixXY"
|
||||
rm $MNT/c001/z001/y001/x003
|
||||
is "unlink" "$(ls $MNT/c001/z001/y001 | tr '\n' ' ')" "x001 x002 "
|
||||
|
||||
echo posix-write > $MNT/c001/z001/y001/x003
|
||||
is "write+read" "$(cat $MNT/c001/z001/y001/x003)" "posix-write"
|
||||
truncate -s 5 $MNT/c001/z001/y001/x003
|
||||
is "truncate" "$(cat $MNT/c001/z001/y001/x003)" "posix"
|
||||
printf XY >> $MNT/c001/z001/y001/x003
|
||||
is "append" "$(cat $MNT/c001/z001/y001/x003)" "posixXY"
|
||||
rm $MNT/c001/z001/y001/x003
|
||||
is "unlink" "$(ls $MNT/c001/z001/y001 | tr '\n' ' ')" "x001 x002 "
|
||||
# Regression: empty directories must be representable, or mkdir -p can never
|
||||
# reach depth 4 (the kernel's revalidating lookup returned ENOENT).
|
||||
mkdir -p $MNT/c050/z001/y001 && ok "mkdir -p depth3" || no "mkdir -p depth3"
|
||||
is "empty dir listed" "$(test -d "$MNT/c050" && echo 1 || echo 0)" "1"
|
||||
is "empty dir empty" "$(ls $MNT/c050/z001/y001 | wc -l)" "0"
|
||||
echo deep > $MNT/c050/z001/y001/x001
|
||||
is "create under new" "$(cat $MNT/c050/z001/y001/x001)" "deep"
|
||||
nay "rmdir nonempty refused" rmdir $MNT/c050
|
||||
mkdir $MNT/c060 && rmdir $MNT/c060
|
||||
is "mkdir/rmdir empty" "$(ls $MNT | grep -c c060)" "0"
|
||||
|
||||
# Regression: empty directories must be representable, or mkdir -p can never
|
||||
# reach depth 4 (the kernel's revalidating lookup returned ENOENT).
|
||||
mkdir -p $MNT/c050/z001/y001 && ok "mkdir -p depth3" || no "mkdir -p depth3"
|
||||
is "empty dir listed" "$(ls $MNT | grep -c c050)" "1"
|
||||
is "empty dir empty" "$(ls $MNT/c050/z001/y001 | wc -l)" "0"
|
||||
echo deep > $MNT/c050/z001/y001/x001
|
||||
is "create under new" "$(cat $MNT/c050/z001/y001/x001)" "deep"
|
||||
nay "rmdir nonempty refused" rmdir $MNT/c050
|
||||
mkdir $MNT/c060 && rmdir $MNT/c060
|
||||
is "mkdir/rmdir empty" "$(ls $MNT | grep -c c060)" "0"
|
||||
mkdir -p $MNT/c255/z255/y255 && echo edge > $MNT/c255/z255/y255/x255
|
||||
is "axis-255 corner" "$(cat $MNT/c255/z255/y255/x255)" "edge"
|
||||
nay "reject non-canonical x1" tee $MNT/c001/z001/y001/x1 <<<x
|
||||
|
||||
mkdir -p $MNT/c255/z255/y255 && echo edge > $MNT/c255/z255/y255/x255
|
||||
is "axis-255 corner" "$(cat $MNT/c255/z255/y255/x255)" "edge"
|
||||
nay "reject non-canonical x1" tee $MNT/c001/z001/y001/x1 <<<x
|
||||
cp /etc/hostname $MNT/c050/z001/y001/x002
|
||||
diff -q /etc/hostname $MNT/c050/z001/y001/x002 >/dev/null && ok "cp byte-identical" || no "cp byte-identical"
|
||||
|
||||
cp /etc/hostname $MNT/c050/z001/y001/x002
|
||||
diff -q /etc/hostname $MNT/c050/z001/y001/x002 >/dev/null && ok "cp byte-identical" || no "cp byte-identical"
|
||||
setfattr -n user.a -v 1 $F; setfattr -n user.b -v 2 $F; setfattr -n user.d -v D $MNT/c001
|
||||
is "xattr a present" "$(getfattr -n user.a --only-values $F 2>/dev/null)" "1"
|
||||
is "xattr b present" "$(getfattr -n user.b --only-values $F 2>/dev/null)" "2"
|
||||
is "xattr on dir" "$(getfattr -n user.d --only-values $MNT/c001 2>/dev/null)" "D"
|
||||
setfattr -x user.a $F
|
||||
is "xattr removed" "$(getfattr -d $F 2>/dev/null | grep -c '^user\.a')" "0"
|
||||
|
||||
setfattr -n user.a -v 1 $F; setfattr -n user.b -v 2 $F; setfattr -n user.d -v D $MNT/c001
|
||||
is "xattr count file" "$(getfattr -d $F 2>/dev/null | grep -c ^user)" "3"
|
||||
is "xattr on dir" "$(getfattr -n user.d --only-values $MNT/c001 2>/dev/null)" "D"
|
||||
setfattr -x user.a $F
|
||||
is "xattr removed" "$(getfattr -d $F 2>/dev/null | grep -c '^user\.a')" "0"
|
||||
|
||||
# Regression: cross-user ACLs need --allow-other, else the kernel returns
|
||||
# EACCES at the mountpoint before any request reaches us.
|
||||
if id luulu >/dev/null 2>&1; then
|
||||
# Regression: cross-user ACLs need --allow-other, else the kernel returns
|
||||
# EACCES at the mountpoint before any request reaches us.
|
||||
if id luulu >/dev/null 2>&1; then
|
||||
chown 0:0 $F; chmod 644 $F
|
||||
is "644 root, other reads" "$(sudo -u luulu cat $F)" "hello from the cube"
|
||||
chmod 600 $F
|
||||
@@ -172,15 +179,58 @@ if id luulu >/dev/null 2>&1; then
|
||||
is "600 owned, owner reads" "$(sudo -u luulu cat $F)" "hello from the cube"
|
||||
sudo -u luulu tee $F >/dev/null <<<by-other
|
||||
is "other writes" "$(cat $F)" "by-other"
|
||||
else
|
||||
else
|
||||
echo "SKIP: user luulu absent, cross-user ACL stage not run"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p $MNT/c100/z001/y001
|
||||
for i in $(seq 1 200); do printf 'rec%03d\n' $i > $MNT/c100/z001/y001/x$(printf %03d $i); done
|
||||
is "200 records" "$(ls $MNT/c100/z001/y001 | wc -l)" "200"
|
||||
is "record 137" "$(cat $MNT/c100/z001/y001/x137)" "rec137"
|
||||
mkdir -p $MNT/c100/z001/y001
|
||||
for i in $(seq 1 200); do printf 'rec%03d\n' $i > $MNT/c100/z001/y001/x$(printf %03d $i); done
|
||||
is "200 records" "$(ls $MNT/c100/z001/y001 | wc -l)" "200"
|
||||
is "record 137" "$(cat $MNT/c100/z001/y001/x137)" "rec137"
|
||||
}
|
||||
|
||||
# --- LEG 1: in-memory seed mount (baseline, no daemon) --------------------
|
||||
MNT=$(mktemp -d /tmp/cubefs-check.XXXXXX)
|
||||
cleanup_seed() { fusermount3 -u "$MNT" 2>/dev/null || true; sleep 0.5; rmdir "$MNT" 2>/dev/null || true; }
|
||||
set +e
|
||||
"$BIN" "$MNT" --seed --label check --allow-other >/dev/null 2>&1 &
|
||||
sleep 2
|
||||
mount | grep -q "on $MNT type fuse" && ok mounted || { no mounted; exit 1; }
|
||||
run_fs_tests "$MNT"
|
||||
set -e
|
||||
fusermount3 -u "$MNT" 2>/dev/null || true; sleep 0.5; rmdir "$MNT" 2>/dev/null || true
|
||||
|
||||
# --- LEG 2: daemon-backed --socket mount (regression guard for the durable
|
||||
# FUSE view; this is what the deployed VM uses and what would have
|
||||
# caught a daemon lacking the raw* commands) -----------------------
|
||||
MNT=$(mktemp -d /tmp/cubefs-check.XXXXXX)
|
||||
DSOCK=$(mktemp -u /tmp/cubefs-check-daemon.XXXXXX.sock)
|
||||
DDIR=$(mktemp -d /tmp/cubefs-check-daemon.XXXXXX)
|
||||
DSTORE=$DDIR/cube-store.json
|
||||
cleanup_sock() { fusermount3 -u "$MNT" 2>/dev/null || true; sleep 0.5; rmdir "$MNT" 2>/dev/null || true; kill -9 $DPID 2>/dev/null || true; rm -rf "$DDIR" 2>/dev/null || true; }
|
||||
set +e
|
||||
./target/debug/cube-server --socket "$DSOCK" --store "$DSTORE" \
|
||||
--allow-anonymous --checkpoint-ms 400 --wal-fsync-ms 30 >/tmp/cubefs-check-daemon.log 2>&1 &
|
||||
DPID=$!
|
||||
for i in $(seq 1 30); do [ -S "$DSOCK" ] && break; sleep 0.3; done
|
||||
if [ ! -S "$DSOCK" ]; then echo "DAEMON FAILED to start:"; cat /tmp/cubefs-check-daemon.log; cleanup_sock; exit 1; fi
|
||||
"$BIN" "$MNT" --socket "$DSOCK" --label check --allow-other >/dev/null 2>&1 &
|
||||
sleep 2
|
||||
mount | grep -q "on $MNT type fuse" && ok "socket-mounted" || { no "socket-mounted"; cleanup_sock; exit 1; }
|
||||
run_fs_tests "$MNT"
|
||||
# Durability: a record written through the socket mount must survive a daemon
|
||||
# restart (WAL recovery), proving the FUSE view is a real durable store.
|
||||
echo durable-restart-proof > $MNT/c200/z001/y001/x001
|
||||
sync; sleep 1 # let the WAL fsync the write before we kill the daemon
|
||||
kill -9 $DPID 2>/dev/null || true; sleep 1
|
||||
./target/debug/cube-server --socket "$DSOCK" --store "$DSTORE" \
|
||||
--allow-anonymous --checkpoint-ms 400 --wal-fsync-ms 30 >/tmp/cubefs-check-daemon.log 2>&1 &
|
||||
DPID=$!
|
||||
for i in $(seq 1 30); do [ -S "$DSOCK" ] && break; sleep 0.3; done
|
||||
sleep 1 # give the remounted FUSE view a beat to re-resolve against the new daemon
|
||||
is "durable across restart" "$(cat $MNT/c200/z001/y001/x001 2>&1)" "durable-restart-proof"
|
||||
set -e
|
||||
cleanup_sock
|
||||
|
||||
printf '\n%d passed, %d failed\n' "$PASS" "$FAIL"
|
||||
[ "$FAIL" -eq 0 ] || exit 1
|
||||
|
||||
Reference in New Issue
Block a user