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 <ndevos@redhat.com>
This commit is contained in:
Niels de Vos 2020-10-14 08:22:20 +02:00 committed by mergify[bot]
parent 0f108edc7b
commit 1fcd1ed7c7
10 changed files with 1249 additions and 0 deletions

View File

@ -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
}

136
vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go generated vendored Normal file
View File

@ -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 <volume> --group_name=<group> <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 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 <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 fsa.marshalMgrCommand(m).noData().End()
}

11
vendor/github.com/ceph/go-ceph/cephfs/admin/doc.go generated vendored Normal file
View File

@ -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

160
vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go generated vendored Normal file
View File

@ -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
}

142
vendor/github.com/ceph/go-ceph/cephfs/admin/response.go generated vendored Normal file
View File

@ -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}
}

View File

@ -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 <volume> --group-name=<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 <volume> --group-name=<group>
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 <volume> --group-name=<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 <volume> --group-name=<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 <volume> --group-name=<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 <volume> --group-name=<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 <volume> --group-name=<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 <volume> --group-name=<group> <source> <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 <volume> --group-name=<group> <subvolume> <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 <volume> --group-name=<group> <subvolume> <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 <volume> --group-name=<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 <volume> --group-name=<group> <subvolume> <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 <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 fsa.marshalMgrCommand(m).filterDeprecated().noData().End()
}
// 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 fsa.marshalMgrCommand(m).filterDeprecated().noData().End()
}

View File

@ -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 <volume> <group_name> ...
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 <volume>
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 <volume> <group_name>
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 <volume> <group_name> --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 <volume> <group_name>
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 <volume> <group> <name>
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 <volume> <group> <name>
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 <volume> <group> <name> --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 <volume> <group>
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))
}

View File

@ -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
}

152
vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go generated vendored Normal file
View File

@ -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 <name>
func (fsa *FSAdmin) VolumeStatus(name string) (*VolumeStatus, error) {
res := fsa.marshalMgrCommand(map[string]string{
"fs": name,
"prefix": "fs status",
"format": "json",
})
return parseVolumeStatus(res)
}

1
vendor/modules.txt vendored
View File

@ -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