Skip to content

Commit

Permalink
cephfs/admin: API for quiesceing io on a subvolume.
Browse files Browse the repository at this point in the history
cephfs has new quiesce feature for subvolume. This change add the
feature to go-ceph.

Signed-off-by: Manish <myathnal@redhat.com>
  • Loading branch information
manishym committed Feb 21, 2024
1 parent 0679355 commit 3e11619
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
128 changes: 128 additions & 0 deletions cephfs/admin/subvolume.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package admin

import "fmt"

// this is the internal type used to create JSON for ceph.
// See SubVolumeOptions for the type that users of the library
// interact with.
Expand Down Expand Up @@ -423,3 +425,129 @@ func (fsa *FSAdmin) UnprotectSubVolumeSnapshot(volume, group, subvolume, name st
}
return fsa.marshalMgrCommand(m).FilterDeprecated().NoData().End()
}

// MyFloat is a custom type that implements the MarshalJSON interface.
// This is used to format float64 values to two decimal places.
// By default these get converted to integers in the JSON output and
// fail the command.
type MyFloat float64

// MarshalJSON provides a custom implementation for the JSON marshalling
// of MyFloat. It formats the float to two decimal places.
func (mf MyFloat) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%.2f", float64(mf))), nil
}

// SubVolumeQuiesceFields is the internal type used to create JSON for ceph.
// See SubVolumeQuiesceOptions for the type that users of the library
// interact with.
type SubVolumeQuiesceFields struct {
Prefix string `json:"prefix"`
VolName string `json:"vol_name"`
GroupName string `json:"group_name,omitempty"`
Members []string `json:"members,omitempty"`
SetId string `json:"set_id,omitempty"`
Timeout MyFloat `json:"timeout,omitempty"`
Expiration MyFloat `json:"expiration,omitempty"`
AwaitFor MyFloat `json:"await_for,omitempty"`
Await bool `json:"await,omitempty"`
IfVersion int `json:"if_version,omitempty"`
Include bool `json:"include,omitempty"`
Exclude bool `json:"exclude,omitempty"`
Reset bool `json:"reset,omitempty"`
Release bool `json:"release,omitempty"`
Query bool `json:"query,omitempty"`
All bool `json:"all,omitempty"`
Cancel bool `json:"cancel,omitempty"`
}

// SubVolumeQuiesceOptions are used to specify optional, non-identifying, values
// to be used when quiescing a subvolume.
type SubVolumeQuiesceOptions struct {
Timeout MyFloat
Expiration MyFloat
AwaitFor MyFloat
Await bool
IfVersion int
Include bool
Exclude bool
Reset bool
Release bool
Query bool
All bool
Cancel bool
}

// toFields is used to convert the SubVolumeQuiesceOptions to the internal
// SubVolumeQuiesceFields type.
func (o *SubVolumeQuiesceOptions) toFields(volume, group string, subvolumes []string, SetId string) *SubVolumeQuiesceFields {
return &SubVolumeQuiesceFields{
Prefix: "fs quiesce",
VolName: volume,
GroupName: group,
Members: subvolumes,
SetId: SetId,
Timeout: o.Timeout,
Expiration: o.Expiration,
AwaitFor: o.AwaitFor,
Await: o.Await,
IfVersion: o.IfVersion,
Include: o.Include,
Exclude: o.Exclude,
Reset: o.Reset,
Release: o.Release,
Query: o.Query,
All: o.All,
Cancel: o.Cancel,
}
}

// QuiesceInfo reports various informational values about a quiesced subvolume.
// This is returned as sets object array in the json.
type QuiesceInfo struct {
Version int `json:"version"`
AgeRef float64 `json:"age_ref"`
State struct {
Name string `json:"name"`
Age float64 `json:"age"`
} `json:"state"`
Timeout float64 `json:"timeout"`
Expiration float64 `json:"expiration"`
Members map[string]struct {
Excluded bool `json:"excluded"`
State struct {
Name string `json:"name"`
Age float64 `json:"age"`
} `json:"state"`
} `json:"members"`
}

// SubVolumeQuiesceInfo reports various informational values about a quiesced subvolume.
type SubVolumeQuiesceInfo struct {
Epoch int `json:"epoch"`
SetVersion int `json:"set_version"`
Sets map[string]QuiesceInfo `json:"sets"`
}

// parseSubVolumeQuiesceInfo is used to parse the response from the quiesce command. It returns a SubVolumeQuiesceInfo object.
func parseSubVolumeQuiesceInfo(res response) (*SubVolumeQuiesceInfo, error) {
var info SubVolumeQuiesceInfo
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
return nil, err
}
return &info, nil
}

// SubVolumeQuiesce will quiesce the specified subvolume.
// Quiescing a subvolume will prevent new writes to the subvolume.
// Similar To:
//
// ceph fs subvolume quiesce <volume>
func (fsa *FSAdmin) SubVolumeQuiesce(volume, group string, subvolumes []string, SetId string, o *SubVolumeQuiesceOptions) (*SubVolumeQuiesceInfo, error) {
if o == nil {
o = &SubVolumeQuiesceOptions{}
}
f := o.toFields(volume, group, subvolumes, SetId)

return parseSubVolumeQuiesceInfo(fsa.marshalMgrCommand(f))
}
30 changes: 30 additions & 0 deletions cephfs/admin/subvolume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,3 +688,33 @@ func TestSubVolumeSnapshotInfo(t *testing.T) {
assert.Error(t, err)
assert.Nil(t, sinfo)
}

func TestSubvolumeQuiesce(t *testing.T) {
fsa := getFSAdmin(t)
volume := "cephfs"
group := NoGroup
fsa.CreateSubVolume(volume, group, "quiesceMe", nil)
defer func() {
err := fsa.RemoveSubVolume(volume, group, "quiesceMe")
assert.NoError(t, err)
}()
ret, err := fsa.SubVolumeQuiesce(volume, group, []string{"quiesceMe"}, "", nil)
assert.NoError(t, err)
for _, val := range ret.Sets {
assert.Equal(t, 0.0, val.Timeout)
}
o := &SubVolumeQuiesceOptions{}
o.Timeout = 10.7
ret, err = fsa.SubVolumeQuiesce(volume, group, []string{"quiesceMe"}, "", o)
assert.NoError(t, err)
for _, val := range ret.Sets {
assert.Equal(t, 10.7, val.Timeout)
}
o.Expiration = 15.2
ret, err = fsa.SubVolumeQuiesce(volume, group, []string{"quiesceMe"}, "", o)
assert.NoError(t, err)
for _, val := range ret.Sets {
assert.Equal(t, 15.2, val.Expiration)
assert.Equal(t, 10.7, val.Timeout)
}
}

0 comments on commit 3e11619

Please sign in to comment.