Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// AllocHintFlags control the behavior of read and write operations.
type AllocHintFlags uint32
const (
// AllocHintNoHint indicates no predefined behavior
AllocHintNoHint = AllocHintFlags(0)
// AllocHintSequentialWrite TODO
AllocHintSequentialWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_WRITE)
// AllocHintRandomWrite TODO
AllocHintRandomWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_WRITE)
// AllocHintSequentialRead TODO
AllocHintSequentialRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_READ)
// AllocHintRandomRead TODO
AllocHintRandomRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_READ)
// AllocHintAppendOnly TODO
AllocHintAppendOnly = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_APPEND_ONLY)
// AllocHintImmutable TODO
AllocHintImmutable = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_IMMUTABLE)
// AllocHintShortlived TODO
AllocHintShortlived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SHORTLIVED)
// AllocHintLonglived TODO
AllocHintLonglived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_LONGLIVED)
// AllocHintCompressible TODO
AllocHintCompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_COMPRESSIBLE)
// AllocHintIncompressible TODO
AllocHintIncompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_INCOMPRESSIBLE)
)
+238
View File
@@ -0,0 +1,238 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
func radosBufferFree(p unsafe.Pointer) {
C.rados_buffer_free((*C.char)(p))
}
// MonCommand sends a command to one of the monitors
func (c *Conn) MonCommand(args []byte) ([]byte, string, error) {
return c.MonCommandWithInputBuffer(args, nil)
}
// MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput([][]byte{args}, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mon_command(
c.cluster,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// PGCommand sends a command to one of the PGs
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_pg_command(rados_t cluster, const char *pgstr,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) PGCommand(pgid []byte, args [][]byte) ([]byte, string, error) {
return c.PGCommandWithInputBuffer(pgid, args, nil)
}
// PGCommandWithInputBuffer sends a command to one of the PGs, with an input buffer
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_pg_command(rados_t cluster, const char *pgstr,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) PGCommandWithInputBuffer(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
name := C.CString(string(pgid))
defer C.free(unsafe.Pointer(name))
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_pg_command(
c.cluster,
name,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// MgrCommand sends a command to a ceph-mgr.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) MgrCommand(args [][]byte) ([]byte, string, error) {
return c.MgrCommandWithInputBuffer(args, nil)
}
// MgrCommandWithInputBuffer sends a command, with an input buffer, to a ceph-mgr.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_mgr_command(rados_t cluster, const char **cmd,
// size_t cmdlen, const char *inbuf,
// size_t inbuflen, char **outbuf,
// size_t *outbuflen, char **outs,
// size_t *outslen);
func (c *Conn) MgrCommandWithInputBuffer(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mgr_command(
c.cluster,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// OsdCommand sends a command to the specified ceph OSD.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) OsdCommand(osd int, args [][]byte) ([]byte, string, error) {
return c.OsdCommandWithInputBuffer(osd, args, nil)
}
// OsdCommandWithInputBuffer sends a command, with an input buffer, to the
// specified ceph OSD.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_osd_command(rados_t cluster, int osdid,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) OsdCommandWithInputBuffer(
osd int, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_osd_command(
c.cluster,
C.int(osd),
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
// MonCommandTarget sends a command to a specified monitor.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
func (c *Conn) MonCommandTarget(name string, args [][]byte) ([]byte, string, error) {
return c.MonCommandTargetWithInputBuffer(name, args, nil)
}
// MonCommandTargetWithInputBuffer sends a command, with an input buffer, to a specified monitor.
//
// The args parameter takes a slice of byte slices but typically a single
// slice element is sufficient. The use of two slices exists to best match
// the structure of the underlying C call which is often a legacy interface
// in Ceph.
//
// Implements:
//
// int rados_mon_command_target(rados_t cluster, const char *name,
// const char **cmd, size_t cmdlen,
// const char *inbuf, size_t inbuflen,
// char **outbuf, size_t *outbuflen,
// char **outs, size_t *outslen);
func (c *Conn) MonCommandTargetWithInputBuffer(
name string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ci := cutil.NewCommandInput(args, inputBuffer)
defer ci.Free()
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
defer co.Free()
ret := C.rados_mon_command_target(
c.cluster,
cName,
(**C.char)(ci.Cmd()),
C.size_t(ci.CmdLen()),
(*C.char)(ci.InBuf()),
C.size_t(ci.InBufLen()),
(**C.char)(co.OutBuf()),
(*C.size_t)(co.OutBufLen()),
(**C.char)(co.Outs()),
(*C.size_t)(co.OutsLen()))
buf, status := co.GoValues()
return buf, status, getError(ret)
}
+313
View File
@@ -0,0 +1,313 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
var argvPlaceholder = "placeholder"
//revive:disable:var-naming old-yet-exported public api
// ClusterStat represents Ceph cluster statistics.
type ClusterStat struct {
Kb uint64
Kb_used uint64
Kb_avail uint64
Num_objects uint64
}
//revive:enable:var-naming
// Conn is a connection handle to a Ceph cluster.
type Conn struct {
cluster C.rados_t
connected bool
}
// ClusterRef represents a fundamental RADOS cluster connection.
type ClusterRef C.rados_t
// Cluster returns the underlying RADOS cluster reference for this Conn.
func (c *Conn) Cluster() ClusterRef {
return ClusterRef(c.cluster)
}
// PingMonitor sends a ping to a monitor and returns the reply.
func (c *Conn) PingMonitor(id string) (string, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var strlen C.size_t
var strout *C.char
ret := C.rados_ping_monitor(c.cluster, cid, &strout, &strlen)
defer C.rados_buffer_free(strout)
if ret == 0 {
reply := C.GoStringN(strout, (C.int)(strlen))
return reply, nil
}
return "", getError(ret)
}
// Connect establishes a connection to a RADOS cluster. It returns an error,
// if any.
func (c *Conn) Connect() error {
ret := C.rados_connect(c.cluster)
if ret != 0 {
return getError(ret)
}
c.connected = true
return nil
}
// Shutdown disconnects from the cluster.
func (c *Conn) Shutdown() {
if err := c.ensureConnected(); err != nil {
return
}
freeConn(c)
}
// ReadConfigFile configures the connection using a Ceph configuration file.
func (c *Conn) ReadConfigFile(path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
ret := C.rados_conf_read_file(c.cluster, cPath)
return getError(ret)
}
// ReadDefaultConfigFile configures the connection using a Ceph configuration
// file located at default locations.
func (c *Conn) ReadDefaultConfigFile() error {
ret := C.rados_conf_read_file(c.cluster, nil)
return getError(ret)
}
// OpenIOContext creates and returns a new IOContext for the given pool.
//
// Implements:
//
// int rados_ioctx_create(rados_t cluster, const char *pool_name,
// rados_ioctx_t *ioctx);
func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
cPool := C.CString(pool)
defer C.free(unsafe.Pointer(cPool))
ioctx := &IOContext{conn: c}
ret := C.rados_ioctx_create(c.cluster, cPool, &ioctx.ioctx)
if ret == 0 {
return ioctx, nil
}
return nil, getError(ret)
}
// ListPools returns the names of all existing pools.
func (c *Conn) ListPools() (names []string, err error) {
buf := make([]byte, 4096)
for {
ret := C.rados_pool_list(c.cluster,
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
if ret < 0 {
return nil, getError(ret)
}
if int(ret) > len(buf) {
buf = make([]byte, ret)
continue
}
names = cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
}
// SetConfigOption sets the value of the configuration option identified by
// the given name.
func (c *Conn) SetConfigOption(option, value string) error {
cOpt, cVal := C.CString(option), C.CString(value)
defer C.free(unsafe.Pointer(cOpt))
defer C.free(unsafe.Pointer(cVal))
ret := C.rados_conf_set(c.cluster, cOpt, cVal)
return getError(ret)
}
// GetConfigOption returns the value of the Ceph configuration option
// identified by the given name.
func (c *Conn) GetConfigOption(name string) (value string, err error) {
cOption := C.CString(name)
defer C.free(unsafe.Pointer(cOption))
var buf []byte
// range from 4k to 256KiB
retry.WithSizes(4096, 1<<18, func(size int) retry.Hint {
buf = make([]byte, size)
ret := C.rados_conf_get(
c.cluster,
cOption,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
err = getError(ret)
return retry.DoubleSize.If(err == errNameTooLong)
})
if err != nil {
return "", err
}
value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
return value, nil
}
// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
// retrieved.
func (c *Conn) WaitForLatestOSDMap() error {
ret := C.rados_wait_for_latest_osdmap(c.cluster)
return getError(ret)
}
func (c *Conn) ensureConnected() error {
if c.connected {
return nil
}
return ErrNotConnected
}
// GetClusterStats returns statistics about the cluster associated with the
// connection.
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
if err := c.ensureConnected(); err != nil {
return ClusterStat{}, err
}
cStat := C.struct_rados_cluster_stat_t{}
ret := C.rados_cluster_stat(c.cluster, &cStat)
if ret < 0 {
return ClusterStat{}, getError(ret)
}
return ClusterStat{
Kb: uint64(cStat.kb),
Kb_used: uint64(cStat.kb_used),
Kb_avail: uint64(cStat.kb_avail),
Num_objects: uint64(cStat.num_objects),
}, nil
}
// ParseConfigArgv configures the connection using a unix style command line
// argument vector.
//
// Implements:
//
// int rados_conf_parse_argv(rados_t cluster, int argc,
// const char **argv);
func (c *Conn) ParseConfigArgv(argv []string) error {
if c.cluster == nil {
return ErrNotConnected
}
if len(argv) == 0 {
return ErrEmptyArgument
}
cargv := make([]*C.char, len(argv))
for i := range argv {
cargv[i] = C.CString(argv[i])
defer C.free(unsafe.Pointer(cargv[i]))
}
ret := C.rados_conf_parse_argv(c.cluster, C.int(len(cargv)), &cargv[0])
return getError(ret)
}
// ParseCmdLineArgs configures the connection from command line arguments.
//
// This function passes a placeholder value to Ceph as argv[0], see
// ParseConfigArgv for a version of this function that allows the caller to
// specify argv[0].
func (c *Conn) ParseCmdLineArgs(args []string) error {
argv := make([]string, len(args)+1)
// Ceph expects a proper argv array as the actual contents with the
// first element containing the executable name
argv[0] = argvPlaceholder
for i := range args {
argv[i+1] = args[i]
}
return c.ParseConfigArgv(argv)
}
// ParseDefaultConfigEnv configures the connection from the default Ceph
// environment variable CEPH_ARGS.
func (c *Conn) ParseDefaultConfigEnv() error {
ret := C.rados_conf_parse_env(c.cluster, nil)
return getError(ret)
}
// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
// is a unique identifier of an entire Ceph cluster.
func (c *Conn) GetFSID() (fsid string, err error) {
buf := make([]byte, 37)
ret := C.rados_cluster_fsid(c.cluster,
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
// FIXME: the success case isn't documented correctly in librados.h
if ret == 36 {
fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
return fsid, nil
}
return "", getError(ret)
}
// GetInstanceID returns a globally unique identifier for the cluster
// connection instance.
func (c *Conn) GetInstanceID() uint64 {
// FIXME: are there any error cases for this?
return uint64(C.rados_get_instance_id(c.cluster))
}
// MakePool creates a new pool with default settings.
func (c *Conn) MakePool(name string) error {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_create(c.cluster, cName)
return getError(ret)
}
// DeletePool deletes a pool and all the data inside the pool.
func (c *Conn) DeletePool(name string) error {
if err := c.ensureConnected(); err != nil {
return err
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_delete(c.cluster, cName)
return getError(ret)
}
// GetPoolByName returns the ID of the pool with a given name.
func (c *Conn) GetPoolByName(name string) (int64, error) {
if err := c.ensureConnected(); err != nil {
return 0, err
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.rados_pool_lookup(c.cluster, cName)
if ret < 0 {
return 0, getError(C.int(ret))
}
return int64(ret), nil
}
// GetPoolByID returns the name of a pool by a given ID.
func (c *Conn) GetPoolByID(id int64) (string, error) {
buf := make([]byte, 4096)
if err := c.ensureConnected(); err != nil {
return "", err
}
cid := C.int64_t(id)
ret := C.rados_pool_reverse_lookup(c.cluster, cid, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
if ret < 0 {
return "", getError(ret)
}
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
}
+4
View File
@@ -0,0 +1,4 @@
/*
Package rados contains a set of wrappers around Ceph's librados API.
*/
package rados
+64
View File
@@ -0,0 +1,64 @@
package rados
/*
#include <errno.h>
*/
import "C"
import (
"errors"
"github.com/ceph/go-ceph/internal/errutil"
)
// Public go errors:
var (
// ErrNotConnected is returned when functions are called
// without a RADOS connection.
ErrNotConnected = getError(-C.ENOTCONN)
// ErrEmptyArgument may be returned if a function argument is passed
// a zero-length slice or map.
ErrEmptyArgument = errors.New("Argument must contain at least one item")
// ErrInvalidIOContext may be returned if an api call requires an IOContext
// but IOContext is not ready for use.
ErrInvalidIOContext = errors.New("IOContext is not ready for use")
// ErrOperationIncomplete is returned from write op or read op steps for
// which the operation has not been performed yet.
ErrOperationIncomplete = errors.New("Operation has not been performed yet")
// ErrNotFound indicates a missing resource.
ErrNotFound = getError(-C.ENOENT)
// ErrPermissionDenied indicates a permissions issue.
ErrPermissionDenied = getError(-C.EPERM)
// ErrObjectExists indicates that an exclusive object creation failed.
ErrObjectExists = getError(-C.EEXIST)
// RadosErrorNotFound indicates a missing resource.
//
// Deprecated: use ErrNotFound instead
RadosErrorNotFound = ErrNotFound
// RadosErrorPermissionDenied indicates a permissions issue.
//
// Deprecated: use ErrPermissionDenied instead
RadosErrorPermissionDenied = ErrPermissionDenied
// Private errors:
errNameTooLong = getError(-C.ENAMETOOLONG)
errRange = getError(-C.ERANGE)
)
func getError(errno C.int) error {
return errutil.GetError("rados", int(errno))
}
// getErrorIfNegative converts a ceph return code to error if negative.
// This is useful for functions that return a usable positive value on
// success but a negative error number on error.
func getErrorIfNegative(ret C.int) error {
if ret >= 0 {
return nil
}
return getError(ret)
}
+725
View File
@@ -0,0 +1,725 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
// char* nextChunk(char **idx) {
// char *copy;
// copy = strdup(*idx);
// *idx += strlen(*idx) + 1;
// return copy;
// }
//
// #if __APPLE__
// #define ceph_time_t __darwin_time_t
// #define ceph_suseconds_t __darwin_suseconds_t
// #elif __GLIBC__
// #define ceph_time_t __time_t
// #define ceph_suseconds_t __suseconds_t
// #else
// #define ceph_time_t time_t
// #define ceph_suseconds_t suseconds_t
// #endif
import "C"
import (
"syscall"
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/retry"
)
// CreateOption is passed to IOContext.Create() and should be one of
// CreateExclusive or CreateIdempotent.
type CreateOption int
const (
// CreateExclusive if used with IOContext.Create() and the object
// already exists, the function will return an error.
CreateExclusive = C.LIBRADOS_CREATE_EXCLUSIVE
// CreateIdempotent if used with IOContext.Create() and the object
// already exists, the function will not return an error.
CreateIdempotent = C.LIBRADOS_CREATE_IDEMPOTENT
defaultListObjectsResultSize = 1000
// listEndSentinel is the value returned by rados_list_object_list_is_end
// when a cursor has reached the end of a pool
listEndSentinel = 1
)
//revive:disable:var-naming old-yet-exported public api
// PoolStat represents Ceph pool statistics.
type PoolStat struct {
// space used in bytes
Num_bytes uint64
// space used in KB
Num_kb uint64
// number of objects in the pool
Num_objects uint64
// number of clones of objects
Num_object_clones uint64
// num_objects * num_replicas
Num_object_copies uint64
Num_objects_missing_on_primary uint64
// number of objects found on no OSDs
Num_objects_unfound uint64
// number of objects replicated fewer times than they should be
// (but found on at least one OSD)
Num_objects_degraded uint64
Num_rd uint64
Num_rd_kb uint64
Num_wr uint64
Num_wr_kb uint64
}
//revive:enable:var-naming
// ObjectStat represents an object stat information
type ObjectStat struct {
// current length in bytes
Size uint64
// last modification time
ModTime time.Time
}
// LockInfo represents information on a current Ceph lock
type LockInfo struct {
NumLockers int
Exclusive bool
Tag string
Clients []string
Cookies []string
Addrs []string
}
// IOContext represents a context for performing I/O within a pool.
type IOContext struct {
ioctx C.rados_ioctx_t
// Hold a reference back to the connection that the ioctx depends on so
// that Go's GC doesn't trigger the Conn's finalizer before this
// IOContext is destroyed.
conn *Conn
}
// validate returns an error if the ioctx is not ready to be used
// with ceph C calls.
func (ioctx *IOContext) validate() error {
if ioctx.ioctx == nil {
return ErrInvalidIOContext
}
return nil
}
// Pointer returns a pointer reference to an internal structure.
// This function should NOT be used outside of go-ceph itself.
func (ioctx *IOContext) Pointer() unsafe.Pointer {
return unsafe.Pointer(ioctx.ioctx)
}
// SetNamespace sets the namespace for objects within this IO context (pool).
// Setting namespace to a empty or zero length string sets the pool to the default namespace.
//
// Implements:
//
// void rados_ioctx_set_namespace(rados_ioctx_t io,
// const char *nspace);
func (ioctx *IOContext) SetNamespace(namespace string) {
var cns *C.char
if len(namespace) > 0 {
cns = C.CString(namespace)
defer C.free(unsafe.Pointer(cns))
}
C.rados_ioctx_set_namespace(ioctx.ioctx, cns)
}
// Create a new object with key oid.
//
// Implements:
//
// void rados_write_op_create(rados_write_op_t write_op, int exclusive,
// const char* category)
func (ioctx *IOContext) Create(oid string, exclusive CreateOption) error {
op := CreateWriteOp()
defer op.Release()
op.Create(exclusive)
return op.operateCompat(ioctx, oid)
}
// Write writes len(data) bytes to the object with key oid starting at byte
// offset offset. It returns an error, if any.
func (ioctx *IOContext) Write(oid string, data []byte, offset uint64) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
dataPointer := unsafe.Pointer(nil)
if len(data) > 0 {
dataPointer = unsafe.Pointer(&data[0])
}
ret := C.rados_write(ioctx.ioctx, coid,
(*C.char)(dataPointer),
(C.size_t)(len(data)),
(C.uint64_t)(offset))
return getError(ret)
}
// WriteFull writes len(data) bytes to the object with key oid.
// The object is filled with the provided data. If the object exists,
// it is atomically truncated and then written. It returns an error, if any.
func (ioctx *IOContext) WriteFull(oid string, data []byte) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
ret := C.rados_write_full(ioctx.ioctx, coid,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// Append appends len(data) bytes to the object with key oid.
// The object is appended with the provided data. If the object exists,
// it is atomically appended to. It returns an error, if any.
func (ioctx *IOContext) Append(oid string, data []byte) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
ret := C.rados_append(ioctx.ioctx, coid,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// Read reads up to len(data) bytes from the object with key oid starting at byte
// offset offset. It returns the number of bytes read and an error, if any.
func (ioctx *IOContext) Read(oid string, data []byte, offset uint64) (int, error) {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
var buf *C.char
if len(data) > 0 {
buf = (*C.char)(unsafe.Pointer(&data[0]))
}
ret := C.rados_read(
ioctx.ioctx,
coid,
buf,
(C.size_t)(len(data)),
(C.uint64_t)(offset))
if ret >= 0 {
return int(ret), nil
}
return 0, getError(ret)
}
// Delete deletes the object with key oid. It returns an error, if any.
func (ioctx *IOContext) Delete(oid string) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_remove(ioctx.ioctx, coid))
}
// Truncate resizes the object with key oid to size size. If the operation
// enlarges the object, the new area is logically filled with zeroes. If the
// operation shrinks the object, the excess data is removed. It returns an
// error, if any.
func (ioctx *IOContext) Truncate(oid string, size uint64) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_trunc(ioctx.ioctx, coid, (C.uint64_t)(size)))
}
// Destroy informs librados that the I/O context is no longer in use.
// Resources associated with the context may not be freed immediately, and the
// context should not be used again after calling this method.
func (ioctx *IOContext) Destroy() {
C.rados_ioctx_destroy(ioctx.ioctx)
}
// GetPoolStats returns a set of statistics about the pool associated with this I/O
// context.
//
// Implements:
//
// int rados_ioctx_pool_stat(rados_ioctx_t io,
// struct rados_pool_stat_t *stats);
func (ioctx *IOContext) GetPoolStats() (stat PoolStat, err error) {
cStat := C.struct_rados_pool_stat_t{}
ret := C.rados_ioctx_pool_stat(ioctx.ioctx, &cStat)
if ret < 0 {
return PoolStat{}, getError(ret)
}
return PoolStat{
Num_bytes: uint64(cStat.num_bytes),
Num_kb: uint64(cStat.num_kb),
Num_objects: uint64(cStat.num_objects),
Num_object_clones: uint64(cStat.num_object_clones),
Num_object_copies: uint64(cStat.num_object_copies),
Num_objects_missing_on_primary: uint64(cStat.num_objects_missing_on_primary),
Num_objects_unfound: uint64(cStat.num_objects_unfound),
Num_objects_degraded: uint64(cStat.num_objects_degraded),
Num_rd: uint64(cStat.num_rd),
Num_rd_kb: uint64(cStat.num_rd_kb),
Num_wr: uint64(cStat.num_wr),
Num_wr_kb: uint64(cStat.num_wr_kb),
}, nil
}
// GetPoolID returns the pool ID associated with the I/O context.
//
// Implements:
//
// int64_t rados_ioctx_get_id(rados_ioctx_t io)
func (ioctx *IOContext) GetPoolID() int64 {
ret := C.rados_ioctx_get_id(ioctx.ioctx)
return int64(ret)
}
// GetPoolName returns the name of the pool associated with the I/O context.
func (ioctx *IOContext) GetPoolName() (name string, err error) {
var (
buf []byte
ret C.int
)
retry.WithSizes(128, 8192, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.rados_ioctx_get_pool_name(
ioctx.ioctx,
(*C.char)(unsafe.Pointer(&buf[0])),
C.unsigned(len(buf)))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return "", err
}
name = C.GoStringN((*C.char)(unsafe.Pointer(&buf[0])), ret)
return name, nil
}
// ObjectListFunc is the type of the function called for each object visited
// by ListObjects.
type ObjectListFunc func(oid string)
// ListObjects lists all of the objects in the pool associated with the I/O
// context, and called the provided listFn function for each object, passing
// to the function the name of the object. Call SetNamespace with
// RadosAllNamespaces before calling this function to return objects from all
// namespaces
func (ioctx *IOContext) ListObjects(listFn ObjectListFunc) error {
pageResults := C.size_t(defaultListObjectsResultSize)
var filterLen C.size_t
results := make([]C.rados_object_list_item, pageResults)
next := C.rados_object_list_begin(ioctx.ioctx)
if next == nil {
return ErrNotFound
}
defer C.rados_object_list_cursor_free(ioctx.ioctx, next)
finish := C.rados_object_list_end(ioctx.ioctx)
if finish == nil {
return ErrNotFound
}
defer C.rados_object_list_cursor_free(ioctx.ioctx, finish)
for {
res := (*C.rados_object_list_item)(unsafe.Pointer(&results[0]))
ret := C.rados_object_list(ioctx.ioctx, next, finish, pageResults, nil, filterLen, res, &next)
if ret < 0 {
return getError(ret)
}
numEntries := int(ret)
for i := 0; i < numEntries; i++ {
item := results[i]
listFn(C.GoStringN(item.oid, (C.int)(item.oid_length)))
}
C.rados_object_list_free(C.size_t(ret), res)
if C.rados_object_list_is_end(ioctx.ioctx, next) == listEndSentinel {
return nil
}
}
}
// Stat returns the size of the object and its last modification time
func (ioctx *IOContext) Stat(object string) (stat ObjectStat, err error) {
var cPsize C.uint64_t
var cPmtime C.time_t
cObject := C.CString(object)
defer C.free(unsafe.Pointer(cObject))
ret := C.rados_stat(
ioctx.ioctx,
cObject,
&cPsize,
&cPmtime)
if ret < 0 {
return ObjectStat{}, getError(ret)
}
return ObjectStat{
Size: uint64(cPsize),
ModTime: time.Unix(int64(cPmtime), 0),
}, nil
}
// GetXattr gets an xattr with key `name`, it returns the length of
// the key read or an error if not successful
func (ioctx *IOContext) GetXattr(object string, name string, data []byte) (int, error) {
cObject := C.CString(object)
cName := C.CString(name)
defer C.free(unsafe.Pointer(cObject))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_getxattr(
ioctx.ioctx,
cObject,
cName,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
if ret >= 0 {
return int(ret), nil
}
return 0, getError(ret)
}
// SetXattr sets an xattr for an object with key `name` with value as `data`
func (ioctx *IOContext) SetXattr(object string, name string, data []byte) error {
cObject := C.CString(object)
cName := C.CString(name)
defer C.free(unsafe.Pointer(cObject))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_setxattr(
ioctx.ioctx,
cObject,
cName,
(*C.char)(unsafe.Pointer(&data[0])),
(C.size_t)(len(data)))
return getError(ret)
}
// ListXattrs lists all the xattrs for an object. The xattrs are returned as a
// mapping of string keys and byte-slice values.
func (ioctx *IOContext) ListXattrs(oid string) (map[string][]byte, error) {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
var it C.rados_xattrs_iter_t
ret := C.rados_getxattrs(ioctx.ioctx, coid, &it)
if ret < 0 {
return nil, getError(ret)
}
defer func() { C.rados_getxattrs_end(it) }()
m := make(map[string][]byte)
for {
var cName, cVal *C.char
var cLen C.size_t
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cVal))
ret := C.rados_getxattrs_next(it, &cName, &cVal, &cLen)
if ret < 0 {
return nil, getError(ret)
}
// rados api returns a null name,val & 0-length upon
// end of iteration
if cName == nil {
return m, nil // stop iteration
}
m[C.GoString(cName)] = C.GoBytes(unsafe.Pointer(cVal), (C.int)(cLen))
}
}
// RmXattr removes an xattr with key `name` from object `oid`
func (ioctx *IOContext) RmXattr(oid string, name string) error {
coid := C.CString(oid)
cName := C.CString(name)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
ret := C.rados_rmxattr(
ioctx.ioctx,
coid,
cName)
return getError(ret)
}
// LockExclusive takes an exclusive lock on an object.
func (ioctx *IOContext) LockExclusive(oid, name, cookie, desc string, duration time.Duration, flags *byte) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
cDesc := C.CString(desc)
var cDuration C.struct_timeval
if duration != 0 {
tv := syscall.NsecToTimeval(duration.Nanoseconds())
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
}
var cFlags C.uint8_t
if flags != nil {
cFlags = C.uint8_t(*flags)
}
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
defer C.free(unsafe.Pointer(cDesc))
ret := C.rados_lock_exclusive(
ioctx.ioctx,
coid,
cName,
cCookie,
cDesc,
&cDuration,
cFlags)
// 0 on success, negative error code on failure
// -EBUSY if the lock is already held by another (client, cookie) pair
// -EEXIST if the lock is already held by the same (client, cookie) pair
switch ret {
case 0:
return int(ret), nil
case -C.EBUSY:
return int(ret), nil
case -C.EEXIST:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// LockShared takes a shared lock on an object.
func (ioctx *IOContext) LockShared(oid, name, cookie, tag, desc string, duration time.Duration, flags *byte) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
cTag := C.CString(tag)
cDesc := C.CString(desc)
var cDuration C.struct_timeval
if duration != 0 {
tv := syscall.NsecToTimeval(duration.Nanoseconds())
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
}
var cFlags C.uint8_t
if flags != nil {
cFlags = C.uint8_t(*flags)
}
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
defer C.free(unsafe.Pointer(cTag))
defer C.free(unsafe.Pointer(cDesc))
ret := C.rados_lock_shared(
ioctx.ioctx,
coid,
cName,
cCookie,
cTag,
cDesc,
&cDuration,
cFlags)
// 0 on success, negative error code on failure
// -EBUSY if the lock is already held by another (client, cookie) pair
// -EEXIST if the lock is already held by the same (client, cookie) pair
switch ret {
case 0:
return int(ret), nil
case -C.EBUSY:
return int(ret), nil
case -C.EEXIST:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// Unlock releases a shared or exclusive lock on an object.
func (ioctx *IOContext) Unlock(oid, name, cookie string) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cCookie := C.CString(cookie)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cCookie))
// 0 on success, negative error code on failure
// -ENOENT if the lock is not held by the specified (client, cookie) pair
ret := C.rados_unlock(
ioctx.ioctx,
coid,
cName,
cCookie)
switch ret {
case 0:
return int(ret), nil
case -C.ENOENT:
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// ListLockers lists clients that have locked the named object lock and
// information about the lock.
// The number of bytes required in each buffer is put in the corresponding size
// out parameter. If any of the provided buffers are too short, -ERANGE is
// returned after these sizes are filled in.
func (ioctx *IOContext) ListLockers(oid, name string) (*LockInfo, error) {
coid := C.CString(oid)
cName := C.CString(name)
cTag := (*C.char)(C.malloc(C.size_t(1024)))
cClients := (*C.char)(C.malloc(C.size_t(1024)))
cCookies := (*C.char)(C.malloc(C.size_t(1024)))
cAddrs := (*C.char)(C.malloc(C.size_t(1024)))
var cExclusive C.int
cTagLen := C.size_t(1024)
cClientsLen := C.size_t(1024)
cCookiesLen := C.size_t(1024)
cAddrsLen := C.size_t(1024)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cTag))
defer C.free(unsafe.Pointer(cClients))
defer C.free(unsafe.Pointer(cCookies))
defer C.free(unsafe.Pointer(cAddrs))
ret := C.rados_list_lockers(
ioctx.ioctx,
coid,
cName,
&cExclusive,
cTag,
&cTagLen,
cClients,
&cClientsLen,
cCookies,
&cCookiesLen,
cAddrs,
&cAddrsLen)
splitCString := func(items *C.char, itemsLen C.size_t) []string {
currLen := 0
clients := []string{}
for currLen < int(itemsLen) {
client := C.GoString(C.nextChunk(&items))
clients = append(clients, client)
currLen += len(client) + 1
}
return clients
}
if ret < 0 {
return nil, getError(C.int(ret))
}
return &LockInfo{int(ret), cExclusive == 1, C.GoString(cTag), splitCString(cClients, cClientsLen), splitCString(cCookies, cCookiesLen), splitCString(cAddrs, cAddrsLen)}, nil
}
// BreakLock releases a shared or exclusive lock on an object, which was taken by the specified client.
func (ioctx *IOContext) BreakLock(oid, name, client, cookie string) (int, error) {
coid := C.CString(oid)
cName := C.CString(name)
cClient := C.CString(client)
cCookie := C.CString(cookie)
defer C.free(unsafe.Pointer(coid))
defer C.free(unsafe.Pointer(cName))
defer C.free(unsafe.Pointer(cClient))
defer C.free(unsafe.Pointer(cCookie))
// 0 on success, negative error code on failure
// -ENOENT if the lock is not held by the specified (client, cookie) pair
// -EINVAL if the client cannot be parsed
ret := C.rados_break_lock(
ioctx.ioctx,
coid,
cName,
cClient,
cCookie)
switch ret {
case 0:
return int(ret), nil
case -C.ENOENT:
return int(ret), nil
case -C.EINVAL: // -EINVAL
return int(ret), nil
default:
return int(ret), getError(ret)
}
}
// GetLastVersion will return the version number of the last object read or
// written to.
//
// Implements:
//
// uint64_t rados_get_last_version(rados_ioctx_t io);
func (ioctx *IOContext) GetLastVersion() (uint64, error) {
if err := ioctx.validate(); err != nil {
return 0, err
}
v := C.rados_get_last_version(ioctx.ioctx)
return uint64(v), nil
}
// GetNamespace gets the namespace used for objects within this IO context.
//
// Implements:
//
// int rados_ioctx_get_namespace(rados_ioctx_t io, char *buf,
// unsigned maxlen);
func (ioctx *IOContext) GetNamespace() (string, error) {
if err := ioctx.validate(); err != nil {
return "", err
}
var (
err error
buf []byte
ret C.int
)
retry.WithSizes(128, 8192, func(size int) retry.Hint {
buf = make([]byte, size)
ret = C.rados_ioctx_get_namespace(
ioctx.ioctx,
(*C.char)(unsafe.Pointer(&buf[0])),
C.unsigned(len(buf)))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return "", err
}
return string(buf[:ret]), nil
}
+37
View File
@@ -0,0 +1,37 @@
//go:build nautilus
// +build nautilus
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// SetPoolFullTry makes sure to send requests to the cluster despite
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
// or return EDQUOT or ENOSPC.
//
// Implements:
//
// void rados_set_osdmap_full_try(rados_ioctx_t io);
func (ioctx *IOContext) SetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_set_osdmap_full_try(ioctx.ioctx)
return nil
}
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
//
// Implements:
//
// void rados_unset_osdmap_full_try(rados_ioctx_t io);
func (ioctx *IOContext) UnsetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_unset_osdmap_full_try(ioctx.ioctx)
return nil
}
+40
View File
@@ -0,0 +1,40 @@
//go:build !nautilus
// +build !nautilus
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// Ceph octopus deprecates rados_set_osdmap_full_try() and implements rados_set_pool_full_try()
// Ceph octopus deprecates rados_unset_osdmap_full_try() and implements rados_unset_pool_full_try()
// SetPoolFullTry makes sure to send requests to the cluster despite
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
// or return EDQUOT or ENOSPC.
//
// Implements:
//
// void rados_set_pool_full_try(rados_ioctx_t io);
func (ioctx *IOContext) SetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_set_pool_full_try(ioctx.ioctx)
return nil
}
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
//
// Implements:
//
// void rados_unset_pool_full_try(rados_ioctx_t io);
func (ioctx *IOContext) UnsetPoolFullTry() error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_unset_pool_full_try(ioctx.ioctx)
return nil
}
+25
View File
@@ -0,0 +1,25 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// Alignment returns the required stripe size in bytes for pools supporting/requiring it, or an error if unsuccessful.
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. To know if the pool requires
// alignment or not, use RequiresAlignment.
//
// Implements:
//
// int rados_ioctx_pool_required_alignment2(rados_ioctx_t io, uint64_t *alignment)
func (ioctx *IOContext) Alignment() (uint64, error) {
var alignSizeBytes C.uint64_t
ret := C.rados_ioctx_pool_required_alignment2(
ioctx.ioctx,
&alignSizeBytes)
if ret != 0 {
return 0, getError(ret)
}
return uint64(alignSizeBytes), nil
}
@@ -0,0 +1,25 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// RequiresAlignment returns true if the pool supports/requires alignment or an error if not successful.
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. See
// Alignment to know how to get the stripe size for pools requiring it.
//
// Implements:
//
// int rados_ioctx_pool_requires_alignment2(rados_ioctx_t io, int *req)
func (ioctx *IOContext) RequiresAlignment() (bool, error) {
var alignRequired C.int
ret := C.rados_ioctx_pool_requires_alignment2(
ioctx.ioctx,
&alignRequired)
if ret != 0 {
return false, getError(ret)
}
return (alignRequired != 0), nil
}
+36
View File
@@ -0,0 +1,36 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetAllocationHint sets allocation hint for an object. This is an advisory
// operation, it will always succeed (as if it was submitted with a
// LIBRADOS_OP_FLAG_FAILOK flag set) and is not guaranteed to do anything on
// the backend.
//
// Implements:
//
// int rados_set_alloc_hint2(rados_ioctx_t io,
// const char *o,
// uint64_t expected_object_size,
// uint64_t expected_write_size,
// uint32_t flags);
func (ioctx *IOContext) SetAllocationHint(oid string, expectedObjectSize uint64, expectedWriteSize uint64, flags AllocHintFlags) error {
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
return getError(C.rados_set_alloc_hint2(
ioctx.ioctx,
coid,
(C.uint64_t)(expectedObjectSize),
(C.uint64_t)(expectedWriteSize),
(C.uint32_t)(flags),
))
}
+92
View File
@@ -0,0 +1,92 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
//
import "C"
// Iter supports iterating over objects in the ioctx.
type Iter struct {
ctx C.rados_list_ctx_t
err error
entry string
namespace string
}
// IterToken supports reporting on and seeking to different positions.
type IterToken uint32
// Iter returns a Iterator object that can be used to list the object names in the current pool
func (ioctx *IOContext) Iter() (*Iter, error) {
iter := Iter{}
if cerr := C.rados_nobjects_list_open(ioctx.ioctx, &iter.ctx); cerr < 0 {
return nil, getError(cerr)
}
return &iter, nil
}
// Token returns a token marking the current position of the iterator. To be used in combination with Iter.Seek()
func (iter *Iter) Token() IterToken {
return IterToken(C.rados_nobjects_list_get_pg_hash_position(iter.ctx))
}
// Seek moves the iterator to the position indicated by the token.
func (iter *Iter) Seek(token IterToken) {
C.rados_nobjects_list_seek(iter.ctx, C.uint32_t(token))
}
// Next retrieves the next object name in the pool/namespace iterator.
// Upon a successful invocation (return value of true), the Value method should
// be used to obtain the name of the retrieved object name. When the iterator is
// exhausted, Next returns false. The Err method should used to verify whether the
// end of the iterator was reached, or the iterator received an error.
//
// Example:
//
// iter := pool.Iter()
// defer iter.Close()
// for iter.Next() {
// fmt.Printf("%v\n", iter.Value())
// }
// return iter.Err()
func (iter *Iter) Next() bool {
var cEntry *C.char
var cNamespace *C.char
if cerr := C.rados_nobjects_list_next(iter.ctx, &cEntry, nil, &cNamespace); cerr < 0 {
iter.err = getError(cerr)
return false
}
iter.entry = C.GoString(cEntry)
iter.namespace = C.GoString(cNamespace)
return true
}
// Value returns the current value of the iterator (object name), after a successful call to Next.
func (iter *Iter) Value() string {
if iter.err != nil {
return ""
}
return iter.entry
}
// Namespace returns the namespace associated with the current value of the iterator (object name), after a successful call to Next.
func (iter *Iter) Namespace() string {
if iter.err != nil {
return ""
}
return iter.namespace
}
// Err checks whether the iterator has encountered an error.
func (iter *Iter) Err() error {
if iter.err == ErrNotFound {
return nil
}
return iter.err
}
// Close the iterator cursor on the server. Be aware that iterators are not closed automatically
// at the end of iteration.
func (iter *Iter) Close() {
C.rados_nobjects_list_close(iter.ctx)
}
+205
View File
@@ -0,0 +1,205 @@
package rados
/*
#cgo LDFLAGS: -lrados
#include <stdlib.h>
#include <rados/librados.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
// OmapKeyValue items are returned by the GetOmapStep's Next call.
type OmapKeyValue struct {
Key string
Value []byte
}
// GetOmapStep values are used to get the results of an GetOmapValues call
// on a WriteOp. Until the Operate method of the WriteOp is called the Next
// call will return an error. After Operate is called, the Next call will
// return valid results.
//
// The life cycle of the GetOmapStep is bound to the ReadOp, if the ReadOp
// Release method is called the public methods of the step must no longer be
// used and may return errors.
type GetOmapStep struct {
// C returned data:
iter C.rados_omap_iter_t
more *C.uchar
rval *C.int
// internal state:
// canIterate is only set after the operation is performed and is
// intended to prevent premature fetching of data
canIterate bool
}
func newGetOmapStep() *GetOmapStep {
gos := &GetOmapStep{
more: (*C.uchar)(C.malloc(C.sizeof_uchar)),
rval: (*C.int)(C.malloc(C.sizeof_int)),
}
runtime.SetFinalizer(gos, opStepFinalizer)
return gos
}
func (gos *GetOmapStep) free() {
gos.canIterate = false
if gos.iter != nil {
C.rados_omap_get_end(gos.iter)
}
gos.iter = nil
C.free(unsafe.Pointer(gos.more))
gos.more = nil
C.free(unsafe.Pointer(gos.rval))
gos.rval = nil
}
func (gos *GetOmapStep) update() error {
err := getError(*gos.rval)
gos.canIterate = (err == nil)
return err
}
// Next returns the next key value pair or nil if iteration is exhausted.
func (gos *GetOmapStep) Next() (*OmapKeyValue, error) {
if !gos.canIterate {
return nil, ErrOperationIncomplete
}
var (
cKey *C.char
cVal *C.char
cKeyLen C.size_t
cValLen C.size_t
)
ret := C.rados_omap_get_next2(gos.iter, &cKey, &cVal, &cKeyLen, &cValLen)
if ret != 0 {
return nil, getError(ret)
}
if cKey == nil {
return nil, nil
}
return &OmapKeyValue{
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
}, nil
}
// More returns true if there are more matching keys available.
func (gos *GetOmapStep) More() bool {
// tad bit hacky, but go can't automatically convert from
// unsigned char to bool
return *gos.more != 0
}
// SetOmap appends the map `pairs` to the omap `oid`
func (ioctx *IOContext) SetOmap(oid string, pairs map[string][]byte) error {
op := CreateWriteOp()
defer op.Release()
op.SetOmap(pairs)
return op.operateCompat(ioctx, oid)
}
// OmapListFunc is the type of the function called for each omap key
// visited by ListOmapValues
type OmapListFunc func(key string, value []byte)
// ListOmapValues iterates over the keys and values in an omap by way of
// a callback function.
//
// `startAfter`: iterate only on the keys after this specified one
// `filterPrefix`: iterate only on the keys beginning with this prefix
// `maxReturn`: iterate no more than `maxReturn` key/value pairs
// `listFn`: the function called at each iteration
func (ioctx *IOContext) ListOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64, listFn OmapListFunc) error {
op := CreateReadOp()
defer op.Release()
gos := op.GetOmapValues(startAfter, filterPrefix, uint64(maxReturn))
err := op.operateCompat(ioctx, oid)
if err != nil {
return err
}
for {
kv, err := gos.Next()
if err != nil {
return err
}
if kv == nil {
break
}
listFn(kv.Key, kv.Value)
}
return nil
}
// GetOmapValues fetches a set of keys and their values from an omap and returns then as a map
// `startAfter`: retrieve only the keys after this specified one
// `filterPrefix`: retrieve only the keys beginning with this prefix
// `maxReturn`: retrieve no more than `maxReturn` key/value pairs
func (ioctx *IOContext) GetOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64) (map[string][]byte, error) {
omap := map[string][]byte{}
err := ioctx.ListOmapValues(
oid, startAfter, filterPrefix, maxReturn,
func(key string, value []byte) {
omap[key] = value
},
)
return omap, err
}
// GetAllOmapValues fetches all the keys and their values from an omap and returns then as a map
// `startAfter`: retrieve only the keys after this specified one
// `filterPrefix`: retrieve only the keys beginning with this prefix
// `iteratorSize`: internal number of keys to fetch during a read operation
func (ioctx *IOContext) GetAllOmapValues(oid string, startAfter string, filterPrefix string, iteratorSize int64) (map[string][]byte, error) {
omap := map[string][]byte{}
omapSize := 0
for {
err := ioctx.ListOmapValues(
oid, startAfter, filterPrefix, iteratorSize,
func(key string, value []byte) {
omap[key] = value
startAfter = key
},
)
if err != nil {
return omap, err
}
// End of omap
if len(omap) == omapSize {
break
}
omapSize = len(omap)
}
return omap, nil
}
// RmOmapKeys removes the specified `keys` from the omap `oid`
func (ioctx *IOContext) RmOmapKeys(oid string, keys []string) error {
op := CreateWriteOp()
defer op.Release()
op.RmOmapKeys(keys)
return op.operateCompat(ioctx, oid)
}
// CleanOmap clears the omap `oid`
func (ioctx *IOContext) CleanOmap(oid string) error {
op := CreateWriteOp()
defer op.Release()
op.CleanOmap()
return op.operateCompat(ioctx, oid)
}
+165
View File
@@ -0,0 +1,165 @@
package rados
// #include <stdlib.h>
import "C"
import (
"fmt"
"strings"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
// The file operation.go exists to support both read op and write op types that
// have some pretty common behaviors between them. In C/C++ its assumed that
// the buffer types and other pointers will not be freed between passing them
// to the action setup calls (things like rados_write_op_write or
// rados_read_op_omap_get_vals2) and the call to Operate(...). Since there's
// nothing stopping one from sleeping for hours between these calls, or passing
// the op to other functions and calling Operate there, we want a mechanism
// that's (fairly) simple to understand and won't run afoul of Go's garbage
// collection. That's one reason the operation type tracks the steps (the
// parts that track complex inputs and outputs) so that as long as the op
// exists it will have a reference to the step, which will have references
// to the C language types.
type opKind string
const (
readOp opKind = "read"
writeOp opKind = "write"
)
// OperationError is an error type that may be returned by an Operate call.
// It captures the error from the operate call itself and any errors from
// steps that can return an error.
type OperationError struct {
kind opKind
OpError error
StepErrors map[int]error
}
func (e OperationError) Error() string {
subErrors := []string{}
if e.OpError != nil {
subErrors = append(subErrors,
fmt.Sprintf("op=%s", e.OpError))
}
for idx, es := range e.StepErrors {
subErrors = append(subErrors,
fmt.Sprintf("Step#%d=%s", idx, es))
}
return fmt.Sprintf(
"%s operation error: %s",
e.kind,
strings.Join(subErrors, ", "))
}
func (e OperationError) Unwrap() []error {
subErrors := make([]error, 0, len(e.StepErrors)+1)
if e.OpError != nil {
subErrors = append(subErrors, e.OpError)
}
for _, es := range e.StepErrors {
subErrors = append(subErrors, es)
}
return subErrors
}
// opStep provides an interface for types that are tied to the management of
// data being input or output from write ops and read ops. The steps are
// meant to simplify the internals of the ops themselves and be exportable when
// appropriate. If a step is not being exported it should not be returned
// from an ops action function. If the step is exported it should be
// returned from an ops action function.
//
// Not all types implementing opStep are expected to need all the functions
// in the interface. However, for the sake of simplicity on the op side, we use
// the same interface for all cases and expect those implementing opStep
// just embed the without* types that provide no-op implementation of
// functions that make up this interface.
type opStep interface {
// update the state of the step after the call to Operate.
// It can be used to convert values from C and cache them and/or
// communicate a failure of the action associated with the step. The
// update call will only be made once. Implementations are not required to
// handle this call being made more than once.
update() error
// free will be called to free any resources, especially C memory, that
// the step is managing. The behavior of free should be idempotent and
// handle being called more than once.
free()
}
// operation represents some of the shared underlying mechanisms for
// both read and write op types.
type operation struct {
steps []opStep
}
// free will call the free method of all the steps this operation
// contains.
func (o *operation) free() {
for i := range o.steps {
o.steps[i].free()
}
}
// update the operation and the steps it contains. The top-level result
// of the rados call is passed in as ret and used to construct errors.
// The update call of each step is used to update the contents of each
// step and gather any errors from those steps.
func (o *operation) update(kind opKind, ret C.int) error {
stepErrors := map[int]error{}
for i := range o.steps {
if err := o.steps[i].update(); err != nil {
stepErrors[i] = err
}
}
if ret == 0 && len(stepErrors) == 0 {
return nil
}
return OperationError{
kind: kind,
OpError: getError(ret),
StepErrors: stepErrors,
}
}
func opStepFinalizer(s opStep) {
if s != nil {
log.Warnf("unreachable opStep object found. Cleaning up.")
s.free()
}
}
// withoutUpdate can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// update function.
type withoutUpdate struct{}
func (*withoutUpdate) update() error { return nil }
// withoutFree can be embedded in a struct to help indicate
// the type implements the opStep interface but has a no-op
// free function.
type withoutFree struct{}
func (*withoutFree) free() {}
// withRefs is a embeddable type to help track and free C memory.
type withRefs struct {
refs []unsafe.Pointer
}
func (w *withRefs) free() {
for i := range w.refs {
C.free(w.refs[i])
}
w.refs = nil
}
func (w *withRefs) add(ptr unsafe.Pointer) {
w.refs = append(w.refs, ptr)
}
+37
View File
@@ -0,0 +1,37 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
// OperationFlags control the behavior of read and write operations.
type OperationFlags int
const (
// OperationNoFlag indicates no special behavior is requested.
OperationNoFlag = OperationFlags(C.LIBRADOS_OPERATION_NOFLAG)
// OperationBalanceReads TODO
OperationBalanceReads = OperationFlags(C.LIBRADOS_OPERATION_BALANCE_READS)
// OperationLocalizeReads TODO
OperationLocalizeReads = OperationFlags(C.LIBRADOS_OPERATION_LOCALIZE_READS)
// OperationOrderReadsWrites TODO
OperationOrderReadsWrites = OperationFlags(C.LIBRADOS_OPERATION_ORDER_READS_WRITES)
// OperationIgnoreCache TODO
OperationIgnoreCache = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_CACHE)
// OperationSkipRWLocks TODO
OperationSkipRWLocks = OperationFlags(C.LIBRADOS_OPERATION_SKIPRWLOCKS)
// OperationIgnoreOverlay TODO
OperationIgnoreOverlay = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_OVERLAY)
// OperationFullTry send request to a full cluster or pool, ops such as delete
// can succeed while other ops will return out-of-space errors.
OperationFullTry = OperationFlags(C.LIBRADOS_OPERATION_FULL_TRY)
// OperationFullForce TODO
OperationFullForce = OperationFlags(C.LIBRADOS_OPERATION_FULL_FORCE)
// OperationIgnoreRedirect TODO
OperationIgnoreRedirect = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_REDIRECT)
// OperationOrderSnap TODO
OperationOrderSnap = OperationFlags(C.LIBRADOS_OPERATION_ORDERSNAP)
)
+132
View File
@@ -0,0 +1,132 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"runtime"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
const (
// AllNamespaces is used to reset a selected namespace to all
// namespaces. See the IOContext SetNamespace function.
AllNamespaces = C.LIBRADOS_ALL_NSPACES
// FIXME: for backwards compatibility
// RadosAllNamespaces is used to reset a selected namespace to all
// namespaces. See the IOContext SetNamespace function.
//
// Deprecated: use AllNamespaces instead
RadosAllNamespaces = AllNamespaces
)
// OpFlags are flags that can be set on a per-op basis.
type OpFlags uint
const (
// OpFlagNone can be use to not set any flags.
OpFlagNone = OpFlags(0)
// OpFlagExcl marks an op to fail a create operation if the object
// already exists.
OpFlagExcl = OpFlags(C.LIBRADOS_OP_FLAG_EXCL)
// OpFlagFailOk allows the transaction to succeed even if the flagged
// op fails.
OpFlagFailOk = OpFlags(C.LIBRADOS_OP_FLAG_FAILOK)
// OpFlagFAdviseRandom indicates read/write op random.
OpFlagFAdviseRandom = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_RANDOM)
// OpFlagFAdviseSequential indicates read/write op sequential.
OpFlagFAdviseSequential = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL)
// OpFlagFAdviseWillNeed indicates read/write data will be accessed in
// the near future (by someone).
OpFlagFAdviseWillNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_WILLNEED)
// OpFlagFAdviseDontNeed indicates read/write data will not accessed in
// the near future (by anyone).
OpFlagFAdviseDontNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
// OpFlagFAdviseNoCache indicates read/write data will not accessed
// again (by *this* client).
OpFlagFAdviseNoCache = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_NOCACHE)
)
// Version returns the major, minor, and patch components of the version of
// the RADOS library linked against.
func Version() (int, int, int) {
var cMajor, cMinor, cPatch C.int
C.rados_version(&cMajor, &cMinor, &cPatch)
return int(cMajor), int(cMinor), int(cPatch)
}
func makeConn() *Conn {
return &Conn{connected: false}
}
func newConn(user *C.char) (*Conn, error) {
conn := makeConn()
ret := C.rados_create(&conn.cluster, user)
if ret != 0 {
return nil, getError(ret)
}
runtime.SetFinalizer(conn, freeConn)
return conn, nil
}
// NewConn creates a new connection object. It returns the connection and an
// error, if any.
func NewConn() (*Conn, error) {
return newConn(nil)
}
// NewConnWithUser creates a new connection object with a custom username.
// The supplied username should not include a 'client.' prefix. It returns
// the connection and an error, if any.
func NewConnWithUser(user string) (*Conn, error) {
cUser := C.CString(user)
defer C.free(unsafe.Pointer(cUser))
return newConn(cUser)
}
// NewConnWithClusterAndUser creates a new connection object for a specific
// cluster and username. The supplied username should be in the 'client.<user>'
// format. It returns the connection and an error, if any.
func NewConnWithClusterAndUser(clusterName string, userName string) (*Conn, error) {
cClusterName := C.CString(clusterName)
defer C.free(unsafe.Pointer(cClusterName))
cName := C.CString(userName)
defer C.free(unsafe.Pointer(cName))
conn := makeConn()
ret := C.rados_create2(&conn.cluster, cClusterName, cName, 0)
if ret != 0 {
return nil, getError(ret)
}
runtime.SetFinalizer(conn, freeConn)
return conn, nil
}
// freeConn releases resources that are allocated while configuring the
// connection to the cluster. rados_shutdown() should only be needed after a
// successful call to rados_connect(), however if the connection has been
// configured with non-default parameters, some of the parameters may be
// allocated before connecting. rados_shutdown() will free the allocated
// resources, even if there has not been a connection yet.
//
// This function is setup as a destructor/finalizer when rados_create() is
// called.
func freeConn(conn *Conn) {
if conn.cluster != nil {
log.Warnf("unreachable Conn object has not been shut down. Cleaning up.")
C.rados_shutdown(conn.cluster)
// prevent calling rados_shutdown() more than once
conn.cluster = nil
}
}
+28
View File
@@ -0,0 +1,28 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"unsafe"
)
// GetAddrs returns the addresses of the RADOS session,
// suitable for blocklisting.
//
// Implements:
//
// int rados_getaddrs(rados_t cluster, char **addrs)
func (c *Conn) GetAddrs() (string, error) {
var cAddrs *C.char
defer C.free(unsafe.Pointer(cAddrs))
ret := C.rados_getaddrs(c.cluster, &cAddrs)
if ret < 0 {
return "", getError(ret)
}
return C.GoString(cAddrs), nil
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !mimic
// +build !mimic
package rados
// #include <rados/librados.h>
import "C"
const (
// OpFlagFAdviseFUA optionally support FUA (force unit access) on write
// requests.
OpFlagFAdviseFUA = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_FUA)
)
@@ -0,0 +1,19 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// AssertVersion ensures that the object exists and that its internal version
// number is equal to "ver" before reading. "ver" should be a version number
// previously obtained with IOContext.GetLastVersion().
//
// Implements:
//
// void rados_read_op_assert_version(rados_read_op_t read_op,
// uint64_t ver)
func (r *ReadOp) AssertVersion(ver uint64) {
C.rados_read_op_assert_version(r.op, C.uint64_t(ver))
}
+28
View File
@@ -0,0 +1,28 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetLocator sets the key for mapping objects to pgs within an io context.
// Until a different locator key is set, all objects in this io context will be placed in the same pg.
// To reset the locator, an empty string must be set.
//
// Implements:
//
// void rados_ioctx_locator_set_key(rados_ioctx_t io, const char *key);
func (ioctx *IOContext) SetLocator(locator string) {
if locator == "" {
C.rados_ioctx_locator_set_key(ioctx.ioctx, nil)
} else {
var cLoc *C.char = C.CString(locator)
defer C.free(unsafe.Pointer(cLoc))
C.rados_ioctx_locator_set_key(ioctx.ioctx, cLoc)
}
}
@@ -0,0 +1,19 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// AssertVersion ensures that the object exists and that its internal version
// number is equal to "ver" before writing. "ver" should be a version number
// previously obtained with IOContext.GetLastVersion().
//
// Implements:
//
// void rados_read_op_assert_version(rados_read_op_t read_op,
// uint64_t ver)
func (w *WriteOp) AssertVersion(ver uint64) {
C.rados_write_op_assert_version(w.op, C.uint64_t(ver))
}
+16
View File
@@ -0,0 +1,16 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
// Remove object.
//
// Implements:
//
// void rados_write_op_remove(rados_write_op_t write_op)
func (w *WriteOp) Remove() {
C.rados_write_op_remove(w.op)
}
+31
View File
@@ -0,0 +1,31 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// SetXattr sets an xattr.
//
// Implements:
//
// void rados_write_op_setxattr(rados_write_op_t write_op,
// const char * name,
// const char * value,
// size_t value_len)
func (w *WriteOp) SetXattr(name string, value []byte) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
C.rados_write_op_setxattr(
w.op,
cName,
(*C.char)(unsafe.Pointer(&value[0])),
C.size_t(len(value)),
)
}
+91
View File
@@ -0,0 +1,91 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"unsafe"
)
// ReadOp manages a set of discrete object read actions that will be performed
// together atomically.
type ReadOp struct {
operation
op C.rados_read_op_t
}
// CreateReadOp returns a newly constructed read operation.
func CreateReadOp() *ReadOp {
return &ReadOp{
op: C.rados_create_read_op(),
}
}
// Release the resources associated with this read operation.
func (r *ReadOp) Release() {
C.rados_release_read_op(r.op)
r.op = nil
r.free()
}
// Operate will perform the operation(s).
func (r *ReadOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
if err := ioctx.validate(); err != nil {
return err
}
cOid := C.CString(oid)
defer C.free(unsafe.Pointer(cOid))
ret := C.rados_read_op_operate(r.op, ioctx.ioctx, cOid, C.int(flags))
return r.update(readOp, ret)
}
func (r *ReadOp) operateCompat(ioctx *IOContext, oid string) error {
switch err := r.Operate(ioctx, oid, OperationNoFlag).(type) {
case nil:
return nil
case OperationError:
return err.OpError
default:
return err
}
}
// AssertExists assures the object targeted by the read op exists.
//
// Implements:
//
// void rados_read_op_assert_exists(rados_read_op_t read_op);
func (r *ReadOp) AssertExists() {
C.rados_read_op_assert_exists(r.op)
}
// GetOmapValues is used to iterate over a set, or sub-set, of omap keys
// as part of a read operation. An GetOmapStep is returned from this
// function. The GetOmapStep may be used to iterate over the key-value
// pairs after the Operate call has been performed.
func (r *ReadOp) GetOmapValues(startAfter, filterPrefix string, maxReturn uint64) *GetOmapStep {
gos := newGetOmapStep()
r.steps = append(r.steps, gos)
cStartAfter := C.CString(startAfter)
cFilterPrefix := C.CString(filterPrefix)
defer C.free(unsafe.Pointer(cStartAfter))
defer C.free(unsafe.Pointer(cFilterPrefix))
C.rados_read_op_omap_get_vals2(
r.op,
cStartAfter,
cFilterPrefix,
C.uint64_t(maxReturn),
&gos.iter,
gos.more,
gos.rval,
)
return gos
}
+108
View File
@@ -0,0 +1,108 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"runtime"
"unsafe"
)
// ReadOpExecStep - step for exec operation code.
type ReadOpExecStep struct {
withoutFree
inBuffPtr *C.char
inBuffLen C.size_t
outBuffPtr *C.char
outBuffLen C.size_t
prval C.int
canReadOutput bool
}
// newExecStepOp - init new *execStepOp.
func newReadOpExecStep(in []byte) *ReadOpExecStep {
es := &ReadOpExecStep{
outBuffPtr: nil,
outBuffLen: 0,
prval: 0,
}
if len(in) > 0 {
es.inBuffPtr = (*C.char)(unsafe.Pointer(&in[0]))
es.inBuffLen = C.size_t(len(in))
}
runtime.SetFinalizer(es, func(es *ReadOpExecStep) {
if es != nil {
es.freeBuffer()
es = nil
}
})
return es
}
// freeBuffer - releases C allocated buffer. It is separated from es.free() because lifespan of C allocated buffer is
// longer than lifespan of read operation.
func (es *ReadOpExecStep) freeBuffer() {
if es.outBuffPtr != nil {
C.free(unsafe.Pointer(es.outBuffPtr))
es.outBuffPtr = nil
es.canReadOutput = false
}
}
// update - update state operation.
func (es *ReadOpExecStep) update() error {
err := getError(es.prval)
es.canReadOutput = err == nil
return err
}
// Bytes returns the result of the executed command as a byte slice.
func (es *ReadOpExecStep) Bytes() ([]byte, error) {
if !es.canReadOutput {
return nil, ErrOperationIncomplete
}
return C.GoBytes(unsafe.Pointer(es.outBuffPtr), C.int(es.outBuffLen)), nil
}
// Exec executes an OSD class method on an object.
// See rados_exec() in the RADOS C api documentation for a general description.
//
// Implements:
//
// void rados_read_op_exec(rados_read_op_t read_op,
// const char *cls,
// const char *method,
// const char *in_buf,
// size_t in_len,
// char **out_buf,
// size_t *out_len,
// int *prval);
func (r *ReadOp) Exec(clsName, method string, in []byte) *ReadOpExecStep {
cClsName := C.CString(clsName)
defer C.free(unsafe.Pointer(cClsName))
cMethod := C.CString(method)
defer C.free(unsafe.Pointer(cMethod))
es := newReadOpExecStep(in)
r.steps = append(r.steps, es)
C.rados_read_op_exec(
r.op,
cClsName,
cMethod,
es.inBuffPtr,
es.inBuffLen,
&es.outBuffPtr,
&es.outBuffLen,
&es.prval,
)
return es
}
@@ -0,0 +1,113 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
)
// ReadOpOmapGetValsByKeysStep holds the result of the
// GetOmapValuesByKeys read operation.
// Result is valid only after Operate() was called.
type ReadOpOmapGetValsByKeysStep struct {
// C arguments
iter C.rados_omap_iter_t
prval *C.int
// Internal state
// canIterate is only set after the operation is performed and is
// intended to prevent premature fetching of data.
canIterate bool
}
func newReadOpOmapGetValsByKeysStep() *ReadOpOmapGetValsByKeysStep {
s := &ReadOpOmapGetValsByKeysStep{
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
return s
}
func (s *ReadOpOmapGetValsByKeysStep) free() {
s.canIterate = false
C.rados_omap_get_end(s.iter)
C.free(unsafe.Pointer(s.prval))
s.prval = nil
}
func (s *ReadOpOmapGetValsByKeysStep) update() error {
err := getError(*s.prval)
s.canIterate = (err == nil)
return err
}
// Next gets the next omap key/value pair referenced by
// ReadOpOmapGetValsByKeysStep's internal iterator.
// If there are no more elements to retrieve, (nil, nil) is returned.
// May be called only after Operate() finished.
func (s *ReadOpOmapGetValsByKeysStep) Next() (*OmapKeyValue, error) {
if !s.canIterate {
return nil, ErrOperationIncomplete
}
var (
cKey *C.char
cVal *C.char
cKeyLen C.size_t
cValLen C.size_t
)
ret := C.rados_omap_get_next2(s.iter, &cKey, &cVal, &cKeyLen, &cValLen)
if ret != 0 {
return nil, getError(ret)
}
if cKey == nil {
// Iterator has reached the end of the list.
return nil, nil
}
return &OmapKeyValue{
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
}, nil
}
// GetOmapValuesByKeys starts iterating over specific key/value pairs.
//
// Implements:
//
// void rados_read_op_omap_get_vals_by_keys2(rados_read_op_t read_op,
// char const * const * keys,
// size_t num_keys,
// const size_t * key_lens,
// rados_omap_iter_t * iter,
// int * prval)
func (r *ReadOp) GetOmapValuesByKeys(keys []string) *ReadOpOmapGetValsByKeysStep {
s := newReadOpOmapGetValsByKeysStep()
r.steps = append(r.steps, s)
cKeys := cutil.NewBufferGroupStrings(keys)
defer cKeys.Free()
C.rados_read_op_omap_get_vals_by_keys2(
r.op,
(**C.char)(cKeys.BuffersPtr()),
C.size_t(len(keys)),
(*C.size_t)(cKeys.LengthsPtr()),
&s.iter,
s.prval,
)
return s
}
+72
View File
@@ -0,0 +1,72 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// ReadOpReadStep holds the result of the Read read operation.
// Result is valid only after Operate() was called.
type ReadOpReadStep struct {
// C returned data:
bytesRead *C.size_t
prval *C.int
BytesRead int64 // Bytes read by this action.
Result int // Result of this action.
}
func (s *ReadOpReadStep) update() error {
s.BytesRead = (int64)(*s.bytesRead)
s.Result = (int)(*s.prval)
return nil
}
func (s *ReadOpReadStep) free() {
C.free(unsafe.Pointer(s.bytesRead))
C.free(unsafe.Pointer(s.prval))
s.bytesRead = nil
s.prval = nil
}
func newReadOpReadStep() *ReadOpReadStep {
return &ReadOpReadStep{
bytesRead: (*C.size_t)(C.malloc(C.sizeof_size_t)),
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
}
// Read bytes from offset into buffer.
// len(buffer) is the maximum number of bytes read from the object.
// buffer[:ReadOpReadStep.BytesRead] then contains object data.
//
// Implements:
//
// void rados_read_op_read(rados_read_op_t read_op,
// uint64_t offset,
// size_t len,
// char * buffer,
// size_t * bytes_read,
// int * prval)
func (r *ReadOp) Read(offset uint64, buffer []byte) *ReadOpReadStep {
oe := newReadStep(buffer, offset)
readStep := newReadOpReadStep()
r.steps = append(r.steps, oe, readStep)
C.rados_read_op_read(
r.op,
oe.cOffset,
oe.cReadLen,
oe.cBuffer,
readStep.bytesRead,
readStep.prval,
)
return readStep
}
+31
View File
@@ -0,0 +1,31 @@
package rados
// #include <stdint.h>
import "C"
import (
"unsafe"
)
type readStep struct {
withoutUpdate
withoutFree
// the c pointer utilizes the Go byteslice data and no free is needed
// inputs:
b []byte
// arguments:
cBuffer *C.char
cReadLen C.size_t
cOffset C.uint64_t
}
func newReadStep(b []byte, offset uint64) *readStep {
return &readStep{
b: b,
cBuffer: (*C.char)(unsafe.Pointer(&b[0])), // TODO: must be pinned
cReadLen: C.size_t(len(b)),
cOffset: C.uint64_t(offset),
}
}
+196
View File
@@ -0,0 +1,196 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
import "C"
import (
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/retry"
)
// CreateSnap creates a pool-wide snapshot.
//
// Implements:
// int rados_ioctx_snap_create(rados_ioctx_t io, const char *snapname)
func (ioctx *IOContext) CreateSnap(snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_create(ioctx.ioctx, cSnapName)
return getError(ret)
}
// RemoveSnap deletes the pool snapshot.
//
// Implements:
//
// int rados_ioctx_snap_remove(rados_ioctx_t io, const char *snapname)
func (ioctx *IOContext) RemoveSnap(snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_remove(ioctx.ioctx, cSnapName)
return getError(ret)
}
// SnapID represents the ID of a rados snapshot.
type SnapID C.rados_snap_t
// LookupSnap returns the ID of a pool snapshot.
//
// Implements:
//
// int rados_ioctx_snap_lookup(rados_ioctx_t io, const char *name, rados_snap_t *id)
func (ioctx *IOContext) LookupSnap(snapName string) (SnapID, error) {
var snapID SnapID
if err := ioctx.validate(); err != nil {
return snapID, err
}
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_lookup(
ioctx.ioctx,
cSnapName,
(*C.rados_snap_t)(&snapID))
return snapID, getError(ret)
}
// GetSnapName returns the name of a pool snapshot with the given snapshot ID.
//
// Implements:
//
// int rados_ioctx_snap_get_name(rados_ioctx_t io, rados_snap_t id, char *name, int maxlen)
func (ioctx *IOContext) GetSnapName(snapID SnapID) (string, error) {
if err := ioctx.validate(); err != nil {
return "", err
}
var (
buf []byte
err error
)
// range from 1k to 64KiB
retry.WithSizes(1024, 1<<16, func(length int) retry.Hint {
cLen := C.int(length)
buf = make([]byte, cLen)
ret := C.rados_ioctx_snap_get_name(
ioctx.ioctx,
(C.rados_snap_t)(snapID),
(*C.char)(unsafe.Pointer(&buf[0])),
cLen)
err = getError(ret)
return retry.Size(int(cLen)).If(err == errRange)
})
if err != nil {
return "", err
}
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
}
// GetSnapStamp returns the time of the pool snapshot creation.
//
// Implements:
//
// int rados_ioctx_snap_get_stamp(rados_ioctx_t io, rados_snap_t id, time_t *t)
func (ioctx *IOContext) GetSnapStamp(snapID SnapID) (time.Time, error) {
var cTime C.time_t
if err := ioctx.validate(); err != nil {
return time.Unix(int64(cTime), 0), err
}
ret := C.rados_ioctx_snap_get_stamp(
ioctx.ioctx,
(C.rados_snap_t)(snapID),
&cTime)
return time.Unix(int64(cTime), 0), getError(ret)
}
// ListSnaps returns a slice containing the SnapIDs of existing pool snapshots.
//
// Implements:
//
// int rados_ioctx_snap_list(rados_ioctx_t io, rados_snap_t *snaps, int maxlen)
func (ioctx *IOContext) ListSnaps() ([]SnapID, error) {
if err := ioctx.validate(); err != nil {
return nil, err
}
var (
snapList []SnapID
cLen C.int
err error
ret C.int
)
retry.WithSizes(100, 1000, func(maxlen int) retry.Hint {
cLen = C.int(maxlen)
snapList = make([]SnapID, cLen)
ret = C.rados_ioctx_snap_list(
ioctx.ioctx,
(*C.rados_snap_t)(unsafe.Pointer(&snapList[0])),
cLen)
err = getErrorIfNegative(ret)
return retry.Size(int(cLen)).If(err == errRange)
})
if err != nil {
return nil, err
}
return snapList[:ret], nil
}
// RollbackSnap rollbacks the object with key oID to the pool snapshot.
// The contents of the object will be the same as when the snapshot was taken.
//
// Implements:
//
// int rados_ioctx_snap_rollback(rados_ioctx_t io, const char *oid, const char *snapname);
func (ioctx *IOContext) RollbackSnap(oid, snapName string) error {
if err := ioctx.validate(); err != nil {
return err
}
coid := C.CString(oid)
defer C.free(unsafe.Pointer(coid))
cSnapName := C.CString(snapName)
defer C.free(unsafe.Pointer(cSnapName))
ret := C.rados_ioctx_snap_rollback(ioctx.ioctx, coid, cSnapName)
return getError(ret)
}
// SnapHead is the representation of LIBRADOS_SNAP_HEAD from librados.
// SnapHead can be used to reset the IOContext to stop reading from a snapshot.
const SnapHead = SnapID(C.LIBRADOS_SNAP_HEAD)
// SetReadSnap sets the snapshot from which reads are performed.
// Subsequent reads will return data as it was at the time of that snapshot.
// Pass SnapHead for no snapshot (i.e. normal operation).
//
// Implements:
//
// void rados_ioctx_snap_set_read(rados_ioctx_t io, rados_snap_t snap);
func (ioctx *IOContext) SetReadSnap(snapID SnapID) error {
if err := ioctx.validate(); err != nil {
return err
}
C.rados_ioctx_snap_set_read(ioctx.ioctx, (C.rados_snap_t)(snapID))
return nil
}
+375
View File
@@ -0,0 +1,375 @@
package rados
/*
#cgo LDFLAGS: -lrados
#include <stdlib.h>
#include <rados/librados.h>
extern void watchNotifyCb(void*, uint64_t, uint64_t, uint64_t, void*, size_t);
extern void watchErrorCb(void*, uint64_t, int);
*/
import "C"
import (
"encoding/binary"
"fmt"
"math"
"sync"
"time"
"unsafe"
"github.com/ceph/go-ceph/internal/log"
)
type (
// WatcherID is the unique id of a Watcher.
WatcherID uint64
// NotifyID is the unique id of a NotifyEvent.
NotifyID uint64
// NotifierID is the unique id of a notifying client.
NotifierID uint64
)
// NotifyEvent is received by a watcher for each notification.
type NotifyEvent struct {
ID NotifyID
WatcherID WatcherID
NotifierID NotifierID
Data []byte
}
// NotifyAck represents an acknowleged notification.
type NotifyAck struct {
WatcherID WatcherID
NotifierID NotifierID
Response []byte
}
// NotifyTimeout represents an unacknowleged notification.
type NotifyTimeout struct {
WatcherID WatcherID
NotifierID NotifierID
}
// Watcher receives all notifications for certain object.
type Watcher struct {
id WatcherID
oid string
ioctx *IOContext
events chan NotifyEvent
errors chan error
done chan struct{}
}
var (
watchers = map[WatcherID]*Watcher{}
watchersMtx sync.RWMutex
)
// Watch creates a Watcher for the specified object.
//
// A Watcher receives all notifications that are sent to the object on which it
// has been created. It exposes two read-only channels: Events() receives all
// the NotifyEvents and Errors() receives all occuring errors. A typical code
// creating a Watcher could look like this:
//
// watcher, err := ioctx.Watch(oid)
// go func() { // event handler
// for ne := range watcher.Events() {
// ...
// ne.Ack([]byte("response data..."))
// ...
// }
// }()
// go func() { // error handler
// for err := range watcher.Errors() {
// ... handle err ...
// }
// }()
//
// CAUTION: the Watcher references the IOContext in which it has been created.
// Therefore all watchers must be deleted with the Delete() method before the
// IOContext is being destroyed.
//
// Implements:
//
// int rados_watch2(rados_ioctx_t io, const char* o, uint64_t* cookie,
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, void* arg)
func (ioctx *IOContext) Watch(obj string) (*Watcher, error) {
return ioctx.WatchWithTimeout(obj, 0)
}
// WatchWithTimeout creates a watcher on an object. Same as Watcher(), but
// different timeout than the default can be specified.
//
// Implements:
//
// int rados_watch3(rados_ioctx_t io, const char *o, uint64_t *cookie,
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, uint32_t timeout,
// void *arg);
func (ioctx *IOContext) WatchWithTimeout(oid string, timeout time.Duration) (*Watcher, error) {
cObj := C.CString(oid)
defer C.free(unsafe.Pointer(cObj))
var id C.uint64_t
watchersMtx.Lock()
defer watchersMtx.Unlock()
ret := C.rados_watch3(
ioctx.ioctx,
cObj,
&id,
(C.rados_watchcb2_t)(C.watchNotifyCb),
(C.rados_watcherrcb_t)(C.watchErrorCb),
C.uint32_t(timeout.Milliseconds()/1000),
nil,
)
if err := getError(ret); err != nil {
return nil, err
}
evCh := make(chan NotifyEvent)
errCh := make(chan error)
w := &Watcher{
id: WatcherID(id),
ioctx: ioctx,
oid: oid,
events: evCh,
errors: errCh,
done: make(chan struct{}),
}
watchers[WatcherID(id)] = w
return w, nil
}
// ID returns the WatcherId of the Watcher
func (w *Watcher) ID() WatcherID {
return w.id
}
// Events returns a read-only channel, that receives all notifications that are
// sent to the object of the Watcher.
func (w *Watcher) Events() <-chan NotifyEvent {
return w.events
}
// Errors returns a read-only channel, that receives all errors for the Watcher.
func (w *Watcher) Errors() <-chan error {
return w.errors
}
// Check on the status of a Watcher.
//
// Returns the time since it was last confirmed. If there is an error, the
// Watcher is no longer valid, and should be destroyed with the Delete() method.
//
// Implements:
//
// int rados_watch_check(rados_ioctx_t io, uint64_t cookie)
func (w *Watcher) Check() (time.Duration, error) {
ret := C.rados_watch_check(w.ioctx.ioctx, C.uint64_t(w.id))
if ret < 0 {
return 0, getError(ret)
}
return time.Millisecond * time.Duration(ret), nil
}
// Delete the watcher. This closes both the event and error channel.
//
// Implements:
//
// int rados_unwatch2(rados_ioctx_t io, uint64_t cookie)
func (w *Watcher) Delete() error {
watchersMtx.Lock()
_, ok := watchers[w.id]
if ok {
delete(watchers, w.id)
}
watchersMtx.Unlock()
if !ok {
return nil
}
ret := C.rados_unwatch2(w.ioctx.ioctx, C.uint64_t(w.id))
if ret != 0 {
return getError(ret)
}
close(w.done) // unblock blocked callbacks
close(w.events)
close(w.errors)
return nil
}
// Notify sends a notification with the provided data to all Watchers of the
// specified object.
//
// CAUTION: even if the error is not nil. the returned slices
// might still contain data.
func (ioctx *IOContext) Notify(obj string, data []byte) ([]NotifyAck, []NotifyTimeout, error) {
return ioctx.NotifyWithTimeout(obj, data, 0)
}
// NotifyWithTimeout is like Notify() but with a different timeout than the
// default.
//
// Implements:
//
// int rados_notify2(rados_ioctx_t io, const char* o, const char* buf, int buf_len,
// uint64_t timeout_ms, char** reply_buffer, size_t* reply_buffer_len)
func (ioctx *IOContext) NotifyWithTimeout(obj string, data []byte, timeout time.Duration) ([]NotifyAck,
[]NotifyTimeout, error) {
cObj := C.CString(obj)
defer C.free(unsafe.Pointer(cObj))
var cResponse *C.char
defer C.rados_buffer_free(cResponse)
var responseLen C.size_t
var dataPtr *C.char
if len(data) > 0 {
dataPtr = (*C.char)(unsafe.Pointer(&data[0]))
}
ret := C.rados_notify2(
ioctx.ioctx,
cObj,
dataPtr,
C.int(len(data)),
C.uint64_t(timeout.Milliseconds()),
&cResponse,
&responseLen,
)
// cResponse has been set even if an error is returned, so we decode it anyway
acks, timeouts := decodeNotifyResponse(cResponse, responseLen)
return acks, timeouts, getError(ret)
}
// Ack sends an acknowledgement with the specified response data to the notfier
// of the NotifyEvent. If a notify is not ack'ed, the originating Notify() call
// blocks and eventiually times out.
//
// Implements:
//
// int rados_notify_ack(rados_ioctx_t io, const char *o, uint64_t notify_id,
// uint64_t cookie, const char *buf, int buf_len)
func (ne *NotifyEvent) Ack(response []byte) error {
watchersMtx.RLock()
w, ok := watchers[ne.WatcherID]
watchersMtx.RUnlock()
if !ok {
return fmt.Errorf("can't ack on deleted watcher %v", ne.WatcherID)
}
cOID := C.CString(w.oid)
defer C.free(unsafe.Pointer(cOID))
var respPtr *C.char
if len(response) > 0 {
respPtr = (*C.char)(unsafe.Pointer(&response[0]))
}
ret := C.rados_notify_ack(
w.ioctx.ioctx,
cOID,
C.uint64_t(ne.ID),
C.uint64_t(ne.WatcherID),
respPtr,
C.int(len(response)),
)
return getError(ret)
}
// WatcherFlush flushes all pending notifications of the cluster.
//
// Implements:
//
// int rados_watch_flush(rados_t cluster)
func (c *Conn) WatcherFlush() error {
if !c.connected {
return ErrNotConnected
}
ret := C.rados_watch_flush(c.cluster)
return getError(ret)
}
// decoder for this notify response format:
//
// le32 num_acks
// {
// le64 gid global id for the client (for client.1234 that's 1234)
// le64 cookie cookie for the client
// le32 buflen length of reply message buffer
// u8 buflen payload
// } num_acks
// le32 num_timeouts
// {
// le64 gid global id for the client
// le64 cookie cookie for the client
// } num_timeouts
//
// NOTE: starting with pacific this is implemented as a C function and this can
// be replaced later
func decodeNotifyResponse(response *C.char, length C.size_t) ([]NotifyAck, []NotifyTimeout) {
if length == 0 || response == nil {
return nil, nil
}
b := (*[math.MaxInt32]byte)(unsafe.Pointer(response))[:length:length]
pos := 0
num := binary.LittleEndian.Uint32(b[pos:])
pos += 4
acks := make([]NotifyAck, num)
for i := range acks {
acks[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
acks[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
dataLen := binary.LittleEndian.Uint32(b[pos:])
pos += 4
if dataLen > 0 {
acks[i].Response = C.GoBytes(unsafe.Pointer(&b[pos]), C.int(dataLen))
pos += int(dataLen)
}
}
num = binary.LittleEndian.Uint32(b[pos:])
pos += 4
timeouts := make([]NotifyTimeout, num)
for i := range timeouts {
timeouts[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
timeouts[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
pos += 8
}
return acks, timeouts
}
//export watchNotifyCb
func watchNotifyCb(_ unsafe.Pointer, notifyID C.uint64_t, id C.uint64_t,
notifierID C.uint64_t, cData unsafe.Pointer, dataLen C.size_t) {
ev := NotifyEvent{
ID: NotifyID(notifyID),
WatcherID: WatcherID(id),
NotifierID: NotifierID(notifierID),
}
if dataLen > 0 {
ev.Data = C.GoBytes(cData, C.int(dataLen))
}
watchersMtx.RLock()
w, ok := watchers[WatcherID(id)]
watchersMtx.RUnlock()
if !ok {
// usually this should not happen, but who knows
log.Warnf("received notification for unknown watcher ID: %#v", ev)
return
}
select {
case <-w.done: // unblock when deleted
case w.events <- ev:
}
}
//export watchErrorCb
func watchErrorCb(_ unsafe.Pointer, id C.uint64_t, err C.int) {
watchersMtx.RLock()
w, ok := watchers[WatcherID(id)]
watchersMtx.RUnlock()
if !ok {
// usually this should not happen, but who knows
log.Warnf("received error for unknown watcher ID: id=%d err=%#v", id, err)
return
}
select {
case <-w.done: // unblock when deleted
case w.errors <- getError(err):
}
}
+199
View File
@@ -0,0 +1,199 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
ts "github.com/ceph/go-ceph/internal/timespec"
)
// Timespec is a public type for the internal C 'struct timespec'
type Timespec ts.Timespec
// WriteOp manages a set of discrete actions that will be performed together
// atomically.
type WriteOp struct {
operation
op C.rados_write_op_t
}
// CreateWriteOp returns a newly constructed write operation.
func CreateWriteOp() *WriteOp {
return &WriteOp{
op: C.rados_create_write_op(),
}
}
// Release the resources associated with this write operation.
func (w *WriteOp) Release() {
C.rados_release_write_op(w.op)
w.op = nil
w.free()
}
func (w WriteOp) operate2(
ioctx *IOContext, oid string, mtime *Timespec, flags OperationFlags) error {
if err := ioctx.validate(); err != nil {
return err
}
cOid := C.CString(oid)
defer C.free(unsafe.Pointer(cOid))
var cMtime *C.struct_timespec
if mtime != nil {
cMtime = &C.struct_timespec{}
ts.CopyToCStruct(
ts.Timespec(*mtime),
ts.CTimespecPtr(cMtime))
}
ret := C.rados_write_op_operate2(
w.op, ioctx.ioctx, cOid, cMtime, C.int(flags))
return w.update(writeOp, ret)
}
// Operate will perform the operation(s).
func (w *WriteOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
return w.operate2(ioctx, oid, nil, flags)
}
// OperateWithMtime will perform the operation while setting the modification
// time stamp to the supplied value.
func (w *WriteOp) OperateWithMtime(
ioctx *IOContext, oid string, mtime Timespec, flags OperationFlags) error {
return w.operate2(ioctx, oid, &mtime, flags)
}
func (w *WriteOp) operateCompat(ioctx *IOContext, oid string) error {
switch err := w.Operate(ioctx, oid, OperationNoFlag).(type) {
case nil:
return nil
case OperationError:
return err.OpError
default:
return err
}
}
// Create a rados object.
func (w *WriteOp) Create(exclusive CreateOption) {
// category, the 3rd param, is deprecated and has no effect so we do not
// implement it in go-ceph
C.rados_write_op_create(w.op, C.int(exclusive), nil)
}
// SetOmap appends the map `pairs` to the omap `oid`.
func (w *WriteOp) SetOmap(pairs map[string][]byte) {
keys := make([]string, len(pairs))
values := make([][]byte, len(pairs))
idx := 0
for k, v := range pairs {
keys[idx] = k
values[idx] = v
idx++
}
cKeys := cutil.NewBufferGroupStrings(keys)
cValues := cutil.NewBufferGroupBytes(values)
defer cKeys.Free()
defer cValues.Free()
C.rados_write_op_omap_set2(
w.op,
(**C.char)(cKeys.BuffersPtr()),
(**C.char)(cValues.BuffersPtr()),
(*C.size_t)(cKeys.LengthsPtr()),
(*C.size_t)(cValues.LengthsPtr()),
(C.size_t)(len(pairs)))
}
// RmOmapKeys removes the specified `keys` from the omap `oid`.
func (w *WriteOp) RmOmapKeys(keys []string) {
cKeys := cutil.NewBufferGroupStrings(keys)
defer cKeys.Free()
C.rados_write_op_omap_rm_keys2(
w.op,
(**C.char)(cKeys.BuffersPtr()),
(*C.size_t)(cKeys.LengthsPtr()),
(C.size_t)(len(keys)))
}
// CleanOmap clears the omap `oid`.
func (w *WriteOp) CleanOmap() {
C.rados_write_op_omap_clear(w.op)
}
// AssertExists assures the object targeted by the write op exists.
//
// Implements:
//
// void rados_write_op_assert_exists(rados_write_op_t write_op);
func (w *WriteOp) AssertExists() {
C.rados_write_op_assert_exists(w.op)
}
// Write a given byte slice at the supplied offset.
//
// Implements:
//
// void rados_write_op_write(rados_write_op_t write_op,
// const char *buffer,
// size_t len,
// uint64_t offset);
func (w *WriteOp) Write(b []byte, offset uint64) {
oe := newWriteStep(b, 0, offset)
w.steps = append(w.steps, oe)
C.rados_write_op_write(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cOffset)
}
// WriteFull writes a given byte slice as the whole object,
// atomically replacing it.
//
// Implements:
//
// void rados_write_op_write_full(rados_write_op_t write_op,
// const char *buffer,
// size_t len);
func (w *WriteOp) WriteFull(b []byte) {
oe := newWriteStep(b, 0, 0)
w.steps = append(w.steps, oe)
C.rados_write_op_write_full(
w.op,
oe.cBuffer,
oe.cDataLen)
}
// WriteSame write a given byte slice to the object multiple times, until
// writeLen is satisfied.
//
// Implements:
//
// void rados_write_op_writesame(rados_write_op_t write_op,
// const char *buffer,
// size_t data_len,
// size_t write_len,
// uint64_t offset);
func (w *WriteOp) WriteSame(b []byte, writeLen, offset uint64) {
oe := newWriteStep(b, writeLen, offset)
w.steps = append(w.steps, oe)
C.rados_write_op_writesame(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cWriteLen,
oe.cOffset)
}
+60
View File
@@ -0,0 +1,60 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <rados/librados.h>
// #include <stdlib.h>
//
import "C"
import (
"unsafe"
)
// WriteOpCmpExtStep holds result of the CmpExt write operation.
// Result is valid only after Operate() was called.
type WriteOpCmpExtStep struct {
// C returned data:
prval *C.int
// Result of the CmpExt write operation.
Result int
}
func (s *WriteOpCmpExtStep) update() error {
s.Result = int(*s.prval)
return nil
}
func (s *WriteOpCmpExtStep) free() {
C.free(unsafe.Pointer(s.prval))
s.prval = nil
}
func newWriteOpCmpExtStep() *WriteOpCmpExtStep {
return &WriteOpCmpExtStep{
prval: (*C.int)(C.malloc(C.sizeof_int)),
}
}
// CmpExt ensures that given object range (extent) satisfies comparison.
//
// Implements:
//
// void rados_write_op_cmpext(rados_write_op_t write_op,
// const char * cmp_buf,
// size_t cmp_len,
// uint64_t off,
// int * prval);
func (w *WriteOp) CmpExt(b []byte, offset uint64) *WriteOpCmpExtStep {
oe := newWriteStep(b, 0, offset)
cmpExtStep := newWriteOpCmpExtStep()
w.steps = append(w.steps, oe, cmpExtStep)
C.rados_write_op_cmpext(
w.op,
oe.cBuffer,
oe.cDataLen,
oe.cOffset,
cmpExtStep.prval)
return cmpExtStep
}
+68
View File
@@ -0,0 +1,68 @@
package rados
// #cgo LDFLAGS: -lrados
// #include <stdlib.h>
// #include <rados/librados.h>
//
import "C"
import (
"unsafe"
)
// writeOpExecStep - exec step in write operation.
type writeOpExecStep struct {
withoutFree
inBuffPtr *C.char
inBuffLen C.size_t
prval C.int
}
// newWriteOpExecStep - init new *writeOpExecStep.
func newWriteOpExecStep(in []byte) *writeOpExecStep {
es := &writeOpExecStep{
prval: 0,
}
if len(in) > 0 {
es.inBuffPtr = (*C.char)(unsafe.Pointer(&in[0]))
es.inBuffLen = C.size_t(len(in))
}
return es
}
// update - update state operation.
func (es *writeOpExecStep) update() error {
return getError(es.prval)
}
// Exec executes an OSD class method on an object.
// See rados_exec() in the RADOS C api documentation for a general description.
//
// Implements:
//
// void rados_write_op_exec(rados_write_op_t write_op,
// const char *cls,
// const char *method,
// const char *in_buf,
// size_t in_len,
// int *prval)
func (w *WriteOp) Exec(clsName, method string, in []byte) {
cClsName := C.CString(clsName)
defer C.free(unsafe.Pointer(cClsName))
cMethod := C.CString(method)
defer C.free(unsafe.Pointer(cMethod))
es := newWriteOpExecStep(in)
w.steps = append(w.steps, es)
C.rados_write_op_exec(
w.op,
cClsName,
cMethod,
es.inBuffPtr,
es.inBuffLen,
&es.prval,
)
}
+26
View File
@@ -0,0 +1,26 @@
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
View File
@@ -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),
}
}