cephfs admin: add functions to create, list, & remove subvolumes

Adds functions and tests for:
* CreateSubVolume
* ListSubVolumes
* RemoveSubVolume

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-08-04 17:00:16 -04:00 committed by John Mulligan
parent 5e6eec6a1a
commit fd10d9b756
3 changed files with 239 additions and 0 deletions

12
cephfs/admin/bytecount.go Normal file
View File

@ -0,0 +1,12 @@
package admin
// ByteCount represents the size of a volume in bytes.
type ByteCount uint64
// SI byte size constants. keep these private for now.
const (
kibiByte ByteCount = 1024
mebiByte = 1024 * kibiByte
gibiByte = 1024 * mebiByte
tebiByte = 1024 * gibiByte
)

95
cephfs/admin/subvolume.go Normal file
View File

@ -0,0 +1,95 @@
package admin
// this is the internal type used to create JSON for ceph.
// See SubVolumeOptions for the type that users of the library
// interact with.
// note that the ceph json takes mode as a string.
type subVolumeFields struct {
Prefix string `json:"prefix"`
Format string `json:"format"`
VolName string `json:"vol_name"`
GroupName string `json:"group_name,omitempty"`
SubName string `json:"sub_name"`
Size ByteCount `json:"size,omitempty"`
Uid int `json:"uid,omitempty"`
Gid int `json:"gid,omitempty"`
Mode string `json:"mode,omitempty"`
PoolLayout string `json:"pool_layout,omitempty"`
NamespaceIsolated bool `json:"namespace_isolated"`
}
// SubVolumeOptions are used to specify optional, non-identifying, values
// to be used when creating a new subvolume.
type SubVolumeOptions struct {
Size ByteCount
Uid int
Gid int
Mode int
PoolLayout string
NamespaceIsolated bool
}
func (s *SubVolumeOptions) toFields(v, g, n string) *subVolumeFields {
return &subVolumeFields{
Prefix: "fs subvolume create",
Format: "json",
VolName: v,
GroupName: g,
SubName: n,
Size: s.Size,
Uid: s.Uid,
Gid: s.Gid,
Mode: modeString(s.Mode, false),
PoolLayout: s.PoolLayout,
NamespaceIsolated: s.NamespaceIsolated,
}
}
// NoGroup should be used when an optional subvolume group name is not
// specified.
const NoGroup = ""
// CreateSubVolume sends a request to create a CephFS subvolume in a volume,
// belonging to an optional subvolume group.
func (fsa *FSAdmin) CreateSubVolume(volume, group, name string, o *SubVolumeOptions) error {
if o == nil {
o = &SubVolumeOptions{}
}
f := o.toFields(volume, group, name)
return checkEmptyResponseExpected(fsa.marshalMgrCommand(f))
}
// command:
// fs subvolume ls <vol_name> <group_name>
// ListSubVolumes returns a list of subvolumes belonging to the volume and
// optional subvolume group.
func (fsa *FSAdmin) ListSubVolumes(volume, group string) ([]string, error) {
m := map[string]string{
"prefix": "fs subvolume ls",
"vol_name": volume,
"format": "json",
}
if group != NoGroup {
m["group_name"] = group
}
return parseListNames(fsa.marshalMgrCommand(m))
}
// command:
// fs subvolume rm <vol_name> <sub_name> <group_name> <force>
// RemoveSubVolume will delete a CephFS subvolume in a volume and optional
// subvolume group.
func (fsa *FSAdmin) RemoveSubVolume(volume, group, name string) error {
m := map[string]string{
"prefix": "fs subvolume rm",
"vol_name": volume,
"sub_name": name,
"format": "json",
}
if group != NoGroup {
m["group_name"] = group
}
return checkEmptyResponseExpected(fsa.marshalMgrCommand(m))
}

View File

@ -0,0 +1,132 @@
package admin
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func delay() {
// ceph seems to do this (partly?) async. So for now, we cheat
// and sleep a little to make subsequent tests more reliable
time.Sleep(50 * time.Millisecond)
}
func TestCreateSubVolume(t *testing.T) {
fsa := getFSAdmin(t)
volume := "cephfs"
type gn struct {
group string
name string
}
created := []gn{}
defer func() {
for _, c := range created {
err := fsa.RemoveSubVolume(volume, c.group, c.name)
assert.NoError(t, err)
delay()
if c.group != NoGroup {
err := fsa.RemoveSubVolumeGroup(volume, c.group)
assert.NoError(t, err)
}
}
}()
t.Run("simple", func(t *testing.T) {
subname := "SubVol1"
err := fsa.CreateSubVolume(volume, NoGroup, subname, nil)
assert.NoError(t, err)
created = append(created, gn{NoGroup, subname})
lsv, err := fsa.ListSubVolumes(volume, NoGroup)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(lsv), 1)
assert.Contains(t, lsv, subname)
})
t.Run("options", func(t *testing.T) {
subname := "SubVol2"
o := &SubVolumeOptions{
Mode: 0777,
Uid: 200,
Gid: 200,
}
err := fsa.CreateSubVolume(volume, NoGroup, subname, o)
assert.NoError(t, err)
created = append(created, gn{NoGroup, subname})
lsv, err := fsa.ListSubVolumes(volume, NoGroup)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(lsv), 1)
assert.Contains(t, lsv, subname)
})
t.Run("withGroup", func(t *testing.T) {
group := "withGroup1"
subname := "SubVol3"
err := fsa.CreateSubVolumeGroup(volume, group, nil)
assert.NoError(t, err)
err = fsa.CreateSubVolume(volume, group, subname, nil)
assert.NoError(t, err)
created = append(created, gn{group, subname})
lsv, err := fsa.ListSubVolumes(volume, group)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(lsv), 1)
assert.Contains(t, lsv, subname)
})
t.Run("groupAndOptions", func(t *testing.T) {
group := "withGroup2"
subname := "SubVol4"
err := fsa.CreateSubVolumeGroup(volume, group, nil)
assert.NoError(t, err)
o := &SubVolumeOptions{
Size: 5 * gibiByte,
Mode: 0777,
Uid: 200,
Gid: 200,
}
err = fsa.CreateSubVolume(volume, group, subname, o)
assert.NoError(t, err)
created = append(created, gn{group, subname})
lsv, err := fsa.ListSubVolumes(volume, group)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(lsv), 1)
assert.Contains(t, lsv, subname)
})
}
func TestRemoveSubVolume(t *testing.T) {
fsa := getFSAdmin(t)
volume := "cephfs"
lsv, err := fsa.ListSubVolumes(volume, NoGroup)
assert.NoError(t, err)
beforeCount := len(lsv)
err = fsa.CreateSubVolume(volume, NoGroup, "deletemev1", nil)
assert.NoError(t, err)
lsv, err = fsa.ListSubVolumes(volume, NoGroup)
assert.NoError(t, err)
afterCount := len(lsv)
assert.Equal(t, beforeCount, afterCount-1)
err = fsa.RemoveSubVolume(volume, NoGroup, "deletemev1")
assert.NoError(t, err)
delay()
lsv, err = fsa.ListSubVolumes(volume, NoGroup)
assert.NoError(t, err)
nowCount := len(lsv)
if !assert.Equal(t, beforeCount, nowCount) {
// this is a hack for debugging a flapping test
assert.Equal(t, []string{}, lsv)
}
}