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
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Noah Watkins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+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))
}
+326
View File
@@ -0,0 +1,326 @@
//go:build ceph_preview
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <dirent.h>
#include <cephfs/libcephfs.h>
// Types and constants are copied from libcephfs.h with added "_" as prefix. This
// prevents redefinition of the types on libcephfs versions that have them
// already.
// struct ceph_file_blockdiff_result lacks a definition, so create an alias and
// treat it as opaque.
typedef struct _ceph_file_blockdiff_result _ceph_file_blockdiff_result;
typedef struct
{
struct ceph_mount_info* cmount;
struct _ceph_file_blockdiff_result* blockp;
} _ceph_file_blockdiff_info;
typedef struct
{
uint64_t offset;
uint64_t len;
} _cblock;
typedef struct
{
uint64_t num_blocks;
struct _cblock *b;
} _ceph_file_blockdiff_changedblocks;
// ceph_file_blockdiff_init_fn matches the ceph_file_blockdiff_init function signature.
typedef int(*ceph_file_blockdiff_init_fn)(struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_file_blockdiff_info* out_info);
// ceph_file_blockdiff_init_dlsym take *fn as ceph_file_blockdiff_init and calls the dynamically loaded
// ceph_file_blockdiff_init function passed as 1st argument.
static inline int ceph_file_blockdiff_init_dlsym(void *fn,
struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_file_blockdiff_info* out_info) {
// cast function pointer fn to ceph_file_blockdiff_init and call the function
return ((ceph_file_blockdiff_init_fn) fn)(cmount, root_path, rel_path, snap1, snap2, out_info);
}
// ceph_file_blockdiff_fn matches the ceph_file_blockdiff function signature.
typedef int(*ceph_file_blockdiff_fn)(_ceph_file_blockdiff_info* info,
_ceph_file_blockdiff_changedblocks* blocks);
// ceph_file_blockdiff_dlsym take *fn as ceph_file_blockdiff and calls the dynamically loaded
// ceph_file_blockdiff function passed as 1st argument.
static inline int ceph_file_blockdiff_dlsym(void *fn,
_ceph_file_blockdiff_info* info,
_ceph_file_blockdiff_changedblocks* blocks) {
// cast function pointer fn to ceph_file_blockdiff and call the function
return ((ceph_file_blockdiff_fn) fn)(info, blocks);
}
// ceph_free_file_blockdiff_buffer_fn matches the ceph_free_file_blockdiff_buffer function signature.
typedef void(*ceph_free_file_blockdiff_buffer_fn)(_ceph_file_blockdiff_changedblocks* blocks);
// ceph_free_file_blockdiff_buffer_dlsym take *fn as ceph_free_file_blockdiff_buffer and calls the dynamically loaded
// ceph_free_file_blockdiff_buffer function passed as 1st argument.
static inline void ceph_free_file_blockdiff_buffer_dlsym(void *fn,
_ceph_file_blockdiff_changedblocks* blocks) {
// cast function pointer fn to ceph_free_file_blockdiff_buffer and call the function
((ceph_free_file_blockdiff_buffer_fn) fn)(blocks);
}
// ceph_file_blockdiff_finish_fn matches the ceph_file_blockdiff_finish function signature.
typedef int(*ceph_file_blockdiff_finish_fn)(_ceph_file_blockdiff_info* info);
// ceph_file_blockdiff_finish_dlsym take *fn as ceph_file_blockdiff_finish and calls the dynamically loaded
// ceph_file_blockdiff_finish function passed as 1st argument.
static inline int ceph_file_blockdiff_finish_dlsym(void *fn,
_ceph_file_blockdiff_info* info) {
// cast function pointer fn to ceph_file_blockdiff_finish and call the function
return ((ceph_file_blockdiff_finish_fn) fn)(info);
}
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
"github.com/ceph/go-ceph/internal/dlsym"
)
var (
cephFileBlockDiffInitOnce sync.Once
cephFileBlockDiffOnce sync.Once
cephFreeFileBlockDiffBufferOnce sync.Once
cephFileBlockDiffFinishOnce sync.Once
cephFileBlockDiffInitErr error
cephFileBlockDiffErr error
cephFreeFileBlockDiffBufferErr error
cephFileBlockDiffFinishErr error
cephFileBlockDiffInit unsafe.Pointer
cephFileBlockDiff unsafe.Pointer
cephFreeFileBlockDiffBuffer unsafe.Pointer
cephFileBlockDiffFinish unsafe.Pointer
)
// FileBlockDiffInfo is a struct that holds the block diff stream handle.
type FileBlockDiffInfo struct {
cMount *MountInfo
cephFileBlockDiffResult *C._ceph_file_blockdiff_result
more bool
}
// ChangedBlock is a struct that holds the offset and length of a block.
type ChangedBlock struct {
Offset uint64
Len uint64
}
// FileBlockDiffChangedBlocks is a struct that holds the number of blocks
// and list of Changed Blocks.
type FileBlockDiffChangedBlocks struct {
NumBlocks uint64
ChangedBlocks []ChangedBlock
}
// FileBlockDiffInit initializes the block diff stream to get file block deltas.
// It takes the mount handle, root path, relative path, snapshot names and returns
// a FileBlockDiffInfo struct that contains the block diff stream handle.
//
// Implements:
//
// int ceph_file_blockdiff_init(struct ceph_mount_info* cmount,
// const char* root_path,
// const char* rel_path,
// const char* snap1,
// const char* snap2,
// struct ceph_file_blockdiff_info* out_info);
func FileBlockDiffInit(mount *MountInfo, rootPath, relPath, snap1, snap2 string) (*FileBlockDiffInfo, error) {
if mount == nil || rootPath == "" || relPath == "" ||
snap1 == "" || snap2 == "" {
return nil, errInvalid
}
cRootPath := C.CString(rootPath)
cRelPath := C.CString(relPath)
cSnap1 := C.CString(snap1)
cSnap2 := C.CString(snap2)
defer func() {
C.free(unsafe.Pointer(cRootPath))
C.free(unsafe.Pointer(cRelPath))
C.free(unsafe.Pointer(cSnap1))
C.free(unsafe.Pointer(cSnap2))
}()
// Load the ceph_file_blockdiff_init function from the shared library.
cephFileBlockDiffInitOnce.Do(func() {
cephFileBlockDiffInit, cephFileBlockDiffInitErr = dlsym.LookupSymbol("ceph_file_blockdiff_init")
})
if cephFileBlockDiffInitErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffInitErr)
}
rawCephBlockDiffInfo := &C._ceph_file_blockdiff_info{}
// Call the ceph_file_blockdiff_init function with the provided arguments.
ret := C.ceph_file_blockdiff_init_dlsym(cephFileBlockDiffInit,
mount.mount,
cRootPath,
cRelPath,
cSnap1,
cSnap2,
rawCephBlockDiffInfo,
)
if ret != 0 {
return nil, getError(ret)
}
mountInfo := &MountInfo{
mount: rawCephBlockDiffInfo.cmount,
}
cephFileBlockDiffInfo := &FileBlockDiffInfo{
cMount: mountInfo,
cephFileBlockDiffResult: rawCephBlockDiffInfo.blockp,
more: true,
}
return cephFileBlockDiffInfo, nil
}
// validate checks if the FileBlockDiffInfo struct is valid.
func (info *FileBlockDiffInfo) validate() error {
if info.cMount == nil || info.cephFileBlockDiffResult == nil {
return errInvalid
}
return nil
}
// Read retrieves the next set of changed blocks for a file.
//
// Implements:
//
// int ceph_file_blockdiff(struct ceph_file_blockdiff_info* info,
// struct ceph_file_blockdiff_changedblocks* blocks);
func (info *FileBlockDiffInfo) Read() (*FileBlockDiffChangedBlocks, error) {
err := info.validate()
if err != nil {
return nil, err
}
// Load the ceph_file_blockdiff function from the shared library.
cephFileBlockDiffOnce.Do(func() {
cephFileBlockDiff, cephFileBlockDiffErr = dlsym.LookupSymbol("ceph_file_blockdiff")
})
if cephFileBlockDiffErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffErr)
}
cephFreeFileBlockDiffBufferOnce.Do(func() {
cephFreeFileBlockDiffBuffer, cephFreeFileBlockDiffBufferErr = dlsym.LookupSymbol("ceph_free_file_blockdiff_buffer")
})
if cephFreeFileBlockDiffBufferErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFreeFileBlockDiffBufferErr)
}
rawCephBlockDiffChangedBlocks := &C._ceph_file_blockdiff_changedblocks{}
rawCephFileBlockDiffInfo := &C._ceph_file_blockdiff_info{
cmount: info.cMount.mount,
blockp: info.cephFileBlockDiffResult,
}
// Call the ceph_file_blockdiff function with the provided arguments.
ret := C.ceph_file_blockdiff_dlsym(cephFileBlockDiff,
rawCephFileBlockDiffInfo,
rawCephBlockDiffChangedBlocks,
)
if ret < 0 {
return nil, getError(ret)
}
// ret>0 indicates that there are more entries after this call.
info.more = (ret > 0)
// Free the memory allocated for the blocks by ceph_file_blockdiff.
defer C.ceph_free_file_blockdiff_buffer_dlsym(cephFreeFileBlockDiffBuffer,
rawCephBlockDiffChangedBlocks)
cNumBlocks := uint64(rawCephBlockDiffChangedBlocks.num_blocks)
// Convert the C struct to Go struct.
cBlocks := make([]ChangedBlock, int(cNumBlocks))
if cNumBlocks == 0 {
return &FileBlockDiffChangedBlocks{
NumBlocks: 0,
ChangedBlocks: cBlocks,
}, nil
}
currentCBlock := (*C._cblock)(unsafe.Pointer(rawCephBlockDiffChangedBlocks.b))
for i := uint64(0); i < cNumBlocks; i++ {
cBlocks[i] = ChangedBlock{
Offset: uint64(currentCBlock.offset),
Len: uint64(currentCBlock.len),
}
currentCBlock = (*C._cblock)(unsafe.Pointer(uintptr(unsafe.Pointer(currentCBlock)) + unsafe.Sizeof(C._cblock{})))
}
return &FileBlockDiffChangedBlocks{
NumBlocks: cNumBlocks,
ChangedBlocks: cBlocks,
}, nil
}
// More returns true if there are more entries to read in the block diff stream.
func (info *FileBlockDiffInfo) More() bool {
return info.more
}
// Close closes the block diff stream.
//
// Implements:
//
// int ceph_file_blockdiff_finish(struct ceph_file_blockdiff_info* info);
func (info *FileBlockDiffInfo) Close() error {
err := info.validate()
if err != nil {
return err
}
// Load the ceph_file_blockdiff_finish function from the shared library.
cephFileBlockDiffFinishOnce.Do(func() {
cephFileBlockDiffFinish, cephFileBlockDiffFinishErr = dlsym.LookupSymbol("ceph_file_blockdiff_finish")
})
if cephFileBlockDiffFinishErr != nil {
return fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffFinishErr)
}
rawCephFileBlockDiffInfo := &C._ceph_file_blockdiff_info{
cmount: info.cMount.mount,
blockp: info.cephFileBlockDiffResult,
}
// Call the ceph_file_blockdiff_finish function with the provided arguments.
ret := C.ceph_file_blockdiff_finish_dlsym(
cephFileBlockDiffFinish,
rawCephFileBlockDiffInfo,
)
if ret != 0 {
return getError(ret)
}
return nil
}
+248
View File
@@ -0,0 +1,248 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/retry"
"github.com/ceph/go-ceph/rados"
)
// MountInfo exports ceph's ceph_mount_info from libcephfs.cc
type MountInfo struct {
mount *C.struct_ceph_mount_info
}
func createMount(id *C.char) (*MountInfo, error) {
mount := &MountInfo{}
ret := C.ceph_create(&mount.mount, id)
if ret != 0 {
return nil, getError(ret)
}
return mount, nil
}
// validate checks whether mount.mount is ready to use or not.
func (mount *MountInfo) validate() error {
if mount.mount == nil {
return ErrNotConnected
}
return nil
}
// Version returns the major, minor, and patch level of the libcephfs library.
func Version() (int, int, int) {
var cMajor, cMinor, cPatch C.int
C.ceph_version(&cMajor, &cMinor, &cPatch)
return int(cMajor), int(cMinor), int(cPatch)
}
// CreateMount creates a mount handle for interacting with Ceph.
func CreateMount() (*MountInfo, error) {
return createMount(nil)
}
// CreateMountWithId creates a mount handle for interacting with Ceph.
// The caller can specify a unique id that will identify this client.
func CreateMountWithId(id string) (*MountInfo, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
return createMount(cid)
}
// CreateFromRados creates a mount handle using an existing rados cluster
// connection.
//
// Implements:
//
// int ceph_create_from_rados(struct ceph_mount_info **cmount, rados_t cluster);
func CreateFromRados(conn *rados.Conn) (*MountInfo, error) {
mount := &MountInfo{}
ret := C.ceph_create_from_rados(&mount.mount, C.rados_t(conn.Cluster()))
if ret != 0 {
return nil, getError(ret)
}
return mount, nil
}
// ReadDefaultConfigFile loads the ceph configuration from the default config file.
//
// Implements:
//
// int ceph_conf_read_file(struct ceph_mount_info *cmount, const char *path_list);
func (mount *MountInfo) ReadDefaultConfigFile() error {
ret := C.ceph_conf_read_file(mount.mount, nil)
return getError(ret)
}
// ReadConfigFile loads the ceph configuration from the specified config file.
//
// Implements:
//
// int ceph_conf_read_file(struct ceph_mount_info *cmount, const char *path_list);
func (mount *MountInfo) ReadConfigFile(path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_conf_read_file(mount.mount, cPath)
return getError(ret)
}
// ParseConfigArgv configures the mount using a unix style command line
// argument vector.
//
// Implements:
//
// int ceph_conf_parse_argv(struct ceph_mount_info *cmount, int argc, const char **argv);
func (mount *MountInfo) ParseConfigArgv(argv []string) error {
if err := mount.validate(); err != nil {
return err
}
if len(argv) == 0 {
return ErrEmptyArgument
}
cargv := make([]*C.char, len(argv))
for i := range argv {
cargv[i] = C.CString(argv[i])
defer C.free(unsafe.Pointer(cargv[i]))
}
ret := C.ceph_conf_parse_argv(mount.mount, C.int(len(cargv)), &cargv[0])
return getError(ret)
}
// ParseDefaultConfigEnv configures the mount from the default Ceph
// environment variable CEPH_ARGS.
//
// Implements:
//
// int ceph_conf_parse_env(struct ceph_mount_info *cmount, const char *var);
func (mount *MountInfo) ParseDefaultConfigEnv() error {
if err := mount.validate(); err != nil {
return err
}
ret := C.ceph_conf_parse_env(mount.mount, nil)
return getError(ret)
}
// SetConfigOption sets the value of the configuration option identified by
// the given name.
//
// Implements:
//
// int ceph_conf_set(struct ceph_mount_info *cmount, const char *option, const char *value);
func (mount *MountInfo) SetConfigOption(option, value string) error {
cOption := C.CString(option)
defer C.free(unsafe.Pointer(cOption))
cValue := C.CString(value)
defer C.free(unsafe.Pointer(cValue))
return getError(C.ceph_conf_set(mount.mount, cOption, cValue))
}
// GetConfigOption returns the value of the Ceph configuration option
// identified by the given name.
//
// Implements:
//
// int ceph_conf_get(struct ceph_mount_info *cmount, const char *option, char *buf, size_t len);
func (mount *MountInfo) GetConfigOption(option string) (string, error) {
cOption := C.CString(option)
defer C.free(unsafe.Pointer(cOption))
var (
err error
buf []byte
)
// range from 4k to 256KiB
retry.WithSizes(4096, 1<<18, func(size int) retry.Hint {
buf = make([]byte, size)
ret := C.ceph_conf_get(
mount.mount,
cOption,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
err = getError(ret)
return retry.DoubleSize.If(err == errNameTooLong)
})
if err != nil {
return "", err
}
value := C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
return value, nil
}
// Init the file system client without actually mounting the file system.
//
// Implements:
//
// int ceph_init(struct ceph_mount_info *cmount);
func (mount *MountInfo) Init() error {
return getError(C.ceph_init(mount.mount))
}
// Mount the file system, establishing a connection capable of I/O.
//
// Implements:
//
// int ceph_mount(struct ceph_mount_info *cmount, const char *root);
func (mount *MountInfo) Mount() error {
ret := C.ceph_mount(mount.mount, nil)
return getError(ret)
}
// MountWithRoot mounts the file system using the path provided for the root of
// the mount. This establishes a connection capable of I/O.
//
// Implements:
//
// int ceph_mount(struct ceph_mount_info *cmount, const char *root);
func (mount *MountInfo) MountWithRoot(root string) error {
croot := C.CString(root)
defer C.free(unsafe.Pointer(croot))
return getError(C.ceph_mount(mount.mount, croot))
}
// Unmount the file system.
//
// Implements:
//
// int ceph_unmount(struct ceph_mount_info *cmount);
func (mount *MountInfo) Unmount() error {
ret := C.ceph_unmount(mount.mount)
return getError(ret)
}
// Release destroys the mount handle.
//
// Implements:
//
// int ceph_release(struct ceph_mount_info *cmount);
func (mount *MountInfo) Release() error {
if mount.mount == nil {
return nil
}
ret := C.ceph_release(mount.mount)
if err := getError(ret); err != nil {
return err
}
mount.mount = nil
return nil
}
// SyncFs synchronizes all filesystem data to persistent media.
func (mount *MountInfo) SyncFs() error {
ret := C.ceph_sync_fs(mount.mount)
return getError(ret)
}
// IsMounted checks mount status.
func (mount *MountInfo) IsMounted() bool {
ret := C.ceph_is_mounted(mount.mount)
return ret == 1
}
+74
View File
@@ -0,0 +1,74 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
func cephBufferFree(p unsafe.Pointer) {
C.ceph_buffer_free((*C.char)(p))
}
// MdsCommand sends commands to the specified MDS.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (mount *MountInfo) MdsCommand(mdsSpec string, args [][]byte) ([]byte, string, error) {
return mount.mdsCommand(mdsSpec, args, nil)
}
// MdsCommandWithInputBuffer sends commands to the specified MDS, with an input
// buffer.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (mount *MountInfo) MdsCommandWithInputBuffer(mdsSpec string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
return mount.mdsCommand(mdsSpec, args, inputBuffer)
}
// mdsCommand supports sending formatted commands to MDS.
//
// Implements:
//
// int ceph_mds_command(struct ceph_mount_info *cmount,
// const char *mds_spec,
// const char **cmd,
// size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (mount *MountInfo) mdsCommand(mdsSpec string, args [][]byte, inputBuffer []byte) (buffer []byte, info string, err error) {
spec := C.CString(mdsSpec)
defer C.free(unsafe.Pointer(spec))
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(cephBufferFree)
defer co.Free()
ret := C.ceph_mds_command(
mount.mount, // cephfs mount ref
spec, // mds spec
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
+30
View File
@@ -0,0 +1,30 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <cephfs/libcephfs.h>
*/
import "C"
// Some general connectivity and mounting functions are new in
// Ceph Nautilus.
// GetFsCid returns the cluster ID for a mounted ceph file system.
// If the object does not refer to a mounted file system, an error
// will be returned.
//
// Note:
//
// Only supported in Ceph Nautilus and newer.
//
// Implements:
//
// int64_t ceph_get_fs_cid(struct ceph_mount_info *cmount);
func (mount *MountInfo) GetFsCid() (int64, error) {
ret := C.ceph_get_fs_cid(mount.mount)
if ret < 0 {
return 0, getError(C.int(ret))
}
return int64(ret), nil
}
+252
View File
@@ -0,0 +1,252 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <dirent.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
)
// Directory represents an open directory handle.
type Directory struct {
mount *MountInfo
dir *C.struct_ceph_dir_result
}
// OpenDir returns a new Directory handle open for I/O.
//
// Implements:
//
// int ceph_opendir(struct ceph_mount_info *cmount, const char *name, struct ceph_dir_result **dirpp);
func (mount *MountInfo) OpenDir(path string) (*Directory, error) {
var dir *C.struct_ceph_dir_result
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_opendir(mount.mount, cPath, &dir)
if ret != 0 {
return nil, getError(ret)
}
return &Directory{
mount: mount,
dir: dir,
}, nil
}
// Close the open directory handle.
//
// Implements:
//
// int ceph_closedir(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp);
func (dir *Directory) Close() error {
if dir.dir == nil {
return nil
}
if err := getError(C.ceph_closedir(dir.mount.mount, dir.dir)); err != nil {
return err
}
dir.dir = nil
return nil
}
// Inode represents an inode number in the file system.
type Inode uint64
// DType values are used to determine, when possible, the file type
// of a directory entry.
type DType uint8
const (
// DTypeBlk indicates a directory entry is a block device.
DTypeBlk = DType(C.DT_BLK)
// DTypeChr indicates a directory entry is a character device.
DTypeChr = DType(C.DT_CHR)
// DTypeDir indicates a directory entry is a directory.
DTypeDir = DType(C.DT_DIR)
// DTypeFIFO indicates a directory entry is a named pipe (FIFO).
DTypeFIFO = DType(C.DT_FIFO)
// DTypeLnk indicates a directory entry is a symbolic link.
DTypeLnk = DType(C.DT_LNK)
// DTypeReg indicates a directory entry is a regular file.
DTypeReg = DType(C.DT_REG)
// DTypeSock indicates a directory entry is a UNIX domain socket.
DTypeSock = DType(C.DT_SOCK)
// DTypeUnknown indicates that the file type could not be determined.
DTypeUnknown = DType(C.DT_UNKNOWN)
)
// DirEntry represents an entry within a directory.
type DirEntry struct {
inode Inode
name string
dtype DType
}
// Name returns the directory entry's name.
func (d *DirEntry) Name() string {
return d.name
}
// Inode returns the directory entry's inode number.
func (d *DirEntry) Inode() Inode {
return d.inode
}
// DType returns the Directory-entry's Type, indicating if it
// is a regular file, directory, etc.
// DType may be unknown and thus require an additional call
// (stat for example) if Unknown.
func (d *DirEntry) DType() DType {
return d.dtype
}
// DirEntryPlus is a DirEntry plus additional data (stat) for an entry
// within a directory.
type DirEntryPlus struct {
DirEntry
// statx: the converted statx returned by ceph_readdirplus_r
statx *CephStatx
}
// Statx returns cached stat metadata for the directory entry.
// This call does not incur an actual file system stat.
func (d *DirEntryPlus) Statx() *CephStatx {
return d.statx
}
// toDirEntry converts a c struct dirent to our go wrapper.
func toDirEntry(de *C.struct_dirent) *DirEntry {
return &DirEntry{
inode: Inode(de.d_ino),
name: C.GoString(&de.d_name[0]),
dtype: DType(de.d_type),
}
}
// toDirEntryPlus converts c structs set by ceph_readdirplus_r to our go
// wrapper.
func toDirEntryPlus(de *C.struct_dirent, s C.struct_ceph_statx) *DirEntryPlus {
return &DirEntryPlus{
DirEntry: *toDirEntry(de),
statx: cStructToCephStatx(s),
}
}
// ReadDir reads a single directory entry from the open Directory.
// A nil DirEntry pointer will be returned when the Directory stream has been
// exhausted.
//
// Implements:
//
// int ceph_readdir_r(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, struct dirent *de);
func (dir *Directory) ReadDir() (*DirEntry, error) {
if dir.dir == nil {
return nil, errBadFile
}
var de C.struct_dirent
ret := C.ceph_readdir_r(dir.mount.mount, dir.dir, &de)
if ret < 0 {
return nil, getError(ret)
}
if ret == 0 {
return nil, nil // End-of-stream
}
return toDirEntry(&de), nil
}
// ReadDirPlus reads a single directory entry and stat information from the
// open Directory.
// A nil DirEntryPlus pointer will be returned when the Directory stream has
// been exhausted.
// See Statx for a description of the wants and flags parameters.
//
// Implements:
//
// int ceph_readdirplus_r(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, struct dirent *de,
// struct ceph_statx *stx, unsigned want, unsigned flags, struct Inode **out);
func (dir *Directory) ReadDirPlus(
want StatxMask, flags AtFlags) (*DirEntryPlus, error) {
if dir.dir == nil {
return nil, errBadFile
}
var (
de C.struct_dirent
s C.struct_ceph_statx
)
ret := C.ceph_readdirplus_r(
dir.mount.mount,
dir.dir,
&de,
&s,
C.uint(want),
C.uint(flags),
nil, // unused, internal Inode type not needed for high level api
)
if ret < 0 {
return nil, getError(ret)
}
if ret == 0 {
return nil, nil // End-of-stream
}
return toDirEntryPlus(&de, s), nil
}
// RewindDir sets the directory stream to the beginning of the directory.
//
// Implements:
//
// void ceph_rewinddir(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp);
func (dir *Directory) RewindDir() {
if dir.dir == nil {
return
}
C.ceph_rewinddir(dir.mount.mount, dir.dir)
}
// dirEntries provides a convenient wrapper around slices of DirEntry items.
// For example, use the Names() call to easily get only the names from a
// DirEntry slice.
type dirEntries []*DirEntry
// list returns all the contents of a directory as a dirEntries slice.
//
// list is implemented using ReadDir. If any of the calls to ReadDir returns
// an error List will return an error. However, all previous entries
// collected will still be returned. Callers of this function may want to check
// the entries return value even when an error is returned.
// List rewinds the handle every time it is called to get a full
// listing of directory contents.
func (dir *Directory) list() (dirEntries, error) {
var (
err error
entry *DirEntry
entries = make(dirEntries, 0)
)
dir.RewindDir()
for {
entry, err = dir.ReadDir()
if err != nil || entry == nil {
break
}
entries = append(entries, entry)
}
return entries, err
}
// names returns a slice of only the name fields from dir entries.
func (entries dirEntries) names() []string {
names := make([]string, len(entries))
for i, v := range entries {
names[i] = v.Name()
}
return names
}
+4
View File
@@ -0,0 +1,4 @@
/*
Package cephfs contains a set of wrappers around Ceph's libcephfs API.
*/
package cephfs
+53
View File
@@ -0,0 +1,53 @@
package cephfs
/*
#include <errno.h>
*/
import "C"
import (
"errors"
"github.com/ceph/go-ceph/internal/errutil"
)
func getError(e C.int) error {
return errutil.GetError("cephfs", int(e))
}
// getErrorIfNegative converts a ceph return code to error if negative.
// This is useful for functions that return a usable positive value on
// success but a negative error number on error.
func getErrorIfNegative(ret C.int) error {
if ret >= 0 {
return nil
}
return getError(ret)
}
// Public go errors:
var (
// ErrEmptyArgument may be returned if a function argument is passed
// a zero-length slice or map.
ErrEmptyArgument = errors.New("Argument must contain at least one item")
// ErrNotConnected may be returned when client is not connected
// to a cluster.
ErrNotConnected = getError(-C.ENOTCONN)
// ErrNotExist indicates a non-specific missing resource.
ErrNotExist = getError(-C.ENOENT)
// ErrOpNotSupported is returned in general for operations that are not
// supported.
ErrOpNotSupported = getError(-C.EOPNOTSUPP)
// ErrNotImplemented indicates a function is not implemented in by libcephfs.
ErrNotImplemented = getError(-C.ENOSYS)
// Private errors:
errInvalid = getError(-C.EINVAL)
errNameTooLong = getError(-C.ENAMETOOLONG)
errRange = getError(-C.ERANGE)
errBadFile = getError(-C.EBADF)
errNotDir = getError(-C.ENOTDIR)
)
+440
View File
@@ -0,0 +1,440 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <cephfs/libcephfs.h>
int _go_ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid) {
return ceph_fchown(cmount, fd, uid, gid);
}
*/
import "C"
import (
"io"
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
const (
// SeekSet is used with Seek to set the absolute position in the file.
SeekSet = int(C.SEEK_SET)
// SeekCur is used with Seek to position the file relative to the current
// position.
SeekCur = int(C.SEEK_CUR)
// SeekEnd is used with Seek to position the file relative to the end.
SeekEnd = int(C.SEEK_END)
)
// SyncChoice is used to control how metadata and/or data is sync'ed to
// the file system.
type SyncChoice int
const (
// SyncAll will synchronize both data and metadata.
SyncAll = SyncChoice(0)
// SyncDataOnly will synchronize only data.
SyncDataOnly = SyncChoice(1)
)
// File represents an open file descriptor in cephfs.
type File struct {
mount *MountInfo
fd C.int
}
// Open a file at the given path. The flags are the same os flags as
// a local open call. Mode is the same mode bits as a local open call.
//
// Implements:
//
// int ceph_open(struct ceph_mount_info *cmount, const char *path, int flags, mode_t mode);
func (mount *MountInfo) Open(path string, flags int, mode uint32) (*File, error) {
if mount.mount == nil {
return nil, ErrNotConnected
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_open(mount.mount, cPath, C.int(flags), C.mode_t(mode))
if ret < 0 {
return nil, getError(ret)
}
return &File{mount: mount, fd: ret}, nil
}
func (f *File) validate() error {
if f.mount == nil {
return ErrNotConnected
}
return nil
}
// Close the file.
//
// Implements:
//
// int ceph_close(struct ceph_mount_info *cmount, int fd);
func (f *File) Close() error {
if f.fd == -1 {
// already closed
return nil
}
if err := f.validate(); err != nil {
return err
}
if err := getError(C.ceph_close(f.mount.mount, f.fd)); err != nil {
return err
}
f.fd = -1
return nil
}
// read directly wraps the ceph_read call. Because read is such a common
// operation we deviate from the ceph naming and expose Read and ReadAt
// wrappers for external callers of the library.
//
// Implements:
//
// int ceph_read(struct ceph_mount_info *cmount, int fd, char *buf, int64_t size, int64_t offset);
func (f *File) read(buf []byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
if len(buf) == 0 {
return 0, nil
}
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
ret := C.ceph_read(
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
switch {
case ret < 0:
return 0, getError(ret)
case ret == 0:
return 0, io.EOF
}
return int(ret), nil
}
// Read data from file. Up to len(buf) bytes will be read from the file.
// The number of bytes read will be returned.
// When nothing is left to read from the file, Read returns, 0, io.EOF.
func (f *File) Read(buf []byte) (int, error) {
// to-consider: should we mimic Go's behavior of returning an
// io.ErrShortWrite error if write length < buf size?
return f.read(buf, -1)
}
// ReadAt will read data from the file starting at the given offset.
// Up to len(buf) bytes will be read from the file.
// The number of bytes read will be returned.
// When nothing is left to read from the file, ReadAt returns, 0, io.EOF.
func (f *File) ReadAt(buf []byte, offset int64) (int, error) {
if offset < 0 {
return 0, errInvalid
}
return f.read(buf, offset)
}
// Preadv will read data from the file, starting at the given offset,
// into the byte-slice data buffers sequentially.
// The number of bytes read will be returned.
// When nothing is left to read from the file the return values will be:
// 0, io.EOF.
//
// Implements:
//
// int ceph_preadv(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
// int64_t offset);
func (f *File) Preadv(data [][]byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
iov := cutil.ByteSlicesToIovec(data)
defer iov.Free()
ret := C.ceph_preadv(
f.mount.mount,
f.fd,
(*C.struct_iovec)(iov.Pointer()),
C.int(iov.Len()),
C.int64_t(offset))
switch {
case ret < 0:
return 0, getError(ret)
case ret == 0:
return 0, io.EOF
}
iov.Sync()
return int(ret), nil
}
// write directly wraps the ceph_write call. Because write is such a common
// operation we deviate from the ceph naming and expose Write and WriteAt
// wrappers for external callers of the library.
//
// Implements:
//
// int ceph_write(struct ceph_mount_info *cmount, int fd, const char *buf,
// int64_t size, int64_t offset);
func (f *File) write(buf []byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
if len(buf) == 0 {
return 0, nil
}
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
ret := C.ceph_write(
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
if ret < 0 {
return 0, getError(ret)
}
return int(ret), nil
}
// Write data from buf to the file.
// The number of bytes written is returned.
func (f *File) Write(buf []byte) (int, error) {
return f.write(buf, -1)
}
// WriteAt writes data from buf to the file at the specified offset.
// The number of bytes written is returned.
func (f *File) WriteAt(buf []byte, offset int64) (int, error) {
if offset < 0 {
return 0, errInvalid
}
return f.write(buf, offset)
}
// Pwritev writes data from the slice of byte-slice buffers to the file at the
// specified offset.
// The number of bytes written is returned.
//
// Implements:
//
// int ceph_pwritev(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
// int64_t offset);
func (f *File) Pwritev(data [][]byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
iov := cutil.ByteSlicesToIovec(data)
defer iov.Free()
ret := C.ceph_pwritev(
f.mount.mount,
f.fd,
(*C.struct_iovec)(iov.Pointer()),
C.int(iov.Len()),
C.int64_t(offset))
if ret < 0 {
return 0, getError(ret)
}
return int(ret), nil
}
// Seek will reposition the file stream based on the given offset.
//
// Implements:
//
// int64_t ceph_lseek(struct ceph_mount_info *cmount, int fd, int64_t offset, int whence);
func (f *File) Seek(offset int64, whence int) (int64, error) {
if err := f.validate(); err != nil {
return 0, err
}
// validate the seek whence value in case the caller skews
// from the seek values we technically support from C as documented.
// TODO: need to support seek-(hole|data) in mimic and later.
switch whence {
case SeekSet, SeekCur, SeekEnd:
default:
return 0, errInvalid
}
ret := C.ceph_lseek(f.mount.mount, f.fd, C.int64_t(offset), C.int(whence))
if ret < 0 {
return 0, getError(C.int(ret))
}
return int64(ret), nil
}
// Fchmod changes the mode bits (permissions) of a file.
//
// Implements:
//
// int ceph_fchmod(struct ceph_mount_info *cmount, int fd, mode_t mode);
func (f *File) Fchmod(mode uint32) error {
if err := f.validate(); err != nil {
return err
}
ret := C.ceph_fchmod(f.mount.mount, f.fd, C.mode_t(mode))
return getError(ret)
}
// Fchown changes the ownership of a file.
//
// Implements:
//
// int ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid);
func (f *File) Fchown(user uint32, group uint32) error {
if err := f.validate(); err != nil {
return err
}
ret := C._go_ceph_fchown(f.mount.mount, f.fd, C.uid_t(user), C.gid_t(group))
return getError(ret)
}
// Fstatx returns information about an open file.
//
// Implements:
//
// int ceph_fstatx(struct ceph_mount_info *cmount, int fd, struct ceph_statx *stx,
// unsigned int want, unsigned int flags);
func (f *File) Fstatx(want StatxMask, flags AtFlags) (*CephStatx, error) {
if err := f.validate(); err != nil {
return nil, err
}
var stx C.struct_ceph_statx
ret := C.ceph_fstatx(
f.mount.mount,
f.fd,
&stx,
C.uint(want),
C.uint(flags),
)
if err := getError(ret); err != nil {
return nil, err
}
return cStructToCephStatx(stx), nil
}
// FallocFlags represent flags which determine the operation to be
// performed on the given range.
// CephFS supports only following two flags.
type FallocFlags int
const (
// FallocNoFlag means default option.
FallocNoFlag = FallocFlags(0)
// FallocFlKeepSize specifies that the file size will not be changed.
FallocFlKeepSize = FallocFlags(C.FALLOC_FL_KEEP_SIZE)
// FallocFlPunchHole specifies that the operation is to deallocate
// space and zero the byte range.
FallocFlPunchHole = FallocFlags(C.FALLOC_FL_PUNCH_HOLE)
)
// Fallocate preallocates or releases disk space for the file for the
// given byte range, the flags determine the operation to be performed
// on the given range.
//
// Implements:
//
// int ceph_fallocate(struct ceph_mount_info *cmount, int fd, int mode,
// int64_t offset, int64_t length);
func (f *File) Fallocate(mode FallocFlags, offset, length int64) error {
if err := f.validate(); err != nil {
return err
}
ret := C.ceph_fallocate(f.mount.mount, f.fd, C.int(mode), C.int64_t(offset), C.int64_t(length))
return getError(ret)
}
// LockOp determines operations/type of locks which can be applied on a file.
type LockOp int
const (
// LockSH places a shared lock.
// More than one process may hold a shared lock for a given file at a given time.
LockSH = LockOp(C.LOCK_SH)
// LockEX places an exclusive lock.
// Only one process may hold an exclusive lock for a given file at a given time.
LockEX = LockOp(C.LOCK_EX)
// LockUN removes an existing lock held by this process.
LockUN = LockOp(C.LOCK_UN)
// LockNB can be ORed with any of the above to make a nonblocking call.
LockNB = LockOp(C.LOCK_NB)
)
// Flock applies or removes an advisory lock on an open file.
// Param owner is the user-supplied identifier for the owner of the
// lock, must be an arbitrary integer.
//
// Implements:
//
// int ceph_flock(struct ceph_mount_info *cmount, int fd, int operation, uint64_t owner);
func (f *File) Flock(operation LockOp, owner uint64) error {
if err := f.validate(); err != nil {
return err
}
// validate the operation values before passing it on.
switch operation &^ LockNB {
case LockSH, LockEX, LockUN:
default:
return errInvalid
}
ret := C.ceph_flock(f.mount.mount, f.fd, C.int(operation), C.uint64_t(owner))
return getError(ret)
}
// Fsync ensures the file content that may be cached is committed to stable
// storage.
// Pass SyncAll to have this call behave like standard fsync and synchronize
// all data and metadata.
// Pass SyncDataOnly to have this call behave more like fdatasync (on linux).
//
// Implements:
//
// int ceph_fsync(struct ceph_mount_info *cmount, int fd, int syncdataonly);
func (f *File) Fsync(sync SyncChoice) error {
if err := f.validate(); err != nil {
return err
}
ret := C.ceph_fsync(
f.mount.mount,
f.fd,
C.int(sync),
)
return getError(ret)
}
// Sync ensures the file content that may be cached is committed to stable
// storage.
// Sync behaves like Go's os package File.Sync function.
func (f *File) Sync() error {
return f.Fsync(SyncAll)
}
// Truncate sets the size of the open file.
// NOTE: In some versions of ceph a bug exists where calling ftruncate on a
// file open for read-only is permitted. The go-ceph wrapper does no additional
// checking and will inherit the issue on affected versions of ceph. Please
// refer to the following issue for details:
// https://tracker.ceph.com/issues/48202
//
// Implements:
//
// int ceph_ftruncate(struct ceph_mount_info *cmount, int fd, int64_t size);
func (f *File) Truncate(size int64) error {
if err := f.validate(); err != nil {
return err
}
ret := C.ceph_ftruncate(
f.mount.mount,
f.fd,
C.int64_t(size),
)
return getError(ret)
}
+15
View File
@@ -0,0 +1,15 @@
package cephfs
// Fd returns the integer open file descriptor in cephfs.
// NOTE: It doesn't make sense to consume the returned integer fd anywhere
// outside CephFS and is recommended not to do so given the undefined behaviour.
// Also, as seen with the Go standard library, the fd is only valid as long as
// the corresponding File object is intact in the sense that an fd from a closed
// File object is invalid.
func (f *File) Fd() int {
if f == nil || f.mount == nil {
return -1
}
return int(f.fd)
}
+44
View File
@@ -0,0 +1,44 @@
package cephfs
// Futime changes file/directory last access and modification times.
//
// Implements:
//
// int ceph_futime(struct ceph_mount_info *cmount, int fd, struct utimbuf *buf);
func (f *File) Futime(times *Utime) error {
if err := f.validate(); err != nil {
return err
}
return f.mount.Futime(int(f.fd), times)
}
// Futimens changes file/directory last access and modification times, here times param
// is an array of Timespec struct having length 2, where times[0] represents the access time
// and times[1] represents the modification time.
//
// Implements:
//
// int ceph_futimens(struct ceph_mount_info *cmount, int fd, struct timespec times[2]);
func (f *File) Futimens(times []Timespec) error {
if err := f.validate(); err != nil {
return err
}
return f.mount.Futimens(int(f.fd), times)
}
// Futimes changes file/directory last access and modification times, here times param
// is an array of Timeval struct type having length 2, where times[0] represents the access time
// and times[1] represents the modification time.
//
// Implements:
//
// int ceph_futimes(struct ceph_mount_info *cmount, int fd, struct timeval times[2]);
func (f *File) Futimes(times []Timeval) error {
if err := f.validate(); err != nil {
return err
}
return f.mount.Futimes(int(f.fd), times)
}
+132
View File
@@ -0,0 +1,132 @@
//go:build !nautilus
// +build !nautilus
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <errno.h>
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
ts "github.com/ceph/go-ceph/internal/timespec"
"unsafe"
)
// Mknod creates a regular, block or character special file.
//
// Implements:
//
// int ceph_mknod(struct ceph_mount_info *cmount, const char *path, mode_t mode,
// dev_t rdev);
func (mount *MountInfo) Mknod(path string, mode uint16, dev uint16) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_mknod(mount.mount, cPath, C.mode_t(mode), C.dev_t(dev))
return getError(ret)
}
// Utime struct is the equivalent of C.struct_utimbuf
type Utime struct {
// AcTime represents the file's access time in seconds since the Unix epoch.
AcTime int64
// ModTime represents the file's modification time in seconds since the Unix epoch.
ModTime int64
}
// Futime changes file/directory last access and modification times.
//
// Implements:
//
// int ceph_futime(struct ceph_mount_info *cmount, int fd, struct utimbuf *buf);
func (mount *MountInfo) Futime(fd int, times *Utime) error {
if err := mount.validate(); err != nil {
return err
}
cFd := C.int(fd)
uTimeBuf := &C.struct_utimbuf{
actime: C.time_t(times.AcTime),
modtime: C.time_t(times.ModTime),
}
ret := C.ceph_futime(mount.mount, cFd, uTimeBuf)
return getError(ret)
}
// Timeval struct is the go equivalent of C.struct_timeval type
type Timeval struct {
// Sec represents seconds
Sec int64
// USec represents microseconds
USec int64
}
// Futimens changes file/directory last access and modification times, here times param
// is an array of Timespec struct having length 2, where times[0] represents the access time
// and times[1] represents the modification time.
//
// Implements:
//
// int ceph_futimens(struct ceph_mount_info *cmount, int fd, struct timespec times[2]);
func (mount *MountInfo) Futimens(fd int, times []Timespec) error {
if err := mount.validate(); err != nil {
return err
}
if len(times) != 2 {
return getError(-C.EINVAL)
}
cFd := C.int(fd)
cTimes := []C.struct_timespec{}
for _, val := range times {
cTs := &C.struct_timespec{}
ts.CopyToCStruct(
ts.Timespec(val),
ts.CTimespecPtr(cTs),
)
cTimes = append(cTimes, *cTs)
}
ret := C.ceph_futimens(mount.mount, cFd, &cTimes[0])
return getError(ret)
}
// Futimes changes file/directory last access and modification times, here times param
// is an array of Timeval struct type having length 2, where times[0] represents the access time
// and times[1] represents the modification time.
//
// Implements:
//
// int ceph_futimes(struct ceph_mount_info *cmount, int fd, struct timeval times[2]);
func (mount *MountInfo) Futimes(fd int, times []Timeval) error {
if err := mount.validate(); err != nil {
return err
}
if len(times) != 2 {
return getError(-C.EINVAL)
}
cFd := C.int(fd)
cTimes := []C.struct_timeval{}
for _, val := range times {
cTimes = append(cTimes, C.struct_timeval{
tv_sec: C.time_t(val.Sec),
tv_usec: C.suseconds_t(val.USec),
})
}
ret := C.ceph_futimes(mount.mount, cFd, &cTimes[0])
return getError(ret)
}
+163
View File
@@ -0,0 +1,163 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
// XattrFlags are used to control the behavior of set-xattr calls.
type XattrFlags int
const (
// XattrDefault specifies that set-xattr calls use the default behavior of
// creating or updating an xattr.
XattrDefault = XattrFlags(0)
// XattrCreate specifies that set-xattr calls only set new xattrs.
XattrCreate = XattrFlags(C.XATTR_CREATE)
// XattrReplace specifies that set-xattr calls only replace existing xattr
// values.
XattrReplace = XattrFlags(C.XATTR_REPLACE)
)
// SetXattr sets an extended attribute on the open file.
//
// NOTE: Attempting to set an xattr value with an empty value may cause the
// xattr to be unset on some older versions of ceph.
// Please refer to https://tracker.ceph.com/issues/46084
//
// Implements:
//
// int ceph_fsetxattr(struct ceph_mount_info *cmount, int fd, const char *name,
// const void *value, size_t size, int flags);
func (f *File) SetXattr(name string, value []byte, flags XattrFlags) error {
if err := f.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
var vptr unsafe.Pointer
if len(value) > 0 {
vptr = unsafe.Pointer(&value[0])
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_fsetxattr(
f.mount.mount,
f.fd,
cName,
vptr,
C.size_t(len(value)),
C.int(flags))
return getError(ret)
}
// GetXattr gets an extended attribute from the open file.
//
// Implements:
//
// int ceph_fgetxattr(struct ceph_mount_info *cmount, int fd, const char *name,
// void *value, size_t size);
func (f *File) GetXattr(name string) ([]byte, error) {
if err := f.validate(); err != nil {
return nil, err
}
if name == "" {
return nil, errInvalid
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_fgetxattr(
f.mount.mount,
f.fd,
cName,
unsafe.Pointer(&buf[0]),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
return buf[:ret], nil
}
// ListXattr returns a slice containing strings for the name of each xattr set
// on the file.
//
// Implements:
//
// int ceph_flistxattr(struct ceph_mount_info *cmount, int fd, char *list, size_t size);
func (f *File) ListXattr() ([]string, error) {
if err := f.validate(); err != nil {
return nil, err
}
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_flistxattr(
f.mount.mount,
f.fd,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
names := cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
// RemoveXattr removes the named xattr from the open file.
//
// Implements:
//
// int ceph_fremovexattr(struct ceph_mount_info *cmount, int fd, const char *name);
func (f *File) RemoveXattr(name string) error {
if err := f.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_fremovexattr(
f.mount.mount,
f.fd,
cName)
return getError(ret)
}
+406
View File
@@ -0,0 +1,406 @@
package cephfs
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"strings"
"time"
"github.com/ceph/go-ceph/internal/log"
)
var (
errIsDir = errors.New("is a directory")
)
// MountWrapper provides a wrapper type that adapts a CephFS Mount into a
// io.FS compatible type.
type MountWrapper struct {
mount *MountInfo
enableTrace bool
}
type fileWrapper struct {
parent *MountWrapper
file *File
name string
}
type dirWrapper struct {
parent *MountWrapper
directory *Directory
name string
}
type dentryWrapper struct {
parent *MountWrapper
de *DirEntryPlus
}
type infoWrapper struct {
parent *MountWrapper
sx *CephStatx
name string
}
// Wrap a CephFS Mount object into a new type that is compatible with Go's io.FS
// interface. CephFS Mounts are not compatible with io.FS directly because the
// go-ceph library predates the addition of io.FS to Go as well as the fact that
// go-ceph attempts to provide APIs that match the cephfs libraries first and
// foremost.
func Wrap(mount *MountInfo) *MountWrapper {
wm := &MountWrapper{mount: mount}
debugf(wm, "Wrap", "created")
return wm
}
/* MountWrapper:
** Implements https://pkg.go.dev/io/fs#FS
** Wraps cephfs.MountInfo
*/
// SetTracing configures the MountWrapper and objects connected to it for debug
// tracing. True enables tracing and false disables it. A debug logging
// function must also be set using go-ceph's common.log.SetDebugf function.
func (mw *MountWrapper) SetTracing(enable bool) {
mw.enableTrace = enable
}
// identify the MountWrapper object for logging purposes.
func (mw *MountWrapper) identify() string {
return fmt.Sprintf("MountWrapper<%p>", mw)
}
// trace returns true if debug tracing is enabled.
func (mw *MountWrapper) trace() bool {
return mw.enableTrace
}
// Open opens the named file. This may be either a regular file or a directory.
// Directories opened with this function will return object compatible with the
// io.ReadDirFile interface.
func (mw *MountWrapper) Open(name string) (fs.File, error) {
debugf(mw, "Open", "(%v)", name)
// there are a bunch of patterns that fsTetster/testfs looks for that seems
// under-documented. They mainly seem to try and enforce "clean" paths.
// look for them and reject them here because ceph libs won't reject on
// its own
if strings.HasPrefix(name, "/") ||
strings.HasSuffix(name, "/.") ||
strings.Contains(name, "//") ||
strings.Contains(name, "/./") ||
strings.Contains(name, "/../") {
return nil, &fs.PathError{Op: "open", Path: name, Err: errInvalid}
}
d, err := mw.mount.OpenDir(name)
if err == nil {
debugf(mw, "Open", "(%v): dir ok", name)
dw := &dirWrapper{parent: mw, directory: d, name: name}
return dw, nil
}
if !errors.Is(err, errNotDir) {
debugf(mw, "Open", "(%v): dir error: %v", name, err)
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
}
f, err := mw.mount.Open(name, os.O_RDONLY, 0)
if err == nil {
debugf(mw, "Open", "(%v): file ok", name)
fw := &fileWrapper{parent: mw, file: f, name: name}
return fw, nil
}
debugf(mw, "Open", "(%v): file error: %v", name, err)
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
}
/* fileWrapper:
** Implements https://pkg.go.dev/io/fs#FS
** Wraps cephfs.File
*/
func (fw *fileWrapper) Stat() (fs.FileInfo, error) {
debugf(fw, "Stat", "()")
sx, err := fw.file.Fstatx(StatxBasicStats, AtSymlinkNofollow)
if err != nil {
debugf(fw, "Stat", "() -> err:%v", err)
return nil, &fs.PathError{Op: "stat", Path: fw.name, Err: err}
}
debugf(fw, "Stat", "() ok")
return &infoWrapper{fw.parent, sx, path.Base(fw.name)}, nil
}
func (fw *fileWrapper) Read(b []byte) (int, error) {
debugf(fw, "Read", "(...)")
return fw.file.Read(b)
}
func (fw *fileWrapper) Close() error {
debugf(fw, "Close", "()")
return fw.file.Close()
}
func (fw *fileWrapper) identify() string {
return fmt.Sprintf("fileWrapper<%p>[%v]", fw, fw.name)
}
func (fw *fileWrapper) trace() bool {
return fw.parent.trace()
}
/* dirWrapper:
** Implements https://pkg.go.dev/io/fs#ReadDirFile
** Wraps cephfs.Directory
*/
func (dw *dirWrapper) Stat() (fs.FileInfo, error) {
debugf(dw, "Stat", "()")
sx, err := dw.parent.mount.Statx(dw.name, StatxBasicStats, AtSymlinkNofollow)
if err != nil {
debugf(dw, "Stat", "() -> err:%v", err)
return nil, &fs.PathError{Op: "stat", Path: dw.name, Err: err}
}
debugf(dw, "Stat", "() ok")
return &infoWrapper{dw.parent, sx, path.Base(dw.name)}, nil
}
func (dw *dirWrapper) Read(_ []byte) (int, error) {
debugf(dw, "Read", "(...)")
return 0, &fs.PathError{Op: "read", Path: dw.name, Err: errIsDir}
}
func (dw *dirWrapper) ReadDir(n int) ([]fs.DirEntry, error) {
debugf(dw, "ReadDir", "(%v)", n)
if n > 0 {
return dw.readDirSome(n)
}
return dw.readDirAll()
}
const defaultDirReadCount = 256 // how many entries to read per loop
func (dw *dirWrapper) readDirAll() ([]fs.DirEntry, error) {
debugf(dw, "readDirAll", "()")
var (
err error
egroup []fs.DirEntry
entries = make([]fs.DirEntry, 0)
size = defaultDirReadCount
)
for {
egroup, err = dw.readDirSome(size)
entries = append(entries, egroup...)
if err == io.EOF {
err = nil
break
}
if err != nil {
break
}
}
debugf(dw, "readDirAll", "() -> len:%v, err:%v", len(entries), err)
return entries, err
}
func (dw *dirWrapper) readDirSome(n int) ([]fs.DirEntry, error) {
debugf(dw, "readDirSome", "(%v)", n)
var (
idx int
err error
entry *DirEntryPlus
entries = make([]fs.DirEntry, n)
)
for {
entry, err = dw.directory.ReadDirPlus(StatxBasicStats, AtSymlinkNofollow)
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entry, err)
if err != nil || entry == nil {
break
}
switch entry.Name() {
case ".", "..":
continue
}
entries[idx] = &dentryWrapper{dw.parent, entry}
idx++
if idx >= n {
break
}
}
if idx == 0 {
debugf(dw, "readDirSome", "(%v): EOF", n)
return nil, io.EOF
}
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entries[:idx], err)
return entries[:idx], err
}
func (dw *dirWrapper) Close() error {
debugf(dw, "Close", "()")
return dw.directory.Close()
}
func (dw *dirWrapper) identify() string {
return fmt.Sprintf("dirWrapper<%p>[%v]", dw, dw.name)
}
func (dw *dirWrapper) trace() bool {
return dw.parent.trace()
}
/* dentryWrapper:
** Implements https://pkg.go.dev/io/fs#DirEntry
** Wraps cephfs.DirEntryPlus
*/
func (dew *dentryWrapper) Name() string {
debugf(dew, "Name", "()")
return dew.de.Name()
}
func (dew *dentryWrapper) IsDir() bool {
v := dew.de.DType() == DTypeDir
debugf(dew, "IsDir", "() -> %v", v)
return v
}
func (dew *dentryWrapper) Type() fs.FileMode {
m := dew.de.Statx().Mode
v := cephModeToFileMode(m).Type()
debugf(dew, "Type", "() -> %v", v)
return v
}
func (dew *dentryWrapper) Info() (fs.FileInfo, error) {
debugf(dew, "Info", "()")
sx := dew.de.Statx()
name := dew.de.Name()
return &infoWrapper{dew.parent, sx, name}, nil
}
func (dew *dentryWrapper) identify() string {
return fmt.Sprintf("dentryWrapper<%p>[%v]", dew, dew.de.Name())
}
func (dew *dentryWrapper) trace() bool {
return dew.parent.trace()
}
/* infoWrapper:
** Implements https://pkg.go.dev/io/fs#FileInfo
** Wraps cephfs.CephStatx
*/
func (iw *infoWrapper) Name() string {
debugf(iw, "Name", "()")
return iw.name
}
func (iw *infoWrapper) Size() int64 {
debugf(iw, "Size", "() -> %v", iw.sx.Size)
return int64(iw.sx.Size)
}
func (iw *infoWrapper) Sys() any {
debugf(iw, "Sys", "()")
return iw.sx
}
func (iw *infoWrapper) Mode() fs.FileMode {
v := cephModeToFileMode(iw.sx.Mode)
debugf(iw, "Mode", "() -> %#o -> %#o/%v", iw.sx.Mode, uint32(v), v.Type())
return v
}
func (iw *infoWrapper) IsDir() bool {
v := iw.sx.Mode&modeIFMT == modeIFDIR
debugf(iw, "IsDir", "() -> %v", v)
return v
}
func (iw *infoWrapper) ModTime() time.Time {
v := time.Unix(iw.sx.Mtime.Sec, iw.sx.Mtime.Nsec)
debugf(iw, "ModTime", "() -> %v", v)
return v
}
func (iw *infoWrapper) identify() string {
return fmt.Sprintf("infoWrapper<%p>[%v]", iw, iw.name)
}
func (iw *infoWrapper) trace() bool {
return iw.parent.trace()
}
/* copy and paste values from the linux headers. We always need to use
** the linux header values, regardless of the platform go-ceph is built
** for. Rather than jumping through header hoops, copy and paste is
** more consistent and reliable.
*/
const (
/* file type mask */
modeIFMT = uint16(0170000)
/* file types */
modeIFDIR = uint16(0040000)
modeIFCHR = uint16(0020000)
modeIFBLK = uint16(0060000)
modeIFREG = uint16(0100000)
modeIFIFO = uint16(0010000)
modeIFLNK = uint16(0120000)
modeIFSOCK = uint16(0140000)
/* protection bits */
modeISUID = uint16(0004000)
modeISGID = uint16(0002000)
modeISVTX = uint16(0001000)
)
// cephModeToFileMode takes a linux compatible cephfs mode value
// and returns a Go-compatiable os-agnostic FileMode value.
func cephModeToFileMode(m uint16) fs.FileMode {
// start with permission bits
mode := fs.FileMode(m & 0777)
// file type - inspired by go's src/os/stat_linux.go
switch m & modeIFMT {
case modeIFBLK:
mode |= fs.ModeDevice
case modeIFCHR:
mode |= fs.ModeDevice | fs.ModeCharDevice
case modeIFDIR:
mode |= fs.ModeDir
case modeIFIFO:
mode |= fs.ModeNamedPipe
case modeIFLNK:
mode |= fs.ModeSymlink
case modeIFREG:
// nothing to do
case modeIFSOCK:
mode |= fs.ModeSocket
}
// protection bits
if m&modeISUID != 0 {
mode |= fs.ModeSetuid
}
if m&modeISGID != 0 {
mode |= fs.ModeSetgid
}
if m&modeISVTX != 0 {
mode |= fs.ModeSticky
}
return mode
}
// wrapperObject helps identify an object to be logged.
type wrapperObject interface {
identify() string
trace() bool
}
// debugf formats info about a function and logs it.
func debugf(o wrapperObject, fname, format string, args ...any) {
if o.trace() {
log.Debugf(fmt.Sprintf("%v.%v: %s", o.identify(), fname, format), args...)
}
}
+29
View File
@@ -0,0 +1,29 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
)
// MakeDirs creates multiple directories at once.
//
// Implements:
//
// int ceph_mkdirs(struct ceph_mount_info *cmount, const char *path, mode_t mode);
func (mount *MountInfo) MakeDirs(path string, mode uint32) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_mkdirs(mount.mount, cPath, C.mode_t(mode))
return getError(ret)
}
+22
View File
@@ -0,0 +1,22 @@
//
// ceph_mount_perms_set available in mimic & later
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <cephfs/libcephfs.h>
*/
import "C"
// SetMountPerms applies the given UserPerm to the mount object, which it will
// then use to define the connection's ownership credentials.
// This function must be called after Init but before Mount.
//
// Implements:
//
// int ceph_mount_perms_set(struct ceph_mount_info *cmount, UserPerm *perm);
func (mount *MountInfo) SetMountPerms(perm *UserPerm) error {
return getError(C.ceph_mount_perms_set(mount.mount, perm.userPerm))
}
+201
View File
@@ -0,0 +1,201 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
)
// CurrentDir gets the current working directory.
func (mount *MountInfo) CurrentDir() string {
if err := mount.validate(); err != nil {
return ""
}
cDir := C.ceph_getcwd(mount.mount)
return C.GoString(cDir)
}
// ChangeDir changes the current working directory.
func (mount *MountInfo) ChangeDir(path string) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_chdir(mount.mount, cPath)
return getError(ret)
}
// MakeDir creates a directory.
func (mount *MountInfo) MakeDir(path string, mode uint32) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_mkdir(mount.mount, cPath, C.mode_t(mode))
return getError(ret)
}
// RemoveDir removes a directory.
func (mount *MountInfo) RemoveDir(path string) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_rmdir(mount.mount, cPath)
return getError(ret)
}
// Unlink removes a file.
//
// Implements:
//
// int ceph_unlink(struct ceph_mount_info *cmount, const char *path);
func (mount *MountInfo) Unlink(path string) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_unlink(mount.mount, cPath)
return getError(ret)
}
// Link creates a new link to an existing file.
//
// Implements:
//
// int ceph_link (struct ceph_mount_info *cmount, const char *existing, const char *newname);
func (mount *MountInfo) Link(oldname, newname string) error {
if err := mount.validate(); err != nil {
return err
}
cOldname := C.CString(oldname)
defer C.free(unsafe.Pointer(cOldname))
cNewname := C.CString(newname)
defer C.free(unsafe.Pointer(cNewname))
ret := C.ceph_link(mount.mount, cOldname, cNewname)
return getError(ret)
}
// Symlink creates a symbolic link to an existing path.
//
// Implements:
//
// int ceph_symlink(struct ceph_mount_info *cmount, const char *existing, const char *newname);
func (mount *MountInfo) Symlink(existing, newname string) error {
if err := mount.validate(); err != nil {
return err
}
cExisting := C.CString(existing)
defer C.free(unsafe.Pointer(cExisting))
cNewname := C.CString(newname)
defer C.free(unsafe.Pointer(cNewname))
ret := C.ceph_symlink(mount.mount, cExisting, cNewname)
return getError(ret)
}
// Readlink returns the value of a symbolic link.
//
// Implements:
//
// int ceph_readlink(struct ceph_mount_info *cmount, const char *path, char *buf, int64_t size);
func (mount *MountInfo) Readlink(path string) (string, error) {
if err := mount.validate(); err != nil {
return "", err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
buf := make([]byte, 4096)
ret := C.ceph_readlink(mount.mount,
cPath,
(*C.char)(unsafe.Pointer(&buf[0])),
C.int64_t(len(buf)))
if ret < 0 {
return "", getError(ret)
}
return string(buf[:ret]), nil
}
// Statx returns information about a file/directory.
//
// Implements:
//
// int ceph_statx(struct ceph_mount_info *cmount, const char *path, struct ceph_statx *stx,
// unsigned int want, unsigned int flags);
func (mount *MountInfo) Statx(path string, want StatxMask, flags AtFlags) (*CephStatx, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
var stx C.struct_ceph_statx
ret := C.ceph_statx(
mount.mount,
cPath,
&stx,
C.uint(want),
C.uint(flags),
)
if err := getError(ret); err != nil {
return nil, err
}
return cStructToCephStatx(stx), nil
}
// Rename a file or directory.
//
// Implements:
//
// int ceph_rename(struct ceph_mount_info *cmount, const char *from, const char *to);
func (mount *MountInfo) Rename(from, to string) error {
if err := mount.validate(); err != nil {
return err
}
cFrom := C.CString(from)
defer C.free(unsafe.Pointer(cFrom))
cTo := C.CString(to)
defer C.free(unsafe.Pointer(cTo))
ret := C.ceph_rename(mount.mount, cFrom, cTo)
return getError(ret)
}
// Truncate sets the size of the specified file.
//
// Implements:
//
// int ceph_truncate(struct ceph_mount_info *cmount, const char *path, int64_t size);
func (mount *MountInfo) Truncate(path string, size int64) error {
if err := mount.validate(); err != nil {
return err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_truncate(
mount.mount,
cPath,
C.int64_t(size),
)
return getError(ret)
}
+291
View File
@@ -0,0 +1,291 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
// SetXattr sets an extended attribute on the file at the supplied path.
//
// NOTE: Attempting to set an xattr value with an empty value may cause
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
//
// Implements:
//
// int ceph_setxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// const void *value, size_t size, int flags);
func (mount *MountInfo) SetXattr(path, name string, value []byte, flags XattrFlags) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
var vptr unsafe.Pointer
if len(value) > 0 {
vptr = unsafe.Pointer(&value[0])
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_setxattr(
mount.mount,
cPath,
cName,
vptr,
C.size_t(len(value)),
C.int(flags))
return getError(ret)
}
// GetXattr gets an extended attribute from the file at the supplied path.
//
// Implements:
//
// int ceph_getxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// void *value, size_t size);
func (mount *MountInfo) GetXattr(path, name string) ([]byte, error) {
if err := mount.validate(); err != nil {
return nil, err
}
if name == "" {
return nil, errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_getxattr(
mount.mount,
cPath,
cName,
unsafe.Pointer(&buf[0]),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
return buf[:ret], nil
}
// ListXattr returns a slice containing strings for the name of each xattr set
// on the file at the supplied path.
//
// Implements:
//
// int ceph_listxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
func (mount *MountInfo) ListXattr(path string) ([]string, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_listxattr(
mount.mount,
cPath,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
names := cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
// RemoveXattr removes the named xattr from the open file.
//
// Implements:
//
// int ceph_removexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
func (mount *MountInfo) RemoveXattr(path, name string) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_removexattr(
mount.mount,
cPath,
cName)
return getError(ret)
}
// LsetXattr sets an extended attribute on the file at the supplied path.
//
// NOTE: Attempting to set an xattr value with an empty value may cause
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
//
// Implements:
//
// int ceph_lsetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// const void *value, size_t size, int flags);
func (mount *MountInfo) LsetXattr(path, name string, value []byte, flags XattrFlags) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
var vptr unsafe.Pointer
if len(value) > 0 {
vptr = unsafe.Pointer(&value[0])
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_lsetxattr(
mount.mount,
cPath,
cName,
vptr,
C.size_t(len(value)),
C.int(flags))
return getError(ret)
}
// LgetXattr gets an extended attribute from the file at the supplied path.
//
// Implements:
//
// int ceph_lgetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// void *value, size_t size);
func (mount *MountInfo) LgetXattr(path, name string) ([]byte, error) {
if err := mount.validate(); err != nil {
return nil, err
}
if name == "" {
return nil, errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_lgetxattr(
mount.mount,
cPath,
cName,
unsafe.Pointer(&buf[0]),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
return buf[:ret], nil
}
// LlistXattr returns a slice containing strings for the name of each xattr set
// on the file at the supplied path.
//
// Implements:
//
// int ceph_llistxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
func (mount *MountInfo) LlistXattr(path string) ([]string, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
var (
ret C.int
err error
buf []byte
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.ceph_llistxattr(
mount.mount,
cPath,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
names := cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
// LremoveXattr removes the named xattr from the open file.
//
// Implements:
//
// int ceph_lremovexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
func (mount *MountInfo) LremoveXattr(path, name string) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_lremovexattr(
mount.mount,
cPath,
cName)
return getError(ret)
}
+48
View File
@@ -0,0 +1,48 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
int _go_ceph_chown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
return ceph_chown(cmount, path, uid, gid);
}
int _go_ceph_lchown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
return ceph_lchown(cmount, path, uid, gid);
}
*/
import "C"
import (
"unsafe"
)
// Chmod changes the mode bits (permissions) of a file/directory.
func (mount *MountInfo) Chmod(path string, mode uint32) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.ceph_chmod(mount.mount, cPath, C.mode_t(mode))
return getError(ret)
}
// Chown changes the ownership of a file/directory.
func (mount *MountInfo) Chown(path string, user uint32, group uint32) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C._go_ceph_chown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
return getError(ret)
}
// Lchown changes the ownership of a file/directory/etc without following symbolic links
func (mount *MountInfo) Lchown(path string, user uint32, group uint32) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C._go_ceph_lchown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
return getError(ret)
}
+31
View File
@@ -0,0 +1,31 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
)
// SelectFilesystem selects a file system to be mounted. If the ceph cluster
// supports more than one cephfs this optional function selects which one to
// use. Can only be called prior to calling Mount. The name of the file system
// is not validated by this call - if the supplied file system name is not
// valid then only the subsequent mount call will fail.
//
// Implements:
//
// int ceph_select_filesystem(struct ceph_mount_info *cmount, const char *fs_name);
func (mount *MountInfo) SelectFilesystem(name string) error {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_select_filesystem(mount.mount, cName)
return getError(ret)
}
+266
View File
@@ -0,0 +1,266 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <dirent.h>
#include <cephfs/libcephfs.h>
// Types and constants are copied from libcephfs.h with added "_" as prefix. This
// prevents redefinition of the types on libcephfs versions that have them
// already.
typedef struct {
struct dirent dir_entry;
uint64_t snapid;
} _ceph_snapdiff_entry_t;
typedef struct {
struct ceph_mount_info* cmount;
struct ceph_dir_result* dir1;
struct ceph_dir_result* dir_aux;
} _ceph_snapdiff_info;
// open_snapdiff_fn matches the open_snapdiff function signature.
typedef int(*open_snapdiff_fn)(struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_snapdiff_info* out);
// open_snapdiff_dlsym take *fn as open_snapdiff_fn and calls the dynamically loaded
// open_snapdiff function passed as 1st argument.
static inline int open_snapdiff_dlsym(void *fn,
struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_snapdiff_info* out) {
// cast function pointer fn to open_snapdiff and call the function
return ((open_snapdiff_fn) fn)(cmount, root_path, rel_path, snap1, snap2, out);
}
// readdir_snapdiff_fn matches the readdir_snapdiff function signature.
typedef int(*readdir_snapdiff_fn)(_ceph_snapdiff_info* snapdiff,
_ceph_snapdiff_entry_t* out);
// readdir_snapdiff_dlsym take *fn as readdir_snapdiff_fn and calls the dynamically loaded
// readdir_snapdiff function passed as 1st argument.
static inline int readdir_snapdiff_dlsym(void *fn,
_ceph_snapdiff_info* snapdiff,
_ceph_snapdiff_entry_t* out) {
// cast function pointer fn to readdir_snapdiff and call the function
return ((readdir_snapdiff_fn) fn)(snapdiff, out);
}
// close_snapdiff_fn matches the close_snapdiff function signature.
typedef int(*close_snapdiff_fn)(_ceph_snapdiff_info* snapdiff);
// close_snapdiff_dlsym take *fn as close_snapdiff_fn and calls the dynamically loaded
// close_snapdiff function passed as 1st argument.
static inline int close_snapdiff_dlsym(void *fn,
_ceph_snapdiff_info* snapdiff) {
// cast function pointer fn to close_snapdiff and call the function
return ((close_snapdiff_fn) fn)(snapdiff);
}
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
"github.com/ceph/go-ceph/internal/dlsym"
)
var (
cephOpenSnapDiffOnce sync.Once
cephReaddirSnapDiffOnce sync.Once
cephCloseSnapDiffOnce sync.Once
cephOpenSnapDiff unsafe.Pointer
cephReaddirSnapDiff unsafe.Pointer
cephCloseSnapDiff unsafe.Pointer
cephOpenSnapDiffErr error
cephReaddirSnapDiffErr error
cephCloseSnapDiffErr error
)
// SnapDiffInfo is a handle to a snapshot diff API.
type SnapDiffInfo struct {
cMount *MountInfo
dir1 *Directory
dirAux *Directory
}
// SnapDiffEntry is a single entry in the snapshot diff.
// It contains a DirEntry and the ID of the snapshot to
// which the directory belongs.
type SnapDiffEntry struct {
DirEntry *DirEntry
SnapID uint64
}
// SnapDiffConfig is used to define the parameters of a open_snapdiff call.
// Snapshot Delta is generated between the passed snapshots snap1 and snap2.
// All fields must be specified before passing to OpenSnapDiff().
type SnapDiffConfig struct {
// CMount is the ceph mount handle that will be used for snap.diff retrieval.
CMount *MountInfo
// RootPath represents the root path for snapshots-in-question.
RootPath string
// RelPath is the subpath under the root to build delta for.
RelPath string
// Snap1 is the first snapshot name.
Snap1 string
// Snap2 is the second snapshot name.
Snap2 string
}
// OpenSnapDiff opens a snapshot diff stream to get snapshots delta
// and returns a SnapDiffInfo struct containing the diff information.
//
// Implements:
//
// int ceph_open_snapdiff(struct ceph_mount_info* cmount,
// const char* root_path,
// const char* rel_path,
// const char* snap1,
// const char* snap2,
// struct ceph_snapdiff_info* out);
func OpenSnapDiff(config SnapDiffConfig) (*SnapDiffInfo, error) {
if config.CMount == nil || config.RootPath == "" || config.RelPath == "" ||
config.Snap1 == "" || config.Snap2 == "" {
return nil, errInvalid
}
cephOpenSnapDiffOnce.Do(func() {
cephOpenSnapDiff, cephOpenSnapDiffErr = dlsym.LookupSymbol("ceph_open_snapdiff")
})
if cephOpenSnapDiffErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephOpenSnapDiffErr)
}
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{}
ret := C.open_snapdiff_dlsym(
cephOpenSnapDiff,
config.CMount.mount,
C.CString(config.RootPath),
C.CString(config.RelPath),
C.CString(config.Snap1),
C.CString(config.Snap2),
rawCephSnapDiffInfo)
if ret != 0 {
return nil, getError(ret)
}
mountInfo := &MountInfo{
mount: rawCephSnapDiffInfo.cmount,
}
cephSnapDiffInfo := &SnapDiffInfo{
cMount: mountInfo,
dir1: &Directory{
mount: mountInfo,
dir: rawCephSnapDiffInfo.dir1,
},
dirAux: &Directory{
mount: mountInfo,
dir: rawCephSnapDiffInfo.dir_aux,
},
}
return cephSnapDiffInfo, nil
}
// validate checks that the SnapDiffInfo struct is valid.
func (info *SnapDiffInfo) validate() error {
if info.cMount == nil || info.dir1 == nil || info.dirAux == nil {
return errInvalid
}
return nil
}
// Readdir returns the next entry in the snapshot diff stream.
// If there are no more entries, it returns (nil, nil).
//
// Implements:
//
// int ceph_readdir_snapdiff(struct ceph_snapdiff_info* snapdiff,
// struct ceph_snapdiff_entry_t* out);
func (info *SnapDiffInfo) Readdir() (*SnapDiffEntry, error) {
if err := info.validate(); err != nil {
return nil, err
}
cephReaddirSnapDiffOnce.Do(func() {
cephReaddirSnapDiff, cephReaddirSnapDiffErr = dlsym.LookupSymbol("ceph_readdir_snapdiff")
})
if cephReaddirSnapDiffErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephReaddirSnapDiffErr)
}
rawSnapDiffEntry := &C._ceph_snapdiff_entry_t{}
rawSnapDiffInfo := &C._ceph_snapdiff_info{
cmount: info.cMount.mount,
dir1: info.dir1.dir,
dir_aux: info.dirAux.dir,
}
ret := C.readdir_snapdiff_dlsym(
cephReaddirSnapDiff,
rawSnapDiffInfo,
rawSnapDiffEntry)
if ret < 0 {
return nil, getError(ret)
}
if ret == 0 {
// return 0 indicates there is not more entries to return.
return nil, nil
}
snapDiffEntry := &SnapDiffEntry{
DirEntry: toDirEntry(&rawSnapDiffEntry.dir_entry),
SnapID: uint64(rawSnapDiffEntry.snapid),
}
return snapDiffEntry, nil
}
// Close closes the snapshot diff handle.
//
// Implements:
//
// int ceph_close_snapdiff(struct ceph_snapdiff_info* snapdiff);
func (info *SnapDiffInfo) Close() error {
if err := info.validate(); err != nil {
return err
}
cephCloseSnapDiffOnce.Do(func() {
cephCloseSnapDiff, cephCloseSnapDiffErr = dlsym.LookupSymbol("ceph_close_snapdiff")
})
if cephCloseSnapDiffErr != nil {
return fmt.Errorf("%w: %w", ErrNotImplemented, cephCloseSnapDiffErr)
}
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{
cmount: info.cMount.mount,
dir1: info.dir1.dir,
dir_aux: info.dirAux.dir,
}
ret := C.close_snapdiff_dlsym(
cephCloseSnapDiff,
rawCephSnapDiffInfo)
if ret != 0 {
return getError(ret)
}
return nil
}
+75
View File
@@ -0,0 +1,75 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
)
// CephStatVFS instances are returned from the StatFS call. It reports
// file-system wide statistics.
type CephStatVFS struct {
// Bsize reports the file system's block size.
Bsize int64
// Fragment reports the file system's fragment size.
Frsize int64
// Blocks reports the number of blocks in the file system.
Blocks uint64
// Bfree reports the number of free blocks.
Bfree uint64
// Bavail reports the number of free blocks for unprivileged users.
Bavail uint64
// Files reports the number of inodes in the file system.
Files uint64
// Ffree reports the number of free indoes.
Ffree uint64
// Favail reports the number of free indoes for unprivileged users.
Favail uint64
// Fsid reports the file system ID number.
Fsid int64
// Flag reports the file system mount flags.
Flag int64
// Namemax reports the maximum file name length.
Namemax int64
}
// StatFS returns file system wide statistics.
// NOTE: Many of the statistics fields reported by ceph are not filled in with
// useful values.
//
// Implements:
//
// int ceph_statfs(struct ceph_mount_info *cmount, const char *path, struct statvfs *stbuf);
func (mount *MountInfo) StatFS(path string) (*CephStatVFS, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
var statvfs C.struct_statvfs
ret := C.ceph_statfs(mount.mount, cPath, &statvfs)
if ret != 0 {
return nil, getError(ret)
}
csfs := &CephStatVFS{
Bsize: int64(statvfs.f_bsize),
Frsize: int64(statvfs.f_frsize),
Blocks: uint64(statvfs.f_blocks),
Bfree: uint64(statvfs.f_bfree),
Bavail: uint64(statvfs.f_bavail),
Files: uint64(statvfs.f_files),
Ffree: uint64(statvfs.f_ffree),
Favail: uint64(statvfs.f_favail),
Fsid: int64(statvfs.f_fsid),
Flag: int64(statvfs.f_flag),
Namemax: int64(statvfs.f_namemax),
}
return csfs, nil
}
+156
View File
@@ -0,0 +1,156 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <cephfs/libcephfs.h>
#ifndef AT_STATX_DONT_SYNC
// for versions earlier than Pacific
#define AT_STATX_DONT_SYNC AT_NO_ATTR_SYNC
#endif
*/
import "C"
import (
ts "github.com/ceph/go-ceph/internal/timespec"
)
// Timespec is a public type for the internal C 'struct timespec'
type Timespec ts.Timespec
// StatxMask values contain bit-flags indicating what data should be
// populated by a statx-type call.
type StatxMask uint32
const (
// StatxMode requests the mode value be filled in.
StatxMode = StatxMask(C.CEPH_STATX_MODE)
// StatxNlink requests the nlink value be filled in.
StatxNlink = StatxMask(C.CEPH_STATX_NLINK)
// StatxUid requests the uid value be filled in.
StatxUid = StatxMask(C.CEPH_STATX_UID)
// StatxRdev requests the rdev value be filled in.
StatxRdev = StatxMask(C.CEPH_STATX_RDEV)
// StatxAtime requests the access-time value be filled in.
StatxAtime = StatxMask(C.CEPH_STATX_ATIME)
// StatxMtime requests the modified-time value be filled in.
StatxMtime = StatxMask(C.CEPH_STATX_MTIME)
// StatxIno requests the inode be filled in.
StatxIno = StatxMask(C.CEPH_STATX_INO)
// StatxSize requests the size value be filled in.
StatxSize = StatxMask(C.CEPH_STATX_SIZE)
// StatxBlocks requests the blocks value be filled in.
StatxBlocks = StatxMask(C.CEPH_STATX_BLOCKS)
// StatxBasicStats requests all the fields that are part of a
// traditional stat call.
StatxBasicStats = StatxMask(C.CEPH_STATX_BASIC_STATS)
// StatxBtime requests the birth-time value be filled in.
StatxBtime = StatxMask(C.CEPH_STATX_BTIME)
// StatxVersion requests the version value be filled in.
StatxVersion = StatxMask(C.CEPH_STATX_VERSION)
// StatxAllStats requests all known stat values be filled in.
StatxAllStats = StatxMask(C.CEPH_STATX_ALL_STATS)
)
// AtFlags represent flags to be passed to calls that control how files
// are used or referenced. For example, not following symlinks.
type AtFlags uint
const (
// AtStatxDontSync requests that the stat call only fetch locally-cached
// values if possible, avoiding round trips to a back-end server.
AtStatxDontSync = AtFlags(C.AT_STATX_DONT_SYNC)
// AtNoAttrSync requests that the stat call only fetch locally-cached
// values if possible, avoiding round trips to a back-end server.
//
// Deprecated: replaced by AtStatxDontSync
AtNoAttrSync = AtStatxDontSync
// AtSymlinkNofollow indicates the call should not follow symlinks
// but operate on the symlink itself.
AtSymlinkNofollow = AtFlags(C.AT_SYMLINK_NOFOLLOW)
)
// NOTE: CephStatx fields are meant to be settable by the callers.
// This is the primary reason we use public fields and not accessors
// for the CephStatx type.
// CephStatx instances are returned by extended stat (statx) calls.
// Note that CephStatx results are similar to but not identical
// to (Linux) system statx results.
type CephStatx struct {
// Mask is a bitmask indicating what fields have been set.
Mask StatxMask
// Blksize represents the file system's block size.
Blksize uint32
// Nlink is the number of links for the file.
Nlink uint32
// Uid (user id) value for the file.
Uid uint32
// Gid (group id) value for the file.
Gid uint32
// Mode is the file's type and mode value.
Mode uint16
// Inode value for the file.
Inode Inode
// Size of the file in bytes.
Size uint64
// Blocks indicates the number of blocks allocated to the file.
Blocks uint64
// Dev describes the device containing this file system.
Dev uint64
// Rdev describes the device of this file, if the file is a device.
Rdev uint64
// Atime is the access time of this file.
Atime Timespec
// Ctime is the status change time of this file.
Ctime Timespec
// Mtime is the modification time of this file.
Mtime Timespec
// Btime is the creation (birth) time of this file.
Btime Timespec
// Version value for the file.
Version uint64
}
func cStructToCephStatx(s C.struct_ceph_statx) *CephStatx {
return &CephStatx{
Mask: StatxMask(s.stx_mask),
Blksize: uint32(s.stx_blksize),
Nlink: uint32(s.stx_nlink),
Uid: uint32(s.stx_uid),
Gid: uint32(s.stx_gid),
Mode: uint16(s.stx_mode),
Inode: Inode(s.stx_ino),
Size: uint64(s.stx_size),
Blocks: uint64(s.stx_blocks),
Dev: uint64(s.stx_dev),
Rdev: uint64(s.stx_rdev),
Atime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_atime))),
Ctime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_ctime))),
Mtime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_mtime))),
Btime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_btime))),
Version: uint64(s.stx_version),
}
}
/* TODO:
- enable later when we can test round -trips
- add time fields
func (c *CephStatx) toCStruct() C.struct_ceph_statx {
var s C.struct_ceph_statx
s.stx_mask = C.uint32_t(c.Mask)
s.stx_blksize = C.uint32_t(c.Blksize)
s.stx_nlink = C.uint32_t(c.Nlink)
s.stx_uid = C.uint32_t(c.Uid)
s.stx_gid = C.uint32_t(c.Gid)
s.stx_mode = C.uint16_t(c.Mode)
s.stx_ino = C.uint64_t(c.Inode)
s.stx_size = C.uint64_t(c.Size)
s.stx_blocks = C.uint64_t(c.Blocks)
s.stx_dev = C.uint64_t(c.Dev)
s.stx_rdev = C.uint64_t(c.Rdev)
s.stx_version = C.uint64_t(c.Version)
return s
}
*/
+79
View File
@@ -0,0 +1,79 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"runtime"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
// UserPerm types may be used to get or change the credentials used by the
// connection or some operations.
type UserPerm struct {
userPerm *C.UserPerm
// cache create-time params
managed bool // if set, the userPerm was created by go-ceph
uid C.uid_t
gid C.gid_t
gidList []C.gid_t
}
// NewUserPerm creates a UserPerm pointer and the underlying ceph resources.
//
// Implements:
//
// UserPerm *ceph_userperm_new(uid_t uid, gid_t gid, int ngids, gid_t *gidlist);
func NewUserPerm(uid, gid int, gidlist []int) *UserPerm {
// the C code does not copy the content of the gid list so we keep the
// inputs stashed in the go type. For completeness we stash everything.
p := &UserPerm{
managed: true,
uid: C.uid_t(uid),
gid: C.gid_t(gid),
gidList: make([]C.gid_t, len(gidlist)),
}
var cgids *C.gid_t
if len(p.gidList) > 0 {
for i, gid := range gidlist {
p.gidList[i] = C.gid_t(gid)
}
cgids = (*C.gid_t)(unsafe.Pointer(&p.gidList[0]))
}
p.userPerm = C.ceph_userperm_new(
p.uid, p.gid, C.int(len(p.gidList)), cgids)
// if the go object is unreachable, we would like to free the c-memory
// since this has no other resources than memory associated with it.
// This is only valid for UserPerm objects created by new, and thus have
// the managed var set.
runtime.SetFinalizer(p, destroyUserPerm)
return p
}
// Destroy will explicitly free ceph resources associated with the UserPerm.
//
// Implements:
//
// void ceph_userperm_destroy(UserPerm *perm);
func (p *UserPerm) Destroy() {
if p.userPerm == nil || !p.managed {
return
}
C.ceph_userperm_destroy(p.userPerm)
p.userPerm = nil
p.gidList = nil
}
func destroyUserPerm(p *UserPerm) {
if p.userPerm != nil && p.managed {
log.Warnf("unreachable UserPerm object has not been destroyed. Cleaning up.")
}
p.Destroy()
}
+17
View File
@@ -0,0 +1,17 @@
package manager
import (
ccom "github.com/ceph/go-ceph/common/commands"
)
// MgrAdmin is used to administrate ceph's manager (mgr).
type MgrAdmin struct {
conn ccom.RadosCommander
}
// NewFromConn creates an new management object from a preexisting
// rados connection. The existing connection can be rados.Conn or any
// type implementing the RadosCommander interface.
func NewFromConn(conn ccom.RadosCommander) *MgrAdmin {
return &MgrAdmin{conn}
}
+5
View File
@@ -0,0 +1,5 @@
/*
Package manager from common/admin contains a set of APIs used to interact
with and administer the Ceph manager (mgr).
*/
package manager
+77
View File
@@ -0,0 +1,77 @@
package manager
import (
"github.com/ceph/go-ceph/internal/commands"
)
// EnableModule will enable the specified manager module.
//
// Similar To:
//
// ceph mgr module enable <module> [--force]
func (fsa *MgrAdmin) EnableModule(module string, force bool) error {
m := map[string]string{
"prefix": "mgr module enable",
"module": module,
"format": "json",
}
if force {
m["force"] = "--force"
}
// Why is this _only_ part of the mon command json? You'd think a mgr
// command would be available as a MgrCommand but I couldn't figure it out.
return commands.MarshalMonCommand(fsa.conn, m).NoData().End()
}
// DisableModule will disable the specified manager module.
//
// Similar To:
//
// ceph mgr module disable <module>
func (fsa *MgrAdmin) DisableModule(module string) error {
m := map[string]string{
"prefix": "mgr module disable",
"module": module,
"format": "json",
}
return commands.MarshalMonCommand(fsa.conn, m).NoData().End()
}
// DisabledModule describes a disabled Ceph mgr module.
// The Ceph JSON structure contains a complex module_options
// substructure that go-ceph does not currently implement.
type DisabledModule struct {
Name string `json:"name"`
CanRun bool `json:"can_run"`
ErrorString string `json:"error_string"`
}
// ModuleInfo contains fields that report the status of modules within the
// ceph mgr.
type ModuleInfo struct {
// EnabledModules lists the names of the enabled modules.
EnabledModules []string `json:"enabled_modules"`
// AlwaysOnModules lists the names of the always-on modules.
AlwaysOnModules []string `json:"always_on_modules"`
// DisabledModules lists structures describing modules that are
// not currently enabled.
DisabledModules []DisabledModule `json:"disabled_modules"`
}
func parseModuleInfo(res commands.Response) (*ModuleInfo, error) {
m := &ModuleInfo{}
if err := res.NoStatus().Unmarshal(m).End(); err != nil {
return nil, err
}
return m, nil
}
// ListModules returns a module info struct reporting the lists of
// enabled, disabled, and always-on modules in the Ceph mgr.
func (fsa *MgrAdmin) ListModules() (*ModuleInfo, error) {
m := map[string]string{
"prefix": "mgr module ls",
"format": "json",
}
return parseModuleInfo(commands.MarshalMonCommand(fsa.conn, m))
}
+7
View File
@@ -0,0 +1,7 @@
/*
Package commands provides types and utility functions that are used for
interfacing with the JSON based command infrastructure in Ceph.
The *rados.Conn type implements many of the interfaces found in this package.
*/
package commands
+40
View File
@@ -0,0 +1,40 @@
package commands
// MgrCommander in an interface for the API needed to execute JSON formatted
// commands on the ceph mgr.
type MgrCommander interface {
MgrCommand(buf [][]byte) ([]byte, string, error)
}
// MgrBufferCommander is an interface for the API needed to execute JSON
// formatted commands with an input buffer on the Ceph mgr.
type MgrBufferCommander interface {
MgrCommandWithInputBuffer([][]byte, []byte) ([]byte, string, error)
}
// MonCommander is an interface for the API needed to execute JSON formatted
// commands on the ceph mon(s).
type MonCommander interface {
MonCommand(buf []byte) ([]byte, string, error)
}
// MonBufferCommander is an interface for the API needed to execute JSON
// formatted commands with an input buffer on the Ceph mon(s).
type MonBufferCommander interface {
MonCommandWithInputBuffer([]byte, []byte) ([]byte, string, error)
}
// RadosCommander provides an interface for APIs needed to execute JSON
// formatted commands on the Ceph cluster.
type RadosCommander interface {
MgrCommander
MonCommander
}
// RadosBufferCommander provides an interface for APIs that need to execute
// JSON formatted commands with an input buffer on the Ceph cluster.
type RadosBufferCommander interface {
RadosCommander
MgrBufferCommander
MonBufferCommander
}
+91
View File
@@ -0,0 +1,91 @@
package commands
import (
"encoding/json"
ccom "github.com/ceph/go-ceph/common/commands"
"github.com/ceph/go-ceph/rados"
)
func validate(m interface{}) error {
if m == 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 RawMgrCommand(m ccom.MgrCommander, buf []byte) Response {
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MgrCommand([][]byte{buf}))
}
// MarshalMgrCommand takes an generic interface{} value, converts it to JSON
// and sends the json to the MGR as a command.
func MarshalMgrCommand(m ccom.MgrCommander, v interface{}) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
return RawMgrCommand(m, b)
}
// RawMonCommand takes a byte buffer and sends it to the MON as a command.
// The buffer is expected to contain preformatted JSON.
func RawMonCommand(m ccom.MonCommander, buf []byte) Response {
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MonCommand(buf))
}
// MarshalMonCommand takes an generic interface{} value, converts it to JSON
// and sends the json to the MGR as a command.
func MarshalMonCommand(m ccom.MonCommander, v interface{}) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
return RawMonCommand(m, b)
}
// MarshalMgrCommandWithBuffer takes a generic interface{} value, converts
// it to JSON and sends the JSON, along with the buffer data, to the MGR as
// a command.
func MarshalMgrCommandWithBuffer(
m ccom.MgrBufferCommander, v interface{}, buf []byte) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MgrCommandWithInputBuffer(
[][]byte{b},
buf,
))
}
// MarshalMonCommandWithBuffer takes a generic interface{} value, converts
// it to JSON and sends the JSON, along with the buffer data, to the MON(s)
// as a command.
func MarshalMonCommandWithBuffer(
m ccom.MonBufferCommander, v interface{}, buf []byte) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MonCommandWithInputBuffer(
b,
buf,
))
}
+193
View File
@@ -0,0 +1,193 @@
package commands
import (
"bytes"
"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
}
// Body returns the response body as a raw byte-slice.
func (r Response) Body() []byte {
return r.body
}
// 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
}
// EmptyBody is similar to NoBody but also accepts an empty JSON object.
func (r Response) EmptyBody() Response {
if !r.Ok() {
return r
}
if len(r.body) != 0 {
d := map[string]interface{}{}
if err := json.Unmarshal(r.body, &d); err != nil {
return Response{r.body, r.status, err}
}
if len(d) != 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()
}
// FilterPrefix sets the status value to an empty string if the status
// value contains the given prefix string.
func (r Response) FilterPrefix(p string) Response {
if !r.Ok() {
return r
}
if strings.HasPrefix(r.status, p) {
return Response{r.body, "", r.err}
}
return r
}
// FilterSuffix sets the status value to an empty string if the status
// value contains the given suffix string.
func (r Response) FilterSuffix(s string) Response {
if !r.Ok() {
return r
}
if strings.HasSuffix(r.status, s) {
return Response{r.body, "", r.err}
}
return r
}
// FilterBodyPrefix sets the body value equivalent to an empty string if the
// body value contains the given prefix string.
func (r Response) FilterBodyPrefix(p string) Response {
if !r.Ok() {
return r
}
if bytes.HasPrefix(r.body, []byte(p)) {
return Response{[]byte(""), r.status, r.err}
}
return r
}
// 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 {
return r.FilterSuffix(deprecatedSuffix)
}
// 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}
}
+89
View File
@@ -0,0 +1,89 @@
package commands
import (
"fmt"
ccom "github.com/ceph/go-ceph/common/commands"
)
// NewTraceCommander is a RadosCommander that wraps a given RadosCommander
// and when commands are executes prints debug level "traces" to the
// standard output.
func NewTraceCommander(c ccom.RadosBufferCommander) ccom.RadosBufferCommander {
return &tracingCommander{c}
}
// tracingCommander serves two purposes: first, it allows one to trace the
// input and output json when running the tests. It can help with actually
// debugging the tests. Second, it demonstrates the rationale for using an
// interface in FSAdmin. You can layer any sort of debugging, error injection,
// or whatnot between the FSAdmin layer and the RADOS layer.
type tracingCommander struct {
conn ccom.RadosBufferCommander
}
func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
fmt.Println("(MGR Command)")
for i := range buf {
fmt.Println("IN:", string(buf[i]))
}
r, s, err := t.conn.MgrCommand(buf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MonCommand(buf []byte) ([]byte, string, error) {
fmt.Println("(MON Command)")
fmt.Println("IN:", string(buf))
r, s, err := t.conn.MonCommand(buf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MgrCommandWithInputBuffer(
cbuf [][]byte, dbuf []byte) ([]byte, string, error) {
fmt.Println("(MGR Command w/buffer)")
for i := range cbuf {
fmt.Println("IN:", string(cbuf[i]))
}
fmt.Println("IN DATA:", string(dbuf))
r, s, err := t.conn.MgrCommandWithInputBuffer(cbuf, dbuf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MonCommandWithInputBuffer(
cbuf []byte, dbuf []byte) ([]byte, string, error) {
fmt.Println("(MON Command w/buffer)")
fmt.Println("IN:", string(cbuf))
fmt.Println("IN DATA:", string(dbuf))
r, s, err := t.conn.MonCommandWithInputBuffer(cbuf, dbuf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
+66
View File
@@ -0,0 +1,66 @@
package cutil
/*
#include <stdlib.h>
#include <string.h>
typedef void* voidptr;
*/
import "C"
import (
"math"
"unsafe"
)
const (
// MaxIdx is the maximum index on 32 bit systems
MaxIdx = math.MaxInt32 // 2GB, max int32 value, should be safe
// PtrSize is the size of a pointer
PtrSize = C.sizeof_voidptr
// SizeTSize is the size of C.size_t
SizeTSize = C.sizeof_size_t
)
// Compile-time assertion ensuring that Go's `int` is at least as large as C's.
const _ = unsafe.Sizeof(int(0)) - C.sizeof_int
// SizeT wraps size_t from C.
type SizeT C.size_t
// This section contains a bunch of types that are basically just
// unsafe.Pointer but have specific types to help "self document" what the
// underlying pointer is really meant to represent.
// CPtr is an unsafe.Pointer to C allocated memory
type CPtr unsafe.Pointer
// CharPtrPtr is an unsafe pointer wrapping C's `char**`.
type CharPtrPtr unsafe.Pointer
// CharPtr is an unsafe pointer wrapping C's `char*`.
type CharPtr unsafe.Pointer
// SizeTPtr is an unsafe pointer wrapping C's `size_t*`.
type SizeTPtr unsafe.Pointer
// FreeFunc is a wrapper around calls to, or act like, C's free function.
type FreeFunc func(unsafe.Pointer)
// Malloc is C.malloc
func Malloc(s SizeT) CPtr { return CPtr(C.malloc(C.size_t(s))) }
// Free is C.free
func Free(p CPtr) { C.free(unsafe.Pointer(p)) }
// CString is C.CString
func CString(s string) CharPtr { return CharPtr((C.CString(s))) }
// CBytes is C.CBytes
func CBytes(b []byte) CPtr { return CPtr(C.CBytes(b)) }
// Memcpy is C.memcpy
func Memcpy(dst, src CPtr, n SizeT) {
C.memcpy(unsafe.Pointer(dst), unsafe.Pointer(src), C.size_t(n))
}
+89
View File
@@ -0,0 +1,89 @@
package cutil
// #include <stdlib.h>
import "C"
import (
"unsafe"
)
// BufferGroup is a helper structure that holds Go-allocated slices of
// C-allocated strings and their respective lengths. Useful for C functions
// that consume byte buffers with explicit length instead of null-terminated
// strings. When used as input arguments in C functions, caller must make sure
// the C code will not hold any pointers to either of the struct's attributes
// after that C function returns.
type BufferGroup struct {
// C-allocated buffers.
Buffers []CharPtr
// Lengths of C buffers, where Lengths[i] = length(Buffers[i]).
Lengths []SizeT
}
// TODO: should BufferGroup implementation change and the slices would contain
// nested Go pointers, they must be pinned with PtrGuard.
// NewBufferGroupStrings returns new BufferGroup constructed from strings.
func NewBufferGroupStrings(strs []string) *BufferGroup {
s := &BufferGroup{
Buffers: make([]CharPtr, len(strs)),
Lengths: make([]SizeT, len(strs)),
}
for i, str := range strs {
bs := []byte(str)
s.Buffers[i] = CharPtr(C.CBytes(bs))
s.Lengths[i] = SizeT(len(bs))
}
return s
}
// NewBufferGroupBytes returns new BufferGroup constructed
// from slice of byte slices.
func NewBufferGroupBytes(bss [][]byte) *BufferGroup {
s := &BufferGroup{
Buffers: make([]CharPtr, len(bss)),
Lengths: make([]SizeT, len(bss)),
}
for i, bs := range bss {
s.Buffers[i] = CharPtr(C.CBytes(bs))
s.Lengths[i] = SizeT(len(bs))
}
return s
}
// Free free()s the C-allocated memory.
func (s *BufferGroup) Free() {
for _, ptr := range s.Buffers {
C.free(unsafe.Pointer(ptr))
}
s.Buffers = nil
s.Lengths = nil
}
// BuffersPtr returns a pointer to the beginning of the Buffers slice.
func (s *BufferGroup) BuffersPtr() CharPtrPtr {
if len(s.Buffers) == 0 {
return nil
}
return CharPtrPtr(&s.Buffers[0])
}
// LengthsPtr returns a pointer to the beginning of the Lengths slice.
func (s *BufferGroup) LengthsPtr() SizeTPtr {
if len(s.Lengths) == 0 {
return nil
}
return SizeTPtr(&s.Lengths[0])
}
func testBufferGroupGet(s *BufferGroup, index int) (str string, length int) {
bs := C.GoBytes(unsafe.Pointer(s.Buffers[index]), C.int(s.Lengths[index]))
return string(bs), int(s.Lengths[index])
}
+62
View File
@@ -0,0 +1,62 @@
package cutil
/*
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
// CommandInput can be used to manage the input to ceph's *_command functions.
type CommandInput struct {
cmd []*C.char
inbuf []byte
}
// NewCommandInput creates C-level pointers from go byte buffers suitable
// for passing off to ceph's *_command functions.
func NewCommandInput(cmd [][]byte, inputBuffer []byte) *CommandInput {
ci := &CommandInput{
cmd: make([]*C.char, len(cmd)),
inbuf: inputBuffer,
}
for i := range cmd {
ci.cmd[i] = C.CString(string(cmd[i]))
}
return ci
}
// Free any C memory managed by this CommandInput.
func (ci *CommandInput) Free() {
for i := range ci.cmd {
C.free(unsafe.Pointer(ci.cmd[i]))
}
ci.cmd = nil
}
// Cmd returns an unsafe wrapper around an array of C-strings.
func (ci *CommandInput) Cmd() CharPtrPtr {
ptr := &ci.cmd[0]
return CharPtrPtr(ptr)
}
// CmdLen returns the length of the array of C-strings returned by
// Cmd.
func (ci *CommandInput) CmdLen() SizeT {
return SizeT(len(ci.cmd))
}
// InBuf returns an unsafe wrapper to a C char*.
func (ci *CommandInput) InBuf() CharPtr {
if len(ci.inbuf) == 0 {
return nil
}
return CharPtr(&ci.inbuf[0])
}
// InBufLen returns the length of the buffer returned by InBuf.
func (ci *CommandInput) InBufLen() SizeT {
return SizeT(len(ci.inbuf))
}
+100
View File
@@ -0,0 +1,100 @@
package cutil
/*
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
// CommandOutput can be used to manage the outputs of ceph's *_command
// functions.
type CommandOutput struct {
free FreeFunc
outBuf *C.char
outBufLen C.size_t
outs *C.char
outsLen C.size_t
}
// NewCommandOutput returns an empty CommandOutput. The pointers that
// a CommandOutput provides can be used to get the results of ceph's
// *_command functions.
func NewCommandOutput() *CommandOutput {
return &CommandOutput{
free: free,
}
}
// SetFreeFunc sets the function used to free memory held by CommandOutput.
// Not all uses of CommandOutput expect to use the basic C.free function
// and either require or prefer the use of a custom deallocation function.
// Use SetFreeFunc to change the free function and return the modified
// CommandOutput object.
func (co *CommandOutput) SetFreeFunc(f FreeFunc) *CommandOutput {
co.free = f
return co
}
// Free any C memory tracked by this object.
func (co *CommandOutput) Free() {
if co.outBuf != nil {
co.free(unsafe.Pointer(co.outBuf))
}
if co.outs != nil {
co.free(unsafe.Pointer(co.outs))
}
}
// OutBuf returns an unsafe wrapper around a pointer to a `char*`.
func (co *CommandOutput) OutBuf() CharPtrPtr {
return CharPtrPtr(&co.outBuf)
}
// OutBufLen returns an unsafe wrapper around a pointer to a size_t.
func (co *CommandOutput) OutBufLen() SizeTPtr {
return SizeTPtr(&co.outBufLen)
}
// Outs returns an unsafe wrapper around a pointer to a `char*`.
func (co *CommandOutput) Outs() CharPtrPtr {
return CharPtrPtr(&co.outs)
}
// OutsLen returns an unsafe wrapper around a pointer to a size_t.
func (co *CommandOutput) OutsLen() SizeTPtr {
return SizeTPtr(&co.outsLen)
}
// GoValues returns native go values converted from the internal C-language
// values tracked by this object.
func (co *CommandOutput) GoValues() (buf []byte, status string) {
if co.outBufLen > 0 {
buf = C.GoBytes(unsafe.Pointer(co.outBuf), C.int(co.outBufLen))
}
if co.outsLen > 0 {
status = C.GoStringN(co.outs, C.int(co.outsLen))
}
return buf, status
}
// testSetString is only used by the unit tests for this file.
// It is located here due to the restriction on having import "C" in
// go test files. :-(
// It mimics a C function that takes a pointer to a
// string and length and allocates memory and sets the pointers
// to the new string and its length.
func testSetString(strp CharPtrPtr, lenp SizeTPtr, s string) {
sp := (**C.char)(strp)
lp := (*C.size_t)(lenp)
*sp = C.CString(s)
*lp = C.size_t(len(s))
}
// free wraps C.free.
// Required for unit tests that may not use cgo directly.
func free(p unsafe.Pointer) {
C.free(p)
}
+76
View File
@@ -0,0 +1,76 @@
package cutil
// The following code needs some explanation:
// This creates slices on top of the C memory buffers allocated before in
// order to safely and comfortably use them as arrays. First the void pointer
// is cast to a pointer to an array of the type that will be stored in the
// array. Because the size of an array is a constant, but the real array size
// is dynamic, we just use the biggest possible index value MaxIdx, to make
// sure it's always big enough. (Nothing is allocated by casting, so the size
// can be arbitrarily big.) So, if the array should store items of myType, the
// cast would be (*[MaxIdx]myItem)(myCMemPtr).
// From that array pointer a slice is created with the [start:end:capacity]
// syntax. The capacity must be set explicitly here, because by default it
// would be set to the size of the original array, which is MaxIdx, which
// doesn't reflect reality in this case. This results in definitions like:
// cSlice := (*[MaxIdx]myItem)(myCMemPtr)[:numOfItems:numOfItems]
////////// CPtr //////////
// CPtrCSlice is a C allocated slice of C pointers.
type CPtrCSlice []CPtr
// NewCPtrCSlice returns a CPtrSlice.
// Similar to CString it must be freed with slice.Free()
func NewCPtrCSlice(size int) CPtrCSlice {
if size == 0 {
return nil
}
cMem := Malloc(SizeT(size) * PtrSize)
cSlice := (*[MaxIdx]CPtr)(cMem)[:size:size]
return cSlice
}
// Ptr returns a pointer to CPtrSlice
func (v *CPtrCSlice) Ptr() CPtr {
if len(*v) == 0 {
return nil
}
return CPtr(&(*v)[0])
}
// Free frees a CPtrSlice
func (v *CPtrCSlice) Free() {
Free(v.Ptr())
*v = nil
}
////////// SizeT //////////
// SizeTCSlice is a C allocated slice of C.size_t.
type SizeTCSlice []SizeT
// NewSizeTCSlice returns a SizeTCSlice.
// Similar to CString it must be freed with slice.Free()
func NewSizeTCSlice(size int) SizeTCSlice {
if size == 0 {
return nil
}
cMem := Malloc(SizeT(size) * SizeTSize)
cSlice := (*[MaxIdx]SizeT)(cMem)[:size:size]
return cSlice
}
// Ptr returns a pointer to SizeTCSlice
func (v *SizeTCSlice) Ptr() CPtr {
if len(*v) == 0 {
return nil
}
return CPtr(&(*v)[0])
}
// Free frees a SizeTCSlice
func (v *SizeTCSlice) Free() {
Free(v.Ptr())
*v = nil
}
+60
View File
@@ -0,0 +1,60 @@
package cutil
/*
#include <stdlib.h>
#include <sys/uio.h>
*/
import "C"
import (
"unsafe"
)
// Iovec is a slice of iovec structs. Might have allocated C memory, so it must
// be freed with the Free() method.
type Iovec struct {
iovec []C.struct_iovec
sbs []*SyncBuffer
}
const iovecSize = C.sizeof_struct_iovec
// ByteSlicesToIovec creates an Iovec and links it to Go buffers in data.
func ByteSlicesToIovec(data [][]byte) (v Iovec) {
n := len(data)
iovecMem := C.malloc(iovecSize * C.size_t(n))
v.iovec = (*[MaxIdx]C.struct_iovec)(iovecMem)[:n:n]
for i, b := range data {
sb := NewSyncBuffer(CPtr(&v.iovec[i].iov_base), b)
v.sbs = append(v.sbs, sb)
v.iovec[i].iov_len = C.size_t(len(b))
}
return
}
// Sync makes sure the slices contain the same as the C buffers
func (v *Iovec) Sync() {
for _, sb := range v.sbs {
sb.Sync()
}
}
// Pointer returns a pointer to the iovec
func (v *Iovec) Pointer() unsafe.Pointer {
return unsafe.Pointer(&v.iovec[0])
}
// Len returns a pointer to the iovec
func (v *Iovec) Len() int {
return len(v.iovec)
}
// Free the C memory in the Iovec.
func (v *Iovec) Free() {
for _, sb := range v.sbs {
sb.Release()
}
if len(v.iovec) != 0 {
C.free(unsafe.Pointer(&v.iovec[0]))
}
v.iovec = nil
}
+37
View File
@@ -0,0 +1,37 @@
//go:build !go1.21
// +build !go1.21
// This code assumes a non-moving garbage collector, which is the case until at
// least go 1.20
package cutil
import (
"unsafe"
)
// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go
// runtime) stored in C memory (allocated by C)
type PtrGuard struct {
cPtr CPtr
goPtr unsafe.Pointer
}
// NewPtrGuard writes the goPtr (pointing to Go memory) into C memory at the
// position cPtr, and returns a PtrGuard object.
func NewPtrGuard(cPtr CPtr, goPtr unsafe.Pointer) *PtrGuard {
var v PtrGuard
v.cPtr = cPtr
v.goPtr = goPtr
p := (*unsafe.Pointer)(unsafe.Pointer(cPtr))
*p = goPtr
return &v
}
// Release removes the guarded Go pointer from the C memory by overwriting it
// with NULL.
func (v *PtrGuard) Release() {
p := (*unsafe.Pointer)(unsafe.Pointer(v.cPtr))
*p = nil
v.goPtr = nil
}
@@ -0,0 +1,35 @@
//go:build go1.21
// +build go1.21
package cutil
import (
"runtime"
"unsafe"
)
// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go
// runtime) stored in C memory (allocated by C)
type PtrGuard struct {
cPtr CPtr
pinner runtime.Pinner
}
// NewPtrGuard writes the goPtr (pointing to Go memory) into C memory at the
// position cPtr, and returns a PtrGuard object.
func NewPtrGuard(cPtr CPtr, goPtr unsafe.Pointer) *PtrGuard {
var v PtrGuard
v.pinner.Pin(goPtr)
v.cPtr = cPtr
p := (*unsafe.Pointer)(unsafe.Pointer(cPtr))
*p = goPtr
return &v
}
// Release removes the guarded Go pointer from the C memory by overwriting it
// with NULL.
func (v *PtrGuard) Release() {
p := (*unsafe.Pointer)(unsafe.Pointer(v.cPtr))
*p = nil
v.pinner.Unpin()
}
+49
View File
@@ -0,0 +1,49 @@
package cutil
import "C"
import (
"bytes"
)
// SplitBuffer splits a byte-slice buffer, typically returned from C code,
// into a slice of strings.
// The contents of the buffer are assumed to be null-byte separated.
// If the buffer contains a sequence of null-bytes it will assume that the
// "space" between the bytes are meant to be empty strings.
func SplitBuffer(b []byte) []string {
return splitBufStrings(b, true)
}
// SplitSparseBuffer splits a byte-slice buffer, typically returned from C code,
// into a slice of strings.
// The contents of the buffer are assumed to be null-byte separated.
// This function assumes that buffer to be "sparse" such that only non-null-byte
// strings will be returned, and no "empty" strings exist if null-bytes
// are found adjacent to each other.
func SplitSparseBuffer(b []byte) []string {
return splitBufStrings(b, false)
}
// If keepEmpty is true, empty substrings will be returned, by default they are
// excluded from the results.
// This is almost certainly a suboptimal implementation, especially for
// keepEmpty=true case. Optimizing the functions is a job for another day.
func splitBufStrings(b []byte, keepEmpty bool) []string {
values := make([]string, 0)
// the final null byte should be the terminating null in C
// we never want to preserve the empty string after it
if len(b) > 0 && b[len(b)-1] == 0 {
b = b[:len(b)-1]
}
if len(b) == 0 {
return values
}
for _, s := range bytes.Split(b, []byte{0}) {
if !keepEmpty && len(s) == 0 {
continue
}
values = append(values, string(s))
}
return values
}
+29
View File
@@ -0,0 +1,29 @@
//go:build !no_ptrguard
// +build !no_ptrguard
package cutil
import (
"unsafe"
)
// SyncBuffer is a C buffer connected to a data slice
type SyncBuffer struct {
pg *PtrGuard
}
// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr
func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer {
var v SyncBuffer
v.pg = NewPtrGuard(cPtr, unsafe.Pointer(&data[0]))
return &v
}
// Release releases the C buffer and nulls its stored pointer
func (v *SyncBuffer) Release() {
v.pg.Release()
}
// Sync asserts that changes in the C buffer are available in the data
// slice
func (*SyncBuffer) Sync() {}
@@ -0,0 +1,38 @@
//go:build no_ptrguard
// +build no_ptrguard
package cutil
// SyncBuffer is a C buffer connected to a data slice
type SyncBuffer struct {
data []byte
cPtr *CPtr
}
// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr
func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer {
var v SyncBuffer
v.data = data
v.cPtr = (*CPtr)(cPtr)
*v.cPtr = CBytes(data)
return &v
}
// Release releases the C buffer and nulls its stored pointer
func (v *SyncBuffer) Release() {
if v.cPtr != nil {
Free(*v.cPtr)
*v.cPtr = nil
v.cPtr = nil
}
v.data = nil
}
// Sync asserts that changes in the C buffer are available in the data
// slice
func (v *SyncBuffer) Sync() {
if v.cPtr == nil {
return
}
Memcpy(CPtr(&v.data[0]), CPtr(*v.cPtr), SizeT(len(v.data)))
}
+39
View File
@@ -0,0 +1,39 @@
package dlsym
// #cgo LDFLAGS: -ldl
//
// #define _GNU_SOURCE
//
// #include <stdlib.h>
// #include <dlfcn.h>
import "C"
import (
"errors"
"fmt"
"unsafe"
)
// ErrUndefinedSymbol is returned by LookupSymbol when the requested symbol
// could not be found.
var ErrUndefinedSymbol = errors.New("symbol not found")
// LookupSymbol resolves the named symbol from the already dynamically loaded
// libraries. If the symbol is found, a pointer to it is returned, in case of a
// failure, the message provided by dlerror() is included in the error message.
func LookupSymbol(symbol string) (unsafe.Pointer, error) {
cSymName := C.CString(symbol)
defer C.free(unsafe.Pointer(cSymName))
// clear dlerror before looking up the symbol
C.dlerror()
// resolve the address of the symbol
sym := C.dlsym(C.RTLD_DEFAULT, cSymName)
e := C.dlerror()
dlerr := C.GoString(e)
if dlerr != "" {
return nil, fmt.Errorf("%w: %s", ErrUndefinedSymbol, dlerr)
}
return sym, nil
}
+57
View File
@@ -0,0 +1,57 @@
package errutil
type cephErrno int
// Error returns the error string for the errno.
func (e cephErrno) Error() string {
_, strerror := FormatErrno(int(e))
return strerror
}
// cephError combines the source/component that generated the error and its
// related errno.
type cephError struct {
source string
errno cephErrno
}
// Error returns the error string with the source and errno.
func (e cephError) Error() string {
return FormatErrorCode(e.source, int(e.errno))
}
// Unwrap returns an error without the source.
func (e cephError) Unwrap() error {
if e.errno == 0 {
return nil
}
return e.errno
}
// Is checks if both errors have the same errno.
func (e cephError) Is(err error) bool {
ce, ok := err.(cephError)
if !ok {
return false
}
return e.errno == ce.errno
}
// ErrorCode returns the errno of the error.
func (e cephError) ErrorCode() int {
return int(e.errno)
}
// GetError returns a new error that can be compared with errors.Is(),
// independently of the source/component of the error.
func GetError(source string, e int) error {
if e == 0 {
return nil
}
return cephError{
source: source,
errno: cephErrno(int(e)),
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
Package errutil provides common functions for dealing with error conditions for
all ceph api wrappers.
*/
package errutil
/* force XSI-complaint strerror_r() */
// #define _POSIX_C_SOURCE 200112L
// #undef _GNU_SOURCE
// #include <stdlib.h>
// #include <errno.h>
// #include <string.h>
import "C"
import (
"fmt"
"unsafe"
)
// FormatErrno returns the absolute value of the errno as well as a string
// describing the errno. The string will be empty is the errno is not known.
func FormatErrno(errno int) (int, string) {
buf := make([]byte, 1024)
// strerror expects errno >= 0
if errno < 0 {
errno = -errno
}
ret := C.strerror_r(
C.int(errno),
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
if ret != 0 {
return errno, ""
}
return errno, C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
}
// FormatErrorCode returns a string that describes the supplied error source
// and error code as a string. Suitable to use in Error() methods. If the
// error code maps to an errno the string will contain a description of the
// error. Otherwise the string will only indicate the source and value if the
// value does not map to a known errno.
func FormatErrorCode(source string, errValue int) string {
_, s := FormatErrno(errValue)
if s == "" {
return fmt.Sprintf("%s: ret=%d", source, errValue)
}
return fmt.Sprintf("%s: ret=%d, %s", source, errValue, s)
}
+14
View File
@@ -0,0 +1,14 @@
// Package log is the internal package for go-ceph logging. This package is only
// used from go-ceph code, not from consumers of go-ceph. go-ceph code uses the
// functions in this package to log information that can't be returned as
// errors. The functions default to no-ops and can be set with the external log
// package common/log by the go-ceph consumers.
package log
func noop(string, ...interface{}) {}
// These variables are set by the common log package.
var (
Warnf = noop
Debugf = noop
)
+64
View File
@@ -0,0 +1,64 @@
package retry
// Hint is a type for retry hints
type Hint interface {
If(bool) Hint
size() int
}
type hintInt int
func (hint hintInt) size() int {
return int(hint)
}
// If is a convenience function, that returns a given hint only if a certain
// condition is met (for example a test for a "buffer too small" error).
// Otherwise it returns a nil which stops the retries.
func (hint hintInt) If(cond bool) Hint {
if cond {
return hint
}
return nil
}
// DoubleSize is a hint to retry with double the size
const DoubleSize = hintInt(0)
// Size returns a hint for a specific size
func Size(s int) Hint {
return hintInt(s)
}
// SizeFunc is used to implement 'resize loops' that hides the complexity of the
// sizing away from most of the application. It's a function that takes a size
// argument and returns nil, if no retry is necessary, or a hint indicating the
// size for the next retry. If errors or other results are required from the
// function, the function can write them to function closures of the surrounding
// scope. See tests for examples.
type SizeFunc func(size int) (hint Hint)
// WithSizes repeatingly calls a SizeFunc with increasing sizes until either it
// returns nil, or the max size has been reached. If the returned hint is
// DoubleSize or indicating a size not greater than the current size, the size
// is doubled. If the hint or next size is greater than the max size, the max
// size is used for a last retry.
func WithSizes(size int, maxSize int, f SizeFunc) {
if size > maxSize {
return
}
for {
hint := f(size)
if hint == nil || size == maxSize {
break
}
if hint.size() > size {
size = hint.size()
} else {
size *= 2
}
if size > maxSize {
size = maxSize
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package timespec
/*
#include <time.h>
*/
import "C"
import (
"unsafe"
"golang.org/x/sys/unix"
)
// Timespec behaves similarly to C's struct timespec.
// Timespec is used to retain fidelity to the C based file systems
// apis that could be lossy with the use of Go time types.
type Timespec unix.Timespec
// CTimespecPtr is an unsafe pointer wrapping C's `struct timespec`.
type CTimespecPtr unsafe.Pointer
// CStructToTimespec creates a new Timespec for the C 'struct timespec'.
func CStructToTimespec(cts CTimespecPtr) Timespec {
t := (*C.struct_timespec)(cts)
return Timespec{
Sec: int64(t.tv_sec),
Nsec: int64(t.tv_nsec),
}
}
// CopyToCStruct copies the time values from a Timespec to a previously
// allocated C `struct timespec`. Due to restrictions on Cgo the C pointer
// must be passed via the CTimespecPtr wrapper.
func CopyToCStruct(ts Timespec, cts CTimespecPtr) {
t := (*C.struct_timespec)(cts)
t.tv_sec = C.time_t(ts.Sec)
t.tv_nsec = C.long(ts.Nsec)
}
+34
View File
@@ -0,0 +1,34 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// AllocHintFlags control the behavior of read and write operations.
type AllocHintFlags uint32
const (
// AllocHintNoHint indicates no predefined behavior
AllocHintNoHint = AllocHintFlags(0)
// AllocHintSequentialWrite TODO
AllocHintSequentialWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_WRITE)
// AllocHintRandomWrite TODO
AllocHintRandomWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_WRITE)
// AllocHintSequentialRead TODO
AllocHintSequentialRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_READ)
// AllocHintRandomRead TODO
AllocHintRandomRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_READ)
// AllocHintAppendOnly TODO
AllocHintAppendOnly = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_APPEND_ONLY)
// AllocHintImmutable TODO
AllocHintImmutable = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_IMMUTABLE)
// AllocHintShortlived TODO
AllocHintShortlived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SHORTLIVED)
// AllocHintLonglived TODO
AllocHintLonglived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_LONGLIVED)
// AllocHintCompressible TODO
AllocHintCompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_COMPRESSIBLE)
// AllocHintIncompressible TODO
AllocHintIncompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_INCOMPRESSIBLE)
)
+238
View File
@@ -0,0 +1,238 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
func radosBufferFree(p unsafe.Pointer) {
C.rados_buffer_free((*C.char)(p))
}
// MonCommand sends a command to one of the monitors
func (c *Conn) MonCommand(args []byte) ([]byte, string, error) {
return c.MonCommandWithInputBuffer(args, nil)
}
// MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput([][]byte{args}, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mon_command(
c.cluster,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// PGCommand sends a command to one of the PGs
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_pg_command(rados_t cluster, const char *pgstr,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) PGCommand(pgid []byte, args [][]byte) ([]byte, string, error) {
return c.PGCommandWithInputBuffer(pgid, args, nil)
}
// PGCommandWithInputBuffer sends a command to one of the PGs, with an input buffer
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_pg_command(rados_t cluster, const char *pgstr,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) PGCommandWithInputBuffer(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
name := C.CString(string(pgid))
defer C.free(unsafe.Pointer(name))
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_pg_command(
c.cluster,
name,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// MgrCommand sends a command to a ceph-mgr.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) MgrCommand(args [][]byte) ([]byte, string, error) {
return c.MgrCommandWithInputBuffer(args, nil)
}
// MgrCommandWithInputBuffer sends a command, with an input buffer, to a ceph-mgr.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_mgr_command(rados_t cluster, const char **cmd,
// size_t cmdlen, const char *inbuf,
// size_t inbuflen, char **outbuf,
// size_t *outbuflen, char **outs,
// size_t *outslen);
func (c *Conn) MgrCommandWithInputBuffer(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mgr_command(
c.cluster,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// OsdCommand sends a command to the specified ceph OSD.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) OsdCommand(osd int, args [][]byte) ([]byte, string, error) {
return c.OsdCommandWithInputBuffer(osd, args, nil)
}
// OsdCommandWithInputBuffer sends a command, with an input buffer, to the
// specified ceph OSD.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_osd_command(rados_t cluster, int osdid,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) OsdCommandWithInputBuffer(
osd int, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_osd_command(
c.cluster,
C.int(osd),
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// MonCommandTarget sends a command to a specified monitor.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) MonCommandTarget(name string, args [][]byte) ([]byte, string, error) {
return c.MonCommandTargetWithInputBuffer(name, args, nil)
}
// MonCommandTargetWithInputBuffer sends a command, with an input buffer, to a specified monitor.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_mon_command_target(rados_t cluster, const char *name,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) MonCommandTargetWithInputBuffer(
name string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mon_command_target(
c.cluster,
cName,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
+313
View File
@@ -0,0 +1,313 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
var argvPlaceholder = "placeholder"
//revive:disable:var-naming old-yet-exported public api
// ClusterStat represents Ceph cluster statistics.
type ClusterStat struct {
Kb uint64
Kb_used uint64
Kb_avail uint64
Num_objects uint64
}
//revive:enable:var-naming
// Conn is a connection handle to a Ceph cluster.
type Conn struct {
cluster C.rados_t
connected bool
}
// ClusterRef represents a fundamental RADOS cluster connection.
type ClusterRef C.rados_t
// Cluster returns the underlying RADOS cluster reference for this Conn.
func (c *Conn) Cluster() ClusterRef {
return ClusterRef(c.cluster)
}
// PingMonitor sends a ping to a monitor and returns the reply.
func (c *Conn) PingMonitor(id string) (string, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var strlen C.size_t
var strout *C.char
ret := C.rados_ping_monitor(c.cluster, cid, &strout, &strlen)
defer C.rados_buffer_free(strout)
if ret == 0 {
reply := C.GoStringN(strout, (C.int)(strlen))
return reply, nil
}
return "", getError(ret)
}
// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
ret := C.rados_connect(c.cluster)
if ret != 0 {
return getError(ret)
}
c.connected = true
return nil
}
// Shutdown disconnects from the cluster.
func (c *Conn) Shutdown() {
if err := c.ensureConnected(); err != nil {
return
}
freeConn(c)
}
// ReadConfigFile configures the connection using a Ceph configuration file.
func (c *Conn) ReadConfigFile(path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.rados_conf_read_file(c.cluster, cPath)
return getError(ret)
}
// ReadDefaultConfigFile configures the connection using a Ceph configuration
// file located at default locations.
func (c *Conn) ReadDefaultConfigFile() error {
ret := C.rados_conf_read_file(c.cluster, nil)
return getError(ret)
}
// OpenIOContext creates and returns a new IOContext for the given pool.
//
// Implements:
//
// int rados_ioctx_create(rados_t cluster, const char *pool_name,
// rados_ioctx_t *ioctx);
func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
cPool := C.CString(pool)
defer C.free(unsafe.Pointer(cPool))
ioctx := &IOContext{conn: c}
ret := C.rados_ioctx_create(c.cluster, cPool, &ioctx.ioctx)
if ret == 0 {
return ioctx, nil
}
return nil, getError(ret)
}
// ListPools returns the names of all existing pools.
func (c *Conn) ListPools() (names []string, err error) {
buf := make([]byte, 4096)
for {
ret := C.rados_pool_list(c.cluster,
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
if ret < 0 {
return nil, getError(ret)
}
if int(ret) > len(buf) {
buf = make([]byte, ret)
continue
}
names = cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
}
// SetConfigOption sets the value of the configuration option identified by
// the given name.
func (c *Conn) SetConfigOption(option, value string) error {
cOpt, cVal := C.CString(option), C.CString(value)
defer C.free(unsafe.Pointer(cOpt))
defer C.free(unsafe.Pointer(cVal))
ret := C.rados_conf_set(c.cluster, cOpt, cVal)
return getError(ret)
}
// GetConfigOption returns the value of the Ceph configuration option
// identified by the given name.
func (c *Conn) GetConfigOption(name string) (value string, err error) {
cOption := C.CString(name)
defer C.free(unsafe.Pointer(cOption))
var buf []byte
// range from 4k to 256KiB
retry.WithSizes(4096, 1<<18, func(size int) retry.Hint {
buf = make([]byte, size)
ret := C.rados_conf_get(
c.cluster,
cOption,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
err = getError(ret)
return retry.DoubleSize.If(err == errNameTooLong)
})
if err != nil {
return "", err
}
value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
return value, nil
}
// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
// retrieved.
func (c *Conn) WaitForLatestOSDMap() error {
ret := C.rados_wait_for_latest_osdmap(c.cluster)
return getError(ret)
}
func (c *Conn) ensureConnected() error {
if c.connected {
return nil
}
return ErrNotConnected
}
// GetClusterStats returns statistics about the cluster associated with the
// connection.
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
if err := c.ensureConnected(); err != nil {
return ClusterStat{}, err
}
cStat := C.struct_rados_cluster_stat_t{}
ret := C.rados_cluster_stat(c.cluster, &cStat)
if ret < 0 {
return ClusterStat{}, getError(ret)
}
return ClusterStat{
Kb: uint64(cStat.kb),
Kb_used: uint64(cStat.kb_used),
Kb_avail: uint64(cStat.kb_avail),
Num_objects: uint64(cStat.num_objects),
}, nil
}
// ParseConfigArgv configures the connection using a unix style command line
// argument vector.
//
// Implements:
//
// int rados_conf_parse_argv(rados_t cluster, int argc,
// const char **argv);
func (c *Conn) ParseConfigArgv(argv []string) error {
if c.cluster == nil {
return ErrNotConnected
}
if len(argv) == 0 {
return ErrEmptyArgument
}
cargv := make([]*C.char, len(argv))
for i := range argv {
cargv[i] = C.CString(argv[i])
defer C.free(unsafe.Pointer(cargv[i]))
}
ret := C.rados_conf_parse_argv(c.cluster, C.int(len(cargv)), &cargv[0])
return getError(ret)
}
// ParseCmdLineArgs configures the connection from command line arguments.
//
// This function passes a placeholder value to Ceph as argv[0], see
// ParseConfigArgv for a version of this function that allows the caller to
// specify argv[0].
func (c *Conn) ParseCmdLineArgs(args []string) error {
argv := make([]string, len(args)+1)
// Ceph expects a proper argv array as the actual contents with the
// first element containing the executable name
argv[0] = argvPlaceholder
for i := range args {
argv[i+1] = args[i]
}
return c.ParseConfigArgv(argv)
}
// ParseDefaultConfigEnv configures the connection from the default Ceph
// environment variable CEPH_ARGS.
func (c *Conn) ParseDefaultConfigEnv() error {
ret := C.rados_conf_parse_env(c.cluster, nil)
return getError(ret)
}
// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
// is a unique identifier of an entire Ceph cluster.
func (c *Conn) GetFSID() (fsid string, err error) {
buf := make([]byte, 37)
ret := C.rados_cluster_fsid(c.cluster,
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
// FIXME: the success case isn't documented correctly in librados.h
if ret == 36 {
fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
return fsid, nil
}
return "", getError(ret)
}
// GetInstanceID returns a globally unique identifier for the cluster
// connection instance.
func (c *Conn) GetInstanceID() uint64 {
// FIXME: are there any error cases for this?
return uint64(C.rados_get_instance_id(c.cluster))
}
// MakePool creates a new pool with default settings.
func (c *Conn) MakePool(name string) error {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_create(c.cluster, cName)
return getError(ret)
}
// DeletePool deletes a pool and all the data inside the pool.
func (c *Conn) DeletePool(name string) error {
if err := c.ensureConnected(); err != nil {
return err
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_delete(c.cluster, cName)
return getError(ret)
}
// GetPoolByName returns the ID of the pool with a given name.
func (c *Conn) GetPoolByName(name string) (int64, error) {
if err := c.ensureConnected(); err != nil {
return 0, err
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_lookup(c.cluster, cName)
if ret < 0 {
return 0, getError(C.int(ret))
}
return int64(ret), nil
}
// GetPoolByID returns the name of a pool by a given ID.
func (c *Conn) GetPoolByID(id int64) (string, error) {
buf := make([]byte, 4096)
if err := c.ensureConnected(); err != nil {
return "", err
}
cid := C.int64_t(id)
ret := C.rados_pool_reverse_lookup(c.cluster, cid, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
if ret < 0 {
return "", getError(ret)
}
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
}
+4
View File
@@ -0,0 +1,4 @@
/*
Package rados contains a set of wrappers around Ceph's librados API.
*/
package rados
+64
View File
@@ -0,0 +1,64 @@
package rados
/*
#include <errno.h>
*/
import "C"
import (
"errors"
"github.com/ceph/go-ceph/internal/errutil"
)
// Public go errors:
var (
// ErrNotConnected is returned when functions are called
// without a RADOS connection.
ErrNotConnected = getError(-C.ENOTCONN)
// ErrEmptyArgument may be returned if a function argument is passed
// a zero-length slice or map.
ErrEmptyArgument = errors.New("Argument must contain at least one item")
// ErrInvalidIOContext may be returned if an api call requires an IOContext
// but IOContext is not ready for use.
ErrInvalidIOContext = errors.New("IOContext is not ready for use")
// ErrOperationIncomplete is returned from write op or read op steps for
// which the operation has not been performed yet.
ErrOperationIncomplete = errors.New("Operation has not been performed yet")
// ErrNotFound indicates a missing resource.
ErrNotFound = getError(-C.ENOENT)
// ErrPermissionDenied indicates a permissions issue.
ErrPermissionDenied = getError(-C.EPERM)
// ErrObjectExists indicates that an exclusive object creation failed.
ErrObjectExists = getError(-C.EEXIST)
// RadosErrorNotFound indicates a missing resource.
//
// Deprecated: use ErrNotFound instead
RadosErrorNotFound = ErrNotFound
// RadosErrorPermissionDenied indicates a permissions issue.
//
// Deprecated: use ErrPermissionDenied instead
RadosErrorPermissionDenied = ErrPermissionDenied
// Private errors:
errNameTooLong = getError(-C.ENAMETOOLONG)
errRange = getError(-C.ERANGE)
)
func getError(errno C.int) error {
return errutil.GetError("rados", int(errno))
}
// getErrorIfNegative converts a ceph return code to error if negative.
// This is useful for functions that return a usable positive value on
// success but a negative error number on error.
func getErrorIfNegative(ret C.int) error {
if ret >= 0 {
return nil
}
return getError(ret)
}
+725
View File
@@ -0,0 +1,725 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
// char* nextChunk(char **idx) {
// char *copy;
// copy = strdup(*idx);
// *idx += strlen(*idx) + 1;
// return copy;
// }
//
// #if __APPLE__
// #define ceph_time_t __darwin_time_t
// #define ceph_suseconds_t __darwin_suseconds_t
// #elif __GLIBC__
// #define ceph_time_t __time_t
// #define ceph_suseconds_t __suseconds_t
// #else
// #define ceph_time_t time_t
// #define ceph_suseconds_t suseconds_t
// #endif
import "C"
import (
"syscall"
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/retry"
)
// CreateOption is passed to IOContext.Create() and should be one of
// CreateExclusive or CreateIdempotent.
type CreateOption int
const (
// CreateExclusive if used with IOContext.Create() and the object
// already exists, the function will return an error.
CreateExclusive = C.LIBRADOS_CREATE_EXCLUSIVE
// CreateIdempotent if used with IOContext.Create() and the object
// already exists, the function will not return an error.
CreateIdempotent = C.LIBRADOS_CREATE_IDEMPOTENT
defaultListObjectsResultSize = 1000
// listEndSentinel is the value returned by rados_list_object_list_is_end
// when a cursor has reached the end of a pool
listEndSentinel = 1
)
//revive:disable:var-naming old-yet-exported public api
// PoolStat represents Ceph pool statistics.
type PoolStat struct {
// space used in bytes
Num_bytes uint64
// space used in KB
Num_kb uint64
// number of objects in the pool
Num_objects uint64
// number of clones of objects
Num_object_clones uint64
// num_objects * num_replicas
Num_object_copies uint64
Num_objects_missing_on_primary uint64
// number of objects found on no OSDs
Num_objects_unfound uint64
// number of objects replicated fewer times than they should be
// (but found on at least one OSD)
Num_objects_degraded uint64
Num_rd uint64
Num_rd_kb uint64
Num_wr uint64
Num_wr_kb uint64
}
//revive:enable:var-naming
// ObjectStat represents an object stat information
type ObjectStat struct {
// current length in bytes
Size uint64
// last modification time
ModTime time.Time
}
// LockInfo represents information on a current Ceph lock
type LockInfo struct {
NumLockers int
Exclusive bool
Tag string
Clients []string
Cookies []string
Addrs []string
}
// IOContext represents a context for performing I/O within a pool.
type IOContext struct {
ioctx C.rados_ioctx_t
// Hold a reference back to the connection that the ioctx depends on so
// that Go's GC doesn't trigger the Conn's finalizer before this
// IOContext is destroyed.
conn *Conn
}
// validate returns an error if the ioctx is not ready to be used
// with ceph C calls.
func (ioctx *IOContext) validate() error {
if ioctx.ioctx == nil {
return ErrInvalidIOContext
}
return nil
}
// Pointer returns a pointer reference to an internal structure.
// This function should NOT be used outside of go-ceph itself.
func (ioctx *IOContext) Pointer() unsafe.Pointer {
return unsafe.Pointer(ioctx.ioctx)
}
// SetNamespace sets the namespace for objects within this IO context (pool).
// Setting namespace to a empty or zero length string sets the pool to the default namespace.
//
// Implements:
//
// void rados_ioctx_set_namespace(rados_ioctx_t io,
// const char *nspace);
func (ioctx *IOContext) SetNamespace(namespace string) {
var cns *C.char
if len(namespace) > 0 {
cns = C.CString(namespace)
defer C.free(unsafe.Pointer(cns))
}
C.rados_ioctx_set_namespace(ioctx.ioctx, cns)
}
// Create a new object with key oid.
//
// Implements:
//
// void rados_write_op_create(rados_write_op_t write_op, int exclusive,
// const char* category)
func (ioctx *IOContext) Create(oid string, exclusive CreateOption) error {
op := CreateWriteOp()
defer op.Release()
op.Create(exclusive)
return op.operateCompat(ioctx, oid)
}
// Write writes len(data) bytes to the object with key oid starting at byte
// offset offset. It returns an error, if any.
func (ioctx *IOContext) Write(oid string, data []byte, offset uint64) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
dataPointer := unsafe.Pointer(nil)
if len(data) > 0 {
dataPointer = unsafe.Pointer(&data[0])
}
ret := C.rados_write(ioctx.ioctx, coid,
(*C.char)(dataPointer),
(C.size_t)(len(data)),
(C.uint64_t)(offset))
return getError(ret)
}
// WriteFull writes len(data) bytes to the object with key oid.
// The object is filled with the provided data. If the object exists,
// it is atomically truncated and then written. It returns an error, if any.
func (ioctx *IOContext) WriteFull(oid string, data []byte) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
ret := C.rados_write_full(ioctx.ioctx, coid,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// Append appends len(data) bytes to the object with key oid.
// The object is appended with the provided data. If the object exists,
// it is atomically appended to. It returns an error, if any.
func (ioctx *IOContext) Append(oid string, data []byte) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
ret := C.rados_append(ioctx.ioctx, coid,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// Read reads up to len(data) bytes from the object with key oid starting at byte
// offset offset. It returns the number of bytes read and an error, if any.
func (ioctx *IOContext) Read(oid string, data []byte, offset uint64) (int, error) {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
var buf *C.char
if len(data) > 0 {
buf = (*C.char)(unsafe.Pointer(&data[0]))
}
ret := C.rados_read(
ioctx.ioctx,
coid,
buf,
(C.size_t)(len(data)),
(C.uint64_t)(offset))
if ret >= 0 {
return int(ret), nil
}
return 0, getError(ret)
}
// Delete deletes the object with key oid. It returns an error, if any.
func (ioctx *IOContext) Delete(oid string) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_remove(ioctx.ioctx, coid))
}
// Truncate resizes the object with key oid to size size. If the operation
// enlarges the object, the new area is logically filled with zeroes. If the
// operation shrinks the object, the excess data is removed. It returns an
// error, if any.
func (ioctx *IOContext) Truncate(oid string, size uint64) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_trunc(ioctx.ioctx, coid, (C.uint64_t)(size)))
}
// Destroy informs librados that the I/O context is no longer in use.
// Resources associated with the context may not be freed immediately, and the
// context should not be used again after calling this method.
func (ioctx *IOContext) Destroy() {
C.rados_ioctx_destroy(ioctx.ioctx)
}
// GetPoolStats returns a set of statistics about the pool associated with this I/O
// context.
//
// Implements:
//
// int rados_ioctx_pool_stat(rados_ioctx_t io,
// struct rados_pool_stat_t *stats);
func (ioctx *IOContext) GetPoolStats() (stat PoolStat, err error) {
cStat := C.struct_rados_pool_stat_t{}
ret := C.rados_ioctx_pool_stat(ioctx.ioctx, &cStat)
if ret < 0 {
return PoolStat{}, getError(ret)
}
return PoolStat{
Num_bytes: uint64(cStat.num_bytes),
Num_kb: uint64(cStat.num_kb),
Num_objects: uint64(cStat.num_objects),
Num_object_clones: uint64(cStat.num_object_clones),
Num_object_copies: uint64(cStat.num_object_copies),
Num_objects_missing_on_primary: uint64(cStat.num_objects_missing_on_primary),
Num_objects_unfound: uint64(cStat.num_objects_unfound),
Num_objects_degraded: uint64(cStat.num_objects_degraded),
Num_rd: uint64(cStat.num_rd),
Num_rd_kb: uint64(cStat.num_rd_kb),
Num_wr: uint64(cStat.num_wr),
Num_wr_kb: uint64(cStat.num_wr_kb),
}, nil
}
// GetPoolID returns the pool ID associated with the I/O context.
//
// Implements:
//
// int64_t rados_ioctx_get_id(rados_ioctx_t io)
func (ioctx *IOContext) GetPoolID() int64 {
ret := C.rados_ioctx_get_id(ioctx.ioctx)
return int64(ret)
}
// GetPoolName returns the name of the pool associated with the I/O context.
func (ioctx *IOContext) GetPoolName() (name string, err error) {
var (
buf []byte
ret C.int
)
retry.WithSizes(128, 8192, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.rados_ioctx_get_pool_name(
ioctx.ioctx,
(*C.char)(unsafe.Pointer(&buf[0])),
C.unsigned(len(buf)))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return "", err
}
name = C.GoStringN((*C.char)(unsafe.Pointer(&buf[0])), ret)
return name, nil
}
// ObjectListFunc is the type of the function called for each object visited
// by ListObjects.
type ObjectListFunc func(oid string)
// ListObjects lists all of the objects in the pool associated with the I/O
// context, and called the provided listFn function for each object, passing
// to the function the name of the object. Call SetNamespace with
// RadosAllNamespaces before calling this function to return objects from all
// namespaces
func (ioctx *IOContext) ListObjects(listFn ObjectListFunc) error {
pageResults := C.size_t(defaultListObjectsResultSize)
var filterLen C.size_t
results := make([]C.rados_object_list_item, pageResults)
next := C.rados_object_list_begin(ioctx.ioctx)
if next == nil {
return ErrNotFound
}
defer C.rados_object_list_cursor_free(ioctx.ioctx, next)
finish := C.rados_object_list_end(ioctx.ioctx)
if finish == nil {
return ErrNotFound
}
defer C.rados_object_list_cursor_free(ioctx.ioctx, finish)
for {
res := (*C.rados_object_list_item)(unsafe.Pointer(&results[0]))
ret := C.rados_object_list(ioctx.ioctx, next, finish, pageResults, nil, filterLen, res, &next)
if ret < 0 {
return getError(ret)
}
numEntries := int(ret)
for i := 0; i < numEntries; i++ {
item := results[i]
listFn(C.GoStringN(item.oid, (C.int)(item.oid_length)))
}
C.rados_object_list_free(C.size_t(ret), res)
if C.rados_object_list_is_end(ioctx.ioctx, next) == listEndSentinel {
return nil
}
}
}
// Stat returns the size of the object and its last modification time
func (ioctx *IOContext) Stat(object string) (stat ObjectStat, err error) {
var cPsize C.uint64_t
var cPmtime C.time_t
cObject := C.CString(object)
defer C.free(unsafe.Pointer(cObject))
ret := C.rados_stat(
ioctx.ioctx,
cObject,
&cPsize,
&cPmtime)
if ret < 0 {
return ObjectStat{}, getError(ret)
}
return ObjectStat{
Size: uint64(cPsize),
ModTime: time.Unix(int64(cPmtime), 0),
}, nil
}
// GetXattr gets an xattr with key `name`, it returns the length of
// the key read or an error if not successful
func (ioctx *IOContext) GetXattr(object string, name string, data []byte) (int, error) {
cObject := C.CString(object)
cName := C.CString(name)
defer C.free(unsafe.Pointer(cObject))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_getxattr(
ioctx.ioctx,
cObject,
cName,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
if ret >= 0 {
return int(ret), nil
}
return 0, getError(ret)
}
// SetXattr sets an xattr for an object with key `name` with value as `data`
func (ioctx *IOContext) SetXattr(object string, name string, data []byte) error {
cObject := C.CString(object)
cName := C.CString(name)
defer C.free(unsafe.Pointer(cObject))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_setxattr(
ioctx.ioctx,
cObject,
cName,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// ListXattrs lists all the xattrs for an object. The xattrs are returned as a
// mapping of string keys and byte-slice values.
func (ioctx *IOContext) ListXattrs(oid string) (map[string][]byte, error) {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
var it C.rados_xattrs_iter_t
ret := C.rados_getxattrs(ioctx.ioctx, coid, &it)
if ret < 0 {
return nil, getError(ret)
}
defer func() { C.rados_getxattrs_end(it) }()
m := make(map[string][]byte)
for {
var cName, cVal *C.char
var cLen C.size_t
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cVal))
ret := C.rados_getxattrs_next(it, &cName, &cVal, &cLen)
if ret < 0 {
return nil, getError(ret)
}
// rados api returns a null name,val & 0-length upon
// end of iteration
if cName == nil {
return m, nil // stop iteration
}
m[C.GoString(cName)] = C.GoBytes(unsafe.Pointer(cVal), (C.int)(cLen))
}
}
// RmXattr removes an xattr with key `name` from object `oid`
func (ioctx *IOContext) RmXattr(oid string, name string) error {
coid := C.CString(oid)
cName := C.CString(name)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_rmxattr(
ioctx.ioctx,
coid,
cName)
return getError(ret)
}
// LockExclusive takes an exclusive lock on an object.
func (ioctx *IOContext) LockExclusive(oid, name, cookie, desc string, duration time.Duration, flags *byte) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
cDesc := C.CString(desc)
var cDuration C.struct_timeval
if duration != 0 {
tv := syscall.NsecToTimeval(duration.Nanoseconds())
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
}
var cFlags C.uint8_t
if flags != nil {
cFlags = C.uint8_t(*flags)
}
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
defer C.free(unsafe.Pointer(cDesc))
ret := C.rados_lock_exclusive(
ioctx.ioctx,
coid,
cName,
cCookie,
cDesc,
&cDuration,
cFlags)
// 0 on success, negative error code on failure
// -EBUSY if the lock is already held by another (client, cookie) pair
// -EEXIST if the lock is already held by the same (client, cookie) pair
switch ret {
case 0:
return int(ret), nil
case -C.EBUSY:
return int(ret), nil
case -C.EEXIST:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// LockShared takes a shared lock on an object.
func (ioctx *IOContext) LockShared(oid, name, cookie, tag, desc string, duration time.Duration, flags *byte) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
cTag := C.CString(tag)
cDesc := C.CString(desc)
var cDuration C.struct_timeval
if duration != 0 {
tv := syscall.NsecToTimeval(duration.Nanoseconds())
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
}
var cFlags C.uint8_t
if flags != nil {
cFlags = C.uint8_t(*flags)
}
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
defer C.free(unsafe.Pointer(cTag))
defer C.free(unsafe.Pointer(cDesc))
ret := C.rados_lock_shared(
ioctx.ioctx,
coid,
cName,
cCookie,
cTag,
cDesc,
&cDuration,
cFlags)
// 0 on success, negative error code on failure
// -EBUSY if the lock is already held by another (client, cookie) pair
// -EEXIST if the lock is already held by the same (client, cookie) pair
switch ret {
case 0:
return int(ret), nil
case -C.EBUSY:
return int(ret), nil
case -C.EEXIST:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// Unlock releases a shared or exclusive lock on an object.
func (ioctx *IOContext) Unlock(oid, name, cookie string) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
// 0 on success, negative error code on failure
// -ENOENT if the lock is not held by the specified (client, cookie) pair
ret := C.rados_unlock(
ioctx.ioctx,
coid,
cName,
cCookie)
switch ret {
case 0:
return int(ret), nil
case -C.ENOENT:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// ListLockers lists clients that have locked the named object lock and
// information about the lock.
// The number of bytes required in each buffer is put in the corresponding size
// out parameter. If any of the provided buffers are too short, -ERANGE is
// returned after these sizes are filled in.
func (ioctx *IOContext) ListLockers(oid, name string) (*LockInfo, error) {
coid := C.CString(oid)
cName := C.CString(name)
cTag := (*C.char)(C.malloc(C.size_t(1024)))
cClients := (*C.char)(C.malloc(C.size_t(1024)))
cCookies := (*C.char)(C.malloc(C.size_t(1024)))
cAddrs := (*C.char)(C.malloc(C.size_t(1024)))
var cExclusive C.int
cTagLen := C.size_t(1024)
cClientsLen := C.size_t(1024)
cCookiesLen := C.size_t(1024)
cAddrsLen := C.size_t(1024)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cTag))
defer C.free(unsafe.Pointer(cClients))
defer C.free(unsafe.Pointer(cCookies))
defer C.free(unsafe.Pointer(cAddrs))
ret := C.rados_list_lockers(
ioctx.ioctx,
coid,
cName,
&cExclusive,
cTag,
&cTagLen,
cClients,
&cClientsLen,
cCookies,
&cCookiesLen,
cAddrs,
&cAddrsLen)
splitCString := func(items *C.char, itemsLen C.size_t) []string {
currLen := 0
clients := []string{}
for currLen < int(itemsLen) {
client := C.GoString(C.nextChunk(&items))
clients = append(clients, client)
currLen += len(client) + 1
}
return clients
}
if ret < 0 {
return nil, getError(C.int(ret))
}
return &LockInfo{int(ret), cExclusive == 1, C.GoString(cTag), splitCString(cClients, cClientsLen), splitCString(cCookies, cCookiesLen), splitCString(cAddrs, cAddrsLen)}, nil
}
// BreakLock releases a shared or exclusive lock on an object, which was taken by the specified client.
func (ioctx *IOContext) BreakLock(oid, name, client, cookie string) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cClient := C.CString(client)
cCookie := C.CString(cookie)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cClient))
defer C.free(unsafe.Pointer(cCookie))
// 0 on success, negative error code on failure
// -ENOENT if the lock is not held by the specified (client, cookie) pair
// -EINVAL if the client cannot be parsed
ret := C.rados_break_lock(
ioctx.ioctx,
coid,
cName,
cClient,
cCookie)
switch ret {
case 0:
return int(ret), nil
case -C.ENOENT:
return int(ret), nil
case -C.EINVAL: // -EINVAL
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// GetLastVersion will return the version number of the last object read or
// written to.
//
// Implements:
//
// uint64_t rados_get_last_version(rados_ioctx_t io);
func (ioctx *IOContext) GetLastVersion() (uint64, error) {
if err := ioctx.validate(); err != nil {
return 0, err
}
v := C.rados_get_last_version(ioctx.ioctx)
return uint64(v), nil
}
// GetNamespace gets the namespace used for objects within this IO context.
//
// Implements:
//
// int rados_ioctx_get_namespace(rados_ioctx_t io, char *buf,
// unsigned maxlen);
func (ioctx *IOContext) GetNamespace() (string, error) {
if err := ioctx.validate(); err != nil {
return "", err
}
var (
err error
buf []byte
ret C.int
)
retry.WithSizes(128, 8192, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.rados_ioctx_get_namespace(
ioctx.ioctx,
(*C.char)(unsafe.Pointer(&buf[0])),
C.unsigned(len(buf)))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return "", err
}
return string(buf[:ret]), nil
}
+37
View File
@@ -0,0 +1,37 @@
//go:build nautilus
// +build nautilus
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// SetPoolFullTry makes sure to send requests to the cluster despite
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
// or return EDQUOT or ENOSPC.
//
// Implements:
//
// void rados_set_osdmap_full_try(rados_ioctx_t io);
func (ioctx *IOContext) SetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_set_osdmap_full_try(ioctx.ioctx)
return nil
}
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
//
// Implements:
//
// void rados_unset_osdmap_full_try(rados_ioctx_t io);
func (ioctx *IOContext) UnsetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_unset_osdmap_full_try(ioctx.ioctx)
return nil
}
+40
View File
@@ -0,0 +1,40 @@
//go:build !nautilus
// +build !nautilus
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// Ceph octopus deprecates rados_set_osdmap_full_try() and implements rados_set_pool_full_try()
// Ceph octopus deprecates rados_unset_osdmap_full_try() and implements rados_unset_pool_full_try()
// SetPoolFullTry makes sure to send requests to the cluster despite
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
// or return EDQUOT or ENOSPC.
//
// Implements:
//
// void rados_set_pool_full_try(rados_ioctx_t io);
func (ioctx *IOContext) SetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_set_pool_full_try(ioctx.ioctx)
return nil
}
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
//
// Implements:
//
// void rados_unset_pool_full_try(rados_ioctx_t io);
func (ioctx *IOContext) UnsetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_unset_pool_full_try(ioctx.ioctx)
return nil
}
+25
View File
@@ -0,0 +1,25 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// Alignment returns the required stripe size in bytes for pools supporting/requiring it, or an error if unsuccessful.
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. To know if the pool requires
// alignment or not, use RequiresAlignment.
//
// Implements:
//
// int rados_ioctx_pool_required_alignment2(rados_ioctx_t io, uint64_t *alignment)
func (ioctx *IOContext) Alignment() (uint64, error) {
var alignSizeBytes C.uint64_t
ret := C.rados_ioctx_pool_required_alignment2(
ioctx.ioctx,
&alignSizeBytes)
if ret != 0 {
return 0, getError(ret)
}
return uint64(alignSizeBytes), nil
}
@@ -0,0 +1,25 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// RequiresAlignment returns true if the pool supports/requires alignment or an error if not successful.
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. See
// Alignment to know how to get the stripe size for pools requiring it.
//
// Implements:
//
// int rados_ioctx_pool_requires_alignment2(rados_ioctx_t io, int *req)
func (ioctx *IOContext) RequiresAlignment() (bool, error) {
var alignRequired C.int
ret := C.rados_ioctx_pool_requires_alignment2(
ioctx.ioctx,
&alignRequired)
if ret != 0 {
return false, getError(ret)
}
return (alignRequired != 0), nil
}
+36
View File
@@ -0,0 +1,36 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetAllocationHint sets allocation hint for an object. This is an advisory
// operation, it will always succeed (as if it was submitted with a
// LIBRADOS_OP_FLAG_FAILOK flag set) and is not guaranteed to do anything on
// the backend.
//
// Implements:
//
// int rados_set_alloc_hint2(rados_ioctx_t io,
// const char *o,
// uint64_t expected_object_size,
// uint64_t expected_write_size,
// uint32_t flags);
func (ioctx *IOContext) SetAllocationHint(oid string, expectedObjectSize uint64, expectedWriteSize uint64, flags AllocHintFlags) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_set_alloc_hint2(
ioctx.ioctx,
coid,
(C.uint64_t)(expectedObjectSize),
(C.uint64_t)(expectedWriteSize),
(C.uint32_t)(flags),
))
}
+92
View File
@@ -0,0 +1,92 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// Iter supports iterating over objects in the ioctx.
type Iter struct {
ctx C.rados_list_ctx_t
err error
entry string
namespace string
}
// IterToken supports reporting on and seeking to different positions.
type IterToken uint32
// Iter returns a Iterator object that can be used to list the object names in the current pool
func (ioctx *IOContext) Iter() (*Iter, error) {
iter := Iter{}
if cerr := C.rados_nobjects_list_open(ioctx.ioctx, &iter.ctx); cerr < 0 {
return nil, getError(cerr)
}
return &iter, nil
}
// Token returns a token marking the current position of the iterator. To be used in combination with Iter.Seek()
func (iter *Iter) Token() IterToken {
return IterToken(C.rados_nobjects_list_get_pg_hash_position(iter.ctx))
}
// Seek moves the iterator to the position indicated by the token.
func (iter *Iter) Seek(token IterToken) {
C.rados_nobjects_list_seek(iter.ctx, C.uint32_t(token))
}
// Next retrieves the next object name in the pool/namespace iterator.
// Upon a successful invocation (return value of true), the Value method should
// be used to obtain the name of the retrieved object name. When the iterator is
// exhausted, Next returns false. The Err method should used to verify whether the
// end of the iterator was reached, or the iterator received an error.
//
// Example:
//
// iter := pool.Iter()
// defer iter.Close()
// for iter.Next() {
// fmt.Printf("%v\n", iter.Value())
// }
// return iter.Err()
func (iter *Iter) Next() bool {
var cEntry *C.char
var cNamespace *C.char
if cerr := C.rados_nobjects_list_next(iter.ctx, &cEntry, nil, &cNamespace); cerr < 0 {
iter.err = getError(cerr)
return false
}
iter.entry = C.GoString(cEntry)
iter.namespace = C.GoString(cNamespace)
return true
}
// Value returns the current value of the iterator (object name), after a successful call to Next.
func (iter *Iter) Value() string {
if iter.err != nil {
return ""
}
return iter.entry
}
// Namespace returns the namespace associated with the current value of the iterator (object name), after a successful call to Next.
func (iter *Iter) Namespace() string {
if iter.err != nil {
return ""
}
return iter.namespace
}
// Err checks whether the iterator has encountered an error.
func (iter *Iter) Err() error {
if iter.err == ErrNotFound {
return nil
}
return iter.err
}
// Close the iterator cursor on the server. Be aware that iterators are not closed automatically
// at the end of iteration.
func (iter *Iter) Close() {
C.rados_nobjects_list_close(iter.ctx)
}
+205
View File
@@ -0,0 +1,205 @@
package rados
/*
#cgo LDFLAGS: -lrados
#include <stdlib.h>
#include <rados/librados.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
// OmapKeyValue items are returned by the GetOmapStep's Next call.
type OmapKeyValue struct {
Key string
Value []byte
}
// GetOmapStep values are used to get the results of an GetOmapValues call
// on a WriteOp. Until the Operate method of the WriteOp is called the Next
// call will return an error. After Operate is called, the Next call will
// return valid results.
//
// The life cycle of the GetOmapStep is bound to the ReadOp, if the ReadOp
// Release method is called the public methods of the step must no longer be
// used and may return errors.
type GetOmapStep struct {
// C returned data:
iter C.rados_omap_iter_t
more *C.uchar
rval *C.int
// internal state:
// canIterate is only set after the operation is performed and is
// intended to prevent premature fetching of data
canIterate bool
}
func newGetOmapStep() *GetOmapStep {
gos := &GetOmapStep{
more: (*C.uchar)(C.malloc(C.sizeof_uchar)),
rval: (*C.int)(C.malloc(C.sizeof_int)),
}
runtime.SetFinalizer(gos, opStepFinalizer)
return gos
}
func (gos *GetOmapStep) free() {
gos.canIterate = false
if gos.iter != nil {
C.rados_omap_get_end(gos.iter)
}
gos.iter = nil
C.free(unsafe.Pointer(gos.more))
gos.more = nil
C.free(unsafe.Pointer(gos.rval))
gos.rval = nil
}
func (gos *GetOmapStep) update() error {
err := getError(*gos.rval)
gos.canIterate = (err == nil)
return err
}
// Next returns the next key value pair or nil if iteration is exhausted.
func (gos *GetOmapStep) Next() (*OmapKeyValue, error) {
if !gos.canIterate {
return nil, ErrOperationIncomplete
}
var (
cKey *C.char
cVal *C.char
cKeyLen C.size_t
cValLen C.size_t
)
ret := C.rados_omap_get_next2(gos.iter, &cKey, &cVal, &cKeyLen, &cValLen)
if ret != 0 {
return nil, getError(ret)
}
if cKey == nil {
return nil, nil
}
return &OmapKeyValue{
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
}, nil
}
// More returns true if there are more matching keys available.
func (gos *GetOmapStep) More() bool {
// tad bit hacky, but go can't automatically convert from
// unsigned char to bool
return *gos.more != 0
}
// SetOmap appends the map `pairs` to the omap `oid`
func (ioctx *IOContext) SetOmap(oid string, pairs map[string][]byte) error {
op := CreateWriteOp()
defer op.Release()
op.SetOmap(pairs)
return op.operateCompat(ioctx, oid)
}
// OmapListFunc is the type of the function called for each omap key
// visited by ListOmapValues
type OmapListFunc func(key string, value []byte)
// ListOmapValues iterates over the keys and values in an omap by way of
// a callback function.
//
// `startAfter`: iterate only on the keys after this specified one
// `filterPrefix`: iterate only on the keys beginning with this prefix
// `maxReturn`: iterate no more than `maxReturn` key/value pairs
// `listFn`: the function called at each iteration
func (ioctx *IOContext) ListOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64, listFn OmapListFunc) error {
op := CreateReadOp()
defer op.Release()
gos := op.GetOmapValues(startAfter, filterPrefix, uint64(maxReturn))
err := op.operateCompat(ioctx, oid)
if err != nil {
return err
}
for {
kv, err := gos.Next()
if err != nil {
return err
}
if kv == nil {
break
}
listFn(kv.Key, kv.Value)
}
return nil
}
// GetOmapValues fetches a set of keys and their values from an omap and returns then as a map
// `startAfter`: retrieve only the keys after this specified one
// `filterPrefix`: retrieve only the keys beginning with this prefix
// `maxReturn`: retrieve no more than `maxReturn` key/value pairs
func (ioctx *IOContext) GetOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64) (map[string][]byte, error) {
omap := map[string][]byte{}
err := ioctx.ListOmapValues(
oid, startAfter, filterPrefix, maxReturn,
func(key string, value []byte) {
omap[key] = value
},
)
return omap, err
}
// GetAllOmapValues fetches all the keys and their values from an omap and returns then as a map
// `startAfter`: retrieve only the keys after this specified one
// `filterPrefix`: retrieve only the keys beginning with this prefix
// `iteratorSize`: internal number of keys to fetch during a read operation
func (ioctx *IOContext) GetAllOmapValues(oid string, startAfter string, filterPrefix string, iteratorSize int64) (map[string][]byte, error) {
omap := map[string][]byte{}
omapSize := 0
for {
err := ioctx.ListOmapValues(
oid, startAfter, filterPrefix, iteratorSize,
func(key string, value []byte) {
omap[key] = value
startAfter = key
},
)
if err != nil {
return omap, err
}
// End of omap
if len(omap) == omapSize {
break
}
omapSize = len(omap)
}
return omap, nil
}
// RmOmapKeys removes the specified `keys` from the omap `oid`
func (ioctx *IOContext) RmOmapKeys(oid string, keys []string) error {
op := CreateWriteOp()
defer op.Release()
op.RmOmapKeys(keys)
return op.operateCompat(ioctx, oid)
}
// CleanOmap clears the omap `oid`
func (ioctx *IOContext) CleanOmap(oid string) error {
op := CreateWriteOp()
defer op.Release()
op.CleanOmap()
return op.operateCompat(ioctx, oid)
}
+165
View File
@@ -0,0 +1,165 @@
package rados
// #include <stdlib.h>
import "C"
import (
"fmt"
"strings"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
// The file operation.go exists to support both read op and write op types that
// have some pretty common behaviors between them. In C/C++ its assumed that
// the buffer types and other pointers will not be freed between passing them
// to the action setup calls (things like rados_write_op_write or
// rados_read_op_omap_get_vals2) and the call to Operate(...). Since there's
// nothing stopping one from sleeping for hours between these calls, or passing
// the op to other functions and calling Operate there, we want a mechanism
// that's (fairly) simple to understand and won't run afoul of Go's garbage
// collection. That's one reason the operation type tracks the steps (the
// parts that track complex inputs and outputs) so that as long as the op
// exists it will have a reference to the step, which will have references
// to the C language types.
type opKind string
const (
readOp opKind = "read"
writeOp opKind = "write"
)
// OperationError is an error type that may be returned by an Operate call.
// It captures the error from the operate call itself and any errors from
// steps that can return an error.
type OperationError struct {
kind opKind
OpError error
StepErrors map[int]error
}
func (e OperationError) Error() string {
subErrors := []string{}
if e.OpError != nil {
subErrors = append(subErrors,
fmt.Sprintf("op=%s", e.OpError))
}
for idx, es := range e.StepErrors {
subErrors = append(subErrors,
fmt.Sprintf("Step#%d=%s", idx, es))
}
return fmt.Sprintf(
"%s operation error: %s",
e.kind,
strings.Join(subErrors, ", "))
}
func (e OperationError) Unwrap() []error {
subErrors := make([]error, 0, len(e.StepErrors)+1)
if e.OpError != nil {
subErrors = append(subErrors, e.OpError)
}
for _, es := range e.StepErrors {
subErrors = append(subErrors, es)
}
return subErrors
}
// opStep provides an interface for types that are tied to the management of
// data being input or output from write ops and read ops. The steps are
// meant to simplify the internals of the ops themselves and be exportable when
// appropriate. If a step is not being exported it should not be returned
// from an ops action function. If the step is exported it should be
// returned from an ops action function.
//
// Not all types implementing opStep are expected to need all the functions
// in the interface. However, for the sake of simplicity on the op side, we use
// the same interface for all cases and expect those implementing opStep
// just embed the without* types that provide no-op implementation of
// functions that make up this interface.
type opStep interface {
// update the state of the step after the call to Operate.
// It can be used to convert values from C and cache them and/or
// communicate a failure of the action associated with the step. The
// update call will only be made once. Implementations are not required to
// handle this call being made more than once.
update() error
// free will be called to free any resources, especially C memory, that
// the step is managing. The behavior of free should be idempotent and
// handle being called more than once.
free()
}
// operation represents some of the shared underlying mechanisms for
// both read and write op types.
type operation struct {
steps []opStep
}
// free will call the free method of all the steps this operation
// contains.
func (o *operation) free() {
for i := range o.steps {
o.steps[i].free()
}
}
// update the operation and the steps it contains. The top-level result
// of the rados call is passed in as ret and used to construct errors.
// The update call of each step is used to update the contents of each
// step and gather any errors from those steps.
func (o *operation) update(kind opKind, ret C.int) error {
stepErrors := map[int]error{}
for i := range o.steps {
if err := o.steps[i].update(); err != nil {
stepErrors[i] = err
}
}
if ret == 0 && len(stepErrors) == 0 {
return nil
}
return OperationError{
kind: kind,
OpError: getError(ret),
StepErrors: stepErrors,
}
}
func opStepFinalizer(s opStep) {
if s != nil {
log.Warnf("unreachable opStep object found. Cleaning up.")
s.free()
}
}
// withoutUpdate can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// update function.
type withoutUpdate struct{}
func (*withoutUpdate) update() error { return nil }
// withoutFree can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// free function.
type withoutFree struct{}
func (*withoutFree) free() {}
// withRefs is a embeddable type to help track and free C memory.
type withRefs struct {
refs []unsafe.Pointer
}
func (w *withRefs) free() {
for i := range w.refs {
C.free(w.refs[i])
}
w.refs = nil
}
func (w *withRefs) add(ptr unsafe.Pointer) {
w.refs = append(w.refs, ptr)
}
+37
View File
@@ -0,0 +1,37 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
// OperationFlags control the behavior of read and write operations.
type OperationFlags int
const (
// OperationNoFlag indicates no special behavior is requested.
OperationNoFlag = OperationFlags(C.LIBRADOS_OPERATION_NOFLAG)
// OperationBalanceReads TODO
OperationBalanceReads = OperationFlags(C.LIBRADOS_OPERATION_BALANCE_READS)
// OperationLocalizeReads TODO
OperationLocalizeReads = OperationFlags(C.LIBRADOS_OPERATION_LOCALIZE_READS)
// OperationOrderReadsWrites TODO
OperationOrderReadsWrites = OperationFlags(C.LIBRADOS_OPERATION_ORDER_READS_WRITES)
// OperationIgnoreCache TODO
OperationIgnoreCache = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_CACHE)
// OperationSkipRWLocks TODO
OperationSkipRWLocks = OperationFlags(C.LIBRADOS_OPERATION_SKIPRWLOCKS)
// OperationIgnoreOverlay TODO
OperationIgnoreOverlay = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_OVERLAY)
// OperationFullTry send request to a full cluster or pool, ops such as delete
// can succeed while other ops will return out-of-space errors.
OperationFullTry = OperationFlags(C.LIBRADOS_OPERATION_FULL_TRY)
// OperationFullForce TODO
OperationFullForce = OperationFlags(C.LIBRADOS_OPERATION_FULL_FORCE)
// OperationIgnoreRedirect TODO
OperationIgnoreRedirect = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_REDIRECT)
// OperationOrderSnap TODO
OperationOrderSnap = OperationFlags(C.LIBRADOS_OPERATION_ORDERSNAP)
)
+132
View File
@@ -0,0 +1,132 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"runtime"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
const (
// AllNamespaces is used to reset a selected namespace to all
// namespaces. See the IOContext SetNamespace function.
AllNamespaces = C.LIBRADOS_ALL_NSPACES
// FIXME: for backwards compatibility
// RadosAllNamespaces is used to reset a selected namespace to all
// namespaces. See the IOContext SetNamespace function.
//
// Deprecated: use AllNamespaces instead
RadosAllNamespaces = AllNamespaces
)
// OpFlags are flags that can be set on a per-op basis.
type OpFlags uint
const (
// OpFlagNone can be use to not set any flags.
OpFlagNone = OpFlags(0)
// OpFlagExcl marks an op to fail a create operation if the object
// already exists.
OpFlagExcl = OpFlags(C.LIBRADOS_OP_FLAG_EXCL)
// OpFlagFailOk allows the transaction to succeed even if the flagged
// op fails.
OpFlagFailOk = OpFlags(C.LIBRADOS_OP_FLAG_FAILOK)
// OpFlagFAdviseRandom indicates read/write op random.
OpFlagFAdviseRandom = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_RANDOM)
// OpFlagFAdviseSequential indicates read/write op sequential.
OpFlagFAdviseSequential = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL)
// OpFlagFAdviseWillNeed indicates read/write data will be accessed in
// the near future (by someone).
OpFlagFAdviseWillNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_WILLNEED)
// OpFlagFAdviseDontNeed indicates read/write data will not accessed in
// the near future (by anyone).
OpFlagFAdviseDontNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
// OpFlagFAdviseNoCache indicates read/write data will not accessed
// again (by *this* client).
OpFlagFAdviseNoCache = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_NOCACHE)
)
// Version returns the major, minor, and patch components of the version of
// the RADOS library linked against.
func Version() (int, int, int) {
var cMajor, cMinor, cPatch C.int
C.rados_version(&cMajor, &cMinor, &cPatch)
return int(cMajor), int(cMinor), int(cPatch)
}
func makeConn() *Conn {
return &Conn{connected: false}
}
func newConn(user *C.char) (*Conn, error) {
conn := makeConn()
ret := C.rados_create(&conn.cluster, user)
if ret != 0 {
return nil, getError(ret)
}
runtime.SetFinalizer(conn, freeConn)
return conn, nil
}
// NewConn creates a new connection object. It returns the connection and an
// error, if any.
func NewConn() (*Conn, error) {
return newConn(nil)
}
// NewConnWithUser creates a new connection object with a custom username.
// The supplied username should not include a 'client.' prefix. It returns
// the connection and an error, if any.
func NewConnWithUser(user string) (*Conn, error) {
cUser := C.CString(user)
defer C.free(unsafe.Pointer(cUser))
return newConn(cUser)
}
// NewConnWithClusterAndUser creates a new connection object for a specific
// cluster and username. The supplied username should be in the 'client.<user>'
// format. It returns the connection and an error, if any.
func NewConnWithClusterAndUser(clusterName string, userName string) (*Conn, error) {
cClusterName := C.CString(clusterName)
defer C.free(unsafe.Pointer(cClusterName))
cName := C.CString(userName)
defer C.free(unsafe.Pointer(cName))
conn := makeConn()
ret := C.rados_create2(&conn.cluster, cClusterName, cName, 0)
if ret != 0 {
return nil, getError(ret)
}
runtime.SetFinalizer(conn, freeConn)
return conn, nil
}
// freeConn releases resources that are allocated while configuring the
// connection to the cluster. rados_shutdown() should only be needed after a
// successful call to rados_connect(), however if the connection has been
// configured with non-default parameters, some of the parameters may be
// allocated before connecting. rados_shutdown() will free the allocated
// resources, even if there has not been a connection yet.
//
// This function is setup as a destructor/finalizer when rados_create() is
// called.
func freeConn(conn *Conn) {
if conn.cluster != nil {
log.Warnf("unreachable Conn object has not been shut down. Cleaning up.")
C.rados_shutdown(conn.cluster)
// prevent calling rados_shutdown() more than once
conn.cluster = nil
}
}
+28
View File
@@ -0,0 +1,28 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
)
// GetAddrs returns the addresses of the RADOS session,
// suitable for blocklisting.
//
// Implements:
//
// int rados_getaddrs(rados_t cluster, char **addrs)
func (c *Conn) GetAddrs() (string, error) {
var cAddrs *C.char
defer C.free(unsafe.Pointer(cAddrs))
ret := C.rados_getaddrs(c.cluster, &cAddrs)
if ret < 0 {
return "", getError(ret)
}
return C.GoString(cAddrs), nil
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !mimic
// +build !mimic
package rados
// #include <rados/librados.h>
import "C"
const (
// OpFlagFAdviseFUA optionally support FUA (force unit access) on write
// requests.
OpFlagFAdviseFUA = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_FUA)
)
@@ -0,0 +1,19 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// AssertVersion ensures that the object exists and that its internal version
// number is equal to "ver" before reading. "ver" should be a version number
// previously obtained with IOContext.GetLastVersion().
//
// Implements:
//
// void rados_read_op_assert_version(rados_read_op_t read_op,
// uint64_t ver)
func (r *ReadOp) AssertVersion(ver uint64) {
C.rados_read_op_assert_version(r.op, C.uint64_t(ver))
}
+28
View File
@@ -0,0 +1,28 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetLocator sets the key for mapping objects to pgs within an io context.
// Until a different locator key is set, all objects in this io context will be placed in the same pg.
// To reset the locator, an empty string must be set.
//
// Implements:
//
// void rados_ioctx_locator_set_key(rados_ioctx_t io, const char *key);
func (ioctx *IOContext) SetLocator(locator string) {
if locator == "" {
C.rados_ioctx_locator_set_key(ioctx.ioctx, nil)
} else {
var cLoc *C.char = C.CString(locator)
defer C.free(unsafe.Pointer(cLoc))
C.rados_ioctx_locator_set_key(ioctx.ioctx, cLoc)
}
}
@@ -0,0 +1,19 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// AssertVersion ensures that the object exists and that its internal version
// number is equal to "ver" before writing. "ver" should be a version number
// previously obtained with IOContext.GetLastVersion().
//
// Implements:
//
// void rados_read_op_assert_version(rados_read_op_t read_op,
// uint64_t ver)
func (w *WriteOp) AssertVersion(ver uint64) {
C.rados_write_op_assert_version(w.op, C.uint64_t(ver))
}
+16
View File
@@ -0,0 +1,16 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// Remove object.
//
// Implements:
//
// void rados_write_op_remove(rados_write_op_t write_op)
func (w *WriteOp) Remove() {
C.rados_write_op_remove(w.op)
}
+31
View File
@@ -0,0 +1,31 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetXattr sets an xattr.
//
// Implements:
//
// void rados_write_op_setxattr(rados_write_op_t write_op,
// const char * name,
// const char * value,
// size_t value_len)
func (w *WriteOp) SetXattr(name string, value []byte) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
C.rados_write_op_setxattr(
w.op,
cName,
(*C.char)(unsafe.Pointer(&value[0])),
C.size_t(len(value)),
)
}
+91
View File
@@ -0,0 +1,91 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"unsafe"
)
// ReadOp manages a set of discrete object read actions that will be performed
// together atomically.
type ReadOp struct {
operation
op C.rados_read_op_t
}
// CreateReadOp returns a newly constructed read operation.
func CreateReadOp() *ReadOp {
return &ReadOp{
op: C.rados_create_read_op(),
}
}
// Release the resources associated with this read operation.
func (r *ReadOp) Release() {
C.rados_release_read_op(r.op)
r.op = nil
r.free()
}
// Operate will perform the operation(s).
func (r *ReadOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
if err := ioctx.validate(); err != nil {
return err
}
cOid := C.CString(oid)
defer C.free(unsafe.Pointer(cOid))
ret := C.rados_read_op_operate(r.op, ioctx.ioctx, cOid, C.int(flags))
return r.update(readOp, ret)
}
func (r *ReadOp) operateCompat(ioctx *IOContext, oid string) error {
switch err := r.Operate(ioctx, oid, OperationNoFlag).(type) {
case nil:
return nil
case OperationError:
return err.OpError
default:
return err
}
}
// AssertExists assures the object targeted by the read op exists.
//
// Implements:
//
// void rados_read_op_assert_exists(rados_read_op_t read_op);
func (r *ReadOp) AssertExists() {
C.rados_read_op_assert_exists(r.op)
}
// GetOmapValues is used to iterate over a set, or sub-set, of omap keys
// as part of a read operation. An GetOmapStep is returned from this
// function. The GetOmapStep may be used to iterate over the key-value
// pairs after the Operate call has been performed.
func (r *ReadOp) GetOmapValues(startAfter, filterPrefix string, maxReturn uint64) *GetOmapStep {
gos := newGetOmapStep()
r.steps = append(r.steps, gos)
cStartAfter := C.CString(startAfter)
cFilterPrefix := C.CString(filterPrefix)
defer C.free(unsafe.Pointer(cStartAfter))
defer C.free(unsafe.Pointer(cFilterPrefix))
C.rados_read_op_omap_get_vals2(
r.op,
cStartAfter,
cFilterPrefix,
C.uint64_t(maxReturn),
&gos.iter,
gos.more,
gos.rval,
)
return gos
}
+108
View File
@@ -0,0 +1,108 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"runtime"
"unsafe"
)
// ReadOpExecStep - step for exec operation code.
type ReadOpExecStep struct {
withoutFree
inBuffPtr *C.char
inBuffLen C.size_t
outBuffPtr *C.char
outBuffLen C.size_t
prval C.int
canReadOutput bool
}
// newExecStepOp - init new *execStepOp.
func newReadOpExecStep(in []byte) *ReadOpExecStep {
es := &ReadOpExecStep{
outBuffPtr: nil,
outBuffLen: 0,
prval: 0,
}
if len(in) > 0 {
es.inBuffPtr = (*C.char)(unsafe.Pointer(&in[0]))
es.inBuffLen = C.size_t(len(in))
}
runtime.SetFinalizer(es, func(es *ReadOpExecStep) {
if es != nil {
es.freeBuffer()
es = nil
}
})
return es
}
// freeBuffer - releases C allocated buffer. It is separated from es.free() because lifespan of C allocated buffer is
// longer than lifespan of read operation.
func (es *ReadOpExecStep) freeBuffer() {
if es.outBuffPtr != nil {
C.free(unsafe.Pointer(es.outBuffPtr))
es.outBuffPtr = nil
es.canReadOutput = false
}
}
// update - update state operation.
func (es *ReadOpExecStep) update() error {
err := getError(es.prval)
es.canReadOutput = err == nil
return err
}
// Bytes returns the result of the executed command as a byte slice.
func (es *ReadOpExecStep) Bytes() ([]byte, error) {
if !es.canReadOutput {
return nil, ErrOperationIncomplete
}
return C.GoBytes(unsafe.Pointer(es.outBuffPtr), C.int(es.outBuffLen)), nil
}
// Exec executes an OSD class method on an object.
// See rados_exec() in the RADOS C api documentation for a general description.
//
// Implements:
//
// void rados_read_op_exec(rados_read_op_t read_op,
// const char *cls,
// const char *method,
// const char *in_buf,
// size_t in_len,
// char **out_buf,
// size_t *out_len,
// int *prval);
func (r *ReadOp) Exec(clsName, method string, in []byte) *ReadOpExecStep {
cClsName := C.CString(clsName)
defer C.free(unsafe.Pointer(cClsName))
cMethod := C.CString(method)
defer C.free(unsafe.Pointer(cMethod))
es := newReadOpExecStep(in)
r.steps = append(r.steps, es)
C.rados_read_op_exec(
r.op,
cClsName,
cMethod,
es.inBuffPtr,
es.inBuffLen,
&es.outBuffPtr,
&es.outBuffLen,
&es.prval,
)
return es
}
@@ -0,0 +1,113 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
// ReadOpOmapGetValsByKeysStep holds the result of the
// GetOmapValuesByKeys read operation.
// Result is valid only after Operate() was called.
type ReadOpOmapGetValsByKeysStep struct {
// C arguments
iter C.rados_omap_iter_t
prval *C.int
// Internal state
// canIterate is only set after the operation is performed and is
// intended to prevent premature fetching of data.
canIterate bool
}
func newReadOpOmapGetValsByKeysStep() *ReadOpOmapGetValsByKeysStep {
s := &ReadOpOmapGetValsByKeysStep{
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
return s
}
func (s *ReadOpOmapGetValsByKeysStep) free() {
s.canIterate = false
C.rados_omap_get_end(s.iter)
C.free(unsafe.Pointer(s.prval))
s.prval = nil
}
func (s *ReadOpOmapGetValsByKeysStep) update() error {
err := getError(*s.prval)
s.canIterate = (err == nil)
return err
}
// Next gets the next omap key/value pair referenced by
// ReadOpOmapGetValsByKeysStep's internal iterator.
// If there are no more elements to retrieve, (nil, nil) is returned.
// May be called only after Operate() finished.
func (s *ReadOpOmapGetValsByKeysStep) Next() (*OmapKeyValue, error) {
if !s.canIterate {
return nil, ErrOperationIncomplete
}
var (
cKey *C.char
cVal *C.char
cKeyLen C.size_t
cValLen C.size_t
)
ret := C.rados_omap_get_next2(s.iter, &cKey, &cVal, &cKeyLen, &cValLen)
if ret != 0 {
return nil, getError(ret)
}
if cKey == nil {
// Iterator has reached the end of the list.
return nil, nil
}
return &OmapKeyValue{
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
}, nil
}
// GetOmapValuesByKeys starts iterating over specific key/value pairs.
//
// Implements:
//
// void rados_read_op_omap_get_vals_by_keys2(rados_read_op_t read_op,
// char const * const * keys,
// size_t num_keys,
// const size_t * key_lens,
// rados_omap_iter_t * iter,
// int * prval)
func (r *ReadOp) GetOmapValuesByKeys(keys []string) *ReadOpOmapGetValsByKeysStep {
s := newReadOpOmapGetValsByKeysStep()
r.steps = append(r.steps, s)
cKeys := cutil.NewBufferGroupStrings(keys)
defer cKeys.Free()
C.rados_read_op_omap_get_vals_by_keys2(
r.op,
(**C.char)(cKeys.BuffersPtr()),
C.size_t(len(keys)),
(*C.size_t)(cKeys.LengthsPtr()),
&s.iter,
s.prval,
)
return s
}
+72
View File
@@ -0,0 +1,72 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// ReadOpReadStep holds the result of the Read read operation.
// Result is valid only after Operate() was called.
type ReadOpReadStep struct {
// C returned data:
bytesRead *C.size_t
prval *C.int
BytesRead int64 // Bytes read by this action.
Result int // Result of this action.
}
func (s *ReadOpReadStep) update() error {
s.BytesRead = (int64)(*s.bytesRead)
s.Result = (int)(*s.prval)
return nil
}
func (s *ReadOpReadStep) free() {
C.free(unsafe.Pointer(s.bytesRead))
C.free(unsafe.Pointer(s.prval))
s.bytesRead = nil
s.prval = nil
}
func newReadOpReadStep() *ReadOpReadStep {
return &ReadOpReadStep{
bytesRead: (*C.size_t)(C.malloc(C.sizeof_size_t)),
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
}
// Read bytes from offset into buffer.
// len(buffer) is the maximum number of bytes read from the object.
// buffer[:ReadOpReadStep.BytesRead] then contains object data.
//
// Implements:
//
// void rados_read_op_read(rados_read_op_t read_op,
// uint64_t offset,
// size_t len,
// char * buffer,
// size_t * bytes_read,
// int * prval)
func (r *ReadOp) Read(offset uint64, buffer []byte) *ReadOpReadStep {
oe := newReadStep(buffer, offset)
readStep := newReadOpReadStep()
r.steps = append(r.steps, oe, readStep)
C.rados_read_op_read(
r.op,
oe.cOffset,
oe.cReadLen,
oe.cBuffer,
readStep.bytesRead,
readStep.prval,
)
return readStep
}
+31
View File
@@ -0,0 +1,31 @@
package rados
// #include <stdint.h>
import "C"
import (
"unsafe"
)
type readStep struct {
withoutUpdate
withoutFree
// the c pointer utilizes the Go byteslice data and no free is needed
// inputs:
b []byte
// arguments:
cBuffer *C.char
cReadLen C.size_t
cOffset C.uint64_t
}
func newReadStep(b []byte, offset uint64) *readStep {
return &readStep{
b: b,
cBuffer: (*C.char)(unsafe.Pointer(&b[0])), // TODO: must be pinned
cReadLen: C.size_t(len(b)),
cOffset: C.uint64_t(offset),
}
}
+196
View File
@@ -0,0 +1,196 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/retry"
)
// CreateSnap creates a pool-wide snapshot.
//
// Implements:
// int rados_ioctx_snap_create(rados_ioctx_t io, const char *snapname)
func (ioctx *IOContext) CreateSnap(snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_create(ioctx.ioctx, cSnapName)
return getError(ret)
}
// RemoveSnap deletes the pool snapshot.
//
// Implements:
//
// int rados_ioctx_snap_remove(rados_ioctx_t io, const char *snapname)
func (ioctx *IOContext) RemoveSnap(snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_remove(ioctx.ioctx, cSnapName)
return getError(ret)
}
// SnapID represents the ID of a rados snapshot.
type SnapID C.rados_snap_t
// LookupSnap returns the ID of a pool snapshot.
//
// Implements:
//
// int rados_ioctx_snap_lookup(rados_ioctx_t io, const char *name, rados_snap_t *id)
func (ioctx *IOContext) LookupSnap(snapName string) (SnapID, error) {
var snapID SnapID
if err := ioctx.validate(); err != nil {
return snapID, err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_lookup(
ioctx.ioctx,
cSnapName,
(*C.rados_snap_t)(&snapID))
return snapID, getError(ret)
}
// GetSnapName returns the name of a pool snapshot with the given snapshot ID.
//
// Implements:
//
// int rados_ioctx_snap_get_name(rados_ioctx_t io, rados_snap_t id, char *name, int maxlen)
func (ioctx *IOContext) GetSnapName(snapID SnapID) (string, error) {
if err := ioctx.validate(); err != nil {
return "", err
}
var (
buf []byte
err error
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(length int) retry.Hint {
cLen := C.int(length)
buf = make([]byte, cLen)
ret := C.rados_ioctx_snap_get_name(
ioctx.ioctx,
(C.rados_snap_t)(snapID),
(*C.char)(unsafe.Pointer(&buf[0])),
cLen)
err = getError(ret)
return retry.Size(int(cLen)).If(err == errRange)
})
if err != nil {
return "", err
}
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
}
// GetSnapStamp returns the time of the pool snapshot creation.
//
// Implements:
//
// int rados_ioctx_snap_get_stamp(rados_ioctx_t io, rados_snap_t id, time_t *t)
func (ioctx *IOContext) GetSnapStamp(snapID SnapID) (time.Time, error) {
var cTime C.time_t
if err := ioctx.validate(); err != nil {
return time.Unix(int64(cTime), 0), err
}
ret := C.rados_ioctx_snap_get_stamp(
ioctx.ioctx,
(C.rados_snap_t)(snapID),
&cTime)
return time.Unix(int64(cTime), 0), getError(ret)
}
// ListSnaps returns a slice containing the SnapIDs of existing pool snapshots.
//
// Implements:
//
// int rados_ioctx_snap_list(rados_ioctx_t io, rados_snap_t *snaps, int maxlen)
func (ioctx *IOContext) ListSnaps() ([]SnapID, error) {
if err := ioctx.validate(); err != nil {
return nil, err
}
var (
snapList []SnapID
cLen C.int
err error
ret C.int
)
retry.WithSizes(100, 1000, func(maxlen int) retry.Hint {
cLen = C.int(maxlen)
snapList = make([]SnapID, cLen)
ret = C.rados_ioctx_snap_list(
ioctx.ioctx,
(*C.rados_snap_t)(unsafe.Pointer(&snapList[0])),
cLen)
err = getErrorIfNegative(ret)
return retry.Size(int(cLen)).If(err == errRange)
})
if err != nil {
return nil, err
}
return snapList[:ret], nil
}
// RollbackSnap rollbacks the object with key oID to the pool snapshot.
// The contents of the object will be the same as when the snapshot was taken.
//
// Implements:
//
// int rados_ioctx_snap_rollback(rados_ioctx_t io, const char *oid, const char *snapname);
func (ioctx *IOContext) RollbackSnap(oid, snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_rollback(ioctx.ioctx, coid, cSnapName)
return getError(ret)
}
// SnapHead is the representation of LIBRADOS_SNAP_HEAD from librados.
// SnapHead can be used to reset the IOContext to stop reading from a snapshot.
const SnapHead = SnapID(C.LIBRADOS_SNAP_HEAD)
// SetReadSnap sets the snapshot from which reads are performed.
// Subsequent reads will return data as it was at the time of that snapshot.
// Pass SnapHead for no snapshot (i.e. normal operation).
//
// Implements:
//
// void rados_ioctx_snap_set_read(rados_ioctx_t io, rados_snap_t snap);
func (ioctx *IOContext) SetReadSnap(snapID SnapID) error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_ioctx_snap_set_read(ioctx.ioctx, (C.rados_snap_t)(snapID))
return nil
}
+375
View File
@@ -0,0 +1,375 @@
package rados
/*
#cgo LDFLAGS: -lrados
#include <stdlib.h>
#include <rados/librados.h>
extern void watchNotifyCb(void*, uint64_t, uint64_t, uint64_t, void*, size_t);
extern void watchErrorCb(void*, uint64_t, int);
*/
import "C"
import (
"encoding/binary"
"fmt"
"math"
"sync"
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
type (
// WatcherID is the unique id of a Watcher.
WatcherID uint64
// NotifyID is the unique id of a NotifyEvent.
NotifyID uint64
// NotifierID is the unique id of a notifying client.
NotifierID uint64
)
// NotifyEvent is received by a watcher for each notification.
type NotifyEvent struct {
ID NotifyID
WatcherID WatcherID
NotifierID NotifierID
Data []byte
}
// NotifyAck represents an acknowleged notification.
type NotifyAck struct {
WatcherID WatcherID
NotifierID NotifierID
Response []byte
}
// NotifyTimeout represents an unacknowleged notification.
type NotifyTimeout struct {
WatcherID WatcherID
NotifierID NotifierID
}
// Watcher receives all notifications for certain object.
type Watcher struct {
id WatcherID
oid string
ioctx *IOContext
events chan NotifyEvent
errors chan error
done chan struct{}
}
var (
watchers = map[WatcherID]*Watcher{}
watchersMtx sync.RWMutex
)
// Watch creates a Watcher for the specified object.
//
// A Watcher receives all notifications that are sent to the object on which it
// has been created. It exposes two read-only channels: Events() receives all
// the NotifyEvents and Errors() receives all occuring errors. A typical code
// creating a Watcher could look like this:
//
// watcher, err := ioctx.Watch(oid)
// go func() { // event handler
// for ne := range watcher.Events() {
// ...
// ne.Ack([]byte("response data..."))
// ...
// }
// }()
// go func() { // error handler
// for err := range watcher.Errors() {
// ... handle err ...
// }
// }()
//
// CAUTION: the Watcher references the IOContext in which it has been created.
// Therefore all watchers must be deleted with the Delete() method before the
// IOContext is being destroyed.
//
// Implements:
//
// int rados_watch2(rados_ioctx_t io, const char* o, uint64_t* cookie,
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, void* arg)
func (ioctx *IOContext) Watch(obj string) (*Watcher, error) {
return ioctx.WatchWithTimeout(obj, 0)
}
// WatchWithTimeout creates a watcher on an object. Same as Watcher(), but
// different timeout than the default can be specified.
//
// Implements:
//
// int rados_watch3(rados_ioctx_t io, const char *o, uint64_t *cookie,
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, uint32_t timeout,
// void *arg);
func (ioctx *IOContext) WatchWithTimeout(oid string, timeout time.Duration) (*Watcher, error) {
cObj := C.CString(oid)
defer C.free(unsafe.Pointer(cObj))
var id C.uint64_t
watchersMtx.Lock()
defer watchersMtx.Unlock()
ret := C.rados_watch3(
ioctx.ioctx,
cObj,
&id,
(C.rados_watchcb2_t)(C.watchNotifyCb),
(C.rados_watcherrcb_t)(C.watchErrorCb),
C.uint32_t(timeout.Milliseconds()/1000),
nil,
)
if err := getError(ret); err != nil {
return nil, err
}
evCh := make(chan NotifyEvent)
errCh := make(chan error)
w := &Watcher{
id: WatcherID(id),
ioctx: ioctx,
oid: oid,
events: evCh,
errors: errCh,
done: make(chan struct{}),
}
watchers[WatcherID(id)] = w
return w, nil
}
// ID returns the WatcherId of the Watcher
func (w *Watcher) ID() WatcherID {
return w.id
}
// Events returns a read-only channel, that receives all notifications that are
// sent to the object of the Watcher.
func (w *Watcher) Events() <-chan NotifyEvent {
return w.events
}
// Errors returns a read-only channel, that receives all errors for the Watcher.
func (w *Watcher) Errors() <-chan error {
return w.errors
}
// Check on the status of a Watcher.
//
// Returns the time since it was last confirmed. If there is an error, the
// Watcher is no longer valid, and should be destroyed with the Delete() method.
//
// Implements:
//
// int rados_watch_check(rados_ioctx_t io, uint64_t cookie)
func (w *Watcher) Check() (time.Duration, error) {
ret := C.rados_watch_check(w.ioctx.ioctx, C.uint64_t(w.id))
if ret < 0 {
return 0, getError(ret)
}
return time.Millisecond * time.Duration(ret), nil
}
// Delete the watcher. This closes both the event and error channel.
//
// Implements:
//
// int rados_unwatch2(rados_ioctx_t io, uint64_t cookie)
func (w *Watcher) Delete() error {
watchersMtx.Lock()
_, ok := watchers[w.id]
if ok {
delete(watchers, w.id)
}
watchersMtx.Unlock()
if !ok {
return nil
}
ret := C.rados_unwatch2(w.ioctx.ioctx, C.uint64_t(w.id))
if ret != 0 {
return getError(ret)
}
close(w.done) // unblock blocked callbacks
close(w.events)
close(w.errors)
return nil
}
// Notify sends a notification with the provided data to all Watchers of the
// specified object.
//
// CAUTION: even if the error is not nil. the returned slices
// might still contain data.
func (ioctx *IOContext) Notify(obj string, data []byte) ([]NotifyAck, []NotifyTimeout, error) {
return ioctx.NotifyWithTimeout(obj, data, 0)
}
// NotifyWithTimeout is like Notify() but with a different timeout than the
// default.
//
// Implements:
//
// int rados_notify2(rados_ioctx_t io, const char* o, const char* buf, int buf_len,
// uint64_t timeout_ms, char** reply_buffer, size_t* reply_buffer_len)
func (ioctx *IOContext) NotifyWithTimeout(obj string, data []byte, timeout time.Duration) ([]NotifyAck,
[]NotifyTimeout, error) {
cObj := C.CString(obj)
defer C.free(unsafe.Pointer(cObj))
var cResponse *C.char
defer C.rados_buffer_free(cResponse)
var responseLen C.size_t
var dataPtr *C.char
if len(data) > 0 {
dataPtr = (*C.char)(unsafe.Pointer(&data[0]))
}
ret := C.rados_notify2(
ioctx.ioctx,
cObj,
dataPtr,
C.int(len(data)),
C.uint64_t(timeout.Milliseconds()),
&cResponse,
&responseLen,
)
// cResponse has been set even if an error is returned, so we decode it anyway
acks, timeouts := decodeNotifyResponse(cResponse, responseLen)
return acks, timeouts, getError(ret)
}
// Ack sends an acknowledgement with the specified response data to the notfier
// of the NotifyEvent. If a notify is not ack'ed, the originating Notify() call
// blocks and eventiually times out.
//
// Implements:
//
// int rados_notify_ack(rados_ioctx_t io, const char *o, uint64_t notify_id,
// uint64_t cookie, const char *buf, int buf_len)
func (ne *NotifyEvent) Ack(response []byte) error {
watchersMtx.RLock()
w, ok := watchers[ne.WatcherID]
watchersMtx.RUnlock()
if !ok {
return fmt.Errorf("can't ack on deleted watcher %v", ne.WatcherID)
}
cOID := C.CString(w.oid)
defer C.free(unsafe.Pointer(cOID))
var respPtr *C.char
if len(response) > 0 {
respPtr = (*C.char)(unsafe.Pointer(&response[0]))
}
ret := C.rados_notify_ack(
w.ioctx.ioctx,
cOID,
C.uint64_t(ne.ID),
C.uint64_t(ne.WatcherID),
respPtr,
C.int(len(response)),
)
return getError(ret)
}
// WatcherFlush flushes all pending notifications of the cluster.
//
// Implements:
//
// int rados_watch_flush(rados_t cluster)
func (c *Conn) WatcherFlush() error {
if !c.connected {
return ErrNotConnected
}
ret := C.rados_watch_flush(c.cluster)
return getError(ret)
}
// decoder for this notify response format:
//
// le32 num_acks
// {
// le64 gid global id for the client (for client.1234 that's 1234)
// le64 cookie cookie for the client
// le32 buflen length of reply message buffer
// u8 buflen payload
// } num_acks
// le32 num_timeouts
// {
// le64 gid global id for the client
// le64 cookie cookie for the client
// } num_timeouts
//
// NOTE: starting with pacific this is implemented as a C function and this can
// be replaced later
func decodeNotifyResponse(response *C.char, length C.size_t) ([]NotifyAck, []NotifyTimeout) {
if length == 0 || response == nil {
return nil, nil
}
b := (*[math.MaxInt32]byte)(unsafe.Pointer(response))[:length:length]
pos := 0
num := binary.LittleEndian.Uint32(b[pos:])
pos += 4
acks := make([]NotifyAck, num)
for i := range acks {
acks[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
acks[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
dataLen := binary.LittleEndian.Uint32(b[pos:])
pos += 4
if dataLen > 0 {
acks[i].Response = C.GoBytes(unsafe.Pointer(&b[pos]), C.int(dataLen))
pos += int(dataLen)
}
}
num = binary.LittleEndian.Uint32(b[pos:])
pos += 4
timeouts := make([]NotifyTimeout, num)
for i := range timeouts {
timeouts[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
timeouts[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
}
return acks, timeouts
}
//export watchNotifyCb
func watchNotifyCb(_ unsafe.Pointer, notifyID C.uint64_t, id C.uint64_t,
notifierID C.uint64_t, cData unsafe.Pointer, dataLen C.size_t) {
ev := NotifyEvent{
ID: NotifyID(notifyID),
WatcherID: WatcherID(id),
NotifierID: NotifierID(notifierID),
}
if dataLen > 0 {
ev.Data = C.GoBytes(cData, C.int(dataLen))
}
watchersMtx.RLock()
w, ok := watchers[WatcherID(id)]
watchersMtx.RUnlock()
if !ok {
// usually this should not happen, but who knows
log.Warnf("received notification for unknown watcher ID: %#v", ev)
return
}
select {
case <-w.done: // unblock when deleted
case w.events <- ev:
}
}
//export watchErrorCb
func watchErrorCb(_ unsafe.Pointer, id C.uint64_t, err C.int) {
watchersMtx.RLock()
w, ok := watchers[WatcherID(id)]
watchersMtx.RUnlock()
if !ok {
// usually this should not happen, but who knows
log.Warnf("received error for unknown watcher ID: id=%d err=%#v", id, err)
return
}
select {
case <-w.done: // unblock when deleted
case w.errors <- getError(err):
}
}
+199
View File
@@ -0,0 +1,199 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
ts "github.com/ceph/go-ceph/internal/timespec"
)
// Timespec is a public type for the internal C 'struct timespec'
type Timespec ts.Timespec
// WriteOp manages a set of discrete actions that will be performed together
// atomically.
type WriteOp struct {
operation
op C.rados_write_op_t
}
// CreateWriteOp returns a newly constructed write operation.
func CreateWriteOp() *WriteOp {
return &WriteOp{
op: C.rados_create_write_op(),
}
}
// Release the resources associated with this write operation.
func (w *WriteOp) Release() {
C.rados_release_write_op(w.op)
w.op = nil
w.free()
}
func (w WriteOp) operate2(
ioctx *IOContext, oid string, mtime *Timespec, flags OperationFlags) error {
if err := ioctx.validate(); err != nil {
return err
}
cOid := C.CString(oid)
defer C.free(unsafe.Pointer(cOid))
var cMtime *C.struct_timespec
if mtime != nil {
cMtime = &C.struct_timespec{}
ts.CopyToCStruct(
ts.Timespec(*mtime),
ts.CTimespecPtr(cMtime))
}
ret := C.rados_write_op_operate2(
w.op, ioctx.ioctx, cOid, cMtime, C.int(flags))
return w.update(writeOp, ret)
}
// Operate will perform the operation(s).
func (w *WriteOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
return w.operate2(ioctx, oid, nil, flags)
}
// OperateWithMtime will perform the operation while setting the modification
// time stamp to the supplied value.
func (w *WriteOp) OperateWithMtime(
ioctx *IOContext, oid string, mtime Timespec, flags OperationFlags) error {
return w.operate2(ioctx, oid, &mtime, flags)
}
func (w *WriteOp) operateCompat(ioctx *IOContext, oid string) error {
switch err := w.Operate(ioctx, oid, OperationNoFlag).(type) {
case nil:
return nil
case OperationError:
return err.OpError
default:
return err
}
}
// Create a rados object.
func (w *WriteOp) Create(exclusive CreateOption) {
// category, the 3rd param, is deprecated and has no effect so we do not
// implement it in go-ceph
C.rados_write_op_create(w.op, C.int(exclusive), nil)
}
// SetOmap appends the map `pairs` to the omap `oid`.
func (w *WriteOp) SetOmap(pairs map[string][]byte) {
keys := make([]string, len(pairs))
values := make([][]byte, len(pairs))
idx := 0
for k, v := range pairs {
keys[idx] = k
values[idx] = v
idx++
}
cKeys := cutil.NewBufferGroupStrings(keys)
cValues := cutil.NewBufferGroupBytes(values)
defer cKeys.Free()
defer cValues.Free()
C.rados_write_op_omap_set2(
w.op,
(**C.char)(cKeys.BuffersPtr()),
(**C.char)(cValues.BuffersPtr()),
(*C.size_t)(cKeys.LengthsPtr()),
(*C.size_t)(cValues.LengthsPtr()),
(C.size_t)(len(pairs)))
}
// RmOmapKeys removes the specified `keys` from the omap `oid`.
func (w *WriteOp) RmOmapKeys(keys []string) {
cKeys := cutil.NewBufferGroupStrings(keys)
defer cKeys.Free()
C.rados_write_op_omap_rm_keys2(
w.op,
(**C.char)(cKeys.BuffersPtr()),
(*C.size_t)(cKeys.LengthsPtr()),
(C.size_t)(len(keys)))
}
// CleanOmap clears the omap `oid`.
func (w *WriteOp) CleanOmap() {
C.rados_write_op_omap_clear(w.op)
}
// AssertExists assures the object targeted by the write op exists.
//
// Implements:
//
// void rados_write_op_assert_exists(rados_write_op_t write_op);
func (w *WriteOp) AssertExists() {
C.rados_write_op_assert_exists(w.op)
}
// Write a given byte slice at the supplied offset.
//
// Implements:
//
// void rados_write_op_write(rados_write_op_t write_op,
// const char *buffer,
// size_t len,
// uint64_t offset);
func (w *WriteOp) Write(b []byte, offset uint64) {
oe := newWriteStep(b, 0, offset)
w.steps = append(w.steps, oe)
C.rados_write_op_write(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cOffset)
}
// WriteFull writes a given byte slice as the whole object,
// atomically replacing it.
//
// Implements:
//
// void rados_write_op_write_full(rados_write_op_t write_op,
// const char *buffer,
// size_t len);
func (w *WriteOp) WriteFull(b []byte) {
oe := newWriteStep(b, 0, 0)
w.steps = append(w.steps, oe)
C.rados_write_op_write_full(
w.op,
oe.cBuffer,
oe.cDataLen)
}
// WriteSame write a given byte slice to the object multiple times, until
// writeLen is satisfied.
//
// Implements:
//
// void rados_write_op_writesame(rados_write_op_t write_op,
// const char *buffer,
// size_t data_len,
// size_t write_len,
// uint64_t offset);
func (w *WriteOp) WriteSame(b []byte, writeLen, offset uint64) {
oe := newWriteStep(b, writeLen, offset)
w.steps = append(w.steps, oe)
C.rados_write_op_writesame(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cWriteLen,
oe.cOffset)
}
+60
View File
@@ -0,0 +1,60 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// WriteOpCmpExtStep holds result of the CmpExt write operation.
// Result is valid only after Operate() was called.
type WriteOpCmpExtStep struct {
// C returned data:
prval *C.int
// Result of the CmpExt write operation.
Result int
}
func (s *WriteOpCmpExtStep) update() error {
s.Result = int(*s.prval)
return nil
}
func (s *WriteOpCmpExtStep) free() {
C.free(unsafe.Pointer(s.prval))
s.prval = nil
}
func newWriteOpCmpExtStep() *WriteOpCmpExtStep {
return &WriteOpCmpExtStep{
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
}
// CmpExt ensures that given object range (extent) satisfies comparison.
//
// Implements:
//
// void rados_write_op_cmpext(rados_write_op_t write_op,
// const char * cmp_buf,
// size_t cmp_len,
// uint64_t off,
// int * prval);
func (w *WriteOp) CmpExt(b []byte, offset uint64) *WriteOpCmpExtStep {
oe := newWriteStep(b, 0, offset)
cmpExtStep := newWriteOpCmpExtStep()
w.steps = append(w.steps, oe, cmpExtStep)
C.rados_write_op_cmpext(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cOffset,
cmpExtStep.prval)
return cmpExtStep
}

Some files were not shown because too many files have changed in this diff Show More