From 3e81d128e6953f851769655238c254387a6da965 Mon Sep 17 00:00:00 2001 From: John Mulligan Date: Wed, 2 Sep 2020 13:51:13 -0400 Subject: [PATCH] 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 --- cephfs/admin/subvolume.go | 51 ++++++++++++ cephfs/admin/subvolume_test.go | 142 +++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) diff --git a/cephfs/admin/subvolume.go b/cephfs/admin/subvolume.go index ca6e632..d17ce32 100644 --- a/cephfs/admin/subvolume.go +++ b/cephfs/admin/subvolume.go @@ -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 --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)) +} diff --git a/cephfs/admin/subvolume_test.go b/cephfs/admin/subvolume_test.go index 57bed10..f78f1da 100644 --- a/cephfs/admin/subvolume_test.go +++ b/cephfs/admin/subvolume_test.go @@ -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()) +}