* ci: move the fusermount3 repair into a composite action
Three copies of the same block were already drifting apart, and the
target comes from PATH: only ever add setuid root to a root-owned,
non-symlink binary under the system bin paths, and say why otherwise.
* test: say that the process exited in the wait errors
"process exit status 1 before ... accepted connections" is missing its
verb. Also mark the SIGTERM return discarded - it fails with
os.ErrProcessDone exactly when the select below already handles it.
* ci: prefer the distro fusermount3 over escalating a shadow copy
The shadowing /usr/local/bin/fusermount3 is not root-owned either, so
setting its setuid bit would have handed root to a binary the runner
user owns - the repair now symlinks the distro one earlier in PATH and
touches nothing, keeping the in-place chmod for a root-owned binary with
no distro alternative. A setuid bit only grants root when root owns the
file, so accept an existing one only then.
* ci: run the FUSE workflows when the shared action changes
Their paths filters listed each workflow file but not the composite
action all three now call.
* ci: restore the setuid bit on a shadowed fusermount3
Newer ubuntu-22.04 runner images carry a source-built fusermount3 in
/usr/local/bin that shadows the distro one in PATH and is not setuid
root. go-fuse looks the helper up through PATH, so every unprivileged
mount fails with "mount failed: Operation not permitted".
* test: fail a fuse test as soon as its mount process dies
A mount that cannot mount at all exits within a second, but the harness
still waited out the 30s readiness timeout and then reported "mount
point not ready within timeout", leaving the real cause buried in the
log tail. Watch the child processes and report their exit instead.
* mount: report a failed mount without a goroutine dump
A mount failure is an environment problem - no /dev/fuse, fusermount not
setuid, stale mount point - and the all-goroutine stack dump Fatalf adds
buries the one line that says so.
* mount: batched announcer + pooled peer conns for mount-to-mount RPCs
* peer_announcer.go: non-blocking EnqueueAnnounce + ticker flush that
groups fids by HRW owner, fans out one ChunkAnnounce per owner in
parallel. announcedAt is pruned at 2× TTL so it stays bounded.
* peer_dialer.go: PeerConnPool caches one grpc.ClientConn per peer
address; the announcer and (next PR) the fetcher share it so
steady-state owner RPCs skip the handshake cost entirely. Bounded
at 4096 cached entries; shutdown conns are transparently replaced.
* WFS starts both alongside the gRPC server; stops them on unmount.
* mount: wire tryPeerRead via FetchChunk streaming gRPC
Replaces the HTTP GET byte-transfer path with a gRPC server-stream
FetchChunk call. Same fall-through semantics: any failure drops
through to entryChunkGroup.ReadDataAt, so reads never slow below
status quo.
* peer_fetcher.go: tryPeerRead resolves the offset to a leaf chunk
(flattening manifests), asks the HRW owner for holders via
ChunkLookup, then opens FetchChunk on each holder in LRU order
(PR #5) until one succeeds. Assembled bytes are verified against
FileChunk.ETag end-to-end — the peer is still treated as
untrusted. Reuses the shared PeerConnPool from PR #6 for all
outbound gRPC.
* peer_grpc.go: expose SelfAddr() so the fetcher can avoid dialing
itself on a self-owned fid.
* filehandle_read.go: tryPeerRead slot between tryRDMARead and
entryChunkGroup.ReadDataAt. Gated by option.PeerEnabled and the
presence of peerGrpcServer (the single identity test).
Read ordering with the feature enabled is now:
local cache -> RDMA sidecar -> peer mount (gRPC stream) -> volume server
One port, one identity, one connection pool — no more HTTP bytecast.
* test(fuse_p2p): end-to-end CI test for peer chunk sharing
Adds a FUSE-backed integration test that proves mount B can satisfy a
read from mount A's chunk cache instead of the volume tier.
Layout (modelled on test/fuse_dlm):
test/fuse_p2p/framework_test.go — cluster harness (1 master,
1 volume, 1 filer, N mounts,
all with -peer.enable)
test/fuse_p2p/peer_chunk_sharing_test.go
— writer-reader scenario
The test (TestPeerChunkSharing_ReadersPullFromPeerCache):
1. Starts 3 mounts. Three is the sweet spot: with 2 mounts, HRW owner
of a chunk is self ~50 % of the time (peer path short-circuits);
with 3+ it drops to ≤ 1/3, so a multi-chunk file almost certainly
exercises the remote-owner fan-out.
2. Mount 0 writes a ~8 MiB file, then reads it back through its own
FUSE to warm its chunk cache.
3. Waits for seed convergence (one full MountList refresh) plus an
announcer flush cycle, so chunk-holder entries have reached each
HRW owner.
4. Mount 1 reads the same file.
5. Verifies byte-for-byte equality AND greps mount 1's log for
"peer read successful" — content matching alone is not proof
(the volume fallback would also succeed), so the log marker is
what distinguishes p2p from fallback.
Workflow .github/workflows/fuse-p2p-integration.yml triggers on any
change to mount/filer peer code, the p2p protos, or the test itself.
Failure artifacts (server + mount logs) are uploaded for 3 days.
Mounts run with -v=4 so the tryPeerRead success/failure glog messages
land in the log file the test greps.