mirror of https://github.com/ceph/go-ceph
cephfs admin: add basic functionality for clone support
I think I'm a clone now Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
parent
beb3351f24
commit
07722a0111
|
@ -0,0 +1,111 @@
|
||||||
|
// +build !luminous,!mimic
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
// CloneOptions are used to specify optional values to be used when creating a
|
||||||
|
// new subvolume clone.
|
||||||
|
type CloneOptions struct {
|
||||||
|
TargetGroup string
|
||||||
|
PoolLayout string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneSubVolumeSnapshot Clones the specified snapshot from the subvolume.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
// ceph fs subvolume snapshot clone <volume> <subvolume> <snapshot> <name>
|
||||||
|
func (fsa *FSAdmin) CloneSubVolumeSnapshot(volume, group, subvolume, snapshot, name string, o *CloneOptions) error {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs subvolume snapshot clone",
|
||||||
|
"vol_name": volume,
|
||||||
|
"sub_name": subvolume,
|
||||||
|
"snap_name": snapshot,
|
||||||
|
"target_sub_name": name,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
if o != nil && o.TargetGroup != NoGroup {
|
||||||
|
m["target_group_name"] = group
|
||||||
|
}
|
||||||
|
if o != nil && o.PoolLayout != "" {
|
||||||
|
m["pool_layout"] = o.PoolLayout
|
||||||
|
}
|
||||||
|
return checkEmptyResponseExpected(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneState is used to define constant values used to determine the state of
|
||||||
|
// a clone.
|
||||||
|
type CloneState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ClonePending is the state of a pending clone.
|
||||||
|
ClonePending = CloneState("pending")
|
||||||
|
// CloneInProgress is the state of a clone in progress.
|
||||||
|
CloneInProgress = CloneState("in-progress")
|
||||||
|
// CloneComplete is the state of a complete clone.
|
||||||
|
CloneComplete = CloneState("complete")
|
||||||
|
// CloneFailed is the state of a failed clone.
|
||||||
|
CloneFailed = CloneState("failed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloneSource contains values indicating the source of a clone.
|
||||||
|
type CloneSource struct {
|
||||||
|
Volume string `json:"volume"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
SubVolume string `json:"subvolume"`
|
||||||
|
Snapshot string `json:"snapshot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneStatus reports on the status of a subvolume clone.
|
||||||
|
type CloneStatus struct {
|
||||||
|
State CloneState `json:"state"`
|
||||||
|
Source CloneSource `json:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloneStatusWrapper struct {
|
||||||
|
Status CloneStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCloneStatus(r []byte, s string, err error) (*CloneStatus, error) {
|
||||||
|
var status cloneStatusWrapper
|
||||||
|
if err := unmarshalResponseJSON(r, s, err, &status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &status.Status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneStatus returns data reporting the status of a subvolume clone.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
// ceph fs clone status <volume> --group_name=<group> <clone>
|
||||||
|
func (fsa *FSAdmin) CloneStatus(volume, group, clone string) (*CloneStatus, error) {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs clone status",
|
||||||
|
"vol_name": volume,
|
||||||
|
"clone_name": clone,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
return parseCloneStatus(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelClone stops the background processes that populate a clone.
|
||||||
|
// CancelClone does not delete the clone.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
// ceph fs clone cancel <volume> --group_name=<group> <clone>
|
||||||
|
func (fsa *FSAdmin) CancelClone(volume, group, clone string) error {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs clone cancel",
|
||||||
|
"vol_name": volume,
|
||||||
|
"clone_name": clone,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
return checkEmptyResponseExpected(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
// +build !luminous,!mimic
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sampleCloneStatusPending = []byte(`{
|
||||||
|
"status": {
|
||||||
|
"state": "pending",
|
||||||
|
"source": {
|
||||||
|
"volume": "cephfs",
|
||||||
|
"subvolume": "jurrasic",
|
||||||
|
"snapshot": "dinodna",
|
||||||
|
"group": "park"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var sampleCloneStatusInProg = []byte(`{
|
||||||
|
"status": {
|
||||||
|
"state": "in-progress",
|
||||||
|
"source": {
|
||||||
|
"volume": "cephfs",
|
||||||
|
"subvolume": "subvol1",
|
||||||
|
"snapshot": "snap1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
func TestParseCloneStatus(t *testing.T) {
|
||||||
|
t.Run("error", func(t *testing.T) {
|
||||||
|
_, err := parseCloneStatus(nil, "", errors.New("flub"))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "flub", err.Error())
|
||||||
|
})
|
||||||
|
t.Run("statusSet", func(t *testing.T) {
|
||||||
|
_, err := parseCloneStatus(nil, "unexpected!", nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("badJSON", func(t *testing.T) {
|
||||||
|
_, err := parseCloneStatus([]byte("_XxXxX"), "", nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("okPending", func(t *testing.T) {
|
||||||
|
status, err := parseCloneStatus(sampleCloneStatusPending, "", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, status) {
|
||||||
|
assert.EqualValues(t, ClonePending, status.State)
|
||||||
|
assert.EqualValues(t, "cephfs", status.Source.Volume)
|
||||||
|
assert.EqualValues(t, "jurrasic", status.Source.SubVolume)
|
||||||
|
assert.EqualValues(t, "dinodna", status.Source.Snapshot)
|
||||||
|
assert.EqualValues(t, "park", status.Source.Group)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("okInProg", func(t *testing.T) {
|
||||||
|
status, err := parseCloneStatus(sampleCloneStatusInProg, "", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, status) {
|
||||||
|
assert.EqualValues(t, CloneInProgress, status.State)
|
||||||
|
assert.EqualValues(t, "cephfs", status.Source.Volume)
|
||||||
|
assert.EqualValues(t, "subvol1", status.Source.SubVolume)
|
||||||
|
assert.EqualValues(t, "snap1", status.Source.Snapshot)
|
||||||
|
assert.EqualValues(t, "", status.Source.Group)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneSubVolumeSnapshot(t *testing.T) {
|
||||||
|
fsa := getFSAdmin(t)
|
||||||
|
volume := "cephfs"
|
||||||
|
group := "Park"
|
||||||
|
subname := "Jurrasic"
|
||||||
|
snapname := "dinodna0"
|
||||||
|
clonename := "babydino"
|
||||||
|
|
||||||
|
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)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = fsa.CreateSubVolumeSnapshot(volume, group, subname, snapname)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
err := fsa.RemoveSubVolumeSnapshot(volume, group, subname, snapname)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = fsa.ProtectSubVolumeSnapshot(volume, group, subname, snapname)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
err := fsa.UnprotectSubVolumeSnapshot(volume, group, subname, snapname)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = fsa.CloneSubVolumeSnapshot(
|
||||||
|
volume, group, subname, snapname, clonename,
|
||||||
|
&CloneOptions{TargetGroup: group})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
err := fsa.RemoveSubVolume(volume, group, clonename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for done := false; !done; {
|
||||||
|
status, err := fsa.CloneStatus(volume, group, clonename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, status)
|
||||||
|
switch status.State {
|
||||||
|
case ClonePending, CloneInProgress:
|
||||||
|
case CloneComplete:
|
||||||
|
done = true
|
||||||
|
case CloneFailed:
|
||||||
|
t.Fatal("clone failed")
|
||||||
|
default:
|
||||||
|
t.Fatalf("invalid status.State: %q", status.State)
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
|
@ -264,3 +264,39 @@ func (fsa *FSAdmin) ListSubVolumeSnapshots(volume, group, name string) ([]string
|
||||||
}
|
}
|
||||||
return parseListNames(fsa.marshalMgrCommand(m))
|
return parseListNames(fsa.marshalMgrCommand(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProtectSubVolumeSnapshot protects the specified snapshot.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
// ceph fs subvolume snapshot protect <volume> --group-name=<group> <subvolume> <name>
|
||||||
|
func (fsa *FSAdmin) ProtectSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs subvolume snapshot protect",
|
||||||
|
"vol_name": volume,
|
||||||
|
"sub_name": subvolume,
|
||||||
|
"snap_name": name,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
return checkEmptyResponseExpected(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnprotectSubVolumeSnapshot removes protection from the specified snapshot.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
// ceph fs subvolume snapshot unprotect <volume> --group-name=<group> <subvolume> <name>
|
||||||
|
func (fsa *FSAdmin) UnprotectSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs subvolume snapshot unprotect",
|
||||||
|
"vol_name": volume,
|
||||||
|
"sub_name": subvolume,
|
||||||
|
"snap_name": name,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
return checkEmptyResponseExpected(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue