Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
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
}
+158
View File
@@ -0,0 +1,158 @@
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"`
}
// CloneProgressReport contains the progress report of a subvolume clone.
type CloneProgressReport struct {
PercentageCloned string `json:"percentage cloned"`
AmountCloned string `json:"amount cloned"`
FilesCloned string `json:"files cloned"`
}
// CloneStatus reports on the status of a subvolume clone.
type CloneStatus struct {
State CloneState `json:"state"`
Source CloneSource `json:"source"`
ProgressReport CloneProgressReport `json:"progress_report"`
// failure can be obtained through .GetFailure()
failure *CloneFailure
}
// CloneFailure reports details of a failure after a subvolume clone failed.
type CloneFailure struct {
Errno string `json:"errno"`
ErrStr string `json:"errstr"`
}
type cloneStatusWrapper struct {
Status CloneStatus `json:"status"`
Failure CloneFailure `json:"failure"`
}
func parseCloneStatus(res response) (*CloneStatus, error) {
var status cloneStatusWrapper
if err := res.NoStatus().Unmarshal(&status).End(); err != nil {
return nil, err
}
if status.Failure.Errno != "" || status.Failure.ErrStr != "" {
status.Status.failure = &status.Failure
}
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
View File
@@ -0,0 +1,11 @@
package admin
// GetFailure returns details about the CloneStatus when in CloneFailed state.
//
// Similar To:
//
// Reading the .failure object from the JSON returned by "ceph fs subvolume
// snapshot clone"
func (cs *CloneStatus) GetFailure() *CloneFailure {
return cs.failure
}
+11
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
+58
View File
@@ -0,0 +1,58 @@
package admin
// For APIs that accept extra sets of "boolean" flags we may end up wanting
// multiple different sets of supported flags. Example: most rm functions
// accept a force flag, but only subvolume delete has retain snapshots.
// To make this somewhat uniform in the admin package we define a utility
// interface and helper function to merge flags with naming options.
type flagSet interface {
flags() map[string]bool
}
type commonRmFlags struct {
force bool
}
func (f commonRmFlags) flags() map[string]bool {
o := make(map[string]bool)
if f.force {
o["force"] = true
}
return o
}
// SubVolRmFlags does not embed other types to simplify and keep the
// interface with the type flat and simple. At the cost of some code
// duplication we get a nicer UX for those using the library.
// SubVolRmFlags may be used to specify behavior modifying flags when
// removing sub volumes.
type SubVolRmFlags struct {
Force bool
RetainSnapshots bool
}
func (f SubVolRmFlags) flags() map[string]bool {
o := make(map[string]bool)
if f.Force {
o["force"] = true
}
if f.RetainSnapshots {
o["retain_snapshots"] = true
}
return o
}
// mergeFlags combines a set of key-value settings with any type implementing
// the flagSet interface.
func mergeFlags(m map[string]string, f flagSet) map[string]interface{} {
o := make(map[string]interface{})
for k, v := range m {
o[k] = v
}
for k, v := range f.flags() {
o[k] = v
}
return o
}
+133
View File
@@ -0,0 +1,133 @@
package admin
import "fmt"
// fixedPointFloat 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 fixedPointFloat float64
// MarshalJSON provides a custom implementation for the JSON marshalling
// of fixedPointFloat. It formats the float to two decimal places.
func (fpf fixedPointFloat) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%.2f", float64(fpf))), nil
}
// fSQuiesceFields is the internal type used to create JSON for ceph.
// See FSQuiesceOptions for the type that users of the library
// interact with.
type fSQuiesceFields 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 fixedPointFloat `json:"timeout,omitempty"`
Expiration fixedPointFloat `json:"expiration,omitempty"`
AwaitFor fixedPointFloat `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"`
}
// FSQuiesceOptions are used to specify optional, non-identifying, values
// to be used when quiescing a cephfs volume.
type FSQuiesceOptions struct {
Timeout float64
Expiration float64
AwaitFor float64
Await bool
IfVersion int
Include bool
Exclude bool
Reset bool
Release bool
Query bool
All bool
Cancel bool
}
// toFields is used to convert the FSQuiesceOptions to the internal
// fSQuiesceFields type.
func (o *FSQuiesceOptions) toFields(volume, group string, subvolumes []string, setId string) *fSQuiesceFields {
return &fSQuiesceFields{
Prefix: "fs quiesce",
VolName: volume,
GroupName: group,
Members: subvolumes,
SetId: setId,
Timeout: fixedPointFloat(o.Timeout),
Expiration: fixedPointFloat(o.Expiration),
AwaitFor: fixedPointFloat(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,
}
}
// QuiesceState is used to report the state of a quiesced fs volume.
type QuiesceState struct {
Name string `json:"name"`
Age float64 `json:"age"`
}
// QuiesceInfoMember is used to report the state of a quiesced fs volume.
// This is part of sets members object array in the json.
type QuiesceInfoMember struct {
Excluded bool `json:"excluded"`
State QuiesceState `json:"state"`
}
// QuiesceInfo reports various informational values about a quiesced volume.
// This is returned as sets object array in the json.
type QuiesceInfo struct {
Version int `json:"version"`
AgeRef float64 `json:"age_ref"`
State QuiesceState `json:"state"`
Timeout float64 `json:"timeout"`
Expiration float64 `json:"expiration"`
Members map[string]QuiesceInfoMember `json:"members"`
}
// FSQuiesceInfo reports various informational values about quiesced volumes.
type FSQuiesceInfo struct {
Epoch int `json:"epoch"`
SetVersion int `json:"set_version"`
Sets map[string]QuiesceInfo `json:"sets"`
}
// parseFSQuiesceInfo is used to parse the response from the quiesce command. It returns a FSQuiesceInfo object.
func parseFSQuiesceInfo(res response) (*FSQuiesceInfo, error) {
var info FSQuiesceInfo
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
return nil, err
}
return &info, nil
}
// FSQuiesce will quiesce the specified subvolumes in a volume.
// Quiescing a fs will prevent new writes to the subvolumes.
// Similar To:
//
// ceph fs quiesce <volume>
func (fsa *FSAdmin) FSQuiesce(volume, group string, subvolumes []string, setId string, o *FSQuiesceOptions) (*FSQuiesceInfo, error) {
if o == nil {
o = &FSQuiesceOptions{}
}
f := o.toFields(volume, group, subvolumes, setId)
return parseFSQuiesceInfo(fsa.marshalMgrCommand(f))
}
+116
View File
@@ -0,0 +1,116 @@
package admin
import (
"strconv"
ccom "github.com/ceph/go-ceph/common/commands"
"github.com/ceph/go-ceph/internal/commands"
"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 = ccom.RadosCommander
// FSAdmin is used to administrate CephFS within a ceph cluster.
type FSAdmin struct {
conn RadosCommander
}
// 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 {
return commands.RawMgrCommand(fsa.conn, 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 {
return commands.MarshalMgrCommand(fsa.conn, v)
}
// 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 {
return commands.RawMonCommand(fsa.conn, 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 {
return commands.MarshalMonCommand(fsa.conn, v)
}
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
}
func parseListKeyValues(res response) (map[string]string, error) {
var x map[string]string
if err := res.NoStatus().Unmarshal(&x).End(); err != nil {
return nil, err
}
return x, 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)
}
+107
View File
@@ -0,0 +1,107 @@
//go:build !(nautilus || octopus || pacific)
// +build !nautilus,!octopus,!pacific
package admin
// GetMetadata gets custom metadata on the subvolume in a volume belonging to
// an optional subvolume group based on provided key name.
//
// Similar To:
//
// ceph fs subvolume metadata get <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) GetMetadata(volume, group, subvolume, key string) (string, error) {
m := map[string]string{
"prefix": "fs subvolume metadata get",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"key_name": key,
}
if group != NoGroup {
m["group_name"] = group
}
return parsePathResponse(fsa.marshalMgrCommand(m))
}
// SetMetadata sets custom metadata on the subvolume in a volume belonging to
// an optional subvolume group as a key-value pair.
//
// Similar To:
//
// ceph fs subvolume metadata set <vol_name> <sub_name> <key_name> <value> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) SetMetadata(volume, group, subvolume, key, value string) error {
m := map[string]string{
"prefix": "fs subvolume metadata set",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"key_name": key,
"value": value,
}
if group != NoGroup {
m["group_name"] = group
}
return fsa.marshalMgrCommand(m).NoData().End()
}
// RemoveMetadata removes custom metadata set on the subvolume in a volume
// belonging to an optional subvolume group using the metadata key.
//
// Similar To:
//
// ceph fs subvolume metadata rm <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) RemoveMetadata(volume, group, subvolume, key string) error {
return fsa.rmSubVolumeMetadata(volume, group, subvolume, key, commonRmFlags{})
}
// ForceRemoveMetadata attempt to forcefully remove custom metadata set on
// the subvolume in a volume belonging to an optional subvolume group using
// the metadata key.
//
// Similar To:
//
// ceph fs subvolume metadata rm <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>] --force
func (fsa *FSAdmin) ForceRemoveMetadata(volume, group, subvolume, key string) error {
return fsa.rmSubVolumeMetadata(volume, group, subvolume, key, commonRmFlags{force: true})
}
func (fsa *FSAdmin) rmSubVolumeMetadata(volume, group, subvolume, key string, o commonRmFlags) error {
m := map[string]string{
"prefix": "fs subvolume metadata rm",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"key_name": key,
}
if group != NoGroup {
m["group_name"] = group
}
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
}
// ListMetadata lists custom metadata (key-value pairs) set on the subvolume
// in a volume belonging to an optional subvolume group.
//
// Similar To:
//
// ceph fs subvolume metadata ls <vol_name> <sub_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) ListMetadata(volume, group, subvolume string) (map[string]string, error) {
m := map[string]string{
"prefix": "fs subvolume metadata ls",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
}
if group != NoGroup {
m["group_name"] = group
}
return parseListKeyValues(fsa.marshalMgrCommand(m))
}
+27
View File
@@ -0,0 +1,27 @@
package admin
import (
"github.com/ceph/go-ceph/common/admin/manager"
)
const mirroring = "mirroring"
// EnableMirroringModule will enable the mirroring module for cephfs.
//
// Similar To:
//
// ceph mgr module enable mirroring [--force]
func (fsa *FSAdmin) EnableMirroringModule(force bool) error {
mgradmin := manager.NewFromConn(fsa.conn)
return mgradmin.EnableModule(mirroring, force)
}
// DisableMirroringModule will disable the mirroring module for cephfs.
//
// Similar To:
//
// ceph mgr module disable mirroring
func (fsa *FSAdmin) DisableMirroringModule() error {
mgradmin := manager.NewFromConn(fsa.conn)
return mgradmin.DisableModule(mirroring)
}
+242
View File
@@ -0,0 +1,242 @@
package admin
import (
ccom "github.com/ceph/go-ceph/common/commands"
"github.com/ceph/go-ceph/internal/commands"
)
// SnapshotMirrorAdmin helps administer the snapshot mirroring features of
// cephfs. Snapshot mirroring is only available in ceph pacific and later.
type SnapshotMirrorAdmin struct {
conn ccom.MgrCommander
}
// SnapshotMirror returns a new SnapshotMirrorAdmin to be used for the
// administration of snapshot mirroring features.
func (fsa *FSAdmin) SnapshotMirror() *SnapshotMirrorAdmin {
return &SnapshotMirrorAdmin{conn: fsa.conn}
}
// Enable snapshot mirroring for the given file system.
//
// Similar To:
//
// ceph fs snapshot mirror enable <fs_name>
func (sma *SnapshotMirrorAdmin) Enable(fsname string) error {
m := map[string]string{
"prefix": "fs snapshot mirror enable",
"fs_name": fsname,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
// Disable snapshot mirroring for the given file system.
//
// Similar To:
//
// ceph fs snapshot mirror disable <fs_name>
func (sma *SnapshotMirrorAdmin) Disable(fsname string) error {
m := map[string]string{
"prefix": "fs snapshot mirror disable",
"fs_name": fsname,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
// Add a path in the file system to be mirrored.
//
// Similar To:
//
// ceph fs snapshot mirror add <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Add(fsname, path string) error {
m := map[string]string{
"prefix": "fs snapshot mirror add",
"fs_name": fsname,
"path": path,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
// Remove a path in the file system from mirroring.
//
// Similar To:
//
// ceph fs snapshot mirror remove <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Remove(fsname, path string) error {
m := map[string]string{
"prefix": "fs snapshot mirror remove",
"fs_name": fsname,
"path": path,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
type bootstrapTokenResponse struct {
Token string `json:"token"`
}
// CreatePeerBootstrapToken returns a token that can be used to create
// a peering association between this site an another site.
//
// Similar To:
//
// ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>
func (sma *SnapshotMirrorAdmin) CreatePeerBootstrapToken(
fsname, client, site string) (string, error) {
m := map[string]string{
"prefix": "fs snapshot mirror peer_bootstrap create",
"fs_name": fsname,
"client_name": client,
"format": "json",
}
if site != "" {
m["site_name"] = site
}
var bt bootstrapTokenResponse
err := commands.MarshalMgrCommand(sma.conn, m).NoStatus().Unmarshal(&bt).End()
return bt.Token, err
}
// ImportPeerBoostrapToken creates an association between another site, one
// that has provided a token, with the current site.
//
// Similar To:
//
// ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>
func (sma *SnapshotMirrorAdmin) ImportPeerBoostrapToken(fsname, token string) error {
m := map[string]string{
"prefix": "fs snapshot mirror peer_bootstrap import",
"fs_name": fsname,
"token": token,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
// DaemonID represents the ID of a cephfs mirroring daemon.
type DaemonID uint
// FileSystemID represents the ID of a cephfs file system.
type FileSystemID uint
// PeerUUID represents the UUID of a cephfs mirroring peer.
type PeerUUID string
// DaemonStatusPeer contains fields detailing a remote peer.
type DaemonStatusPeer struct {
ClientName string `json:"client_name"`
ClusterName string `json:"cluster_name"`
FSName string `json:"fs_name"`
}
// DaemonStatusPeerStats contains fields detailing the a remote peer's stats.
type DaemonStatusPeerStats struct {
FailureCount uint64 `json:"failure_count"`
RecoveryCount uint64 `json:"recovery_count"`
}
// DaemonStatusPeerInfo contains fields representing information about a remote peer.
type DaemonStatusPeerInfo struct {
UUID PeerUUID `json:"uuid"`
Remote DaemonStatusPeer `json:"remote"`
Stats DaemonStatusPeerStats `json:"stats"`
}
// DaemonStatusFileSystemInfo represents information about a mirrored file system.
type DaemonStatusFileSystemInfo struct {
FileSystemID FileSystemID `json:"filesystem_id"`
Name string `json:"name"`
DirectoryCount int64 `json:"directory_count"`
Peers []DaemonStatusPeerInfo `json:"peers"`
}
// DaemonStatusInfo maps file system IDs to information about that file system.
type DaemonStatusInfo struct {
DaemonID DaemonID `json:"daemon_id"`
FileSystems []DaemonStatusFileSystemInfo `json:"filesystems"`
}
// DaemonStatusResults maps mirroring daemon IDs to information about that
// mirroring daemon.
type DaemonStatusResults []DaemonStatusInfo
func parseDaemonStatus(res response) (DaemonStatusResults, error) {
var dsr DaemonStatusResults
if err := res.NoStatus().Unmarshal(&dsr).End(); err != nil {
return nil, err
}
return dsr, nil
}
// DaemonStatus returns information on the status of cephfs mirroring daemons
// associated with the given file system.
//
// Similar To:
//
// ceph fs snapshot mirror daemon status <fs_name>
func (sma *SnapshotMirrorAdmin) DaemonStatus(fsname string) (
DaemonStatusResults, error) {
// ---
m := map[string]string{
"prefix": "fs snapshot mirror daemon status",
"fs_name": fsname,
"format": "json",
}
return parseDaemonStatus(commands.MarshalMgrCommand(sma.conn, m))
}
// PeerInfo includes information about a cephfs mirroring peer.
type PeerInfo struct {
ClientName string `json:"client_name"`
SiteName string `json:"site_name"`
FSName string `json:"fs_name"`
MonHost string `json:"mon_host"`
}
// PeerListResults maps a peer's UUID to information about that peer.
type PeerListResults map[PeerUUID]PeerInfo
func parsePeerList(res response) (PeerListResults, error) {
var plr PeerListResults
if err := res.NoStatus().Unmarshal(&plr).End(); err != nil {
return nil, err
}
return plr, nil
}
// PeerList returns information about peers associated with the given file system.
//
// Similar To:
//
// ceph fs snapshot mirror peer_list <fs_name>
func (sma *SnapshotMirrorAdmin) PeerList(fsname string) (
PeerListResults, error) {
// ---
m := map[string]string{
"prefix": "fs snapshot mirror peer_list",
"fs_name": fsname,
"format": "json",
}
return parsePeerList(commands.MarshalMgrCommand(sma.conn, m))
}
/*
DirMap - figure out what last_shuffled is supposed to mean and, if it is a time
like it seems to be, how best to represent in Go.
DirMap TODO
ceph fs snapshot mirror dirmap
func (sma *SnapshotMirrorAdmin) DirMap(fsname, path string) error {
m := map[string]string{
"prefix": "fs snapshot mirror dirmap",
"fs_name": fsname,
"path": path,
"format": "json",
}
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
*/
+46
View File
@@ -0,0 +1,46 @@
//go:build !nautilus
// +build !nautilus
package admin
// PinSubVolume pins subvolume to ranks according to policies. A valid pin
// setting value depends on the type of pin as described in the docs from
// https://docs.ceph.com/en/latest/cephfs/multimds/#cephfs-pinning and
// https://docs.ceph.com/en/latest/cephfs/multimds/#setting-subtree-partitioning-policies
//
// Similar To:
//
// ceph fs subvolume pin <vol_name> <sub_name> <pin_type> <pin_setting>
func (fsa *FSAdmin) PinSubVolume(volume, subvolume, pintype, pinsetting string) (string, error) {
m := map[string]string{
"prefix": "fs subvolume pin",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"pin_type": pintype,
"pin_setting": pinsetting,
}
return parsePathResponse(fsa.marshalMgrCommand(m))
}
// PinSubVolumeGroup pins subvolume to ranks according to policies. A valid pin
// setting value depends on the type of pin as described in the docs from
// https://docs.ceph.com/en/latest/cephfs/multimds/#cephfs-pinning and
// https://docs.ceph.com/en/latest/cephfs/multimds/#setting-subtree-partitioning-policies
//
// Similar To:
//
// ceph fs subvolumegroup pin <vol_name> <group_name> <pin_type> <pin_setting>
func (fsa *FSAdmin) PinSubVolumeGroup(volume, group, pintype, pinsetting string) (string, error) {
m := map[string]string{
"prefix": "fs subvolumegroup pin",
"format": "json",
"vol_name": volume,
"group_name": group,
"pin_type": pintype,
"pin_setting": pinsetting,
}
return parsePathResponse(fsa.marshalMgrCommand(m))
}
+22
View File
@@ -0,0 +1,22 @@
package admin
import (
"github.com/ceph/go-ceph/internal/commands"
)
var (
// ErrStatusNotEmpty is an alias for commands.ErrStatusNotEmpty
ErrStatusNotEmpty = commands.ErrStatusNotEmpty
// ErrBodyNotEmpty is an alias for commands.ErrBodyNotEmpty
ErrBodyNotEmpty = commands.ErrBodyNotEmpty
)
type response = commands.Response
// NotImplementedError is an alias for commands.NotImplementedError.
type NotImplementedError = commands.NotImplementedError
// newResponse returns a response.
func newResponse(b []byte, s string, e error) response {
return commands.NewResponse(b, s, e)
}
+112
View File
@@ -0,0 +1,112 @@
//go:build !(nautilus || octopus || pacific)
// +build !nautilus,!octopus,!pacific
package admin
// GetSnapshotMetadata gets custom metadata on the subvolume snapshot in a
// volume belonging to an optional subvolume group based on provided key name.
//
// Similar To:
//
// ceph fs subvolume snapshot metadata get <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) GetSnapshotMetadata(volume, group, subvolume, snapname, key string) (string, error) {
m := map[string]string{
"prefix": "fs subvolume snapshot metadata get",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"snap_name": snapname,
"key_name": key,
}
if group != NoGroup {
m["group_name"] = group
}
return parsePathResponse(fsa.marshalMgrCommand(m))
}
// SetSnapshotMetadata sets custom metadata on the subvolume snapshot in a
// volume belonging to an optional subvolume group as a key-value pair.
//
// Similar To:
//
// ceph fs subvolume snapshot metadata set <vol_name> <sub_name> <snap_name> <key_name> <value> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) SetSnapshotMetadata(volume, group, subvolume, snapname, key, value string) error {
m := map[string]string{
"prefix": "fs subvolume snapshot metadata set",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"snap_name": snapname,
"key_name": key,
"value": value,
}
if group != NoGroup {
m["group_name"] = group
}
return fsa.marshalMgrCommand(m).NoData().End()
}
// RemoveSnapshotMetadata removes custom metadata set on the subvolume
// snapshot in a volume belonging to an optional subvolume group using the
// metadata key.
//
// Similar To:
//
// ceph fs subvolume snapshot metadata rm <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) RemoveSnapshotMetadata(volume, group, subvolume, snapname, key string) error {
return fsa.rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key, commonRmFlags{})
}
// ForceRemoveSnapshotMetadata attempt to forcefully remove custom metadata
// set on the subvolume snapshot in a volume belonging to an optional
// subvolume group using the metadata key.
//
// Similar To:
//
// ceph fs subvolume snapshot metadata rm <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>] --force
func (fsa *FSAdmin) ForceRemoveSnapshotMetadata(volume, group, subvolume, snapname, key string) error {
return fsa.rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key, commonRmFlags{force: true})
}
func (fsa *FSAdmin) rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key string, o commonRmFlags) error {
m := map[string]string{
"prefix": "fs subvolume snapshot metadata rm",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"snap_name": snapname,
"key_name": key,
}
if group != NoGroup {
m["group_name"] = group
}
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
}
// ListSnapshotMetadata lists custom metadata (key-value pairs) set on the subvolume
// snapshot in a volume belonging to an optional subvolume group.
//
// Similar To:
//
// ceph fs subvolume snapshot metadata ls <vol_name> <sub_name> <snap_name> [--group_name <subvol_group_name>]
func (fsa *FSAdmin) ListSnapshotMetadata(volume, group, subvolume, snapname string) (map[string]string, error) {
m := map[string]string{
"prefix": "fs subvolume snapshot metadata ls",
"format": "json",
"vol_name": volume,
"sub_name": subvolume,
"snap_name": snapname,
}
if group != NoGroup {
m["group_name"] = group
}
return parseListKeyValues(fsa.marshalMgrCommand(m))
}
+425
View File
@@ -0,0 +1,425 @@
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.RemoveSubVolumeWithFlags(volume, group, name, SubVolRmFlags{})
}
// 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.RemoveSubVolumeWithFlags(volume, group, name, SubVolRmFlags{Force: true})
}
// RemoveSubVolumeWithFlags will delete a CephFS subvolume in a volume and
// optional subvolume group. This function accepts a SubVolRmFlags type that
// can be used to specify flags that modify the operations behavior.
// Equivalent to RemoveSubVolume with no flags set.
// Equivalent to ForceRemoveSubVolume if only the "Force" flag is set.
//
// Similar To:
//
// ceph fs subvolume rm <volume> --group-name=<group> <name> [...flags...]
func (fsa *FSAdmin) RemoveSubVolumeWithFlags(volume, group, name string, o SubVolRmFlags) 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(mergeFlags(m, o)).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")
)
// SubVolumeState is used to define constant value for the state of
// a subvolume.
type SubVolumeState string
const (
// StateUnset indicates a subvolume without any state.
StateUnset = SubVolumeState("")
// StateInit indicates that the subvolume is in initializing state.
StateInit = SubVolumeState("init")
// StatePending indicates that the subvolume is in pending state.
StatePending = SubVolumeState("pending")
// StateInProgress indicates that the subvolume is in in-progress state.
StateInProgress = SubVolumeState("in-progress")
// StateFailed indicates that the subvolume is in failed state.
StateFailed = SubVolumeState("failed")
// StateComplete indicates that the subvolume is in complete state.
StateComplete = SubVolumeState("complete")
// StateCanceled indicates that the subvolume is in canceled state.
StateCanceled = SubVolumeState("canceled")
// StateSnapRetained indicates that the subvolume is in
// snapshot-retained state.
StateSnapRetained = SubVolumeState("snapshot-retained")
)
// SubVolumeInfo reports various informational values about a subvolume.
type SubVolumeInfo struct {
Type string `json:"type"`
Path string `json:"path"`
State SubVolumeState `json:"state"`
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, commonRmFlags{})
}
// 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, commonRmFlags{force: true})
}
func (fsa *FSAdmin) rmSubVolumeSnapshot(volume, group, subvolume, name string, o commonRmFlags) 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(mergeFlags(m, o)).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()
}
@@ -0,0 +1,22 @@
//go:build !(octopus || pacific || quincy || reef || squid)
package admin
// SubVolumeSnapshotPath returns the path for a snapshot from the source subvolume.
//
// Similar To:
//
// ceph fs subvolume snapshot getpath <volume> --group-name=<group> <source> <name>
func (fsa *FSAdmin) SubVolumeSnapshotPath(volume, group, source, name string) (string, error) {
m := map[string]string{
"prefix": "fs subvolume snapshot getpath",
"vol_name": volume,
"sub_name": source,
"snap_name": name,
"format": "json",
}
if group != NoGroup {
m["group_name"] = group
}
return parsePathResponse(fsa.marshalMgrCommand(m))
}
+108
View File
@@ -0,0 +1,108 @@
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, commonRmFlags{})
}
// 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, commonRmFlags{force: true})
}
func (fsa *FSAdmin) rmSubVolumeGroup(volume, name string, o commonRmFlags) error {
res := fsa.marshalMgrCommand(mergeFlags(map[string]string{
"prefix": "fs subvolumegroup rm",
"vol_name": volume,
"group_name": name,
"format": "json",
}, o))
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))
}
+37
View File
@@ -0,0 +1,37 @@
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
}
+186
View File
@@ -0,0 +1,186 @@
package admin
import (
"bytes"
"encoding/json"
)
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()
}
var dump fsDump
if err := res.FilterPrefix(dumpOkPrefix).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"`
}
type mdsVersionField struct {
Version string
Items []struct {
Version string `json:"version"`
}
}
func (m *mdsVersionField) UnmarshalJSON(data []byte) (err error) {
if err = json.Unmarshal(data, &m.Version); err == nil {
return
}
return json.Unmarshal(data, &m.Items)
}
// volumeStatusResponse deals with the changing output of the mgr
// api json
type volumeStatusResponse struct {
Pools []VolumePool `json:"pools"`
MDSVersion mdsVersionField `json:"mds_version"`
}
func (v *volumeStatusResponse) volumeStatus() *VolumeStatus {
vstatus := &VolumeStatus{}
vstatus.Pools = v.Pools
if v.MDSVersion.Version != "" {
vstatus.MDSVersion = v.MDSVersion.Version
} else if len(v.MDSVersion.Items) > 0 {
vstatus.MDSVersion = v.MDSVersion.Items[0].Version
}
return vstatus
}
func parseVolumeStatus(res response) (*volumeStatusResponse, error) {
var vs volumeStatusResponse
res = res.NoStatus()
if !res.Ok() {
return nil, res.End()
}
res = res.Unmarshal(&vs)
if !res.Ok() {
if bytes.HasPrefix(res.Body(), []byte("ceph")) {
return nil, NotImplementedError{
Response: newResponse(res.Body(), invalidTextualResponse, res.Unwrap()),
}
}
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",
})
v, err := parseVolumeStatus(res)
if err != nil {
return nil, err
}
return v.volumeStatus(), nil
}
+48
View File
@@ -0,0 +1,48 @@
//go:build !(nautilus || octopus)
// +build !nautilus,!octopus
package admin
// PoolInfo reports various properties of a pool.
type PoolInfo struct {
Available int `json:"avail"`
Name string `json:"name"`
Used int `json:"used"`
}
// PoolType indicates the type of pool related to a volume.
type PoolType struct {
DataPool []PoolInfo `json:"data"`
MetadataPool []PoolInfo `json:"metadata"`
}
// VolInfo holds various informational values about a volume.
type VolInfo struct {
MonAddrs []string `json:"mon_addrs"`
PendingSubvolDels int `json:"pending_subvolume_deletions"`
Pools PoolType `json:"pools"`
UsedSize int `json:"used_size"`
}
func parseVolumeInfo(res response) (*VolInfo, error) {
var info VolInfo
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
return nil, err
}
return &info, nil
}
// FetchVolumeInfo fetches the information of a CephFS volume.
//
// Similar To:
//
// ceph fs volume info <vol_name>
func (fsa *FSAdmin) FetchVolumeInfo(volume string) (*VolInfo, error) {
m := map[string]string{
"prefix": "fs volume info",
"vol_name": volume,
"format": "json",
}
return parseVolumeInfo(fsa.marshalMgrCommand(m))
}