2022-10-17 16:50:20 +00:00
|
|
|
//go:build !(nautilus || octopus)
|
|
|
|
// +build !nautilus,!octopus
|
2022-02-22 17:03:00 +00:00
|
|
|
|
|
|
|
package nfs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2022-03-10 19:45:39 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
2022-02-22 17:03:00 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
tsuite "github.com/stretchr/testify/suite"
|
|
|
|
|
2023-04-03 20:59:17 +00:00
|
|
|
"github.com/ceph/go-ceph/cephfs"
|
2022-02-22 17:03:00 +00:00
|
|
|
"github.com/ceph/go-ceph/internal/admintest"
|
|
|
|
"github.com/ceph/go-ceph/internal/commands"
|
2022-03-10 19:45:39 +00:00
|
|
|
"github.com/ceph/go-ceph/rados"
|
2022-02-22 17:03:00 +00:00
|
|
|
)
|
|
|
|
|
2023-04-03 20:59:17 +00:00
|
|
|
var (
|
|
|
|
radosConnector = admintest.NewConnector()
|
|
|
|
usableDirNames = []string{"january", "february", "march", "sept"}
|
|
|
|
)
|
2022-02-22 17:03:00 +00:00
|
|
|
|
|
|
|
func TestNFSAdmin(t *testing.T) {
|
|
|
|
tsuite.Run(t, new(NFSAdminSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
// NFSAdminSuite is a suite of tests for the nfs admin package.
|
|
|
|
// A suite is used because creating/managing NFS has certain expectations for
|
|
|
|
// the cluster that we may need to mock, especially when running in the
|
|
|
|
// standard go-ceph test container. Using a suite allows us to have suite
|
|
|
|
// setup/validate the environment and the tests can largely be ignorant of the
|
|
|
|
// environment.
|
|
|
|
type NFSAdminSuite struct {
|
|
|
|
tsuite.Suite
|
|
|
|
|
|
|
|
fileSystemName string
|
|
|
|
clusterID string
|
2022-03-10 19:45:39 +00:00
|
|
|
mockConfig bool
|
2022-02-22 17:03:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) SetupSuite() {
|
|
|
|
suite.fileSystemName = "cephfs"
|
|
|
|
suite.clusterID = "goceph"
|
2022-03-10 19:45:39 +00:00
|
|
|
suite.mockConfig = true
|
|
|
|
|
|
|
|
mock := os.Getenv("GO_CEPH_TEST_MOCK_NFS")
|
|
|
|
if ok, err := strconv.ParseBool(mock); err == nil {
|
|
|
|
suite.mockConfig = ok
|
|
|
|
}
|
|
|
|
|
|
|
|
if suite.mockConfig {
|
|
|
|
suite.setupMockNFSConfig()
|
|
|
|
}
|
2023-04-03 20:59:17 +00:00
|
|
|
suite.setupDirs()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) TearDownSuite() {
|
|
|
|
suite.removeDirs()
|
2022-03-10 19:45:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) setupMockNFSConfig() {
|
|
|
|
require := suite.Require()
|
|
|
|
conn := radosConnector.GetConn(suite.T())
|
|
|
|
|
|
|
|
err := conn.MakePool(".nfs")
|
|
|
|
if err != nil && !errors.Is(err, rados.ErrObjectExists) {
|
|
|
|
suite.T().Fatalf("failed to make pool: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ioctx, err := conn.OpenIOContext(".nfs")
|
|
|
|
require.NoError(err)
|
|
|
|
defer ioctx.Destroy()
|
|
|
|
|
|
|
|
ioctx.SetNamespace(suite.clusterID)
|
|
|
|
|
|
|
|
err = ioctx.Create(
|
|
|
|
fmt.Sprintf("conf-nfs.%s", suite.clusterID),
|
|
|
|
rados.CreateIdempotent)
|
|
|
|
require.NoError(err)
|
2022-02-22 17:03:00 +00:00
|
|
|
}
|
|
|
|
|
2023-04-03 20:59:17 +00:00
|
|
|
func (suite *NFSAdminSuite) setupDirs() {
|
|
|
|
require := suite.Require()
|
|
|
|
conn := radosConnector.GetConn(suite.T())
|
|
|
|
fs, err := cephfs.CreateFromRados(conn)
|
|
|
|
require.NoError(err)
|
|
|
|
defer func() {
|
|
|
|
require.NoError(fs.Release())
|
|
|
|
}()
|
|
|
|
err = fs.Mount()
|
|
|
|
require.NoError(err)
|
|
|
|
defer func() {
|
|
|
|
require.NoError(fs.Unmount())
|
|
|
|
}()
|
|
|
|
|
|
|
|
// establish a "random" list of dir names to work with
|
|
|
|
for _, name := range usableDirNames {
|
|
|
|
err = fs.MakeDir(name, 0777)
|
|
|
|
require.NoError(err, "failed to make dir "+name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) removeDirs() {
|
|
|
|
require := suite.Require()
|
|
|
|
conn := radosConnector.GetConn(suite.T())
|
|
|
|
fs, err := cephfs.CreateFromRados(conn)
|
|
|
|
require.NoError(err)
|
|
|
|
defer func() {
|
|
|
|
require.NoError(fs.Release())
|
|
|
|
}()
|
|
|
|
err = fs.Mount()
|
|
|
|
require.NoError(err)
|
|
|
|
defer func() {
|
|
|
|
require.NoError(fs.Unmount())
|
|
|
|
}()
|
|
|
|
|
|
|
|
// establish a "random" list of dir names to work with
|
|
|
|
for _, name := range usableDirNames {
|
|
|
|
err = fs.RemoveDir(name)
|
|
|
|
require.NoError(err, "failed to remove dir "+name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-22 17:03:00 +00:00
|
|
|
func (suite *NFSAdminSuite) TestCreateDeleteCephFSExport() {
|
|
|
|
require := suite.Require()
|
|
|
|
ra := radosConnector.Get(suite.T())
|
|
|
|
nfsa := NewFromConn(ra)
|
|
|
|
|
|
|
|
res, err := nfsa.CreateCephFSExport(CephFSExportSpec{
|
|
|
|
FileSystemName: suite.fileSystemName,
|
|
|
|
ClusterID: suite.clusterID,
|
|
|
|
PseudoPath: "/cheese",
|
|
|
|
})
|
|
|
|
require.NoError(err)
|
|
|
|
require.Equal("/cheese", res.Bind)
|
|
|
|
|
|
|
|
err = nfsa.RemoveExport(suite.clusterID, "/cheese")
|
|
|
|
require.NoError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) TestListDetailedExports() {
|
|
|
|
require := suite.Require()
|
|
|
|
ra := radosConnector.Get(suite.T())
|
|
|
|
nfsa := NewFromConn(ra)
|
|
|
|
|
|
|
|
_, err := nfsa.CreateCephFSExport(CephFSExportSpec{
|
|
|
|
FileSystemName: suite.fileSystemName,
|
|
|
|
ClusterID: suite.clusterID,
|
|
|
|
PseudoPath: "/01",
|
|
|
|
Path: "/january",
|
|
|
|
})
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err = nfsa.RemoveExport(suite.clusterID, "/01")
|
|
|
|
require.NoError(err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
_, err = nfsa.CreateCephFSExport(CephFSExportSpec{
|
|
|
|
FileSystemName: suite.fileSystemName,
|
|
|
|
ClusterID: suite.clusterID,
|
|
|
|
PseudoPath: "/02",
|
|
|
|
Path: "/february",
|
|
|
|
})
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err = nfsa.RemoveExport(suite.clusterID, "/02")
|
|
|
|
require.NoError(err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
l, err := nfsa.ListDetailedExports(suite.clusterID)
|
|
|
|
require.NoError(err)
|
|
|
|
require.Len(l, 2)
|
|
|
|
var e1, e2 ExportInfo
|
|
|
|
for _, e := range l {
|
|
|
|
if e.PseudoPath == "/01" {
|
|
|
|
e1 = e
|
|
|
|
}
|
|
|
|
if e.PseudoPath == "/02" {
|
|
|
|
e2 = e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(e1.PseudoPath, "/01")
|
|
|
|
require.Equal(e2.PseudoPath, "/02")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *NFSAdminSuite) TestExportInfo() {
|
|
|
|
require := suite.Require()
|
|
|
|
ra := radosConnector.Get(suite.T())
|
|
|
|
nfsa := NewFromConn(ra)
|
|
|
|
|
|
|
|
_, err := nfsa.CreateCephFSExport(CephFSExportSpec{
|
|
|
|
FileSystemName: suite.fileSystemName,
|
|
|
|
ClusterID: suite.clusterID,
|
|
|
|
PseudoPath: "/03",
|
|
|
|
Path: "/march",
|
|
|
|
})
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err = nfsa.RemoveExport(suite.clusterID, "/03")
|
|
|
|
require.NoError(err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
e1, err := nfsa.ExportInfo(suite.clusterID, "/03")
|
|
|
|
require.NoError(err)
|
|
|
|
require.Equal(e1.PseudoPath, "/03")
|
|
|
|
|
|
|
|
_, err = nfsa.ExportInfo(suite.clusterID, "/88")
|
|
|
|
require.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
const resultExport1 = `{
|
|
|
|
"bind": "/cheese",
|
|
|
|
"fs": "cephfs",
|
|
|
|
"path": "/",
|
|
|
|
"cluster": "foobar",
|
|
|
|
"mode": "RW"
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
func TestParseExportResult(t *testing.T) {
|
|
|
|
t.Run("resultExport", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(resultExport1), "", nil)
|
|
|
|
e, err := parseExportResult(r)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, e.Bind, "/cheese")
|
|
|
|
})
|
|
|
|
t.Run("errorSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "", errors.New("beep"))
|
|
|
|
_, err := parseExportResult(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
t.Run("statusSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "boo", nil)
|
|
|
|
_, err := parseExportResult(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// # ceph nfs export ls --cluster-id foobar --detailed
|
|
|
|
const exportList1 = `[
|
|
|
|
{
|
|
|
|
"export_id": 1,
|
|
|
|
"path": "/",
|
|
|
|
"cluster_id": "foobar",
|
|
|
|
"pseudo": "/cheese",
|
|
|
|
"access_type": "RW",
|
|
|
|
"squash": "none",
|
|
|
|
"security_label": true,
|
|
|
|
"protocols": [
|
|
|
|
4
|
|
|
|
],
|
|
|
|
"transports": [
|
|
|
|
"TCP"
|
|
|
|
],
|
|
|
|
"fsal": {
|
|
|
|
"name": "CEPH",
|
|
|
|
"user_id": "nfs.foobar.1",
|
|
|
|
"fs_name": "cephfs"
|
|
|
|
},
|
|
|
|
"clients": []
|
|
|
|
}
|
|
|
|
]`
|
|
|
|
|
|
|
|
func TestParseExportsList(t *testing.T) {
|
|
|
|
t.Run("exportList1", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(exportList1), "", nil)
|
|
|
|
l, err := parseExportsList(r)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if assert.Len(t, l, 1) {
|
|
|
|
e := l[0]
|
|
|
|
assert.Equal(t, e.Path, "/")
|
|
|
|
assert.Equal(t, e.PseudoPath, "/cheese")
|
|
|
|
if assert.Len(t, e.Protocols, 1) {
|
|
|
|
assert.Equal(t, e.Protocols[0], 4)
|
|
|
|
}
|
|
|
|
if assert.Len(t, e.Transports, 1) {
|
|
|
|
assert.Equal(t, e.Transports[0], "TCP")
|
|
|
|
}
|
|
|
|
assert.Equal(t, e.FSAL.Name, "CEPH")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("errorSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "", errors.New("beep"))
|
|
|
|
_, err := parseExportsList(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
t.Run("statusSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "boo", nil)
|
|
|
|
_, err := parseExportsList(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// # ceph nfs export info --cluster-id foobar --pseudo-path /cheese
|
|
|
|
const exportInfo1 = `{
|
|
|
|
"export_id": 1,
|
|
|
|
"path": "/",
|
|
|
|
"cluster_id": "foobar",
|
|
|
|
"pseudo": "/cheese",
|
|
|
|
"access_type": "RW",
|
|
|
|
"squash": "none",
|
|
|
|
"security_label": true,
|
|
|
|
"protocols": [
|
|
|
|
4
|
|
|
|
],
|
|
|
|
"transports": [
|
|
|
|
"TCP"
|
|
|
|
],
|
|
|
|
"fsal": {
|
|
|
|
"name": "CEPH",
|
|
|
|
"user_id": "nfs.foobar.1",
|
|
|
|
"fs_name": "cephfs"
|
|
|
|
},
|
|
|
|
"clients": []
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
func TestParseExportInfo(t *testing.T) {
|
|
|
|
t.Run("exportInfo1", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(exportInfo1), "", nil)
|
|
|
|
e, err := parseExportInfo(r)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, e.Path, "/")
|
|
|
|
assert.Equal(t, e.PseudoPath, "/cheese")
|
|
|
|
if assert.Len(t, e.Protocols, 1) {
|
|
|
|
assert.Equal(t, e.Protocols[0], 4)
|
|
|
|
}
|
|
|
|
if assert.Len(t, e.Transports, 1) {
|
|
|
|
assert.Equal(t, e.Transports[0], "TCP")
|
|
|
|
}
|
|
|
|
assert.Equal(t, e.FSAL.Name, "CEPH")
|
|
|
|
})
|
|
|
|
t.Run("errorSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "", errors.New("beep"))
|
|
|
|
_, err := parseExportInfo(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
t.Run("statusSet", func(t *testing.T) {
|
|
|
|
r := commands.NewResponse([]byte(""), "boo", nil)
|
|
|
|
_, err := parseExportInfo(r)
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
}
|