cephfs admin: add a SubVolumeInfo function

Add a SubVolumeInfo function that works like the `ceph fs subvolume
info` command. This function returns a SubVolumeInfo type from parsing
the response JSON.

In order to re-use the existing QuotaSize type, which is an interface
and thus can't be unmarshaled directly, we use a non-exported wrapper
type for the unmarshaling and then we fix up the BytesQuota field so
that users of the library don't need to directly deal with the quota
size placeholder type, which can unmarshal the value.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-09-02 13:51:13 -04:00 committed by John Mulligan
parent ed23d71ff8
commit 3e81d128e6
2 changed files with 193 additions and 0 deletions

View File

@ -160,3 +160,54 @@ func (fsa *FSAdmin) SubVolumePath(volume, group, name string) (string, error) {
}
return extractPathResponse(fsa.marshalMgrCommand(m))
}
// SubVolumeInfo reports various informational values about a subvolume.
type SubVolumeInfo struct {
Type string `json:"type"`
Path string `json:"path"`
Uid int `json:"uid"`
Gid int `json:"gid"`
Mode int `json:"mode"`
BytesPercent string `json:"bytes_pcent"`
BytesUsed ByteCount `json:"bytes_used"`
BytesQuota QuotaSize `json:"-"`
DataPool string `json:"data_pool"`
PoolNamespace string `json:"pool_namespace"`
Atime TimeStamp `json:"atime"`
Mtime TimeStamp `json:"mtime"`
Ctime TimeStamp `json:"ctime"`
CreatedAt TimeStamp `json:"created_at"`
}
type subVolumeInfoWrapper struct {
SubVolumeInfo
VBytesQuota *quotaSizePlaceholder `json:"bytes_quota"`
}
func parseSubVolumeInfo(r []byte, s string, err error) (*SubVolumeInfo, error) {
var info subVolumeInfoWrapper
if err := unmarshalResponseJSON(r, s, err, &info); err != nil {
return nil, err
}
if info.VBytesQuota != nil {
info.BytesQuota = info.VBytesQuota.Value
}
return &info.SubVolumeInfo, nil
}
// SubVolumeInfo returns information about the specified subvolume.
//
// Similar To:
// ceph fs subvolume info <volume> --group-name=<group> <name>
func (fsa *FSAdmin) SubVolumeInfo(volume, group, name string) (*SubVolumeInfo, error) {
m := map[string]string{
"prefix": "fs subvolume info",
"vol_name": volume,
"sub_name": name,
"format": "json",
}
if group != NoGroup {
m["group_name"] = group
}
return parseSubVolumeInfo(fsa.marshalMgrCommand(m))
}

View File

@ -3,6 +3,7 @@
package admin
import (
"errors"
"testing"
"time"
@ -205,3 +206,144 @@ func TestSubVolumePath(t *testing.T) {
assert.Error(t, err)
assert.Equal(t, "", path)
}
var sampleSubVolumeInfo1 = []byte(`
{
"atime": "2020-08-31 19:53:43",
"bytes_pcent": "undefined",
"bytes_quota": "infinite",
"bytes_used": 0,
"created_at": "2020-08-31 19:53:43",
"ctime": "2020-08-31 19:57:15",
"data_pool": "cephfs_data",
"gid": 0,
"mode": 16877,
"mon_addrs": [
"127.0.0.1:6789"
],
"mtime": "2020-08-31 19:53:43",
"path": "/volumes/_nogroup/nibbles/df11be81-a648-4a7b-8549-f28306e3ad93",
"pool_namespace": "",
"type": "subvolume",
"uid": 0
}
`)
var sampleSubVolumeInfo2 = []byte(`
{
"atime": "2020-09-01 17:49:25",
"bytes_pcent": "0.00",
"bytes_quota": 444444,
"bytes_used": 0,
"created_at": "2020-09-01 17:49:25",
"ctime": "2020-09-01 23:49:22",
"data_pool": "cephfs_data",
"gid": 0,
"mode": 16877,
"mon_addrs": [
"127.0.0.1:6789"
],
"mtime": "2020-09-01 17:49:25",
"path": "/volumes/_nogroup/nibbles/d6e062df-7fa0-46ca-872a-9adf728e0e00",
"pool_namespace": "",
"type": "subvolume",
"uid": 0
}
`)
var badSampleSubVolumeInfo1 = []byte(`
{
"bytes_quota": "fishy",
"uid": 0
}
`)
var badSampleSubVolumeInfo2 = []byte(`
{
"bytes_quota": true,
"uid": 0
}
`)
func TestParseSubVolumeInfo(t *testing.T) {
t.Run("error", func(t *testing.T) {
_, err := parseSubVolumeInfo(nil, "", errors.New("gleep glop"))
assert.Error(t, err)
assert.Equal(t, "gleep glop", err.Error())
})
t.Run("statusSet", func(t *testing.T) {
_, err := parseSubVolumeInfo(nil, "unexpected!", nil)
assert.Error(t, err)
})
t.Run("ok", func(t *testing.T) {
info, err := parseSubVolumeInfo(sampleSubVolumeInfo1, "", nil)
assert.NoError(t, err)
if assert.NotNil(t, info) {
assert.Equal(t,
"/volumes/_nogroup/nibbles/df11be81-a648-4a7b-8549-f28306e3ad93",
info.Path)
assert.Equal(t, 0, info.Uid)
assert.Equal(t, Infinite, info.BytesQuota)
assert.Equal(t, 040755, info.Mode)
assert.Equal(t, 2020, info.Ctime.Year())
assert.Equal(t, "2020-08-31 19:57:15", info.Ctime.String())
}
})
t.Run("ok2", func(t *testing.T) {
info, err := parseSubVolumeInfo(sampleSubVolumeInfo2, "", nil)
assert.NoError(t, err)
if assert.NotNil(t, info) {
assert.Equal(t,
"/volumes/_nogroup/nibbles/d6e062df-7fa0-46ca-872a-9adf728e0e00",
info.Path)
assert.Equal(t, 0, info.Uid)
assert.Equal(t, ByteCount(444444), info.BytesQuota)
assert.Equal(t, 040755, info.Mode)
assert.Equal(t, 2020, info.Ctime.Year())
assert.Equal(t, "2020-09-01 23:49:22", info.Ctime.String())
}
})
t.Run("invalidBytesQuotaValue", func(t *testing.T) {
info, err := parseSubVolumeInfo(badSampleSubVolumeInfo1, "", nil)
assert.Error(t, err)
assert.Nil(t, info)
})
t.Run("invalidBytesQuotaType", func(t *testing.T) {
info, err := parseSubVolumeInfo(badSampleSubVolumeInfo2, "", nil)
assert.Error(t, err)
assert.Nil(t, info)
})
}
func TestSubVolumeInfo(t *testing.T) {
fsa := getFSAdmin(t)
volume := "cephfs"
group := "hoagie"
subname := "grinder"
err := fsa.CreateSubVolumeGroup(volume, group, nil)
assert.NoError(t, err)
defer func() {
err := fsa.RemoveSubVolumeGroup(volume, group)
assert.NoError(t, err)
}()
svopts := &SubVolumeOptions{
Mode: 0750,
Size: 20 * gibiByte,
}
err = fsa.CreateSubVolume(volume, group, subname, svopts)
assert.NoError(t, err)
defer func() {
err := fsa.RemoveSubVolume(volume, group, subname)
assert.NoError(t, err)
}()
vinfo, err := fsa.SubVolumeInfo(volume, group, subname)
assert.NoError(t, err)
assert.NotNil(t, vinfo)
assert.Equal(t, 0, vinfo.Uid)
assert.Equal(t, 20*gibiByte, vinfo.BytesQuota)
assert.Equal(t, 040750, vinfo.Mode)
assert.GreaterOrEqual(t, 2020, vinfo.Ctime.Year())
}