2024-03-28 13:09:42 +00:00
|
|
|
package nfs_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2024-06-19 07:50:22 +00:00
|
|
|
"os"
|
2024-03-28 13:09:42 +00:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
nfs "git.kmsign.ru/royalcat/tstor/pkg/go-nfs"
|
|
|
|
"git.kmsign.ru/royalcat/tstor/pkg/go-nfs/helpers"
|
|
|
|
"git.kmsign.ru/royalcat/tstor/pkg/go-nfs/helpers/memfs"
|
|
|
|
|
|
|
|
nfsc "github.com/willscott/go-nfs-client/nfs"
|
|
|
|
rpc "github.com/willscott/go-nfs-client/nfs/rpc"
|
|
|
|
"github.com/willscott/go-nfs-client/nfs/util"
|
|
|
|
"github.com/willscott/go-nfs-client/nfs/xdr"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestNFS(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
if testing.Verbose() {
|
|
|
|
util.DefaultLogger.SetDebug(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// make an empty in-memory server.
|
|
|
|
listener, err := net.Listen("tcp", "localhost:0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mem := helpers.WrapBillyFS(memfs.New())
|
|
|
|
// File needs to exist in the root for memfs to acknowledge the root exists.
|
|
|
|
_, _ = mem.Create(ctx, "/test")
|
|
|
|
|
|
|
|
handler := helpers.NewNullAuthHandler(mem)
|
|
|
|
cacheHelper := helpers.NewCachingHandler(handler, 1024)
|
|
|
|
go func() {
|
|
|
|
_ = nfs.Serve(listener, cacheHelper)
|
|
|
|
}()
|
|
|
|
|
|
|
|
c, err := rpc.DialTCP(listener.Addr().Network(), listener.Addr().(*net.TCPAddr).String(), false)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer c.Close()
|
|
|
|
|
|
|
|
var mounter nfsc.Mount
|
|
|
|
mounter.Client = c
|
|
|
|
target, err := mounter.Mount("/", rpc.AuthNull)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = mounter.Unmount()
|
|
|
|
}()
|
|
|
|
|
|
|
|
_, err = target.FSInfo()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate sample file creation
|
|
|
|
_, err = target.Create("/helloworld.txt", 0666)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if info, err := mem.Stat(ctx, "/helloworld.txt"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
if info.Size() != 0 || info.Mode().Perm() != 0666 {
|
|
|
|
t.Fatal("incorrect creation.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate writing to a file.
|
|
|
|
f, err := target.OpenFile("/helloworld.txt", 0666)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
b := []byte("hello world")
|
|
|
|
_, err = f.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2024-06-19 07:50:22 +00:00
|
|
|
mf, _ := mem.OpenFile(ctx, "/helloworld.txt", os.O_RDONLY, 0)
|
2024-03-28 13:09:42 +00:00
|
|
|
buf := make([]byte, len(b))
|
2024-07-16 20:58:22 +00:00
|
|
|
if _, err = mf.ReadAt(ctx, buf[:], 0); err != nil {
|
2024-03-28 13:09:42 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(buf, b) {
|
|
|
|
t.Fatal("written does not match expected")
|
|
|
|
}
|
|
|
|
|
|
|
|
// for test nfs.ReadDirPlus in case of many files
|
|
|
|
dirF1, err := mem.ReadDir(ctx, "/")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
shouldBeNames := []string{}
|
|
|
|
for _, f := range dirF1 {
|
|
|
|
shouldBeNames = append(shouldBeNames, f.Name())
|
|
|
|
}
|
|
|
|
for i := 0; i < 2000; i++ {
|
|
|
|
fName := fmt.Sprintf("f-%04d.txt", i)
|
|
|
|
shouldBeNames = append(shouldBeNames, fName)
|
|
|
|
f, err := mem.Create(ctx, fName)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
f.Close(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
manyEntitiesPlus, err := target.ReadDirPlus("/")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
actualBeNamesPlus := []string{}
|
|
|
|
for _, e := range manyEntitiesPlus {
|
|
|
|
actualBeNamesPlus = append(actualBeNamesPlus, e.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
as := sort.StringSlice(shouldBeNames)
|
|
|
|
bs := sort.StringSlice(actualBeNamesPlus)
|
|
|
|
as.Sort()
|
|
|
|
bs.Sort()
|
|
|
|
if !reflect.DeepEqual(as, bs) {
|
|
|
|
t.Fatal("nfs.ReadDirPlus error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// for test nfs.ReadDir in case of many files
|
|
|
|
manyEntities, err := readDir(target, "/")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
actualBeNames := []string{}
|
|
|
|
for _, e := range manyEntities {
|
|
|
|
actualBeNames = append(actualBeNames, e.FileName)
|
|
|
|
}
|
|
|
|
|
|
|
|
as2 := sort.StringSlice(shouldBeNames)
|
|
|
|
bs2 := sort.StringSlice(actualBeNames)
|
|
|
|
as2.Sort()
|
|
|
|
bs2.Sort()
|
|
|
|
if !reflect.DeepEqual(as2, bs2) {
|
|
|
|
fmt.Printf("should be %v\n", as2)
|
|
|
|
fmt.Printf("actual be %v\n", bs2)
|
|
|
|
t.Fatal("nfs.ReadDir error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// confirm rename works as expected
|
|
|
|
oldFA, _, err := target.Lookup("/f-0010.txt", false)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := target.Rename("/f-0010.txt", "/g-0010.txt"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
new, _, err := target.Lookup("/g-0010.txt", false)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if new.Sys() != oldFA.Sys() {
|
|
|
|
t.Fatal("rename failed to update")
|
|
|
|
}
|
|
|
|
_, _, err = target.Lookup("/f-0010.txt", false)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("old handle should be invalid")
|
|
|
|
}
|
|
|
|
|
|
|
|
// for test nfs.ReadDirPlus in case of empty directory
|
|
|
|
_, err = target.Mkdir("/empty", 0755)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
emptyEntitiesPlus, err := target.ReadDirPlus("/empty")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(emptyEntitiesPlus) != 0 {
|
|
|
|
t.Fatal("nfs.ReadDirPlus error reading empty dir")
|
|
|
|
}
|
|
|
|
|
|
|
|
// for test nfs.ReadDir in case of empty directory
|
|
|
|
emptyEntities, err := readDir(target, "/empty")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(emptyEntities) != 0 {
|
|
|
|
t.Fatal("nfs.ReadDir error reading empty dir")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type readDirEntry struct {
|
|
|
|
FileId uint64
|
|
|
|
FileName string
|
|
|
|
Cookie uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// readDir implementation "appropriated" from go-nfs-client implementation of READDIRPLUS
|
|
|
|
func readDir(target *nfsc.Target, dir string) ([]*readDirEntry, error) {
|
|
|
|
_, fh, err := target.Lookup(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type readDirArgs struct {
|
|
|
|
rpc.Header
|
|
|
|
Handle []byte
|
|
|
|
Cookie uint64
|
|
|
|
CookieVerif uint64
|
|
|
|
Count uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type readDirList struct {
|
|
|
|
IsSet bool `xdr:"union"`
|
|
|
|
Entry readDirEntry `xdr:"unioncase=1"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type readDirListOK struct {
|
|
|
|
DirAttrs nfsc.PostOpAttr
|
|
|
|
CookieVerf uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
cookie := uint64(0)
|
|
|
|
cookieVerf := uint64(0)
|
|
|
|
eof := false
|
|
|
|
|
|
|
|
var entries []*readDirEntry
|
|
|
|
for !eof {
|
|
|
|
res, err := target.Call(&readDirArgs{
|
|
|
|
Header: rpc.Header{
|
|
|
|
Rpcvers: 2,
|
|
|
|
Vers: nfsc.Nfs3Vers,
|
|
|
|
Prog: nfsc.Nfs3Prog,
|
|
|
|
Proc: uint32(nfs.NFSProcedureReadDir),
|
|
|
|
Cred: rpc.AuthNull,
|
|
|
|
Verf: rpc.AuthNull,
|
|
|
|
},
|
|
|
|
Handle: fh,
|
|
|
|
Cookie: cookie,
|
|
|
|
CookieVerif: cookieVerf,
|
|
|
|
Count: 4096,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
status, err := xdr.ReadUint32(res)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = nfsc.NFS3Error(status); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dirListOK := new(readDirListOK)
|
|
|
|
if err = xdr.Read(res, dirListOK); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
var item readDirList
|
|
|
|
if err = xdr.Read(res, &item); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !item.IsSet {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
cookie = item.Entry.Cookie
|
|
|
|
if item.Entry.FileName == "." || item.Entry.FileName == ".." {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
entries = append(entries, &item.Entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = xdr.Read(res, &eof); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cookieVerf = dirListOK.CookieVerf
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries, nil
|
|
|
|
}
|