diff --git a/go.mod b/go.mod index ad2f535c5..bdb0d6d08 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ceph/ceph-csi go 1.13 require ( - github.com/ceph/go-ceph v0.7.0 + github.com/ceph/go-ceph v0.8.0 github.com/container-storage-interface/spec v1.3.0 github.com/go-logr/logr v0.2.1 // indirect github.com/golang/protobuf v1.4.3 diff --git a/go.sum b/go.sum index ed1c7ed21..7df3cd054 100644 --- a/go.sum +++ b/go.sum @@ -203,8 +203,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f h1:gJzxrodnNd/CtPXjO3WYiakyNzHg3rtAi7rO74ejHYU= github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE= -github.com/ceph/go-ceph v0.7.0 h1:+4oWZCuvh9B7/kZVJVw/DSuk9Qby38KWY1pMQ5gYGyY= -github.com/ceph/go-ceph v0.7.0/go.mod h1:wd+keAOqrcsN//20VQnHBGtnBnY0KHl0PA024Ng8HfQ= +github.com/ceph/go-ceph v0.8.0 h1:d+VP0eyconBl9RrvKVUq7S0npyK969ErLkCt5pg2fp0= +github.com/ceph/go-ceph v0.8.0/go.mod h1:wd+keAOqrcsN//20VQnHBGtnBnY0KHl0PA024Ng8HfQ= github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/flags.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/flags.go index 393ebe356..1ec994aed 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/flags.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/flags.go @@ -41,7 +41,7 @@ func (f SubVolRmFlags) flags() map[string]bool { o["force"] = true } if f.RetainSnapshots { - o["retain-snapshots"] = true + o["retain_snapshots"] = true } return o } diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go index ac189832a..69fb4f99d 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go @@ -197,23 +197,48 @@ const ( SnapshotRetentionFeature = Feature("snapshot-retention") ) +// SubVolumeState is used to define constant value for the state of +// a subvolume. +type SubVolumeState string + +const ( + // StateUnset indicates a subvolume without any state. + StateUnset = SubVolumeState("") + // StateInit indicates that the subvolume is in initializing state. + StateInit = SubVolumeState("init") + // StatePending indicates that the subvolume is in pending state. + StatePending = SubVolumeState("pending") + // StateInProgress indicates that the subvolume is in in-progress state. + StateInProgress = SubVolumeState("in-progress") + // StateFailed indicates that the subvolume is in failed state. + StateFailed = SubVolumeState("failed") + // StateComplete indicates that the subvolume is in complete state. + StateComplete = SubVolumeState("complete") + // StateCanceled indicates that the subvolume is in canceled state. + StateCanceled = SubVolumeState("canceled") + // StateSnapRetained indicates that the subvolume is in + // snapshot-retained state. + StateSnapRetained = SubVolumeState("snapshot-retained") +) + // SubVolumeInfo reports various informational values about a subvolume. type SubVolumeInfo struct { - Type string `json:"type"` - Path string `json:"path"` - Uid int `json:"uid"` - Gid int `json:"gid"` - Mode int `json:"mode"` - BytesPercent string `json:"bytes_pcent"` - BytesUsed ByteCount `json:"bytes_used"` - BytesQuota QuotaSize `json:"-"` - DataPool string `json:"data_pool"` - PoolNamespace string `json:"pool_namespace"` - Atime TimeStamp `json:"atime"` - Mtime TimeStamp `json:"mtime"` - Ctime TimeStamp `json:"ctime"` - CreatedAt TimeStamp `json:"created_at"` - Features []Feature `json:"features"` + Type string `json:"type"` + Path string `json:"path"` + State SubVolumeState `json:"state"` + Uid int `json:"uid"` + Gid int `json:"gid"` + Mode int `json:"mode"` + BytesPercent string `json:"bytes_pcent"` + BytesUsed ByteCount `json:"bytes_used"` + BytesQuota QuotaSize `json:"-"` + DataPool string `json:"data_pool"` + PoolNamespace string `json:"pool_namespace"` + Atime TimeStamp `json:"atime"` + Mtime TimeStamp `json:"mtime"` + Ctime TimeStamp `json:"ctime"` + CreatedAt TimeStamp `json:"created_at"` + Features []Feature `json:"features"` } type subVolumeInfoWrapper struct { diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go b/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go new file mode 100644 index 000000000..a9af61a20 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go @@ -0,0 +1,62 @@ +package cutil + +/* +#include +#include +typedef void* voidptr; +*/ +import "C" + +import ( + "unsafe" +) + +const ( + // MaxIdx is the maximum index on 32 bit systems + MaxIdx = 1<<31 - 1 // 2GB, max int32 value, should be safe + + // PtrSize is the size of a pointer + PtrSize = C.sizeof_voidptr + + // SizeTSize is the size of C.size_t + SizeTSize = C.sizeof_size_t +) + +// SizeT wraps size_t from C. +type SizeT C.size_t + +// This section contains a bunch of types that are basically just +// unsafe.Pointer but have specific types to help "self document" what the +// underlying pointer is really meant to represent. + +// CPtr is an unsafe.Pointer to C allocated memory +type CPtr unsafe.Pointer + +// CharPtrPtr is an unsafe pointer wrapping C's `char**`. +type CharPtrPtr unsafe.Pointer + +// CharPtr is an unsafe pointer wrapping C's `char*`. +type CharPtr unsafe.Pointer + +// SizeTPtr is an unsafe pointer wrapping C's `size_t*`. +type SizeTPtr unsafe.Pointer + +// FreeFunc is a wrapper around calls to, or act like, C's free function. +type FreeFunc func(unsafe.Pointer) + +// Malloc is C.malloc +func Malloc(s SizeT) CPtr { return CPtr(C.malloc(C.size_t(s))) } + +// Free is C.free +func Free(p CPtr) { C.free(unsafe.Pointer(p)) } + +// CString is C.CString +func CString(s string) CharPtr { return CharPtr((C.CString(s))) } + +// CBytes is C.CBytes +func CBytes(b []byte) CPtr { return CPtr(C.CBytes(b)) } + +// Memcpy is C.memcpy +func Memcpy(dst, src CPtr, n SizeT) { + C.memcpy(unsafe.Pointer(dst), unsafe.Pointer(src), C.size_t(n)) +} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/iovec.go b/vendor/github.com/ceph/go-ceph/internal/cutil/iovec.go index d89de06e1..a711369cc 100644 --- a/vendor/github.com/ceph/go-ceph/internal/cutil/iovec.go +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/iovec.go @@ -5,74 +5,56 @@ package cutil #include */ import "C" - import ( "unsafe" ) -var iovecSize uintptr - -// StructIovecPtr is an unsafe pointer wrapping C's `*struct iovec`. -type StructIovecPtr unsafe.Pointer - -// Iovec helps manage struct iovec arrays needed by some C functions. +// Iovec is a slice of iovec structs. Might have allocated C memory, so it must +// be freed with the Free() method. type Iovec struct { - // cvec represents an array of struct iovec C memory - cvec unsafe.Pointer - // length of the array (in elements) - length int + iovec []C.struct_iovec + sbs []*SyncBuffer } -// NewIovec creates an Iovec, and underlying C memory, of the specified size. -func NewIovec(l int) *Iovec { - r := &Iovec{ - cvec: C.malloc(C.size_t(l) * C.size_t(iovecSize)), - length: l, +const iovecSize = C.sizeof_struct_iovec + +// ByteSlicesToIovec creates an Iovec and links it to Go buffers in data. +func ByteSlicesToIovec(data [][]byte) (v Iovec) { + n := len(data) + iovecMem := C.malloc(iovecSize * C.size_t(n)) + v.iovec = (*[MaxIdx]C.struct_iovec)(iovecMem)[:n:n] + for i, b := range data { + sb := NewSyncBuffer(CPtr(&v.iovec[i].iov_base), b) + v.sbs = append(v.sbs, sb) + v.iovec[i].iov_len = C.size_t(len(b)) } - return r + return } -// ByteSlicesToIovec takes a slice of byte slices and returns a new iovec that -// maps the slice data to struct iovec entries. -func ByteSlicesToIovec(data [][]byte) *Iovec { - iov := NewIovec(len(data)) - for i := range data { - iov.Set(i, data[i]) +// Sync makes sure the slices contain the same as the C buffers +func (v *Iovec) Sync() { + for _, sb := range v.sbs { + sb.Sync() } - return iov } -// Pointer returns a StructIovecPtr that represents the C memory of the -// underlying array. -func (v *Iovec) Pointer() StructIovecPtr { - return StructIovecPtr(unsafe.Pointer(v.cvec)) +// Pointer returns a pointer to the iovec +func (v *Iovec) Pointer() unsafe.Pointer { + return unsafe.Pointer(&v.iovec[0]) } -// Len returns the number of entries in the Iovec. +// Len returns a pointer to the iovec func (v *Iovec) Len() int { - return v.length + return len(v.iovec) } // Free the C memory in the Iovec. func (v *Iovec) Free() { - if v.cvec != nil { - C.free(v.cvec) - v.cvec = nil - v.length = 0 + for _, sb := range v.sbs { + sb.Release() } -} - -// Set will map the memory of the given byte slice to the iovec at the -// specified position. -func (v *Iovec) Set(i int, buf []byte) { - offset := uintptr(i) * iovecSize - iov := (*C.struct_iovec)(unsafe.Pointer( - uintptr(unsafe.Pointer(v.cvec)) + offset)) - iov.iov_base = unsafe.Pointer(&buf[0]) - iov.iov_len = C.size_t(len(buf)) -} - -func init() { - var iovec C.struct_iovec - iovecSize = unsafe.Sizeof(iovec) + if len(v.iovec) != 0 { + C.free(unsafe.Pointer(&v.iovec[0])) + } + v.iovec = nil } diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go b/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go new file mode 100644 index 000000000..545e0c084 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go @@ -0,0 +1,79 @@ +package cutil + +import ( + "sync" + "unsafe" +) + +// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go +// runtime) stored in C memory (allocated by C) +type PtrGuard struct { + // These mutexes will be used as binary semaphores for signalling events from + // one thread to another, which - in contrast to other languages like C++ - is + // possible in Go, that is a Mutex can be locked in one thread and unlocked in + // another. + stored, release sync.Mutex + released bool +} + +// WARNING: using binary semaphores (mutexes) for signalling like this is quite +// a delicate task in order to avoid deadlocks or panics. Whenever changing the +// code logic, please review at least three times that there is no unexpected +// state possible. Usually the natural choice would be to use channels instead, +// but these can not easily passed to C code because of the pointer-to-pointer +// cgo rule, and would require the use of a Go object registry. + +// NewPtrGuard writes the goPtr (pointing to Go memory) into C memory at the +// position cPtr, and returns a PtrGuard object. +func NewPtrGuard(cPtr CPtr, goPtr unsafe.Pointer) *PtrGuard { + var v PtrGuard + // Since the mutexes are used for signalling, they have to be initialized to + // locked state, so that following lock attempts will block. + v.release.Lock() + v.stored.Lock() + // Start a background go routine that lives until Release is called. This + // calls a special function that makes sure the garbage collector doesn't touch + // goPtr, stores it into C memory at position cPtr and then waits until it + // reveices the "release" signal, after which it nulls out the C memory at + // cPtr and then exits. + go func() { + storeUntilRelease(&v, (*CPtr)(cPtr), uintptr(goPtr)) + }() + // Wait for the "stored" signal from the go routine when the Go pointer has + // been stored to the C memory. <--(1) + v.stored.Lock() + return &v +} + +// Release removes the guarded Go pointer from the C memory by overwriting it +// with NULL. +func (v *PtrGuard) Release() { + if !v.released { + v.released = true + v.release.Unlock() // Send the "release" signal to the go routine. -->(2) + v.stored.Lock() // Wait for the second "stored" signal when the C memory + // has been nulled out. <--(3) + + } +} + +//go:uintptrescapes + +// From https://golang.org/src/cmd/compile/internal/gc/lex.go: +// For the next function declared in the file any uintptr arguments may be +// pointer values converted to uintptr. This directive ensures that the +// referenced allocated object, if any, is retained and not moved until the call +// completes, even though from the types alone it would appear that the object +// is no longer needed during the call. The conversion to uintptr must appear in +// the argument list. +// Also see https://golang.org/cmd/compile/#hdr-Compiler_Directives + +func storeUntilRelease(v *PtrGuard, cPtr *CPtr, goPtr uintptr) { + uip := (*uintptr)(unsafe.Pointer(cPtr)) + *uip = goPtr // store Go pointer in C memory at c_ptr + v.stored.Unlock() // send "stored" signal to main thread -->(1) + v.release.Lock() // wait for "release" signal from main thread when + // Release() has been called. <--(2) + *uip = 0 // reset C memory to NULL + v.stored.Unlock() // send second "stored" signal to main thread -->(3) +} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer.go b/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer.go new file mode 100644 index 000000000..9fdc38ebf --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer.go @@ -0,0 +1,28 @@ +// +build ptrguard + +package cutil + +import ( + "unsafe" +) + +// SyncBuffer is a C buffer connected to a data slice +type SyncBuffer struct { + pg *PtrGuard +} + +// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr +func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer { + var v SyncBuffer + v.pg = NewPtrGuard(cPtr, unsafe.Pointer(&data[0])) + return &v +} + +// Release releases the C buffer and nulls its stored pointer +func (v *SyncBuffer) Release() { + v.pg.Release() +} + +// Sync asserts that changes in the C buffer are available in the data +// slice +func (v *SyncBuffer) Sync() {} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer_memcpy.go b/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer_memcpy.go new file mode 100644 index 000000000..137ae62c4 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/sync_buffer_memcpy.go @@ -0,0 +1,37 @@ +// +build !ptrguard + +package cutil + +// SyncBuffer is a C buffer connected to a data slice +type SyncBuffer struct { + data []byte + cPtr *CPtr +} + +// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr +func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer { + var v SyncBuffer + v.data = data + v.cPtr = (*CPtr)(cPtr) + *v.cPtr = CBytes(data) + return &v +} + +// Release releases the C buffer and nulls its stored pointer +func (v *SyncBuffer) Release() { + if v.cPtr != nil { + Free(*v.cPtr) + *v.cPtr = nil + v.cPtr = nil + } + v.data = nil +} + +// Sync asserts that changes in the C buffer are available in the data +// slice +func (v *SyncBuffer) Sync() { + if v.cPtr == nil { + return + } + Memcpy(CPtr(&v.data[0]), CPtr(*v.cPtr), SizeT(len(v.data))) +} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/type_aliases.go b/vendor/github.com/ceph/go-ceph/internal/cutil/type_aliases.go deleted file mode 100644 index 79495ef98..000000000 --- a/vendor/github.com/ceph/go-ceph/internal/cutil/type_aliases.go +++ /dev/null @@ -1,28 +0,0 @@ -package cutil - -import "C" - -import ( - "unsafe" -) - -// Basic types from C that we can make "public" without too much fuss. - -// SizeT wraps size_t from C. -type SizeT C.size_t - -// This section contains a bunch of types that are basically just -// unsafe.Pointer but have specific types to help "self document" what the -// underlying pointer is really meant to represent. - -// CharPtrPtr is an unsafe pointer wrapping C's `char**`. -type CharPtrPtr unsafe.Pointer - -// CharPtr is an unsafe pointer wrapping C's `char*`. -type CharPtr unsafe.Pointer - -// SizeTPtr is an unsafe pointer wrapping C's `size_t*`. -type SizeTPtr unsafe.Pointer - -// FreeFunc is a wrapper around calls to, or act like, C's free function. -type FreeFunc func(unsafe.Pointer) diff --git a/vendor/github.com/ceph/go-ceph/internal/timespec/timespec.go b/vendor/github.com/ceph/go-ceph/internal/timespec/timespec.go index 54076e3a3..1e4b09acd 100644 --- a/vendor/github.com/ceph/go-ceph/internal/timespec/timespec.go +++ b/vendor/github.com/ceph/go-ceph/internal/timespec/timespec.go @@ -28,3 +28,12 @@ func CStructToTimespec(cts CTimespecPtr) Timespec { Nsec: int64(t.tv_nsec), } } + +// CopyToCStruct copies the time values from a Timespec to a previously +// allocated C `struct timespec`. Due to restrictions on Cgo the C pointer +// must be passed via the CTimespecPtr wrapper. +func CopyToCStruct(ts Timespec, cts CTimespecPtr) { + t := (*C.struct_timespec)(cts) + t.tv_sec = C.time_t(ts.Sec) + t.tv_nsec = C.long(ts.Nsec) +} diff --git a/vendor/github.com/ceph/go-ceph/rados/errors.go b/vendor/github.com/ceph/go-ceph/rados/errors.go index 6039d1203..d0de0c80b 100644 --- a/vendor/github.com/ceph/go-ceph/rados/errors.go +++ b/vendor/github.com/ceph/go-ceph/rados/errors.go @@ -52,6 +52,9 @@ var ( // ErrInvalidIOContext may be returned if an api call requires an IOContext // but IOContext is not ready for use. ErrInvalidIOContext = errors.New("IOContext is not ready for use") + // ErrOperationIncomplete is returned from write op or read op steps for + // which the operation has not been performed yet. + ErrOperationIncomplete = errors.New("Operation has not been performed yet") ) // Public radosErrors: diff --git a/vendor/github.com/ceph/go-ceph/rados/ioctx.go b/vendor/github.com/ceph/go-ceph/rados/ioctx.go index d7a5d214c..b3462e626 100644 --- a/vendor/github.com/ceph/go-ceph/rados/ioctx.go +++ b/vendor/github.com/ceph/go-ceph/rados/ioctx.go @@ -128,15 +128,10 @@ func (ioctx *IOContext) SetNamespace(namespace string) { // 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 { - c_oid := C.CString(oid) - defer C.free(unsafe.Pointer(c_oid)) - - op := C.rados_create_write_op() - C.rados_write_op_create(op, C.int(exclusive), nil) - ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) - C.rados_release_write_op(op) - - return getError(ret) + 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 diff --git a/vendor/github.com/ceph/go-ceph/rados/omap.go b/vendor/github.com/ceph/go-ceph/rados/omap.go index 7d279c2b4..79b4c8e40 100644 --- a/vendor/github.com/ceph/go-ceph/rados/omap.go +++ b/vendor/github.com/ceph/go-ceph/rados/omap.go @@ -1,69 +1,230 @@ package rados -// #cgo LDFLAGS: -lrados -// #include -// #include -// +/* +#cgo LDFLAGS: -lrados +#include +#include + +typedef void* voidptr; + +*/ import "C" import ( + "runtime" "unsafe" ) -// SetOmap appends the map `pairs` to the omap `oid` -func (ioctx *IOContext) SetOmap(oid string, pairs map[string][]byte) error { - c_oid := C.CString(oid) - defer C.free(unsafe.Pointer(c_oid)) +const ( + ptrSize = C.sizeof_voidptr + sizeTSize = C.sizeof_size_t +) - var s C.size_t - var c *C.char - ptrSize := unsafe.Sizeof(c) +// setOmapStep is a write op step. It holds C memory used in the operation. +type setOmapStep struct { + withRefs + withoutUpdate - c_keys := C.malloc(C.size_t(len(pairs)) * C.size_t(ptrSize)) - c_values := C.malloc(C.size_t(len(pairs)) * C.size_t(ptrSize)) - c_lengths := C.malloc(C.size_t(len(pairs)) * C.size_t(unsafe.Sizeof(s))) + // C arguments + cKeys **C.char + cValues **C.char + cLengths *C.size_t + cNum C.size_t +} - defer C.free(unsafe.Pointer(c_keys)) - defer C.free(unsafe.Pointer(c_values)) - defer C.free(unsafe.Pointer(c_lengths)) +func newSetOmapStep(pairs map[string][]byte) *setOmapStep { - i := 0 + maplen := C.size_t(len(pairs)) + cKeys := C.malloc(maplen * ptrSize) + cValues := C.malloc(maplen * ptrSize) + cLengths := C.malloc(maplen * sizeTSize) + + sos := &setOmapStep{ + cKeys: (**C.char)(cKeys), + cValues: (**C.char)(cValues), + cLengths: (*C.size_t)(cLengths), + cNum: C.size_t(len(pairs)), + } + sos.add(cKeys) + sos.add(cValues) + sos.add(cLengths) + + var i uintptr for key, value := range pairs { // key - c_key_ptr := (**C.char)(unsafe.Pointer(uintptr(c_keys) + uintptr(i)*ptrSize)) - *c_key_ptr = C.CString(key) - defer C.free(unsafe.Pointer(*c_key_ptr)) + ck := C.CString(key) + sos.add(unsafe.Pointer(ck)) + ckp := (**C.char)(unsafe.Pointer(uintptr(cKeys) + i*ptrSize)) + *ckp = ck // value and its length - c_value_ptr := (**C.char)(unsafe.Pointer(uintptr(c_values) + uintptr(i)*ptrSize)) - - var c_length C.size_t - if len(value) > 0 { - *c_value_ptr = (*C.char)(unsafe.Pointer(&value[0])) - c_length = C.size_t(len(value)) + cvp := (**C.char)(unsafe.Pointer(uintptr(cValues) + i*ptrSize)) + vlen := C.size_t(len(value)) + if vlen > 0 { + cv := C.CBytes(value) + sos.add(cv) + *cvp = (*C.char)(cv) } else { - *c_value_ptr = nil - c_length = C.size_t(0) + *cvp = nil } - c_length_ptr := (*C.size_t)(unsafe.Pointer(uintptr(c_lengths) + uintptr(i)*ptrSize)) - *c_length_ptr = c_length + clp := (*C.size_t)(unsafe.Pointer(uintptr(cLengths) + i*ptrSize)) + *clp = vlen i++ } - op := C.rados_create_write_op() - C.rados_write_op_omap_set( - op, - (**C.char)(c_keys), - (**C.char)(c_values), - (*C.size_t)(c_lengths), - C.size_t(len(pairs))) + runtime.SetFinalizer(sos, opStepFinalizer) + return sos +} - ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) - C.rados_release_write_op(op) +func (sos *setOmapStep) free() { + sos.cKeys = nil + sos.cValues = nil + sos.cLengths = nil + sos.withRefs.free() +} - return getError(ret) +// 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 { + // inputs: + startAfter string + filterPrefix string + maxReturn uint64 + + // arguments: + cStartAfter *C.char + cFilterPrefix *C.char + + // 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(startAfter, filterPrefix string, maxReturn uint64) *GetOmapStep { + gos := &GetOmapStep{ + startAfter: startAfter, + filterPrefix: filterPrefix, + maxReturn: maxReturn, + cStartAfter: C.CString(startAfter), + cFilterPrefix: C.CString(filterPrefix), + } + 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 + gos.more = 0 + gos.rval = 0 + C.free(unsafe.Pointer(gos.cStartAfter)) + gos.cStartAfter = nil + C.free(unsafe.Pointer(gos.cFilterPrefix)) + gos.cFilterPrefix = 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 + cLen C.size_t + ) + ret := C.rados_omap_get_next(gos.iter, &cKey, &cVal, &cLen) + if ret != 0 { + return nil, getError(ret) + } + if cKey == nil { + return nil, nil + } + return &OmapKeyValue{ + Key: C.GoString(cKey), + Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cLen)), + }, 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 +} + +// removeOmapKeysStep is a write operation step used to track state, especially +// C memory, across the setup and use of a WriteOp. +type removeOmapKeysStep struct { + withRefs + withoutUpdate + + // arguments: + cKeys **C.char + cNum C.size_t +} + +func newRemoveOmapKeysStep(keys []string) *removeOmapKeysStep { + cKeys := C.malloc(C.size_t(len(keys)) * ptrSize) + roks := &removeOmapKeysStep{ + cKeys: (**C.char)(cKeys), + cNum: C.size_t(len(keys)), + } + roks.add(cKeys) + + i := 0 + for _, key := range keys { + ckp := (**C.char)(unsafe.Pointer(uintptr(cKeys) + uintptr(i)*ptrSize)) + *ckp = C.CString(key) + roks.add(unsafe.Pointer(*ckp)) + i++ + } + + runtime.SetFinalizer(roks, opStepFinalizer) + return roks +} + +func (roks *removeOmapKeysStep) free() { + roks.cKeys = nil + roks.withRefs.free() +} + +// 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 @@ -78,58 +239,25 @@ type OmapListFunc func(key string, value []byte) // `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 { - c_oid := C.CString(oid) - c_start_after := C.CString(startAfter) - c_filter_prefix := C.CString(filterPrefix) - c_max_return := C.uint64_t(maxReturn) - defer C.free(unsafe.Pointer(c_oid)) - defer C.free(unsafe.Pointer(c_start_after)) - defer C.free(unsafe.Pointer(c_filter_prefix)) - - op := C.rados_create_read_op() - - var c_iter C.rados_omap_iter_t - var c_prval C.int - C.rados_read_op_omap_get_vals2( - op, - c_start_after, - c_filter_prefix, - c_max_return, - &c_iter, - nil, - &c_prval, - ) - - ret := C.rados_read_op_operate(op, ioctx.ioctx, c_oid, 0) - - if int(ret) != 0 { - return getError(ret) - } else if int(c_prval) != 0 { - return getError(c_prval) + op := CreateReadOp() + defer op.Release() + gos := op.GetOmapValues(startAfter, filterPrefix, uint64(maxReturn)) + err := op.operateCompat(ioctx, oid) + if err != nil { + return err } for { - var c_key *C.char - var c_val *C.char - var c_len C.size_t - - ret = C.rados_omap_get_next(c_iter, &c_key, &c_val, &c_len) - - if int(ret) != 0 { - return getError(ret) + kv, err := gos.Next() + if err != nil { + return err } - - if c_key == nil { + if kv == nil { break } - - listFn(C.GoString(c_key), C.GoBytes(unsafe.Pointer(c_val), C.int(c_len))) + listFn(kv.Key, kv.Value) } - - C.rados_omap_get_end(c_iter) - C.rados_release_read_op(op) - return nil } @@ -184,45 +312,16 @@ func (ioctx *IOContext) GetAllOmapValues(oid string, startAfter string, filterPr // RmOmapKeys removes the specified `keys` from the omap `oid` func (ioctx *IOContext) RmOmapKeys(oid string, keys []string) error { - c_oid := C.CString(oid) - defer C.free(unsafe.Pointer(c_oid)) - - var c *C.char - ptrSize := unsafe.Sizeof(c) - - c_keys := C.malloc(C.size_t(len(keys)) * C.size_t(ptrSize)) - defer C.free(unsafe.Pointer(c_keys)) - - i := 0 - for _, key := range keys { - c_key_ptr := (**C.char)(unsafe.Pointer(uintptr(c_keys) + uintptr(i)*ptrSize)) - *c_key_ptr = C.CString(key) - defer C.free(unsafe.Pointer(*c_key_ptr)) - i++ - } - - op := C.rados_create_write_op() - C.rados_write_op_omap_rm_keys( - op, - (**C.char)(c_keys), - C.size_t(len(keys))) - - ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) - C.rados_release_write_op(op) - - return getError(ret) + 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 { - c_oid := C.CString(oid) - defer C.free(unsafe.Pointer(c_oid)) - - op := C.rados_create_write_op() - C.rados_write_op_omap_clear(op) - - ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) - C.rados_release_write_op(op) - - return getError(ret) + op := CreateWriteOp() + defer op.Release() + op.CleanOmap() + return op.operateCompat(ioctx, oid) } diff --git a/vendor/github.com/ceph/go-ceph/rados/operation.go b/vendor/github.com/ceph/go-ceph/rados/operation.go new file mode 100644 index 000000000..ca3ab386b --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/operation.go @@ -0,0 +1,151 @@ +package rados + +// #include +import "C" + +import ( + "fmt" + "strings" + "unsafe" +) + +// The file operation.go exists to support both read op and write op types that +// have some pretty common behaviors between them. In C/C++ its assumed that +// the buffer types and other pointers will not be freed between passing them +// to the action setup calls (things like rados_write_op_write or +// rados_read_op_omap_get_vals2) and the call to Operate(...). Since there's +// nothing stopping one from sleeping for hours between these calls, or passing +// the op to other functions and calling Operate there, we want a mechanism +// that's (fairly) simple to understand and won't run afoul of Go's garbage +// collection. That's one reason the operation type tracks the steps (the +// parts that track complex inputs and outputs) so that as long as the op +// exists it will have a reference to the step, which will have references +// to the C language types. + +type opKind string + +const ( + readOp opKind = "read" + writeOp opKind = "write" +) + +// OperationError is an error type that may be returned by an Operate call. +// It captures the error from the operate call itself and any errors from +// steps that can return an error. +type OperationError struct { + kind opKind + OpError error + StepErrors map[int]error +} + +func (e OperationError) Error() string { + subErrors := []string{} + if e.OpError != nil { + subErrors = append(subErrors, + fmt.Sprintf("op=%s", e.OpError)) + } + for idx, es := range e.StepErrors { + subErrors = append(subErrors, + fmt.Sprintf("Step#%d=%s", idx, es)) + } + return fmt.Sprintf( + "%s operation error: %s", + e.kind, + strings.Join(subErrors, ", ")) +} + +// opStep provides an interface for types that are tied to the management of +// data being input or output from write ops and read ops. The steps are +// meant to simplify the internals of the ops themselves and be exportable when +// appropriate. If a step is not being exported it should not be returned +// from an ops action function. If the step is exported it should be +// returned from an ops action function. +// +// Not all types implementing opStep are expected to need all the functions +// in the interface. However, for the sake of simplicity on the op side, we use +// the same interface for all cases and expect those implementing opStep +// just embed the without* types that provide no-op implementation of +// functions that make up this interface. +type opStep interface { + // update the state of the step after the call to Operate. + // It can be used to convert values from C and cache them and/or + // communicate a failure of the action associated with the step. The + // update call will only be made once. Implementations are not required to + // handle this call being made more than once. + update() error + // free will be called to free any resources, especially C memory, that + // the step is managing. The behavior of free should be idempotent and + // handle being called more than once. + free() +} + +// operation represents some of the shared underlying mechanisms for +// both read and write op types. +type operation struct { + steps []opStep +} + +// free will call the free method of all the steps this operation +// contains. +func (o *operation) free() { + for i := range o.steps { + o.steps[i].free() + } +} + +// update the operation and the steps it contains. The top-level result +// of the rados call is passed in as ret and used to construct errors. +// The update call of each step is used to update the contents of each +// step and gather any errors from those steps. +func (o *operation) update(kind opKind, ret C.int) error { + stepErrors := map[int]error{} + for i := range o.steps { + if err := o.steps[i].update(); err != nil { + stepErrors[i] = err + } + } + if ret == 0 && len(stepErrors) == 0 { + return nil + } + return OperationError{ + kind: kind, + OpError: getError(ret), + StepErrors: stepErrors, + } +} + +func opStepFinalizer(s opStep) { + if s != nil { + 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) +} diff --git a/vendor/github.com/ceph/go-ceph/rados/operation_flags.go b/vendor/github.com/ceph/go-ceph/rados/operation_flags.go new file mode 100644 index 000000000..957f4f2b2 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/operation_flags.go @@ -0,0 +1,37 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// #include +// +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) +) diff --git a/vendor/github.com/ceph/go-ceph/rados/read_op.go b/vendor/github.com/ceph/go-ceph/rados/read_op.go new file mode 100644 index 000000000..4f95e630c --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/read_op.go @@ -0,0 +1,84 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// #include +// +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(startAfter, filterPrefix, maxReturn) + r.steps = append(r.steps, gos) + C.rados_read_op_omap_get_vals2( + r.op, + gos.cStartAfter, + gos.cFilterPrefix, + C.uint64_t(gos.maxReturn), + &gos.iter, + &gos.more, + &gos.rval, + ) + return gos +} diff --git a/vendor/github.com/ceph/go-ceph/rados/write_op.go b/vendor/github.com/ceph/go-ceph/rados/write_op.go new file mode 100644 index 000000000..0fab21042 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/write_op.go @@ -0,0 +1,179 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// #include +// +import "C" + +import ( + "unsafe" + + 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) { + sos := newSetOmapStep(pairs) + w.steps = append(w.steps, sos) + C.rados_write_op_omap_set( + w.op, + sos.cKeys, + sos.cValues, + sos.cLengths, + sos.cNum) +} + +// RmOmapKeys removes the specified `keys` from the omap `oid`. +func (w *WriteOp) RmOmapKeys(keys []string) { + roks := newRemoveOmapKeysStep(keys) + w.steps = append(w.steps, roks) + C.rados_write_op_omap_rm_keys( + w.op, + roks.cKeys, + roks.cNum) +} + +// 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) +} diff --git a/vendor/github.com/ceph/go-ceph/rados/write_step.go b/vendor/github.com/ceph/go-ceph/rados/write_step.go new file mode 100644 index 000000000..7ece03f12 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/write_step.go @@ -0,0 +1,33 @@ +package rados + +// #include +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])), + cDataLen: C.size_t(len(b)), + cWriteLen: C.size_t(writeLen), + cOffset: C.uint64_t(offset), + } +} diff --git a/vendor/github.com/ceph/go-ceph/rbd/group.go b/vendor/github.com/ceph/go-ceph/rbd/group.go new file mode 100644 index 000000000..25668967b --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rbd/group.go @@ -0,0 +1,258 @@ +package rbd + +/* +#cgo LDFLAGS: -lrbd +#include +#include +*/ +import "C" + +import ( + "unsafe" + + "github.com/ceph/go-ceph/internal/cutil" + "github.com/ceph/go-ceph/internal/retry" + "github.com/ceph/go-ceph/rados" +) + +// GroupCreate is used to create an image group. +// +// Implements: +// int rbd_group_create(rados_ioctx_t p, const char *name); +func GroupCreate(ioctx *rados.IOContext, name string) error { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + ret := C.rbd_group_create(cephIoctx(ioctx), cName) + return getError(ret) +} + +// GroupRemove is used to remove an image group. +// +// Implements: +// int rbd_group_remove(rados_ioctx_t p, const char *name); +func GroupRemove(ioctx *rados.IOContext, name string) error { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + ret := C.rbd_group_remove(cephIoctx(ioctx), cName) + return getError(ret) +} + +// GroupRename will rename an existing image group. +// +// Implements: +// int rbd_group_rename(rados_ioctx_t p, const char *src_name, +// const char *dest_name); +func GroupRename(ioctx *rados.IOContext, src, dest string) error { + cSrc := C.CString(src) + defer C.free(unsafe.Pointer(cSrc)) + cDest := C.CString(dest) + defer C.free(unsafe.Pointer(cDest)) + + ret := C.rbd_group_rename(cephIoctx(ioctx), cSrc, cDest) + return getError(ret) +} + +// GroupList returns a slice of image group names. +// +// Implements: +// int rbd_group_list(rados_ioctx_t p, char *names, size_t *size); +func GroupList(ioctx *rados.IOContext) ([]string, error) { + var ( + buf []byte + err error + ret C.int + ) + retry.WithSizes(1024, 262144, func(size int) retry.Hint { + cSize := C.size_t(size) + buf = make([]byte, cSize) + ret = C.rbd_group_list( + cephIoctx(ioctx), + (*C.char)(unsafe.Pointer(&buf[0])), + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + + if err != nil { + return nil, err + } + + // cSize is not set to the expected size when it is sufficiently large + // but ret will be set to the size in a non-error condition. + groups := cutil.SplitBuffer(buf[:ret]) + return groups, nil +} + +// GroupImageAdd will add the specified image to the named group. +// An io context must be supplied for both the group and image. +// +// Implements: +// int rbd_group_image_add(rados_ioctx_t group_p, +// const char *group_name, +// rados_ioctx_t image_p, +// const char *image_name); +func GroupImageAdd(groupIoctx *rados.IOContext, groupName string, + imageIoctx *rados.IOContext, imageName string) error { + + cGroupName := C.CString(groupName) + defer C.free(unsafe.Pointer(cGroupName)) + cImageName := C.CString(imageName) + defer C.free(unsafe.Pointer(cImageName)) + + ret := C.rbd_group_image_add( + cephIoctx(groupIoctx), + cGroupName, + cephIoctx(imageIoctx), + cImageName) + return getError(ret) +} + +// GroupImageRemove will remove the specified image from the named group. +// An io context must be supplied for both the group and image. +// +// Implements: +// int rbd_group_image_remove(rados_ioctx_t group_p, +// const char *group_name, +// rados_ioctx_t image_p, +// const char *image_name); +func GroupImageRemove(groupIoctx *rados.IOContext, groupName string, + imageIoctx *rados.IOContext, imageName string) error { + + cGroupName := C.CString(groupName) + defer C.free(unsafe.Pointer(cGroupName)) + cImageName := C.CString(imageName) + defer C.free(unsafe.Pointer(cImageName)) + + ret := C.rbd_group_image_remove( + cephIoctx(groupIoctx), + cGroupName, + cephIoctx(imageIoctx), + cImageName) + return getError(ret) +} + +// GroupImageRemoveByID will remove the specified image from the named group. +// An io context must be supplied for both the group and image. +// +// Implements: +// CEPH_RBD_API int rbd_group_image_remove_by_id(rados_ioctx_t group_p, +// const char *group_name, +// rados_ioctx_t image_p, +// const char *image_id); +func GroupImageRemoveByID(groupIoctx *rados.IOContext, groupName string, + imageIoctx *rados.IOContext, imageID string) error { + + cGroupName := C.CString(groupName) + defer C.free(unsafe.Pointer(cGroupName)) + cid := C.CString(imageID) + defer C.free(unsafe.Pointer(cid)) + + ret := C.rbd_group_image_remove_by_id( + cephIoctx(groupIoctx), + cGroupName, + cephIoctx(imageIoctx), + cid) + return getError(ret) +} + +// GroupImageState indicates an image's state in a group. +type GroupImageState int + +const ( + // GroupImageStateAttached is equivalent to RBD_GROUP_IMAGE_STATE_ATTACHED + GroupImageStateAttached = GroupImageState(C.RBD_GROUP_IMAGE_STATE_ATTACHED) + // GroupImageStateIncomplete is equivalent to RBD_GROUP_IMAGE_STATE_INCOMPLETE + GroupImageStateIncomplete = GroupImageState(C.RBD_GROUP_IMAGE_STATE_INCOMPLETE) +) + +// GroupImageInfo reports on images within a group. +type GroupImageInfo struct { + Name string + PoolID int64 + State GroupImageState +} + +// GroupImageList returns a slice of GroupImageInfo types based on the +// images that are part of the named group. +// +// Implements: +// int rbd_group_image_list(rados_ioctx_t group_p, +// const char *group_name, +// rbd_group_image_info_t *images, +// size_t group_image_info_size, +// size_t *num_entries); +func GroupImageList(ioctx *rados.IOContext, name string) ([]GroupImageInfo, error) { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + var ( + cImages []C.rbd_group_image_info_t + cSize C.size_t + err error + ) + retry.WithSizes(1024, 262144, func(size int) retry.Hint { + cSize = C.size_t(size) + cImages = make([]C.rbd_group_image_info_t, cSize) + ret := C.rbd_group_image_list( + cephIoctx(ioctx), + cName, + (*C.rbd_group_image_info_t)(unsafe.Pointer(&cImages[0])), + C.sizeof_rbd_group_image_info_t, + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + + if err != nil { + return nil, err + } + + images := make([]GroupImageInfo, cSize) + for i := range images { + images[i].Name = C.GoString(cImages[i].name) + images[i].PoolID = int64(cImages[i].pool) + images[i].State = GroupImageState(cImages[i].state) + } + + // free C memory allocated by C.rbd_group_image_list call + ret := C.rbd_group_image_list_cleanup( + (*C.rbd_group_image_info_t)(unsafe.Pointer(&cImages[0])), + C.sizeof_rbd_group_image_info_t, + cSize) + return images, getError(ret) +} + +// GroupInfo contains the name and pool id of a RBD group. +type GroupInfo struct { + Name string + PoolID int64 +} + +// GetGroup returns group info for the group this image is part of. +// +// Implements: +// int rbd_get_group(rbd_image_t image, rbd_group_info_t *group_info, +// size_t group_info_size); +func (image *Image) GetGroup() (GroupInfo, error) { + if err := image.validate(imageIsOpen); err != nil { + return GroupInfo{}, err + } + + var cgi C.rbd_group_info_t + ret := C.rbd_get_group( + image.image, + &cgi, + C.sizeof_rbd_group_info_t) + if err := getErrorIfNegative(ret); err != nil { + return GroupInfo{}, err + } + + gi := GroupInfo{ + Name: C.GoString(cgi.name), + PoolID: int64(cgi.pool), + } + ret = C.rbd_group_info_cleanup(&cgi, C.sizeof_rbd_group_info_t) + return gi, getError(ret) +} diff --git a/vendor/github.com/ceph/go-ceph/rbd/group_snap.go b/vendor/github.com/ceph/go-ceph/rbd/group_snap.go new file mode 100644 index 000000000..fbd3b3f09 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rbd/group_snap.go @@ -0,0 +1,223 @@ +package rbd + +/* +#cgo LDFLAGS: -lrbd +#include +#include +#include + +extern int snapRollbackCallback(uint64_t, uint64_t, uintptr_t); + +// inline wrapper to cast uintptr_t to void* +static inline int wrap_rbd_group_snap_rollback_with_progress( + rados_ioctx_t group_p, const char *group_name, + const char *snap_name, uintptr_t arg) { + return rbd_group_snap_rollback_with_progress( + group_p, group_name, snap_name, (librbd_progress_fn_t)snapRollbackCallback, (void*)arg); +}; +*/ +import "C" + +import ( + "unsafe" + + "github.com/ceph/go-ceph/internal/callbacks" + "github.com/ceph/go-ceph/internal/retry" + "github.com/ceph/go-ceph/rados" +) + +// GroupSnapCreate will create a group snapshot. +// +// Implements: +// int rbd_group_snap_create(rados_ioctx_t group_p, +// const char *group_name, +// const char *snap_name); +func GroupSnapCreate(ioctx *rados.IOContext, group, snap string) error { + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + cSnapName := C.CString(snap) + defer C.free(unsafe.Pointer(cSnapName)) + + ret := C.rbd_group_snap_create(cephIoctx(ioctx), cGroupName, cSnapName) + return getError(ret) +} + +// GroupSnapRemove removes an existing group snapshot. +// +// Implements: +// int rbd_group_snap_remove(rados_ioctx_t group_p, +// const char *group_name, +// const char *snap_name); +func GroupSnapRemove(ioctx *rados.IOContext, group, snap string) error { + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + cSnapName := C.CString(snap) + defer C.free(unsafe.Pointer(cSnapName)) + + ret := C.rbd_group_snap_remove(cephIoctx(ioctx), cGroupName, cSnapName) + return getError(ret) +} + +// GroupSnapRename will rename an existing group snapshot. +// +// Implements: +// int rbd_group_snap_rename(rados_ioctx_t group_p, +// const char *group_name, +// const char *old_snap_name, +// const char *new_snap_name); +func GroupSnapRename(ioctx *rados.IOContext, group, src, dest string) error { + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + cOldSnapName := C.CString(src) + defer C.free(unsafe.Pointer(cOldSnapName)) + cNewSnapName := C.CString(dest) + defer C.free(unsafe.Pointer(cNewSnapName)) + + ret := C.rbd_group_snap_rename( + cephIoctx(ioctx), cGroupName, cOldSnapName, cNewSnapName) + return getError(ret) +} + +// GroupSnapState represents the state of a group snapshot in GroupSnapInfo. +type GroupSnapState int + +const ( + // GroupSnapStateIncomplete is equivalent to RBD_GROUP_SNAP_STATE_INCOMPLETE. + GroupSnapStateIncomplete = GroupSnapState(C.RBD_GROUP_SNAP_STATE_INCOMPLETE) + // GroupSnapStateComplete is equivalent to RBD_GROUP_SNAP_STATE_COMPLETE. + GroupSnapStateComplete = GroupSnapState(C.RBD_GROUP_SNAP_STATE_COMPLETE) +) + +// GroupSnapInfo values are returned by GroupSnapList, representing the +// snapshots that are part of an rbd group. +type GroupSnapInfo struct { + Name string + State GroupSnapState +} + +// GroupSnapList returns a slice of snapshots in a group. +// +// Implements: +// int rbd_group_snap_list(rados_ioctx_t group_p, +// const char *group_name, +// rbd_group_snap_info_t *snaps, +// size_t group_snap_info_size, +// size_t *num_entries); +func GroupSnapList(ioctx *rados.IOContext, group string) ([]GroupSnapInfo, error) { + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + + var ( + cSnaps []C.rbd_group_snap_info_t + cSize C.size_t + err error + ) + retry.WithSizes(1024, 262144, func(size int) retry.Hint { + cSize = C.size_t(size) + cSnaps = make([]C.rbd_group_snap_info_t, cSize) + ret := C.rbd_group_snap_list( + cephIoctx(ioctx), + cGroupName, + (*C.rbd_group_snap_info_t)(unsafe.Pointer(&cSnaps[0])), + C.sizeof_rbd_group_snap_info_t, + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + + if err != nil { + return nil, err + } + + snaps := make([]GroupSnapInfo, cSize) + for i := range snaps { + snaps[i].Name = C.GoString(cSnaps[i].name) + snaps[i].State = GroupSnapState(cSnaps[i].state) + } + + // free C memory allocated by C.rbd_group_snap_list call + ret := C.rbd_group_snap_list_cleanup( + (*C.rbd_group_snap_info_t)(unsafe.Pointer(&cSnaps[0])), + C.sizeof_rbd_group_snap_info_t, + cSize) + return snaps, getError(ret) +} + +// GroupSnapRollback will roll back the images in the group to that of the +// given snapshot. +// +// Implements: +// int rbd_group_snap_rollback(rados_ioctx_t group_p, +// const char *group_name, +// const char *snap_name); +func GroupSnapRollback(ioctx *rados.IOContext, group, snap string) error { + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + cSnapName := C.CString(snap) + defer C.free(unsafe.Pointer(cSnapName)) + + ret := C.rbd_group_snap_rollback(cephIoctx(ioctx), cGroupName, cSnapName) + return getError(ret) +} + +// GroupSnapRollbackCallback defines the function signature needed for the +// GroupSnapRollbackWithProgress callback. +// +// This callback will be called by GroupSnapRollbackWithProgress when it +// wishes to report progress rolling back a group snapshot. +type GroupSnapRollbackCallback func(uint64, uint64, interface{}) int + +var groupSnapRollbackCallbacks = callbacks.New() + +// GroupSnapRollbackWithProgress will roll back the images in the group +// to that of given snapshot. The given progress callback will be called +// to report on the progress of the snapshot rollback. +// +// Implements: +// int rbd_group_snap_rollback_with_progress(rados_ioctx_t group_p, +// const char *group_name, +// const char *snap_name, +// librbd_progress_fn_t cb, +// void *cbdata); +func GroupSnapRollbackWithProgress( + ioctx *rados.IOContext, group, snap string, + cb GroupSnapRollbackCallback, data interface{}) error { + // the provided callback must be a real function + if cb == nil { + return rbdError(C.EINVAL) + } + + cGroupName := C.CString(group) + defer C.free(unsafe.Pointer(cGroupName)) + cSnapName := C.CString(snap) + defer C.free(unsafe.Pointer(cSnapName)) + + ctx := gsnapRollbackCallbackCtx{ + callback: cb, + data: data, + } + cbIndex := groupSnapRollbackCallbacks.Add(ctx) + defer diffIterateCallbacks.Remove(cbIndex) + + ret := C.wrap_rbd_group_snap_rollback_with_progress( + cephIoctx(ioctx), + cGroupName, + cSnapName, + C.uintptr_t(cbIndex)) + + return getError(ret) +} + +type gsnapRollbackCallbackCtx struct { + callback GroupSnapRollbackCallback + data interface{} +} + +//export snapRollbackCallback +func snapRollbackCallback( + offset, total C.uint64_t, index uintptr) C.int { + + v := groupSnapRollbackCallbacks.Lookup(index) + ctx := v.(gsnapRollbackCallbackCtx) + return C.int(ctx.callback(uint64(offset), uint64(total), ctx.data)) +} diff --git a/vendor/github.com/ceph/go-ceph/rbd/mirror.go b/vendor/github.com/ceph/go-ceph/rbd/mirror.go new file mode 100644 index 000000000..1087494d0 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rbd/mirror.go @@ -0,0 +1,235 @@ +// +build !nautilus + +// Initially, we're only providing mirroring related functions for octopus as +// that version of ceph deprecated a number of the functions in nautilus. If +// you need mirroring on an earlier supported version of ceph please file an +// issue in our tracker. + +package rbd + +// #cgo LDFLAGS: -lrbd +// #include +// #include +import "C" + +import ( + "unsafe" + + "github.com/ceph/go-ceph/internal/retry" + "github.com/ceph/go-ceph/rados" +) + +// MirrorMode is used to indicate an approach used for RBD mirroring. +type MirrorMode int64 + +const ( + // MirrorModeDisabled disables mirroring. + MirrorModeDisabled = MirrorMode(C.RBD_MIRROR_MODE_DISABLED) + // MirrorModeImage enables mirroring on a per-image basis. + MirrorModeImage = MirrorMode(C.RBD_MIRROR_MODE_IMAGE) + // MirrorModePool enables mirroring on all journaled images. + MirrorModePool = MirrorMode(C.RBD_MIRROR_MODE_POOL) +) + +// ImageMirrorMode is used to indicate the mirroring approach for an RBD image. +type ImageMirrorMode int64 + +const ( + // ImageMirrorModeJournal uses journaling to propagate RBD images between + // ceph clusters. + ImageMirrorModeJournal = ImageMirrorMode(C.RBD_MIRROR_IMAGE_MODE_JOURNAL) + // ImageMirrorModeSnapshot uses snapshots to propagate RBD images between + // ceph clusters. + ImageMirrorModeSnapshot = ImageMirrorMode(C.RBD_MIRROR_IMAGE_MODE_SNAPSHOT) +) + +// SetMirrorMode is used to enable or disable pool level mirroring with either +// an automatic or per-image behavior. +// +// Implements: +// int rbd_mirror_mode_set(rados_ioctx_t io_ctx, +// rbd_mirror_mode_t mirror_mode); +func SetMirrorMode(ioctx *rados.IOContext, mode MirrorMode) error { + ret := C.rbd_mirror_mode_set( + cephIoctx(ioctx), + C.rbd_mirror_mode_t(mode)) + return getError(ret) +} + +// GetMirrorMode is used to fetch the current mirroring mode for a pool. +// +// Implements: +// int rbd_mirror_mode_get(rados_ioctx_t io_ctx, +// rbd_mirror_mode_t *mirror_mode); +func GetMirrorMode(ioctx *rados.IOContext) (MirrorMode, error) { + var mode C.rbd_mirror_mode_t + + ret := C.rbd_mirror_mode_get( + cephIoctx(ioctx), + &mode) + if err := getError(ret); err != nil { + return MirrorModeDisabled, err + } + return MirrorMode(mode), nil +} + +// MirrorEnable will enable mirroring for an image using the specified mode. +// +// Implements: +// int rbd_mirror_image_enable2(rbd_image_t image, +// rbd_mirror_image_mode_t mode); +func (image *Image) MirrorEnable(mode ImageMirrorMode) error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + ret := C.rbd_mirror_image_enable2(image.image, C.rbd_mirror_image_mode_t(mode)) + return getError(ret) +} + +// MirrorDisable will disable mirroring for the image. +// +// Implements: +// int rbd_mirror_image_disable(rbd_image_t image, bool force); +func (image *Image) MirrorDisable(force bool) error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + ret := C.rbd_mirror_image_disable(image.image, C.bool(force)) + return getError(ret) +} + +// MirrorPromote will promote the image to primary status. +// +// Implements: +// int rbd_mirror_image_promote(rbd_image_t image, bool force); +func (image *Image) MirrorPromote(force bool) error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + ret := C.rbd_mirror_image_promote(image.image, C.bool(force)) + return getError(ret) +} + +// MirrorDemote will demote the image to secondary status. +// +// Implements: +// int rbd_mirror_image_demote(rbd_image_t image); +func (image *Image) MirrorDemote() error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + ret := C.rbd_mirror_image_demote(image.image) + return getError(ret) +} + +// MirrorResync is used to manually resolve split-brain status by triggering +// resynchronization. +// +// Implements: +// int rbd_mirror_image_resync(rbd_image_t image); +func (image *Image) MirrorResync() error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + ret := C.rbd_mirror_image_resync(image.image) + return getError(ret) +} + +// MirrorInstanceID returns a string naming the instance id for the image. +// +// Implements: +// int rbd_mirror_image_get_instance_id(rbd_image_t image, +// char *instance_id, +// size_t *id_max_length); +func (image *Image) MirrorInstanceID() (string, error) { + if err := image.validate(imageIsOpen); err != nil { + return "", err + } + var ( + err error + buf []byte + cSize C.size_t + ) + retry.WithSizes(1024, 1<<16, func(size int) retry.Hint { + cSize = C.size_t(size) + buf = make([]byte, cSize) + ret := C.rbd_mirror_image_get_instance_id( + image.image, + (*C.char)(unsafe.Pointer(&buf[0])), + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + if err != nil { + return "", err + } + return string(buf[:cSize]), nil +} + +// MirrorImageState represents the mirroring state of a RBD image. +type MirrorImageState C.rbd_mirror_image_state_t + +const ( + // MirrorImageDisabling is the representation of + // RBD_MIRROR_IMAGE_DISABLING from librbd. + MirrorImageDisabling = MirrorImageState(C.RBD_MIRROR_IMAGE_DISABLING) + // MirrorImageEnabled is the representation of + // RBD_MIRROR_IMAGE_ENABLED from librbd. + MirrorImageEnabled = MirrorImageState(C.RBD_MIRROR_IMAGE_ENABLED) + // MirrorImageDisabled is the representation of + // RBD_MIRROR_IMAGE_DISABLED from librbd. + MirrorImageDisabled = MirrorImageState(C.RBD_MIRROR_IMAGE_DISABLED) +) + +// MirrorImageInfo represents the mirroring status information of a RBD image. +type MirrorImageInfo struct { + GlobalID string + State MirrorImageState + Primary bool +} + +// GetMirrorImageInfo fetches the mirroring status information of a RBD image. +// +// Implements: +// int rbd_mirror_image_get_info(rbd_image_t image, +// rbd_mirror_image_info_t *mirror_image_info, +// size_t info_size) +func (image *Image) GetMirrorImageInfo() (*MirrorImageInfo, error) { + if err := image.validate(imageIsOpen); err != nil { + return nil, err + } + + var cInfo C.rbd_mirror_image_info_t + + ret := C.rbd_mirror_image_get_info( + image.image, + &cInfo, + C.sizeof_rbd_mirror_image_info_t) + if ret < 0 { + return nil, getError(ret) + } + + mii := MirrorImageInfo{ + GlobalID: C.GoString(cInfo.global_id), + State: MirrorImageState(cInfo.state), + Primary: bool(cInfo.primary), + } + + // free C memory allocated by C.rbd_mirror_image_get_info call + C.rbd_mirror_image_get_info_cleanup(&cInfo) + return &mii, nil +} + +// GetImageMirrorMode fetches the mirroring approach for an RBD image. +// +// Implements: +// int rbd_mirror_image_get_mode(rbd_image_t image, rbd_mirror_image_mode_t *mode); +func (image *Image) GetImageMirrorMode() (ImageMirrorMode, error) { + var mode C.rbd_mirror_image_mode_t + if err := image.validate(imageIsOpen); err != nil { + return ImageMirrorMode(mode), err + } + + ret := C.rbd_mirror_image_get_mode(image.image, &mode) + return ImageMirrorMode(mode), getError(ret) +} diff --git a/vendor/github.com/ceph/go-ceph/rbd/rbd.go b/vendor/github.com/ceph/go-ceph/rbd/rbd.go index 7eb4bb8bd..c94fdd470 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/rbd.go +++ b/vendor/github.com/ceph/go-ceph/rbd/rbd.go @@ -95,7 +95,11 @@ type TrashInfo struct { // cephIoctx returns a ceph rados_ioctx_t given a go-ceph rados IOContext. func cephIoctx(radosIoctx *rados.IOContext) C.rados_ioctx_t { - return C.rados_ioctx_t(radosIoctx.Pointer()) + p := radosIoctx.Pointer() + if p == nil { + panic("invalid IOContext pointer") + } + return C.rados_ioctx_t(p) } // test if a bit is set in the given value diff --git a/vendor/modules.txt b/vendor/modules.txt index 23702fdf1..2974fb953 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -26,7 +26,7 @@ github.com/aws/aws-sdk-go/service/ec2 github.com/beorn7/perks/quantile # github.com/blang/semver v3.5.1+incompatible github.com/blang/semver -# github.com/ceph/go-ceph v0.7.0 +# github.com/ceph/go-ceph v0.8.0 github.com/ceph/go-ceph/cephfs/admin github.com/ceph/go-ceph/internal/callbacks github.com/ceph/go-ceph/internal/cutil