From 1fcd1ed7c72cf24151e95d8428c0bb68f33f847e Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Wed, 14 Oct 2020 08:22:20 +0200 Subject: [PATCH] rebase: vendor ceph/go-ceph/cephfs/admin The CephFS Admin package has been made available with go-ceph v0.6. This will be used for provisioning and managing CephFS volumes. Signed-off-by: Niels de Vos --- .../ceph/go-ceph/cephfs/admin/bytecount.go | 68 ++++ .../ceph/go-ceph/cephfs/admin/clone.go | 136 +++++++ .../ceph/go-ceph/cephfs/admin/doc.go | 11 + .../ceph/go-ceph/cephfs/admin/fsadmin.go | 160 ++++++++ .../ceph/go-ceph/cephfs/admin/response.go | 142 +++++++ .../ceph/go-ceph/cephfs/admin/subvolume.go | 379 ++++++++++++++++++ .../go-ceph/cephfs/admin/subvolumegroup.go | 161 ++++++++ .../ceph/go-ceph/cephfs/admin/timestamp.go | 39 ++ .../ceph/go-ceph/cephfs/admin/volume.go | 152 +++++++ vendor/modules.txt | 1 + 10 files changed, 1249 insertions(+) create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/bytecount.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/doc.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/response.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/timestamp.go create mode 100644 vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/bytecount.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/bytecount.go new file mode 100644 index 000000000..f490bc38d --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/bytecount.go @@ -0,0 +1,68 @@ +// +build !luminous,!mimic + +package admin + +import ( + "encoding/json" + "fmt" +) + +// ByteCount represents the size of a volume in bytes. +type ByteCount uint64 + +// SI byte size constants. keep these private for now. +const ( + kibiByte ByteCount = 1024 + mebiByte = 1024 * kibiByte + gibiByte = 1024 * mebiByte + tebiByte = 1024 * gibiByte +) + +// resizeValue returns a size value as a string, as needed by the subvolume +// resize command json. +func (bc ByteCount) resizeValue() string { + return uint64String(uint64(bc)) +} + +// QuotaSize interface values can be used to change the size of a volume. +type QuotaSize interface { + resizeValue() string +} + +// specialSize is a custom non-numeric quota size value. +type specialSize string + +// resizeValue for a specialSize returns the original string value. +func (s specialSize) resizeValue() string { + return string(s) +} + +// Infinite is a special QuotaSize value that can be used to clear size limits +// on a subvolume. +const Infinite = specialSize("infinite") + +// quotaSizePlaceholder types are helpful to extract QuotaSize typed values +// from JSON responses. +type quotaSizePlaceholder struct { + Value QuotaSize +} + +func (p *quotaSizePlaceholder) UnmarshalJSON(b []byte) error { + var val interface{} + if err := json.Unmarshal(b, &val); err != nil { + return err + } + switch v := val.(type) { + case string: + if v == string(Infinite) { + p.Value = Infinite + } else { + return fmt.Errorf("quota size: invalid string value: %q", v) + } + case float64: + p.Value = ByteCount(v) + default: + return fmt.Errorf("quota size: invalid type, string or number required: %v (%T)", val, val) + } + return nil +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go new file mode 100644 index 000000000..d31431041 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go @@ -0,0 +1,136 @@ +// +build !luminous,!mimic + +package admin + +import ( + "strings" +) + +const notProtectedSuffix = "is not protected" + +// NotProtectedError error values will be returned by CloneSubVolumeSnapshot in +// the case that the source snapshot needs to be protected but is not. The +// requirement for a snapshot to be protected prior to cloning varies by Ceph +// version. +type NotProtectedError struct { + response +} + +// 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. +// The group, subvolume, and snapshot parameters specify the source for the +// clone, and only the source. Additional properties of the clone, such as the +// subvolume group that the clone will be created in and the pool layout may be +// specified using the clone options parameter. +// +// Similar To: +// ceph fs subvolume snapshot clone --group_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 checkCloneResponse(fsa.marshalMgrCommand(m)) +} + +func checkCloneResponse(res response) error { + if strings.HasSuffix(res.status, notProtectedSuffix) { + return NotProtectedError{response: res} + } + return res.noData().End() +} + +// 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(res response) (*CloneStatus, error) { + var status cloneStatusWrapper + if err := res.noStatus().unmarshal(&status).End(); 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 --group_name= +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 --group_name= +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 fsa.marshalMgrCommand(m).noData().End() +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/doc.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/doc.go new file mode 100644 index 000000000..3ff539860 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/doc.go @@ -0,0 +1,11 @@ +/* +Package admin is a convenience layer to support the administration of +CephFS volumes, subvolumes, etc. + +Unlike the cephfs package this API does not map to APIs provided by +ceph libraries themselves. This API is not yet stable and is subject +to change. + +This package only supports ceph "nautilus" and "octopus" at this time. +*/ +package admin diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go new file mode 100644 index 000000000..2b49980f2 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go @@ -0,0 +1,160 @@ +// +build !luminous,!mimic + +package admin + +import ( + "encoding/json" + "strconv" + + "github.com/ceph/go-ceph/rados" +) + +// RadosCommander provides an interface to execute JSON-formatted commands that +// allow the cephfs administrative functions to interact with the Ceph cluster. +type RadosCommander interface { + MgrCommand(buf [][]byte) ([]byte, string, error) + MonCommand(buf []byte) ([]byte, string, error) +} + +// FSAdmin is used to administrate CephFS within a ceph cluster. +type FSAdmin struct { + conn RadosCommander +} + +// New creates an FSAdmin automatically based on the default ceph +// configuration file. If more customization is needed, create a +// *rados.Conn as you see fit and use NewFromConn to use that +// connection with these administrative functions. +func New() (*FSAdmin, error) { + conn, err := rados.NewConn() + if err != nil { + return nil, err + } + err = conn.ReadDefaultConfigFile() + if err != nil { + return nil, err + } + err = conn.Connect() + if err != nil { + return nil, err + } + return NewFromConn(conn), nil +} + +// NewFromConn creates an FSAdmin management object from a preexisting +// rados connection. The existing connection can be rados.Conn or any +// type implementing the RadosCommander interface. This may be useful +// if the calling layer needs to inject additional logging, error handling, +// fault injection, etc. +func NewFromConn(conn RadosCommander) *FSAdmin { + return &FSAdmin{conn} +} + +func (fsa *FSAdmin) validate() error { + if fsa.conn == nil { + return rados.ErrNotConnected + } + return nil +} + +// rawMgrCommand takes a byte buffer and sends it to the MGR as a command. +// The buffer is expected to contain preformatted JSON. +func (fsa *FSAdmin) rawMgrCommand(buf []byte) response { + if err := fsa.validate(); err != nil { + return response{err: err} + } + return newResponse(fsa.conn.MgrCommand([][]byte{buf})) +} + +// marshalMgrCommand takes an generic interface{} value, converts it to JSON and +// sends the json to the MGR as a command. +func (fsa *FSAdmin) marshalMgrCommand(v interface{}) response { + b, err := json.Marshal(v) + if err != nil { + return response{err: err} + } + return fsa.rawMgrCommand(b) +} + +// rawMonCommand takes a byte buffer and sends it to the MON as a command. +// The buffer is expected to contain preformatted JSON. +func (fsa *FSAdmin) rawMonCommand(buf []byte) response { + if err := fsa.validate(); err != nil { + return response{err: err} + } + return newResponse(fsa.conn.MonCommand(buf)) +} + +// marshalMonCommand takes an generic interface{} value, converts it to JSON and +// sends the json to the MGR as a command. +func (fsa *FSAdmin) marshalMonCommand(v interface{}) response { + b, err := json.Marshal(v) + if err != nil { + return response{err: err} + } + return fsa.rawMonCommand(b) +} + +type listNamedResult struct { + Name string `json:"name"` +} + +func parseListNames(res response) ([]string, error) { + var r []listNamedResult + if err := res.noStatus().unmarshal(&r).End(); err != nil { + return nil, err + } + vl := make([]string, len(r)) + for i := range r { + vl[i] = r[i].Name + } + return vl, nil +} + +// parsePathResponse returns a cleaned up path from requests that get a path +// unless an error is encountered, then an error is returned. +func parsePathResponse(res response) (string, error) { + if res2 := res.noStatus(); !res2.Ok() { + return "", res.End() + } + b := res.body + // if there's a trailing newline in the buffer strip it. + // ceph assumes a CLI wants the output of the buffer and there's + // no format=json mode available currently. + for len(b) >= 1 && b[len(b)-1] == '\n' { + b = b[:len(b)-1] + } + return string(b), nil +} + +// modeString converts a unix-style mode value to a string-ified version in an +// octal representation (e.g. "777", "700", etc). This format is expected by +// some of the ceph JSON command inputs. +func modeString(m int, force bool) string { + if force || m != 0 { + return strconv.FormatInt(int64(m), 8) + } + return "" +} + +// uint64String converts a uint64 to a string. Some of the ceph json commands +// can take a string or "int" (as a string). This is a common function for +// doing that conversion. +func uint64String(v uint64) string { + return strconv.FormatUint(uint64(v), 10) +} + +type rmFlags struct { + force bool +} + +func (f rmFlags) Update(m map[string]string) map[string]interface{} { + o := make(map[string]interface{}) + for k, v := range m { + o[k] = v + } + if f.force { + o["force"] = true + } + return o +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go new file mode 100644 index 000000000..a1b6bf3a0 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go @@ -0,0 +1,142 @@ +// +build !luminous,!mimic + +package admin + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +var ( + // ErrStatusNotEmpty may be returned if a call should not have a status + // string set but one is. + ErrStatusNotEmpty = errors.New("response status not empty") + // ErrBodyNotEmpty may be returned if a call should have an empty body but + // a body value is present. + ErrBodyNotEmpty = errors.New("response body not empty") +) + +const ( + deprecatedSuffix = "call is deprecated and will be removed in a future release" + missingPrefix = "No handler found" + einval = -22 +) + +type cephError interface { + ErrorCode() int +} + +// NotImplementedError error values will be returned in the case that an API +// call is not available in the version of Ceph that is running in the target +// cluster. +type NotImplementedError struct { + response +} + +// Error implements the error interface. +func (e NotImplementedError) Error() string { + return fmt.Sprintf("API call not implemented server-side: %s", e.status) +} + +// response encapsulates the data returned by ceph and supports easy processing +// pipelines. +type response struct { + body []byte + status string + err error +} + +// Ok returns true if the response contains no error. +func (r response) Ok() bool { + return r.err == nil +} + +// Error implements the error interface. +func (r response) Error() string { + if r.status == "" { + return r.err.Error() + } + return fmt.Sprintf("%s: %q", r.err, r.status) +} + +// Unwrap returns the error this response contains. +func (r response) Unwrap() error { + return r.err +} + +// Status returns the status string value. +func (r response) Status() string { + return r.status +} + +// End returns an error if the response contains an error or nil, indicating +// that response is no longer needed for processing. +func (r response) End() error { + if !r.Ok() { + if ce, ok := r.err.(cephError); ok { + if ce.ErrorCode() == einval && strings.HasPrefix(r.status, missingPrefix) { + return NotImplementedError{response: r} + } + } + return r + } + return nil +} + +// noStatus asserts that the input response has no status value. +func (r response) noStatus() response { + if !r.Ok() { + return r + } + if r.status != "" { + return response{r.body, r.status, ErrStatusNotEmpty} + } + return r +} + +// noBody asserts that the input response has no body value. +func (r response) noBody() response { + if !r.Ok() { + return r + } + if len(r.body) != 0 { + return response{r.body, r.status, ErrBodyNotEmpty} + } + return r +} + +// noData asserts that the input response has no status or body values. +func (r response) noData() response { + return r.noStatus().noBody() +} + +// filterDeprecated removes deprecation warnings from the response status. +// Use it when checking the response from calls that may be deprecated in ceph +// if you want those calls to continue working if the warning is present. +func (r response) filterDeprecated() response { + if !r.Ok() { + return r + } + if strings.HasSuffix(r.status, deprecatedSuffix) { + return response{r.body, "", r.err} + } + return r +} + +// unmarshal data from the response body into v. +func (r response) unmarshal(v interface{}) response { + if !r.Ok() { + return r + } + if err := json.Unmarshal(r.body, v); err != nil { + return response{body: r.body, err: err} + } + return r +} + +// newResponse returns a response. +func newResponse(b []byte, s string, e error) response { + return response{b, s, e} +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go new file mode 100644 index 000000000..b0d69353a --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go @@ -0,0 +1,379 @@ +// +build !luminous,!mimic + +package admin + +// this is the internal type used to create JSON for ceph. +// See SubVolumeOptions for the type that users of the library +// interact with. +// note that the ceph json takes mode as a string. +type subVolumeFields struct { + Prefix string `json:"prefix"` + Format string `json:"format"` + VolName string `json:"vol_name"` + GroupName string `json:"group_name,omitempty"` + SubName string `json:"sub_name"` + Size ByteCount `json:"size,omitempty"` + Uid int `json:"uid,omitempty"` + Gid int `json:"gid,omitempty"` + Mode string `json:"mode,omitempty"` + PoolLayout string `json:"pool_layout,omitempty"` + NamespaceIsolated bool `json:"namespace_isolated"` +} + +// SubVolumeOptions are used to specify optional, non-identifying, values +// to be used when creating a new subvolume. +type SubVolumeOptions struct { + Size ByteCount + Uid int + Gid int + Mode int + PoolLayout string + NamespaceIsolated bool +} + +func (s *SubVolumeOptions) toFields(v, g, n string) *subVolumeFields { + return &subVolumeFields{ + Prefix: "fs subvolume create", + Format: "json", + VolName: v, + GroupName: g, + SubName: n, + Size: s.Size, + Uid: s.Uid, + Gid: s.Gid, + Mode: modeString(s.Mode, false), + PoolLayout: s.PoolLayout, + NamespaceIsolated: s.NamespaceIsolated, + } +} + +// NoGroup should be used when an optional subvolume group name is not +// specified. +const NoGroup = "" + +// CreateSubVolume sends a request to create a CephFS subvolume in a volume, +// belonging to an optional subvolume group. +// +// Similar To: +// ceph fs subvolume create --group-name= ... +func (fsa *FSAdmin) CreateSubVolume(volume, group, name string, o *SubVolumeOptions) error { + if o == nil { + o = &SubVolumeOptions{} + } + f := o.toFields(volume, group, name) + return fsa.marshalMgrCommand(f).noData().End() +} + +// ListSubVolumes returns a list of subvolumes belonging to the volume and +// optional subvolume group. +// +// Similar To: +// ceph fs subvolume ls --group-name= +func (fsa *FSAdmin) ListSubVolumes(volume, group string) ([]string, error) { + m := map[string]string{ + "prefix": "fs subvolume ls", + "vol_name": volume, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return parseListNames(fsa.marshalMgrCommand(m)) +} + +// RemoveSubVolume will delete a CephFS subvolume in a volume and optional +// subvolume group. +// +// Similar To: +// ceph fs subvolume rm --group-name= +func (fsa *FSAdmin) RemoveSubVolume(volume, group, name string) error { + return fsa.rmSubVolume(volume, group, name, rmFlags{}) +} + +// ForceRemoveSubVolume will delete a CephFS subvolume in a volume and optional +// subvolume group. +// +// Similar To: +// ceph fs subvolume rm --group-name= --force +func (fsa *FSAdmin) ForceRemoveSubVolume(volume, group, name string) error { + return fsa.rmSubVolume(volume, group, name, rmFlags{force: true}) +} + +func (fsa *FSAdmin) rmSubVolume(volume, group, name string, o rmFlags) error { + m := map[string]string{ + "prefix": "fs subvolume rm", + "vol_name": volume, + "sub_name": name, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return fsa.marshalMgrCommand(o.Update(m)).noData().End() +} + +type subVolumeResizeFields struct { + Prefix string `json:"prefix"` + Format string `json:"format"` + VolName string `json:"vol_name"` + GroupName string `json:"group_name,omitempty"` + SubName string `json:"sub_name"` + NewSize string `json:"new_size"` + NoShrink bool `json:"no_shrink"` +} + +// SubVolumeResizeResult reports the size values returned by the +// ResizeSubVolume function, as reported by Ceph. +type SubVolumeResizeResult struct { + BytesUsed ByteCount `json:"bytes_used"` + BytesQuota ByteCount `json:"bytes_quota"` + BytesPercent string `json:"bytes_pcent"` +} + +// ResizeSubVolume will resize a CephFS subvolume. The newSize value may be a +// ByteCount or the special Infinite constant. Setting noShrink to true will +// prevent reducing the size of the volume below the current used size. +// +// Similar To: +// ceph fs subvolume resize --group-name= ... +func (fsa *FSAdmin) ResizeSubVolume( + volume, group, name string, + newSize QuotaSize, noShrink bool) (*SubVolumeResizeResult, error) { + + f := &subVolumeResizeFields{ + Prefix: "fs subvolume resize", + Format: "json", + VolName: volume, + GroupName: group, + SubName: name, + NewSize: newSize.resizeValue(), + NoShrink: noShrink, + } + var result []*SubVolumeResizeResult + res := fsa.marshalMgrCommand(f) + if err := res.noStatus().unmarshal(&result).End(); err != nil { + return nil, err + } + return result[0], nil +} + +// SubVolumePath returns the path to the subvolume from the root of the file system. +// +// Similar To: +// ceph fs subvolume getpath --group-name= +func (fsa *FSAdmin) SubVolumePath(volume, group, name string) (string, error) { + m := map[string]string{ + "prefix": "fs subvolume getpath", + "vol_name": volume, + "sub_name": name, + // ceph doesn't respond in json for this cmd (even if you ask) + } + if group != NoGroup { + m["group_name"] = group + } + return parsePathResponse(fsa.marshalMgrCommand(m)) +} + +// Feature is used to define constant values for optional features on +// subvolumes. +type Feature string + +const ( + // SnapshotCloneFeature indicates a subvolume supports cloning. + SnapshotCloneFeature = Feature("snapshot-clone") + // SnapshotAutoprotectFeature indicates a subvolume does not require + // manually protecting a subvolume before cloning. + SnapshotAutoprotectFeature = Feature("snapshot-autoprotect") + // SnapshotRetentionFeature indicates a subvolume supports retaining + // snapshots on subvolume removal. + SnapshotRetentionFeature = Feature("snapshot-retention") +) + +// 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"` + Features []Feature `json:"features"` +} + +type subVolumeInfoWrapper struct { + SubVolumeInfo + VBytesQuota *quotaSizePlaceholder `json:"bytes_quota"` +} + +func parseSubVolumeInfo(res response) (*SubVolumeInfo, error) { + var info subVolumeInfoWrapper + if err := res.noStatus().unmarshal(&info).End(); 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)) +} + +// CreateSubVolumeSnapshot creates a new snapshot from the source subvolume. +// +// Similar To: +// ceph fs subvolume snapshot create --group-name= +func (fsa *FSAdmin) CreateSubVolumeSnapshot(volume, group, source, name string) error { + m := map[string]string{ + "prefix": "fs subvolume snapshot create", + "vol_name": volume, + "sub_name": source, + "snap_name": name, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return fsa.marshalMgrCommand(m).noData().End() +} + +// RemoveSubVolumeSnapshot removes the specified snapshot from the subvolume. +// +// Similar To: +// ceph fs subvolume snapshot rm --group-name= +func (fsa *FSAdmin) RemoveSubVolumeSnapshot(volume, group, subvolume, name string) error { + return fsa.rmSubVolumeSnapshot(volume, group, subvolume, name, rmFlags{}) +} + +// ForceRemoveSubVolumeSnapshot removes the specified snapshot from the subvolume. +// +// Similar To: +// ceph fs subvolume snapshot rm --group-name= --force +func (fsa *FSAdmin) ForceRemoveSubVolumeSnapshot(volume, group, subvolume, name string) error { + return fsa.rmSubVolumeSnapshot(volume, group, subvolume, name, rmFlags{force: true}) +} + +func (fsa *FSAdmin) rmSubVolumeSnapshot(volume, group, subvolume, name string, o rmFlags) error { + + m := map[string]string{ + "prefix": "fs subvolume snapshot rm", + "vol_name": volume, + "sub_name": subvolume, + "snap_name": name, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return fsa.marshalMgrCommand(o.Update(m)).noData().End() +} + +// ListSubVolumeSnapshots returns a listing of snapshots for a given subvolume. +// +// Similar To: +// ceph fs subvolume snapshot ls --group-name= +func (fsa *FSAdmin) ListSubVolumeSnapshots(volume, group, name string) ([]string, error) { + m := map[string]string{ + "prefix": "fs subvolume snapshot ls", + "vol_name": volume, + "sub_name": name, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return parseListNames(fsa.marshalMgrCommand(m)) +} + +// SubVolumeSnapshotInfo reports various informational values about a subvolume. +type SubVolumeSnapshotInfo struct { + CreatedAt TimeStamp `json:"created_at"` + DataPool string `json:"data_pool"` + HasPendingClones string `json:"has_pending_clones"` + Protected string `json:"protected"` + Size ByteCount `json:"size"` +} + +func parseSubVolumeSnapshotInfo(res response) (*SubVolumeSnapshotInfo, error) { + var info SubVolumeSnapshotInfo + if err := res.noStatus().unmarshal(&info).End(); err != nil { + return nil, err + } + return &info, nil +} + +// SubVolumeSnapshotInfo returns information about the specified subvolume snapshot. +// +// Similar To: +// ceph fs subvolume snapshot info --group-name= +func (fsa *FSAdmin) SubVolumeSnapshotInfo(volume, group, subvolume, name string) (*SubVolumeSnapshotInfo, error) { + m := map[string]string{ + "prefix": "fs subvolume snapshot info", + "vol_name": volume, + "sub_name": subvolume, + "snap_name": name, + "format": "json", + } + if group != NoGroup { + m["group_name"] = group + } + return parseSubVolumeSnapshotInfo(fsa.marshalMgrCommand(m)) +} + +// ProtectSubVolumeSnapshot protects the specified snapshot. +// +// Similar To: +// ceph fs subvolume snapshot protect --group-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 fsa.marshalMgrCommand(m).filterDeprecated().noData().End() +} + +// UnprotectSubVolumeSnapshot removes protection from the specified snapshot. +// +// Similar To: +// ceph fs subvolume snapshot unprotect --group-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 fsa.marshalMgrCommand(m).filterDeprecated().noData().End() +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go new file mode 100644 index 000000000..f5426bac4 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go @@ -0,0 +1,161 @@ +// +build !luminous,!mimic + +package admin + +// this is the internal type used to create JSON for ceph. +// See SubVolumeGroupOptions for the type that users of the library +// interact with. +// note that the ceph json takes mode as a string. +type subVolumeGroupFields struct { + Prefix string `json:"prefix"` + Format string `json:"format"` + VolName string `json:"vol_name"` + GroupName string `json:"group_name"` + Uid int `json:"uid,omitempty"` + Gid int `json:"gid,omitempty"` + Mode string `json:"mode,omitempty"` + PoolLayout string `json:"pool_layout,omitempty"` +} + +// SubVolumeGroupOptions are used to specify optional, non-identifying, values +// to be used when creating a new subvolume group. +type SubVolumeGroupOptions struct { + Uid int + Gid int + Mode int + PoolLayout string +} + +func (s *SubVolumeGroupOptions) toFields(v, g string) *subVolumeGroupFields { + return &subVolumeGroupFields{ + Prefix: "fs subvolumegroup create", + Format: "json", + VolName: v, + GroupName: g, + Uid: s.Uid, + Gid: s.Gid, + Mode: modeString(s.Mode, false), + PoolLayout: s.PoolLayout, + } +} + +// CreateSubVolumeGroup sends a request to create a subvolume group in a volume. +// +// Similar To: +// ceph fs subvolumegroup create ... +func (fsa *FSAdmin) CreateSubVolumeGroup(volume, name string, o *SubVolumeGroupOptions) error { + if o == nil { + o = &SubVolumeGroupOptions{} + } + res := fsa.marshalMgrCommand(o.toFields(volume, name)) + return res.noData().End() +} + +// ListSubVolumeGroups returns a list of subvolume groups belonging to the +// specified volume. +// +// Similar To: +// ceph fs subvolumegroup ls cephfs +func (fsa *FSAdmin) ListSubVolumeGroups(volume string) ([]string, error) { + res := fsa.marshalMgrCommand(map[string]string{ + "prefix": "fs subvolumegroup ls", + "vol_name": volume, + "format": "json", + }) + return parseListNames(res) +} + +// RemoveSubVolumeGroup will delete a subvolume group in a volume. +// Similar To: +// ceph fs subvolumegroup rm +func (fsa *FSAdmin) RemoveSubVolumeGroup(volume, name string) error { + return fsa.rmSubVolumeGroup(volume, name, rmFlags{}) +} + +// ForceRemoveSubVolumeGroup will delete a subvolume group in a volume. +// Similar To: +// ceph fs subvolumegroup rm --force +func (fsa *FSAdmin) ForceRemoveSubVolumeGroup(volume, name string) error { + return fsa.rmSubVolumeGroup(volume, name, rmFlags{force: true}) +} + +func (fsa *FSAdmin) rmSubVolumeGroup(volume, name string, o rmFlags) error { + res := fsa.marshalMgrCommand(o.Update(map[string]string{ + "prefix": "fs subvolumegroup rm", + "vol_name": volume, + "group_name": name, + "format": "json", + })) + return res.noData().End() +} + +// SubVolumeGroupPath returns the path to the subvolume from the root of the +// file system. +// +// Similar To: +// ceph fs subvolumegroup getpath +func (fsa *FSAdmin) SubVolumeGroupPath(volume, name string) (string, error) { + m := map[string]string{ + "prefix": "fs subvolumegroup getpath", + "vol_name": volume, + "group_name": name, + // ceph doesn't respond in json for this cmd (even if you ask) + } + return parsePathResponse(fsa.marshalMgrCommand(m)) +} + +// CreateSubVolumeGroupSnapshot creates a new snapshot from the source subvolume group. +// +// Similar To: +// ceph fs subvolumegroup snapshot create +func (fsa *FSAdmin) CreateSubVolumeGroupSnapshot(volume, group, name string) error { + m := map[string]string{ + "prefix": "fs subvolumegroup snapshot create", + "vol_name": volume, + "group_name": group, + "snap_name": name, + "format": "json", + } + return fsa.marshalMgrCommand(m).noData().End() +} + +// RemoveSubVolumeGroupSnapshot removes the specified snapshot from the subvolume group. +// +// Similar To: +// ceph fs subvolumegroup snapshot rm +func (fsa *FSAdmin) RemoveSubVolumeGroupSnapshot(volume, group, name string) error { + return fsa.rmSubVolumeGroupSnapshot(volume, group, name, rmFlags{}) +} + +// ForceRemoveSubVolumeGroupSnapshot removes the specified snapshot from the subvolume group. +// +// Similar To: +// ceph fs subvolumegroup snapshot rm --force +func (fsa *FSAdmin) ForceRemoveSubVolumeGroupSnapshot(volume, group, name string) error { + return fsa.rmSubVolumeGroupSnapshot(volume, group, name, rmFlags{force: true}) +} + +func (fsa *FSAdmin) rmSubVolumeGroupSnapshot(volume, group, name string, o rmFlags) error { + m := map[string]string{ + "prefix": "fs subvolumegroup snapshot rm", + "vol_name": volume, + "group_name": group, + "snap_name": name, + "format": "json", + } + return fsa.marshalMgrCommand(o.Update(m)).noData().End() +} + +// ListSubVolumeGroupSnapshots returns a listing of snapshots for a given subvolume group. +// +// Similar To: +// ceph fs subvolumegroup snapshot ls +func (fsa *FSAdmin) ListSubVolumeGroupSnapshots(volume, group string) ([]string, error) { + m := map[string]string{ + "prefix": "fs subvolumegroup snapshot ls", + "vol_name": volume, + "group_name": group, + "format": "json", + } + return parseListNames(fsa.marshalMgrCommand(m)) +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/timestamp.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/timestamp.go new file mode 100644 index 000000000..424fab632 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/timestamp.go @@ -0,0 +1,39 @@ +// +build !luminous,!mimic + +package admin + +import ( + "encoding/json" + "time" +) + +// golang's date parsing approach is rather bizarre +var cephTSLayout = "2006-01-02 15:04:05" + +// TimeStamp abstracts some of the details about date+time stamps +// returned by ceph via JSON. +type TimeStamp struct { + time.Time +} + +// String returns a string representing the date+time as presented +// by ceph. +func (ts TimeStamp) String() string { + return ts.Format(cephTSLayout) +} + +// UnmarshalJSON implements the json Unmarshaler interface. +func (ts *TimeStamp) UnmarshalJSON(b []byte) error { + var raw string + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + // AFAICT, ceph always returns the time in UTC so Parse, as opposed to + // ParseInLocation, is appropriate here. + t, err := time.Parse(cephTSLayout, raw) + if err != nil { + return err + } + *ts = TimeStamp{t} + return nil +} diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go new file mode 100644 index 000000000..082fa4d8b --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go @@ -0,0 +1,152 @@ +// +build !luminous,!mimic + +package admin + +import ( + "bytes" +) + +var ( + listVolumesCmd = []byte(`{"prefix":"fs volume ls"}`) + dumpVolumesCmd = []byte(`{"prefix":"fs dump","format":"json"}`) + listFsCmd = []byte(`{"prefix":"fs ls","format":"json"}`) +) + +// ListVolumes return a list of volumes in this Ceph cluster. +// +// Similar To: +// ceph fs volume ls +func (fsa *FSAdmin) ListVolumes() ([]string, error) { + res := fsa.rawMgrCommand(listVolumesCmd) + return parseListNames(res) +} + +// FSPoolInfo contains the name of a file system as well as the metadata and +// data pools. Pool information is available by ID or by name. +type FSPoolInfo struct { + Name string `json:"name"` + MetadataPool string `json:"metadata_pool"` + MetadataPoolID int `json:"metadata_pool_id"` + DataPools []string `json:"data_pools"` + DataPoolIDs []int `json:"data_pool_ids"` +} + +// ListFileSystems lists file systems along with the pools occupied by those +// file systems. +// +// Similar To: +// ceph fs ls +func (fsa *FSAdmin) ListFileSystems() ([]FSPoolInfo, error) { + res := fsa.rawMonCommand(listFsCmd) + return parseFsList(res) +} + +func parseFsList(res response) ([]FSPoolInfo, error) { + var listing []FSPoolInfo + if err := res.noStatus().unmarshal(&listing).End(); err != nil { + return nil, err + } + return listing, nil +} + +// VolumeIdent contains a pair of file system identifying values: the volume +// name and the volume ID. +type VolumeIdent struct { + Name string + ID int64 +} + +type cephFileSystem struct { + ID int64 `json:"id"` + MDSMap struct { + FSName string `json:"fs_name"` + } `json:"mdsmap"` +} + +type fsDump struct { + FileSystems []cephFileSystem `json:"filesystems"` +} + +const ( + dumpOkPrefix = "dumped fsmap epoch" + dumpOkLen = len(dumpOkPrefix) + + invalidTextualResponse = "this ceph version returns a non-parsable volume status response" +) + +func parseDumpToIdents(res response) ([]VolumeIdent, error) { + if !res.Ok() { + return nil, res.End() + } + if len(res.status) >= dumpOkLen && res.status[:dumpOkLen] == dumpOkPrefix { + // Unhelpfully, ceph drops a status string on success responses for this + // call. this hacks around that by ignoring its typical prefix + res.status = "" + } + var dump fsDump + if err := res.noStatus().unmarshal(&dump).End(); err != nil { + return nil, err + } + // copy the dump json into the simpler enumeration list + idents := make([]VolumeIdent, len(dump.FileSystems)) + for i := range dump.FileSystems { + idents[i].ID = dump.FileSystems[i].ID + idents[i].Name = dump.FileSystems[i].MDSMap.FSName + } + return idents, nil +} + +// EnumerateVolumes returns a list of volume-name volume-id pairs. +func (fsa *FSAdmin) EnumerateVolumes() ([]VolumeIdent, error) { + // We base our enumeration on the ceph fs dump json. This may not be the + // only way to do it, but it's the only one I know of currently. Because of + // this and to keep our initial implementation simple, we expose our own + // simplified type only, rather do a partial implementation of dump. + return parseDumpToIdents(fsa.rawMonCommand(dumpVolumesCmd)) +} + +// VolumePool reports on the pool status for a CephFS volume. +type VolumePool struct { + ID int `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Available uint64 `json:"avail"` + Used uint64 `json:"used"` +} + +// VolumeStatus reports various properties of a CephFS volume. +// TODO: Fill in. +type VolumeStatus struct { + MDSVersion string `json:"mds_version"` + Pools []VolumePool `json:"pools"` +} + +func parseVolumeStatus(res response) (*VolumeStatus, error) { + var vs VolumeStatus + res = res.noStatus() + if !res.Ok() { + return nil, res.End() + } + res = res.unmarshal(&vs) + if !res.Ok() { + if bytes.HasPrefix(res.body, []byte("ceph")) { + res.status = invalidTextualResponse + return nil, NotImplementedError{response: res} + } + return nil, res.End() + } + return &vs, nil +} + +// VolumeStatus returns a VolumeStatus object for the given volume name. +// +// Similar To: +// ceph fs status cephfs +func (fsa *FSAdmin) VolumeStatus(name string) (*VolumeStatus, error) { + res := fsa.marshalMgrCommand(map[string]string{ + "fs": name, + "prefix": "fs status", + "format": "json", + }) + return parseVolumeStatus(res) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4d9f00175..848b97c06 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -3,6 +3,7 @@ github.com/beorn7/perks/quantile # github.com/blang/semver v3.5.0+incompatible github.com/blang/semver # github.com/ceph/go-ceph v0.6.0 +github.com/ceph/go-ceph/cephfs/admin github.com/ceph/go-ceph/internal/callbacks github.com/ceph/go-ceph/internal/cutil github.com/ceph/go-ceph/internal/errutil