312 lines
9.6 KiB
Go
312 lines
9.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
package nfs4
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The location attributes round trip: the fs_locations attribute
|
||
|
|
// decodes back to the servers and root paths, and the info attribute
|
||
|
|
// carries the flags, the validity window and the replica entries.
|
||
|
|
func TestFsLocationsShapes(t *testing.T) {
|
||
|
|
blob := AppendFsLocations(nil, []string{"exports"}, []FsLocation{{
|
||
|
|
Servers: []string{"a.example", "b.example"},
|
||
|
|
RootPath: []string{"exports", "data"},
|
||
|
|
}})
|
||
|
|
root, locations, err := DecodeFsLocations(blob)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if len(root) != 1 || root[0] != "exports" {
|
||
|
|
t.Fatalf("root %v", root)
|
||
|
|
}
|
||
|
|
if len(locations) != 1 || len(locations[0].Servers) != 2 ||
|
||
|
|
locations[0].Servers[1] != "b.example" ||
|
||
|
|
len(locations[0].RootPath) != 2 || locations[0].RootPath[1] != "data" {
|
||
|
|
t.Fatalf("locations %+v", locations)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The fs_locations_info attribute: flags, validity, items with the
|
||
|
|
// opaque info bytes.
|
||
|
|
info := AppendFsLocationsInfo(nil, Fsli4ifVarSub, 60, []string{"exports"},
|
||
|
|
[]FsItem{{Entries: []FsServer{{
|
||
|
|
Currency: 3, Info: []byte{Fsli4gfWritable | Fsli4gfCurReq}, Server: "a.example",
|
||
|
|
}}, RootPath: []string{"data"}}})
|
||
|
|
d := xdr.NewDecoder(info)
|
||
|
|
if flags, err := d.Uint32(); err != nil || flags != Fsli4ifVarSub {
|
||
|
|
t.Fatalf("flags %d: %v", flags, err)
|
||
|
|
}
|
||
|
|
if valid, err := d.Int32(); err != nil || valid != 60 {
|
||
|
|
t.Fatalf("valid %d: %v", valid, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("root count %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if rootComponent, err := d.String(); err != nil || rootComponent != "exports" {
|
||
|
|
t.Fatalf("root component %q: %v", rootComponent, err)
|
||
|
|
}
|
||
|
|
if _, err := d.Uint32(); err != nil { // items count
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("entries %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if cur, err := d.Int32(); err != nil || cur != 3 {
|
||
|
|
t.Fatalf("currency %d: %v", cur, err)
|
||
|
|
}
|
||
|
|
if info2, err := d.VarOpaque(); err != nil || len(info2) != 1 ||
|
||
|
|
info2[0]&Fsli4gfWritable == 0 || info2[0]&Fsli4gfCurReq == 0 {
|
||
|
|
t.Fatalf("info % x: %v", info2, err)
|
||
|
|
}
|
||
|
|
if s, err := d.String(); err != nil || s != "a.example" {
|
||
|
|
t.Fatalf("server %q: %v", s, err)
|
||
|
|
}
|
||
|
|
// The item closes with the root path of the replicas.
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("rootpath count %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if c, err := d.String(); err != nil || c != "data" {
|
||
|
|
t.Fatalf("rootpath %q: %v", c, err)
|
||
|
|
}
|
||
|
|
if d.Remaining() != 0 {
|
||
|
|
t.Fatalf("%d bytes left", d.Remaining())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The emulated layout family bodies carry the device id first and the
|
||
|
|
// family payload after; the GETDEVICELIST pair round trips.
|
||
|
|
func TestLayoutFamilyShapes(t *testing.T) {
|
||
|
|
var dev [16]byte
|
||
|
|
copy(dev[:], "0123456789abcdef")
|
||
|
|
|
||
|
|
files := AppendFileLayoutBody(nil, dev, 4096, 2, 8,
|
||
|
|
[][]byte{[]byte("fh1"), []byte("fh2")})
|
||
|
|
d := xdr.NewDecoder(files)
|
||
|
|
if got, err := d.Raw(16); err != nil || !bytes.Equal(got, dev[:]) {
|
||
|
|
t.Fatalf("device % x: %v", got, err)
|
||
|
|
}
|
||
|
|
if u, err := d.Uint32(); err != nil || u != 4096 {
|
||
|
|
t.Fatalf("util %d: %v", u, err)
|
||
|
|
}
|
||
|
|
if fs, err := d.Uint32(); err != nil || fs != 2 {
|
||
|
|
t.Fatalf("first stripe %d: %v", fs, err)
|
||
|
|
}
|
||
|
|
if po, err := d.Uint64(); err != nil || po != 8 {
|
||
|
|
t.Fatalf("pattern %d: %v", po, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 2 {
|
||
|
|
t.Fatalf("fh count %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if fh, err := d.VarOpaque(); err != nil || string(fh) != "fh1" {
|
||
|
|
t.Fatalf("fh %q: %v", fh, err)
|
||
|
|
}
|
||
|
|
if fh, err := d.VarOpaque(); err != nil || string(fh) != "fh2" {
|
||
|
|
t.Fatalf("fh2 %q: %v", fh, err)
|
||
|
|
}
|
||
|
|
if d.Remaining() != 0 {
|
||
|
|
t.Fatalf("%d bytes left", d.Remaining())
|
||
|
|
}
|
||
|
|
|
||
|
|
// The device address: stripe indices and the multipath list.
|
||
|
|
addr := AppendFileDeviceAddr(nil, []uint32{0, 1},
|
||
|
|
[]NetAddr{{Netid: "tcp", Uaddr: "10.0.0.1.8.1"}})
|
||
|
|
d = xdr.NewDecoder(addr)
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 2 {
|
||
|
|
t.Fatalf("indices %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if _, err := d.Raw(8); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("addrs %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if id, err := d.String(); err != nil || id != "tcp" {
|
||
|
|
t.Fatalf("netid %q: %v", id, err)
|
||
|
|
}
|
||
|
|
if ua, err := d.String(); err != nil || ua != "10.0.0.1.8.1" {
|
||
|
|
t.Fatalf("uaddr %q: %v", ua, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The block volume, the SCSI extent and the OSD component bodies
|
||
|
|
// carry their shapes.
|
||
|
|
block := AppendBlockDeviceAddr(nil, BlockVolume{DeviceID: dev, BaseOffset: 8, BlockCount: 64})
|
||
|
|
d = xdr.NewDecoder(block)
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("volumes %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if typ, err := d.Uint32(); err != nil || typ != 1 {
|
||
|
|
t.Fatalf("volume type %d: %v", typ, err)
|
||
|
|
}
|
||
|
|
raw, err := d.Raw(16)
|
||
|
|
if err != nil || !bytes.Equal(raw, dev[:]) {
|
||
|
|
t.Fatalf("volume device % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
if bo, err := d.Uint64(); err != nil || bo != 8 {
|
||
|
|
t.Fatalf("base offset %d: %v", bo, err)
|
||
|
|
}
|
||
|
|
if bc, err := d.Uint64(); err != nil || bc != 64 {
|
||
|
|
t.Fatalf("block count %d: %v", bc, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
scsi := AppendScsiLayoutBody(nil, dev, 1, 2, 3)
|
||
|
|
d = xdr.NewDecoder(scsi)
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("extents %d: %v", n, err)
|
||
|
|
}
|
||
|
|
raw, err = d.Raw(16)
|
||
|
|
if err != nil || !bytes.Equal(raw, dev[:]) {
|
||
|
|
t.Fatalf("extent device % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
for _, want := range []uint64{1, 2, 3} {
|
||
|
|
if v, err := d.Uint64(); err != nil || v != want {
|
||
|
|
t.Fatalf("extent field %d: %v", v, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
obj := AppendObjectLayoutBody(nil, dev, ObjectLayout{NumComponents: 1,
|
||
|
|
StripeUnit: 4096, GroupWidth: 1, GroupDepth: 1, RefTagSize: 4})
|
||
|
|
d = xdr.NewDecoder(obj)
|
||
|
|
if alg, err := d.Uint32(); err != nil || alg != 0 {
|
||
|
|
t.Fatalf("raid algorithm %d: %v", alg, err)
|
||
|
|
}
|
||
|
|
if su, err := d.Uint64(); err != nil || su != 4096 {
|
||
|
|
t.Fatalf("stripe unit %d: %v", su, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The GETDEVICELIST pair round trips.
|
||
|
|
list := AppendGetDeviceListArgs(nil, LayoutTypeFlexfiles, 8, 5, [8]byte{9})
|
||
|
|
d = xdr.NewDecoder(list[4:])
|
||
|
|
if typ, err := d.Uint32(); err != nil || typ != LayoutTypeFlexfiles {
|
||
|
|
t.Fatalf("list type %d: %v", typ, err)
|
||
|
|
}
|
||
|
|
if md, err := d.Uint32(); err != nil || md != 8 {
|
||
|
|
t.Fatalf("max devices %d: %v", md, err)
|
||
|
|
}
|
||
|
|
if c, err := d.Uint64(); err != nil || c != 5 {
|
||
|
|
t.Fatalf("cookie %d: %v", c, err)
|
||
|
|
}
|
||
|
|
raw, err = d.Raw(8)
|
||
|
|
if err != nil || raw[0] != 9 {
|
||
|
|
t.Fatalf("verifier % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
res := AppendGetDeviceListRes(nil, 5, [8]byte{9}, [][16]byte{dev, {1}}, true)
|
||
|
|
d = xdr.NewDecoder(res)
|
||
|
|
if c, err := d.Uint64(); err != nil || c != 5 {
|
||
|
|
t.Fatalf("res cookie %d: %v", c, err)
|
||
|
|
}
|
||
|
|
if _, err := d.Raw(8); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 2 {
|
||
|
|
t.Fatalf("devices %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if _, err := d.Raw(32); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if eof, err := d.Bool(); err != nil || !eof {
|
||
|
|
t.Fatalf("eof %v: %v", eof, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The OPENATTR and READ argop encoders carry their shapes.
|
||
|
|
func TestOpenattrReadArgs(t *testing.T) {
|
||
|
|
oa := AppendOpenattrArgs(nil, true)
|
||
|
|
d := xdr.NewDecoder(oa[4:])
|
||
|
|
if created, err := d.Bool(); err != nil || !created {
|
||
|
|
t.Fatalf("created %v: %v", created, err)
|
||
|
|
}
|
||
|
|
ra := AppendReadArgs(nil, Stateid{7}, 99, 1024)
|
||
|
|
d = xdr.NewDecoder(ra[4:])
|
||
|
|
raw, err := d.Raw(16)
|
||
|
|
if err != nil || raw[0] != 7 {
|
||
|
|
t.Fatalf("stateid % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
if off, err := d.Uint64(); err != nil || off != 99 {
|
||
|
|
t.Fatalf("offset %d: %v", off, err)
|
||
|
|
}
|
||
|
|
if cnt, err := d.Uint32(); err != nil || cnt != 1024 {
|
||
|
|
t.Fatalf("count %d: %v", cnt, err)
|
||
|
|
}
|
||
|
|
if d.Remaining() != 0 {
|
||
|
|
t.Fatalf("%d bytes left", d.Remaining())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The flexfiles version 2 layout body of
|
||
|
|
// draft-haynes-nfsv4-flex-filesv2-00 round trips: the data server
|
||
|
|
// carries the stateid array, the file handle list and the RPC
|
||
|
|
// credential the draft adds.
|
||
|
|
func TestFlexFileLayoutV2Shape(t *testing.T) {
|
||
|
|
var dev [16]byte
|
||
|
|
copy(dev[:], "v2flexdeviceid!!")
|
||
|
|
st := Stateid{2, 'F', 'F', 'V', '2'}
|
||
|
|
body := AppendFlexFileLayoutBodyV2(nil, 3, 0, []FlexMirrorV2{{
|
||
|
|
DataServers: []FlexDataServerV2{{
|
||
|
|
DeviceID: dev,
|
||
|
|
Efficiency: 7,
|
||
|
|
Stateids: []Stateid{st},
|
||
|
|
FHs: [][]byte{[]byte("fh-v2")},
|
||
|
|
User: "petr",
|
||
|
|
Group: "nfs",
|
||
|
|
AuthFlavor: 0,
|
||
|
|
AuthBody: nil,
|
||
|
|
}},
|
||
|
|
}})
|
||
|
|
d := xdr.NewDecoder(body)
|
||
|
|
if su, err := d.Uint64(); err != nil || su != 0 {
|
||
|
|
t.Fatalf("stripe unit %d: %v", su, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("mirrors %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("servers %d: %v", n, err)
|
||
|
|
}
|
||
|
|
raw, err := d.Raw(16)
|
||
|
|
if err != nil || !bytes.Equal(raw, dev[:]) {
|
||
|
|
t.Fatalf("device % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
if eff, err := d.Uint32(); err != nil || eff != 7 {
|
||
|
|
t.Fatalf("efficiency %d: %v", eff, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("stateids %d: %v", n, err)
|
||
|
|
}
|
||
|
|
raw, err = d.Raw(16)
|
||
|
|
if err != nil || !bytes.Equal(raw, st[:]) {
|
||
|
|
t.Fatalf("stateid % x: %v", raw, err)
|
||
|
|
}
|
||
|
|
if n, err := d.Uint32(); err != nil || n != 1 {
|
||
|
|
t.Fatalf("fhs %d: %v", n, err)
|
||
|
|
}
|
||
|
|
if fh, err := d.VarOpaque(); err != nil || string(fh) != "fh-v2" {
|
||
|
|
t.Fatalf("fh %q: %v", fh, err)
|
||
|
|
}
|
||
|
|
if u, err := d.String(); err != nil || u != "petr" {
|
||
|
|
t.Fatalf("user %q: %v", u, err)
|
||
|
|
}
|
||
|
|
if g, err := d.String(); err != nil || g != "nfs" {
|
||
|
|
t.Fatalf("group %q: %v", g, err)
|
||
|
|
}
|
||
|
|
if flavor, err := d.Uint32(); err != nil || flavor != 0 {
|
||
|
|
t.Fatalf("auth flavor %d: %v", flavor, err)
|
||
|
|
}
|
||
|
|
if authBody, err := d.VarOpaque(); err != nil || len(authBody) != 0 {
|
||
|
|
t.Fatalf("auth body % x: %v", authBody, err)
|
||
|
|
}
|
||
|
|
if flags, err := d.Uint32(); err != nil || flags != 3 {
|
||
|
|
t.Fatalf("flags %d: %v", flags, err)
|
||
|
|
}
|
||
|
|
if hint, err := d.Uint32(); err != nil || hint != 0 {
|
||
|
|
t.Fatalf("stats hint %d: %v", hint, err)
|
||
|
|
}
|
||
|
|
if d.Remaining() != 0 {
|
||
|
|
t.Fatalf("%d bytes left", d.Remaining())
|
||
|
|
}
|
||
|
|
}
|