cephfs admin: flexibly decode the MDSVersion field in volume status

It appears the JSON being returned from `ceph fs status` has changed.
The field returning the mds version was previously "just" a string.
Now its a more complex thing. This of course breaks the old version
of the go code which is statically typed to expect a string.

In order not to break backwards compatibility we replace the object
used for parsing the JSON with a private object that has a custom
unmarshaller function for the mds version field. If it sees the string
it uses the string. Otherwise, it looks for this new structure and,
if possible, extracts the version string from the first item.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2022-03-18 17:14:19 -04:00 committed by mergify[bot]
parent ac1401b4e2
commit ae44f69791
2 changed files with 55 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package admin
import (
"bytes"
"encoding/json"
)
var (
@ -114,8 +115,40 @@ type VolumeStatus struct {
Pools []VolumePool `json:"pools"`
}
func parseVolumeStatus(res response) (*VolumeStatus, error) {
var vs VolumeStatus
type mdsVersionField struct {
Version string
Items []struct {
Version string `json:"version"`
}
}
func (m *mdsVersionField) UnmarshalJSON(data []byte) (err error) {
if err = json.Unmarshal(data, &m.Version); err == nil {
return
}
return json.Unmarshal(data, &m.Items)
}
// volumeStatusResponse deals with the changing output of the mgr
// api json
type volumeStatusResponse struct {
Pools []VolumePool `json:"pools"`
MDSVersion mdsVersionField `json:"mds_version"`
}
func (v *volumeStatusResponse) volumeStatus() *VolumeStatus {
vstatus := &VolumeStatus{}
vstatus.Pools = v.Pools
if v.MDSVersion.Version != "" {
vstatus.MDSVersion = v.MDSVersion.Version
} else if len(v.MDSVersion.Items) > 0 {
vstatus.MDSVersion = v.MDSVersion.Items[0].Version
}
return vstatus
}
func parseVolumeStatus(res response) (*volumeStatusResponse, error) {
var vs volumeStatusResponse
res = res.NoStatus()
if !res.Ok() {
return nil, res.End()
@ -142,5 +175,9 @@ func (fsa *FSAdmin) VolumeStatus(name string) (*VolumeStatus, error) {
"prefix": "fs status",
"format": "json",
})
return parseVolumeStatus(res)
v, err := parseVolumeStatus(res)
if err != nil {
return nil, err
}
return v.volumeStatus(), nil
}

View File

@ -208,6 +208,10 @@ var sampleVolumeStatus1 = []byte(`
}
`)
var sampleVolumeStatusQ = []byte(`
{"clients": [{"clients": 3, "fs": "cephfs"}], "mds_version": [{"daemon": ["Z"], "version": "ceph version 17.1.0 (c675060073a05d40ef404d5921c81178a52af6e0) quincy (dev)"}], "mdsmap": [{"caps": 11, "dirs": 26, "dns": 49, "inos": 30, "name": "Z", "rank": 0, "rate": 0, "state": "active"}], "pools": [{"avail": 1018405056, "id": 2, "name": "cephfs_metadata", "type": "metadata", "used": 467690}, {"avail": 1018405056, "id": 1, "name": "cephfs_data", "type": "data", "used": 8}]}
`)
var sampleVolumeStatusTextJunk = []byte(`cephfs - 2 clients
======
+------+--------+-----+---------------+-------+-------+
@ -244,8 +248,9 @@ func TestParseVolumeStatus(t *testing.T) {
assert.Error(t, err)
})
t.Run("ok", func(t *testing.T) {
s, err := parseVolumeStatus(R(sampleVolumeStatus1, "", nil))
v, err := parseVolumeStatus(R(sampleVolumeStatus1, "", nil))
assert.NoError(t, err)
s := v.volumeStatus()
if assert.NotNil(t, s) {
assert.Contains(t, s.MDSVersion, "ceph version 15.2.4")
assert.Contains(t, s.MDSVersion, "octopus")
@ -258,6 +263,15 @@ func TestParseVolumeStatus(t *testing.T) {
assert.True(t, errors.As(err, &notImpl))
}
})
t.Run("quincy", func(t *testing.T) {
v, err := parseVolumeStatus(R(sampleVolumeStatusQ, "", nil))
assert.NoError(t, err)
s := v.volumeStatus()
if assert.NotNil(t, s) {
assert.Contains(t, s.MDSVersion, "ceph version 17.1.0")
assert.Contains(t, s.MDSVersion, "quincy")
}
})
}