diff --git a/check b/check index c9ec008..7140eab 100755 --- a/check +++ b/check @@ -100,87 +100,137 @@ 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 -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]"; } +# --- 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"; } +# 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" + + 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" + + 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 <</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" + + # 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 + nay "600 root denies other" sudo -u luulu cat $F + chown 1000:1000 $F; chmod 600 $F + is "600 owned, owner reads" "$(sudo -u luulu cat $F)" "hello from the cube" + sudo -u luulu tee $F >/dev/null << $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; } - -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 " - -# 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 <</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 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 - chown 0:0 $F; chmod 644 $F - is "644 root, other reads" "$(sudo -u luulu cat $F)" "hello from the cube" - chmod 600 $F - nay "600 root denies other" sudo -u luulu cat $F - chown 1000:1000 $F; chmod 600 $F - is "600 owned, owner reads" "$(sudo -u luulu cat $F)" "hello from the cube" - sudo -u luulu tee $F >/dev/null << $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" +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