Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
// A configured referral answers LOOKUP with a stub, GETATTR with the
|
|
// location attributes, and everything that touches the backend with
|
|
// NFS4ERR_MOVED, RFC 5661 sections 8.4.2 and 11.9.
|
|
func TestReferralAndMoved(t *testing.T) {
|
|
h := testTree(t)
|
|
h.SetReferral("migrated", Referral{
|
|
Servers: []string{"10.0.0.9.8.1", "10.0.0.9.8.2"},
|
|
RootPath: []string{"exports", "data"},
|
|
})
|
|
sid, seq := newSession(t, h)
|
|
run := func(s uint32, ops [][]byte) (nfs4.CompoundRes, [][]byte) {
|
|
t.Helper()
|
|
all := append([][]byte{nfs4.AppendSequenceArgs(nil, sid, s, 0, defaultSlots-1, true)}, ops...)
|
|
payload := nfs4.AppendCompoundArgs(nil, "ref", nfs4.MinorVersion, all)
|
|
body, ok := h.compound(payload, cred{uid: 0, gid: 0})
|
|
if !ok {
|
|
t.Fatal("garbage")
|
|
}
|
|
r, bodies, derr := nfs4.DecodeCompoundResBodies(body)
|
|
if derr != nil {
|
|
t.Fatalf("decode: %v", derr)
|
|
}
|
|
return r, bodies[1:]
|
|
}
|
|
|
|
// LOOKUP lands on the stub and GETATTR carries the locations.
|
|
seq++
|
|
res, bodies := run(seq, [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "migrated"),
|
|
// The blob carries only the one attribute: attributes encode in
|
|
// numeric order, so the test reads them one call at a time.
|
|
nfs4.AppendGetattr(nil, nfs4.OfBits(nfs4.AttrFsLocations)),
|
|
})
|
|
if res.Status != nfs4.ErrOK {
|
|
t.Fatalf("lookup stub: status %d", res.Status)
|
|
}
|
|
ad := xdr.NewDecoder(bodies[2])
|
|
words, aerr := ad.Uint32() // the response bitmap: count then words
|
|
if aerr != nil {
|
|
t.Fatal(aerr)
|
|
}
|
|
if _, rerr := ad.Raw(int(words) * 4); rerr != nil {
|
|
t.Fatal(rerr)
|
|
}
|
|
blob, aerr := ad.VarOpaque()
|
|
if aerr != nil {
|
|
t.Fatal(aerr)
|
|
}
|
|
fsRoot, locations, err := nfs4.DecodeFsLocations(blob)
|
|
if err != nil {
|
|
t.Fatalf("decode fs_locations: %v", err)
|
|
}
|
|
if len(locations) != 1 || len(locations[0].Servers) != 2 ||
|
|
locations[0].Servers[1] != "10.0.0.9.8.2" {
|
|
t.Fatalf("locations %+v", locations)
|
|
}
|
|
if len(fsRoot) != 1 || fsRoot[0] != "" {
|
|
t.Fatalf("fs root %v", fsRoot)
|
|
}
|
|
|
|
// A write against the stub is MOVED.
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "migrated"),
|
|
nfs4.AppendWriteArgs(nil, nfs4.Stateid{}, 0, nfs4.StableFileSync, []byte("x")),
|
|
})
|
|
if res.Status != nfs4.ErrMoved {
|
|
t.Fatalf("write on stub: status %d, want MOVED", res.Status)
|
|
}
|
|
|
|
// The real tree still serves files under their own names.
|
|
seq++
|
|
res, _ = run(seq, [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "a.txt"),
|
|
nfs4.AppendGetattr(nil, nfs4.OfBits(nfs4.AttrSize)),
|
|
})
|
|
wantStatus(t, "real file", res.Status, nfs4.ErrOK)
|
|
}
|