275 lines
8.2 KiB
Go
275 lines
8.2 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
package nfs4server
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
||
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||
|
|
)
|
||
|
|
|
||
|
|
// GETDEVICELIST answers the one device of this build for any layout
|
||
|
|
// type; LAYOUTGET accepts the emulated families and returns their wire
|
||
|
|
// bodies.
|
||
|
|
func TestEmulatedLayoutFamilies(t *testing.T) {
|
||
|
|
h := testTree(t)
|
||
|
|
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, "fam", 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:]
|
||
|
|
}
|
||
|
|
|
||
|
|
seq++
|
||
|
|
res, bodies := run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutRootfh(nil),
|
||
|
|
nfs4.AppendGetattr(nil, nfs4.OfBits(nfs4.AttrType)),
|
||
|
|
nfs4.AppendGetDeviceListArgs(nil, nfs4.LayoutTypeFlexfiles, 8, 0, [8]byte{}),
|
||
|
|
})
|
||
|
|
wantStatus(t, "open", res.Status, nfs4.ErrOK)
|
||
|
|
seq++
|
||
|
|
res, bodies = run(seq, [][]byte{
|
||
|
|
nfs4.AppendGetDeviceListArgs(nil, nfs4.LayoutTypeFlexfiles, 8, 0, [8]byte{}),
|
||
|
|
})
|
||
|
|
wantStatus(t, "device list", res.Status, nfs4.ErrOK)
|
||
|
|
gd := xdr.NewDecoder(bodies[0])
|
||
|
|
if cookie, cerr := gd.Uint64(); cerr != nil || cookie != 0 {
|
||
|
|
t.Fatalf("cookie %d", cookie)
|
||
|
|
}
|
||
|
|
if _, cerr := gd.Raw(8); cerr != nil { // verifier
|
||
|
|
t.Fatal(cerr)
|
||
|
|
}
|
||
|
|
n, cerr := gd.Uint32()
|
||
|
|
if cerr != nil || n != 1 {
|
||
|
|
t.Fatalf("devices %d: %v", n, cerr)
|
||
|
|
}
|
||
|
|
raw, _ := gd.Raw(16)
|
||
|
|
if !bytes.Equal(raw, layoutDeviceID[:]) {
|
||
|
|
t.Fatalf("device % x", raw)
|
||
|
|
}
|
||
|
|
if eof, _ := gd.Bool(); !eof {
|
||
|
|
t.Fatal("eof false")
|
||
|
|
}
|
||
|
|
|
||
|
|
// The files layout body decodes with its device and fh list.
|
||
|
|
openSt := nfs4.Stateid{}
|
||
|
|
seq++
|
||
|
|
res, bodies = run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutRootfh(nil),
|
||
|
|
nfs4.AppendOpenArgs(nil, 0x8888, []byte("fam"), nfs4.ShareAccessRead, 0, false, 0, "a.txt"),
|
||
|
|
nfs4.AppendGetfh(nil),
|
||
|
|
})
|
||
|
|
wantStatus(t, "open", res.Status, nfs4.ErrOK)
|
||
|
|
copy(openSt[:], bodies[1])
|
||
|
|
fh, _ := xdr.NewDecoder(bodies[2]).VarOpaque()
|
||
|
|
seq++
|
||
|
|
res, bodies = run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutfh(nil, fh),
|
||
|
|
nfs4.AppendLayoutGetArgs(nil, false, nfs4.LayoutTypeFiles, nfs4.IoModeRead, 0, ^uint64(0), 0, openSt, 1<<16),
|
||
|
|
})
|
||
|
|
wantStatus(t, "files layoutget", res.Status, nfs4.ErrOK)
|
||
|
|
ld := xdr.NewDecoder(bodies[1])
|
||
|
|
var st nfs4.Stateid
|
||
|
|
raw, _ = ld.Raw(16)
|
||
|
|
copy(st[:], raw)
|
||
|
|
if !bytes.HasPrefix(st[4:], []byte("LAYOUT")) {
|
||
|
|
t.Fatalf("stateid % x", st[4:])
|
||
|
|
}
|
||
|
|
if _, _ = ld.Bool(); ld.Remaining() == 0 {
|
||
|
|
t.Fatal("no layout segments")
|
||
|
|
}
|
||
|
|
if n, _ := ld.Uint32(); n != 1 { // the segment count
|
||
|
|
t.Fatalf("segment count %d", n)
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint64(); ld.Remaining() == 0 { // offset
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint64(); ld.Remaining() == 0 { // length
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint32(); ld.Remaining() == 0 { // iomode
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint32(); ld.Remaining() == 0 { // type
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
flex, ferr := ld.VarOpaque()
|
||
|
|
if ferr != nil {
|
||
|
|
t.Fatal(ferr)
|
||
|
|
}
|
||
|
|
// The files layout body: device, util, first stripe, pattern offset,
|
||
|
|
// the fh list with the file's own handle.
|
||
|
|
fd := xdr.NewDecoder(flex)
|
||
|
|
dev, _ := fd.Raw(16)
|
||
|
|
if !bytes.Equal(dev, layoutDeviceID[:]) {
|
||
|
|
t.Fatalf("body device % x len %d", dev, len(flex))
|
||
|
|
}
|
||
|
|
if _, _ = fd.Uint32(); fd.Remaining() == 0 { // util
|
||
|
|
t.Fatal("short files body")
|
||
|
|
}
|
||
|
|
if _, _ = fd.Uint32(); fd.Remaining() == 0 { // first stripe
|
||
|
|
t.Fatal("short files body")
|
||
|
|
}
|
||
|
|
if _, _ = fd.Uint64(); fd.Remaining() == 0 { // pattern offset
|
||
|
|
t.Fatal("short files body")
|
||
|
|
}
|
||
|
|
fn, _ := fd.Uint32()
|
||
|
|
if fn != 1 {
|
||
|
|
t.Fatalf("fh list %d", fn)
|
||
|
|
}
|
||
|
|
if got, _ := fd.VarOpaque(); !bytes.Equal(got, fh) {
|
||
|
|
t.Fatalf("fh % x", got)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The block, objects and SCSI families answer the same way with
|
||
|
|
// their own bodies; the emulated device is always this server.
|
||
|
|
for _, typ := range []uint32{nfs4.LayoutTypeFlexFilesV2, nfs4.LayoutTypeBlock, nfs4.LayoutTypeObjects, nfs4.LayoutTypeScsi} {
|
||
|
|
seq++
|
||
|
|
res, bodies = run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutfh(nil, fh),
|
||
|
|
nfs4.AppendLayoutGetArgs(nil, false, typ, nfs4.IoModeRead, 0, ^uint64(0), 0, openSt, 1<<16),
|
||
|
|
})
|
||
|
|
if res.Status != nfs4.ErrOK {
|
||
|
|
t.Fatalf("family %d: status %d", typ, res.Status)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// LAYOUTGET of the flexfiles version 2 type answers the version two
|
||
|
|
// body: the data server with its stateid array, the file handle and the
|
||
|
|
// AUTH_NONE credential of tight coupling.
|
||
|
|
func TestFlexFilesV2LayoutGet(t *testing.T) {
|
||
|
|
h := testTree(t)
|
||
|
|
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, "ffv2", 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:]
|
||
|
|
}
|
||
|
|
|
||
|
|
seq++
|
||
|
|
res, bodies := run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutRootfh(nil),
|
||
|
|
nfs4.AppendOpenArgs(nil, 0x9999, []byte("ffv2"), nfs4.ShareAccessRead, 0, false, 0, "a.txt"),
|
||
|
|
nfs4.AppendGetfh(nil),
|
||
|
|
})
|
||
|
|
wantStatus(t, "open", res.Status, nfs4.ErrOK)
|
||
|
|
var openSt nfs4.Stateid
|
||
|
|
copy(openSt[:], bodies[1])
|
||
|
|
fh, _ := xdr.NewDecoder(bodies[2]).VarOpaque()
|
||
|
|
|
||
|
|
seq++
|
||
|
|
res, bodies = run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutfh(nil, fh),
|
||
|
|
nfs4.AppendLayoutGetArgs(nil, false, nfs4.LayoutTypeFlexFilesV2,
|
||
|
|
nfs4.IoModeRead, 0, ^uint64(0), 0, openSt, 1<<16),
|
||
|
|
})
|
||
|
|
wantStatus(t, "ffv2 layoutget", res.Status, nfs4.ErrOK)
|
||
|
|
ld := xdr.NewDecoder(bodies[1])
|
||
|
|
var st nfs4.Stateid
|
||
|
|
raw, _ := ld.Raw(16)
|
||
|
|
copy(st[:], raw)
|
||
|
|
if !bytes.HasPrefix(st[4:], []byte("LAYOUT")) {
|
||
|
|
t.Fatalf("stateid % x", st[4:])
|
||
|
|
}
|
||
|
|
if _, _ = ld.Bool(); ld.Remaining() == 0 {
|
||
|
|
t.Fatal("no segments")
|
||
|
|
}
|
||
|
|
if n, _ := ld.Uint32(); n != 1 {
|
||
|
|
t.Fatalf("segments %d", n)
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint64(); ld.Remaining() == 0 { // offset
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint64(); ld.Remaining() == 0 { // length
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if _, _ = ld.Uint32(); ld.Remaining() == 0 { // iomode
|
||
|
|
t.Fatal("short segment")
|
||
|
|
}
|
||
|
|
if typ, _ := ld.Uint32(); typ != nfs4.LayoutTypeFlexFilesV2 {
|
||
|
|
t.Fatalf("segment type %d", typ)
|
||
|
|
}
|
||
|
|
v2, verr := ld.VarOpaque()
|
||
|
|
if verr != nil {
|
||
|
|
t.Fatal(verr)
|
||
|
|
}
|
||
|
|
fd := xdr.NewDecoder(v2)
|
||
|
|
if su, err := fd.Uint64(); err != nil || su != 0 {
|
||
|
|
t.Fatalf("stripe unit %d: %v", su, err)
|
||
|
|
}
|
||
|
|
if n, err := fd.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("mirrors %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if n, err := fd.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("servers %d: %v", n, err)
|
||
|
|
}
|
||
|
|
raw, _ = fd.Raw(16)
|
||
|
|
if !bytes.Equal(raw, layoutDeviceID[:]) {
|
||
|
|
t.Fatalf("device % x", raw)
|
||
|
|
}
|
||
|
|
if eff, err := fd.Uint32(); err != nil || eff != 0 {
|
||
|
|
t.Fatalf("efficiency %d: %v", eff, err)
|
||
|
|
}
|
||
|
|
if n, err := fd.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("stateids %d: %v", n, err)
|
||
|
|
}
|
||
|
|
raw, _ = fd.Raw(16)
|
||
|
|
if !bytes.Equal(raw, st[:]) {
|
||
|
|
t.Fatalf("v2 stateid % x want % x", raw, st[:])
|
||
|
|
}
|
||
|
|
if n, err := fd.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("fhs %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if v2fh, err := fd.VarOpaque(); err != nil || !bytes.Equal(v2fh, fh) {
|
||
|
|
t.Fatalf("v2 fh % x", v2fh)
|
||
|
|
}
|
||
|
|
if _, _ = fd.String(); fd.Remaining() == 0 { // user
|
||
|
|
t.Fatal("short v2 body")
|
||
|
|
}
|
||
|
|
if _, _ = fd.String(); fd.Remaining() == 0 { // group
|
||
|
|
t.Fatal("short v2 body")
|
||
|
|
}
|
||
|
|
if flavor, err := fd.Uint32(); err != nil || flavor != 0 {
|
||
|
|
t.Fatalf("auth flavor %d: %v", flavor, err)
|
||
|
|
}
|
||
|
|
if authBody, err := fd.VarOpaque(); err != nil || len(authBody) != 0 {
|
||
|
|
t.Fatalf("auth body % x", authBody)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GETDEVICEINFO and GETDEVICELIST accept the version two type.
|
||
|
|
seq++
|
||
|
|
res, _ = run(seq, [][]byte{
|
||
|
|
nfs4.AppendPutfh(nil, fh),
|
||
|
|
nfs4.AppendGetDeviceInfoArgs(nil, layoutDeviceID,
|
||
|
|
nfs4.LayoutTypeFlexFilesV2, 1<<16, nfs4.Bitmap{}),
|
||
|
|
})
|
||
|
|
wantStatus(t, "ffv2 deviceinfo", res.Status, nfs4.ErrOK)
|
||
|
|
seq++
|
||
|
|
res, _ = run(seq, [][]byte{
|
||
|
|
nfs4.AppendGetDeviceListArgs(nil, nfs4.LayoutTypeFlexFilesV2, 8, 0, [8]byte{}),
|
||
|
|
})
|
||
|
|
wantStatus(t, "ffv2 devicelist", res.Status, nfs4.ErrOK)
|
||
|
|
}
|