switch to go vendoring
This commit is contained in:
+21
@@ -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
@@ -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
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const notProtectedSuffix = "is not protected"
|
||||
|
||||
// NotProtectedError error values will be returned by CloneSubVolumeSnapshot in
|
||||
// the case that the source snapshot needs to be protected but is not. The
|
||||
// requirement for a snapshot to be protected prior to cloning varies by Ceph
|
||||
// version.
|
||||
type NotProtectedError struct {
|
||||
response
|
||||
}
|
||||
|
||||
// CloneOptions are used to specify optional values to be used when creating a
|
||||
// new subvolume clone.
|
||||
type CloneOptions struct {
|
||||
TargetGroup string
|
||||
PoolLayout string
|
||||
}
|
||||
|
||||
// CloneSubVolumeSnapshot clones the specified snapshot from the subvolume.
|
||||
// The group, subvolume, and snapshot parameters specify the source for the
|
||||
// clone, and only the source. Additional properties of the clone, such as the
|
||||
// subvolume group that the clone will be created in and the pool layout may be
|
||||
// specified using the clone options parameter.
|
||||
//
|
||||
// Similar To:
|
||||
// ceph fs subvolume snapshot clone <volume> --group_name=<group> <subvolume> <snapshot> <name> [...]
|
||||
func (fsa *FSAdmin) CloneSubVolumeSnapshot(volume, group, subvolume, snapshot, name string, o *CloneOptions) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot clone",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapshot,
|
||||
"target_sub_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
if o != nil && o.TargetGroup != NoGroup {
|
||||
m["target_group_name"] = group
|
||||
}
|
||||
if o != nil && o.PoolLayout != "" {
|
||||
m["pool_layout"] = o.PoolLayout
|
||||
}
|
||||
return checkCloneResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
func checkCloneResponse(res response) error {
|
||||
if strings.HasSuffix(res.Status(), notProtectedSuffix) {
|
||||
return NotProtectedError{response: res}
|
||||
}
|
||||
return res.NoData().End()
|
||||
}
|
||||
|
||||
// CloneState is used to define constant values used to determine the state of
|
||||
// a clone.
|
||||
type CloneState string
|
||||
|
||||
const (
|
||||
// ClonePending is the state of a pending clone.
|
||||
ClonePending = CloneState("pending")
|
||||
// CloneInProgress is the state of a clone in progress.
|
||||
CloneInProgress = CloneState("in-progress")
|
||||
// CloneComplete is the state of a complete clone.
|
||||
CloneComplete = CloneState("complete")
|
||||
// CloneFailed is the state of a failed clone.
|
||||
CloneFailed = CloneState("failed")
|
||||
)
|
||||
|
||||
// CloneSource contains values indicating the source of a clone.
|
||||
type CloneSource struct {
|
||||
Volume string `json:"volume"`
|
||||
Group string `json:"group"`
|
||||
SubVolume string `json:"subvolume"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
}
|
||||
|
||||
// CloneStatus reports on the status of a subvolume clone.
|
||||
type CloneStatus struct {
|
||||
State CloneState `json:"state"`
|
||||
Source CloneSource `json:"source"`
|
||||
|
||||
// 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()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
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
@@ -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
@@ -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
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
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
|
||||
}
|
||||
|
||||
// New creates an FSAdmin automatically based on the default ceph
|
||||
// configuration file. If more customization is needed, create a
|
||||
// *rados.Conn as you see fit and use NewFromConn to use that
|
||||
// connection with these administrative functions.
|
||||
func New() (*FSAdmin, error) {
|
||||
conn, err := rados.NewConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.ReadDefaultConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.Connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFromConn(conn), nil
|
||||
}
|
||||
|
||||
// NewFromConn creates an FSAdmin management object from a preexisting
|
||||
// rados connection. The existing connection can be rados.Conn or any
|
||||
// type implementing the RadosCommander interface. This may be useful
|
||||
// if the calling layer needs to inject additional logging, error handling,
|
||||
// fault injection, etc.
|
||||
func NewFromConn(conn RadosCommander) *FSAdmin {
|
||||
return &FSAdmin{conn}
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) validate() error {
|
||||
if fsa.conn == nil {
|
||||
return rados.ErrNotConnected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rawMgrCommand takes a byte buffer and sends it to the MGR as a command.
|
||||
// The buffer is expected to contain preformatted JSON.
|
||||
func (fsa *FSAdmin) rawMgrCommand(buf []byte) response {
|
||||
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)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
//go:build !(nautilus || octopus) && ceph_preview && ceph_pre_quincy
|
||||
// +build !nautilus,!octopus,ceph_preview,ceph_pre_quincy
|
||||
|
||||
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))
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
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)
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
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()
|
||||
}
|
||||
*/
|
||||
+22
@@ -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)
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
//go:build !(nautilus || octopus) && ceph_preview && ceph_pre_quincy
|
||||
// +build !nautilus,!octopus,ceph_preview,ceph_pre_quincy
|
||||
|
||||
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))
|
||||
}
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
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()
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
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
@@ -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
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
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
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
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
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
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.
|
||||
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.
|
||||
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)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
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 {
|
||||
return getError(C.ceph_closedir(dir.mount.mount, dir.dir))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
|
||||
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() {
|
||||
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
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package cephfs contains a set of wrappers around Ceph's libcephfs API.
|
||||
*/
|
||||
package cephfs
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/errutil"
|
||||
)
|
||||
|
||||
// cephFSError represents an error condition returned from the CephFS APIs.
|
||||
type cephFSError int
|
||||
|
||||
// Error returns the error string for the cephFSError type.
|
||||
func (e cephFSError) Error() string {
|
||||
return errutil.FormatErrorCode("cephfs", int(e))
|
||||
}
|
||||
|
||||
func (e cephFSError) ErrorCode() int {
|
||||
return int(e)
|
||||
}
|
||||
|
||||
func getError(e C.int) error {
|
||||
if e == 0 {
|
||||
return nil
|
||||
}
|
||||
return cephFSError(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")
|
||||
)
|
||||
|
||||
// Public CephFSErrors:
|
||||
|
||||
const (
|
||||
// ErrNotConnected may be returned when client is not connected
|
||||
// to a cluster.
|
||||
ErrNotConnected = cephFSError(-C.ENOTCONN)
|
||||
)
|
||||
|
||||
// Private errors:
|
||||
|
||||
const (
|
||||
errInvalid = cephFSError(-C.EINVAL)
|
||||
errNameTooLong = cephFSError(-C.ENAMETOOLONG)
|
||||
errNoEntry = cephFSError(-C.ENOENT)
|
||||
errRange = cephFSError(-C.ERANGE)
|
||||
)
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
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>
|
||||
*/
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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, int uid, int gid);
|
||||
func (f *File) Fchown(user uint32, group uint32) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := C.ceph_fchown(f.mount.mount, f.fd, C.int(user), C.int(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)
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
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)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// 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))
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
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)
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
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)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
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.ceph_chown(mount.mount, cPath, C.int(user), C.int(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.ceph_lchown(mount.mount, cPath, C.int(user), C.int(group))
|
||||
return getError(ret)
|
||||
}
|
||||
+74
@@ -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"
|
||||
)
|
||||
|
||||
// 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
@@ -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
|
||||
}
|
||||
*/
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
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
@@ -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
@@ -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
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
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
@@ -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
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// RadosCommander provides an interface for APIs needed to execute JSON
|
||||
// formatted commands on the Ceph cluster.
|
||||
type RadosCommander interface {
|
||||
MgrCommander
|
||||
MonCommander
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
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)
|
||||
}
|
||||
+193
@@ -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}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
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.RadosCommander) ccom.RadosCommander {
|
||||
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.RadosCommander
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+66
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package cutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go
|
||||
// runtime) stored in C memory (allocated by C)
|
||||
type PtrGuard struct {
|
||||
// These mutexes will be used as binary semaphores for signalling events from
|
||||
// one thread to another, which - in contrast to other languages like C++ - is
|
||||
// possible in Go, that is a Mutex can be locked in one thread and unlocked in
|
||||
// another.
|
||||
stored, release sync.Mutex
|
||||
released bool
|
||||
}
|
||||
|
||||
// WARNING: using binary semaphores (mutexes) for signalling like this is quite
|
||||
// a delicate task in order to avoid deadlocks or panics. Whenever changing the
|
||||
// code logic, please review at least three times that there is no unexpected
|
||||
// state possible. Usually the natural choice would be to use channels instead,
|
||||
// but these can not easily passed to C code because of the pointer-to-pointer
|
||||
// cgo rule, and would require the use of a Go object registry.
|
||||
|
||||
// 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
|
||||
// Since the mutexes are used for signalling, they have to be initialized to
|
||||
// locked state, so that following lock attempts will block.
|
||||
v.release.Lock()
|
||||
v.stored.Lock()
|
||||
// Start a background go routine that lives until Release is called. This
|
||||
// calls a special function that makes sure the garbage collector doesn't touch
|
||||
// goPtr, stores it into C memory at position cPtr and then waits until it
|
||||
// reveices the "release" signal, after which it nulls out the C memory at
|
||||
// cPtr and then exits.
|
||||
go func() {
|
||||
storeUntilRelease(&v, (*CPtr)(cPtr), uintptr(goPtr))
|
||||
}()
|
||||
// Wait for the "stored" signal from the go routine when the Go pointer has
|
||||
// been stored to the C memory. <--(1)
|
||||
v.stored.Lock()
|
||||
return &v
|
||||
}
|
||||
|
||||
// Release removes the guarded Go pointer from the C memory by overwriting it
|
||||
// with NULL.
|
||||
func (v *PtrGuard) Release() {
|
||||
if !v.released {
|
||||
v.released = true
|
||||
v.release.Unlock() // Send the "release" signal to the go routine. -->(2)
|
||||
v.stored.Lock() // Wait for the second "stored" signal when the C memory
|
||||
// has been nulled out. <--(3)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// The uintptrPtr() helper function below assumes that uintptr has the same size
|
||||
// as a pointer, although in theory it could be larger. Therefore we use this
|
||||
// constant expression to assert size equality as a safeguard at compile time.
|
||||
// How it works: if sizes are different, either the inner or outer expression is
|
||||
// negative, which always fails with "constant ... overflows uintptr", because
|
||||
// unsafe.Sizeof() is a uintptr typed constant.
|
||||
const _ = -(unsafe.Sizeof(uintptr(0)) - PtrSize) // size assert
|
||||
func uintptrPtr(p *CPtr) *uintptr {
|
||||
return (*uintptr)(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// From https://golang.org/src/cmd/compile/internal/gc/lex.go:
|
||||
// For the next function declared in the file any uintptr arguments may be
|
||||
// pointer values converted to uintptr. This directive ensures that the
|
||||
// referenced allocated object, if any, is retained and not moved until the call
|
||||
// completes, even though from the types alone it would appear that the object
|
||||
// is no longer needed during the call. The conversion to uintptr must appear in
|
||||
// the argument list.
|
||||
// Also see https://golang.org/cmd/compile/#hdr-Compiler_Directives
|
||||
|
||||
func storeUntilRelease(v *PtrGuard, cPtr *CPtr, goPtr uintptr) {
|
||||
uip := uintptrPtr(cPtr)
|
||||
*uip = goPtr // store Go pointer in C memory at c_ptr
|
||||
v.stored.Unlock() // send "stored" signal to main thread -->(1)
|
||||
v.release.Lock() // wait for "release" signal from main thread when
|
||||
// Release() has been called. <--(2)
|
||||
*uip = 0 // reset C memory to NULL
|
||||
v.stored.Unlock() // send second "stored" signal to main thread -->(3)
|
||||
}
|
||||
+49
@@ -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
@@ -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() {}
|
||||
+38
@@ -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)))
|
||||
}
|
||||
+52
@@ -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
@@ -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
@@ -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, max int, f SizeFunc) {
|
||||
if size > max {
|
||||
return
|
||||
}
|
||||
for {
|
||||
hint := f(size)
|
||||
if hint == nil || size == max {
|
||||
break
|
||||
}
|
||||
if hint.size() > size {
|
||||
size = hint.size()
|
||||
} else {
|
||||
size *= 2
|
||||
}
|
||||
if size > max {
|
||||
size = max
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -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)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//go:build ceph_preview
|
||||
// +build ceph_preview
|
||||
|
||||
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)
|
||||
)
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
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
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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.
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
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.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
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 := int64(C.rados_pool_lookup(c.cluster, cName))
|
||||
if ret < 0 {
|
||||
return 0, radosError(ret)
|
||||
}
|
||||
return 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 := int(C.rados_pool_reverse_lookup(c.cluster, cid, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
if ret < 0 {
|
||||
return "", radosError(ret)
|
||||
}
|
||||
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package rados contains a set of wrappers around Ceph's librados API.
|
||||
*/
|
||||
package rados
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package rados
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/errutil"
|
||||
)
|
||||
|
||||
// radosError represents an error condition returned from the Ceph RADOS APIs.
|
||||
type radosError int
|
||||
|
||||
// Error returns the error string for the radosError type.
|
||||
func (e radosError) Error() string {
|
||||
return errutil.FormatErrorCode("rados", int(e))
|
||||
}
|
||||
|
||||
func (e radosError) ErrorCode() int {
|
||||
return int(e)
|
||||
}
|
||||
|
||||
func getError(e C.int) error {
|
||||
if e == 0 {
|
||||
return nil
|
||||
}
|
||||
return radosError(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 (
|
||||
// ErrNotConnected is returned when functions are called
|
||||
// without a RADOS connection.
|
||||
ErrNotConnected = errors.New("RADOS not connected")
|
||||
// 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")
|
||||
)
|
||||
|
||||
// Public radosErrors:
|
||||
|
||||
const (
|
||||
// ErrNotFound indicates a missing resource.
|
||||
ErrNotFound = radosError(-C.ENOENT)
|
||||
// ErrPermissionDenied indicates a permissions issue.
|
||||
ErrPermissionDenied = radosError(-C.EPERM)
|
||||
// ErrObjectExists indicates that an exclusive object creation failed.
|
||||
ErrObjectExists = radosError(-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:
|
||||
|
||||
const (
|
||||
errNameTooLong = radosError(-C.ENAMETOOLONG)
|
||||
|
||||
errRange = radosError(-C.ERANGE)
|
||||
)
|
||||
+717
@@ -0,0 +1,717 @@
|
||||
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 {
|
||||
ret := C.rados_object_list(ioctx.ioctx, next, finish, pageResults, nil, filterLen, (*C.rados_object_list_item)(unsafe.Pointer(&results[0])), &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)))
|
||||
}
|
||||
|
||||
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, radosError(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
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//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
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//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
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build ceph_preview
|
||||
// +build ceph_preview
|
||||
|
||||
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
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build ceph_preview
|
||||
// +build ceph_preview
|
||||
|
||||
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
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//go:build ceph_preview
|
||||
// +build ceph_preview
|
||||
|
||||
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
@@ -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
@@ -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)
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
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, ", "))
|
||||
}
|
||||
|
||||
// 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
@@ -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)
|
||||
)
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
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.
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+13
@@ -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)
|
||||
)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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))
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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))
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
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)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
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)),
|
||||
)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
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
@@ -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),
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
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(len int) retry.Hint {
|
||||
cLen := C.int(len)
|
||||
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
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
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, len C.size_t) ([]NotifyAck, []NotifyTimeout) {
|
||||
if len == 0 || response == nil {
|
||||
return nil, nil
|
||||
}
|
||||
b := (*[math.MaxInt32]byte)(unsafe.Pointer(response))[:len:len]
|
||||
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):
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
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)
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
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
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
//go:build ceph_preview
|
||||
// +build ceph_preview
|
||||
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// 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:
|
||||
// void rados_write_op_set_alloc_hint2(rados_write_op_t write_op,
|
||||
// uint64_t expected_object_size,
|
||||
// uint64_t expected_write_size,
|
||||
// uint32_t flags);
|
||||
func (w *WriteOp) SetAllocationHint(expectedObjectSize uint64, expectedWriteSize uint64, flags AllocHintFlags) {
|
||||
C.rados_write_op_set_alloc_hint2(
|
||||
w.op,
|
||||
C.uint64_t(expectedObjectSize),
|
||||
C.uint64_t(expectedWriteSize),
|
||||
C.uint32_t(flags))
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package rados
|
||||
|
||||
// #include <stdint.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type writeStep struct {
|
||||
withoutUpdate
|
||||
withoutFree
|
||||
// the c pointer utilizes the Go byteslice data and no free is needed
|
||||
|
||||
// inputs:
|
||||
b []byte
|
||||
|
||||
// arguments:
|
||||
cBuffer *C.char
|
||||
cDataLen C.size_t
|
||||
cWriteLen C.size_t
|
||||
cOffset C.uint64_t
|
||||
}
|
||||
|
||||
func newWriteStep(b []byte, writeLen, offset uint64) *writeStep {
|
||||
return &writeStep{
|
||||
b: b,
|
||||
cBuffer: (*C.char)(unsafe.Pointer(&b[0])), // TODO: must be pinned
|
||||
cDataLen: C.size_t(len(b)),
|
||||
cWriteLen: C.size_t(writeLen),
|
||||
cOffset: C.uint64_t(offset),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user