diff --git a/go.mod b/go.mod index 641cf988b..9cd0c3792 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/aws/aws-sdk-go v1.38.8 - github.com/ceph/go-ceph v0.8.0 + github.com/ceph/go-ceph v0.10.0 github.com/container-storage-interface/spec v1.3.0 github.com/csi-addons/replication-lib-utils v0.2.0 github.com/csi-addons/spec v0.1.0 diff --git a/go.sum b/go.sum index 41cf089f6..268108142 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,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.8.0 h1:d+VP0eyconBl9RrvKVUq7S0npyK969ErLkCt5pg2fp0= -github.com/ceph/go-ceph v0.8.0/go.mod h1:wd+keAOqrcsN//20VQnHBGtnBnY0KHl0PA024Ng8HfQ= +github.com/ceph/go-ceph v0.10.0 h1:c9Bvqdt2ccv7aSyNBFtN9fm4dwMyfj8IX2or15tb4ag= +github.com/ceph/go-ceph v0.10.0/go.mod h1:mafFpf5Vg8Ai8Bd+FAMvKBHLmtdpTXdRP/TNq8XWegY= 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/clone.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go index d31431041..c904792ad 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/clone.go @@ -53,10 +53,10 @@ func (fsa *FSAdmin) CloneSubVolumeSnapshot(volume, group, subvolume, snapshot, n } func checkCloneResponse(res response) error { - if strings.HasSuffix(res.status, notProtectedSuffix) { + if strings.HasSuffix(res.Status(), notProtectedSuffix) { return NotProtectedError{response: res} } - return res.noData().End() + return res.NoData().End() } // CloneState is used to define constant values used to determine the state of @@ -94,7 +94,7 @@ type cloneStatusWrapper struct { func parseCloneStatus(res response) (*CloneStatus, error) { var status cloneStatusWrapper - if err := res.noStatus().unmarshal(&status).End(); err != nil { + if err := res.NoStatus().Unmarshal(&status).End(); err != nil { return nil, err } return &status.Status, nil @@ -132,5 +132,5 @@ func (fsa *FSAdmin) CancelClone(volume, group, clone string) error { if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(m).noData().End() + return fsa.marshalMgrCommand(m).NoData().End() } diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go index e7bed2d16..854f20a1d 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/fsadmin.go @@ -3,18 +3,16 @@ package admin import ( - "encoding/json" "strconv" + ccom "github.com/ceph/go-ceph/common/commands" + "github.com/ceph/go-ceph/internal/commands" "github.com/ceph/go-ceph/rados" ) // RadosCommander provides an interface to execute JSON-formatted commands that // allow the cephfs administrative functions to interact with the Ceph cluster. -type RadosCommander interface { - MgrCommand(buf [][]byte) ([]byte, string, error) - MonCommand(buf []byte) ([]byte, string, error) -} +type RadosCommander = ccom.RadosCommander // FSAdmin is used to administrate CephFS within a ceph cluster. type FSAdmin struct { @@ -60,39 +58,25 @@ func (fsa *FSAdmin) validate() error { // rawMgrCommand takes a byte buffer and sends it to the MGR as a command. // The buffer is expected to contain preformatted JSON. func (fsa *FSAdmin) rawMgrCommand(buf []byte) response { - if err := fsa.validate(); err != nil { - return response{err: err} - } - return newResponse(fsa.conn.MgrCommand([][]byte{buf})) + return commands.RawMgrCommand(fsa.conn, buf) } // marshalMgrCommand takes an generic interface{} value, converts it to JSON and // sends the json to the MGR as a command. func (fsa *FSAdmin) marshalMgrCommand(v interface{}) response { - b, err := json.Marshal(v) - if err != nil { - return response{err: err} - } - return fsa.rawMgrCommand(b) + return commands.MarshalMgrCommand(fsa.conn, v) } // rawMonCommand takes a byte buffer and sends it to the MON as a command. // The buffer is expected to contain preformatted JSON. func (fsa *FSAdmin) rawMonCommand(buf []byte) response { - if err := fsa.validate(); err != nil { - return response{err: err} - } - return newResponse(fsa.conn.MonCommand(buf)) + return commands.RawMonCommand(fsa.conn, buf) } // marshalMonCommand takes an generic interface{} value, converts it to JSON and // sends the json to the MGR as a command. func (fsa *FSAdmin) marshalMonCommand(v interface{}) response { - b, err := json.Marshal(v) - if err != nil { - return response{err: err} - } - return fsa.rawMonCommand(b) + return commands.MarshalMonCommand(fsa.conn, v) } type listNamedResult struct { @@ -101,7 +85,7 @@ type listNamedResult struct { func parseListNames(res response) ([]string, error) { var r []listNamedResult - if err := res.noStatus().unmarshal(&r).End(); err != nil { + if err := res.NoStatus().Unmarshal(&r).End(); err != nil { return nil, err } vl := make([]string, len(r)) @@ -114,10 +98,10 @@ func parseListNames(res response) ([]string, error) { // parsePathResponse returns a cleaned up path from requests that get a path // unless an error is encountered, then an error is returned. func parsePathResponse(res response) (string, error) { - if res2 := res.noStatus(); !res2.Ok() { + if res2 := res.NoStatus(); !res2.Ok() { return "", res.End() } - b := res.body + b := res.Body() // if there's a trailing newline in the buffer strip it. // ceph assumes a CLI wants the output of the buffer and there's // no format=json mode available currently. diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go index a1b6bf3a0..29e2df4b4 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/response.go @@ -3,140 +3,22 @@ package admin import ( - "encoding/json" - "errors" - "fmt" - "strings" + "github.com/ceph/go-ceph/internal/commands" ) var ( - // ErrStatusNotEmpty may be returned if a call should not have a status - // string set but one is. - ErrStatusNotEmpty = errors.New("response status not empty") - // ErrBodyNotEmpty may be returned if a call should have an empty body but - // a body value is present. - ErrBodyNotEmpty = errors.New("response body not empty") + // ErrStatusNotEmpty is an alias for commands.ErrStatusNotEmpty + ErrStatusNotEmpty = commands.ErrStatusNotEmpty + // ErrBodyNotEmpty is an alias for commands.ErrBodyNotEmpty + ErrBodyNotEmpty = commands.ErrBodyNotEmpty ) -const ( - deprecatedSuffix = "call is deprecated and will be removed in a future release" - missingPrefix = "No handler found" - einval = -22 -) +type response = commands.Response -type cephError interface { - ErrorCode() int -} - -// NotImplementedError error values will be returned in the case that an API -// call is not available in the version of Ceph that is running in the target -// cluster. -type NotImplementedError struct { - response -} - -// Error implements the error interface. -func (e NotImplementedError) Error() string { - return fmt.Sprintf("API call not implemented server-side: %s", e.status) -} - -// response encapsulates the data returned by ceph and supports easy processing -// pipelines. -type response struct { - body []byte - status string - err error -} - -// Ok returns true if the response contains no error. -func (r response) Ok() bool { - return r.err == nil -} - -// Error implements the error interface. -func (r response) Error() string { - if r.status == "" { - return r.err.Error() - } - return fmt.Sprintf("%s: %q", r.err, r.status) -} - -// Unwrap returns the error this response contains. -func (r response) Unwrap() error { - return r.err -} - -// Status returns the status string value. -func (r response) Status() string { - return r.status -} - -// End returns an error if the response contains an error or nil, indicating -// that response is no longer needed for processing. -func (r response) End() error { - if !r.Ok() { - if ce, ok := r.err.(cephError); ok { - if ce.ErrorCode() == einval && strings.HasPrefix(r.status, missingPrefix) { - return NotImplementedError{response: r} - } - } - return r - } - return nil -} - -// noStatus asserts that the input response has no status value. -func (r response) noStatus() response { - if !r.Ok() { - return r - } - if r.status != "" { - return response{r.body, r.status, ErrStatusNotEmpty} - } - return r -} - -// noBody asserts that the input response has no body value. -func (r response) noBody() response { - if !r.Ok() { - return r - } - if len(r.body) != 0 { - return response{r.body, r.status, ErrBodyNotEmpty} - } - return r -} - -// noData asserts that the input response has no status or body values. -func (r response) noData() response { - return r.noStatus().noBody() -} - -// filterDeprecated removes deprecation warnings from the response status. -// Use it when checking the response from calls that may be deprecated in ceph -// if you want those calls to continue working if the warning is present. -func (r response) filterDeprecated() response { - if !r.Ok() { - return r - } - if strings.HasSuffix(r.status, deprecatedSuffix) { - return response{r.body, "", r.err} - } - return r -} - -// unmarshal data from the response body into v. -func (r response) unmarshal(v interface{}) response { - if !r.Ok() { - return r - } - if err := json.Unmarshal(r.body, v); err != nil { - return response{body: r.body, err: err} - } - return r -} +// NotImplementedError is an alias for commands.NotImplementedError. +type NotImplementedError = commands.NotImplementedError // newResponse returns a response. func newResponse(b []byte, s string, e error) response { - return response{b, s, e} + return commands.NewResponse(b, s, e) } 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 69fb4f99d..dec96d491 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolume.go @@ -61,7 +61,7 @@ func (fsa *FSAdmin) CreateSubVolume(volume, group, name string, o *SubVolumeOpti o = &SubVolumeOptions{} } f := o.toFields(volume, group, name) - return fsa.marshalMgrCommand(f).noData().End() + return fsa.marshalMgrCommand(f).NoData().End() } // ListSubVolumes returns a list of subvolumes belonging to the volume and @@ -117,7 +117,7 @@ func (fsa *FSAdmin) RemoveSubVolumeWithFlags(volume, group, name string, o SubVo if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(mergeFlags(m, o)).noData().End() + return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End() } type subVolumeResizeFields struct { @@ -159,7 +159,7 @@ func (fsa *FSAdmin) ResizeSubVolume( } var result []*SubVolumeResizeResult res := fsa.marshalMgrCommand(f) - if err := res.noStatus().unmarshal(&result).End(); err != nil { + if err := res.NoStatus().Unmarshal(&result).End(); err != nil { return nil, err } return result[0], nil @@ -248,7 +248,7 @@ type subVolumeInfoWrapper struct { func parseSubVolumeInfo(res response) (*SubVolumeInfo, error) { var info subVolumeInfoWrapper - if err := res.noStatus().unmarshal(&info).End(); err != nil { + if err := res.NoStatus().Unmarshal(&info).End(); err != nil { return nil, err } if info.VBytesQuota != nil { @@ -289,7 +289,7 @@ func (fsa *FSAdmin) CreateSubVolumeSnapshot(volume, group, source, name string) if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(m).noData().End() + return fsa.marshalMgrCommand(m).NoData().End() } // RemoveSubVolumeSnapshot removes the specified snapshot from the subvolume. @@ -320,7 +320,7 @@ func (fsa *FSAdmin) rmSubVolumeSnapshot(volume, group, subvolume, name string, o if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(mergeFlags(m, o)).noData().End() + return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End() } // ListSubVolumeSnapshots returns a listing of snapshots for a given subvolume. @@ -351,7 +351,7 @@ type SubVolumeSnapshotInfo struct { func parseSubVolumeSnapshotInfo(res response) (*SubVolumeSnapshotInfo, error) { var info SubVolumeSnapshotInfo - if err := res.noStatus().unmarshal(&info).End(); err != nil { + if err := res.NoStatus().Unmarshal(&info).End(); err != nil { return nil, err } return &info, nil @@ -390,7 +390,7 @@ func (fsa *FSAdmin) ProtectSubVolumeSnapshot(volume, group, subvolume, name stri if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(m).filterDeprecated().noData().End() + return fsa.marshalMgrCommand(m).FilterDeprecated().NoData().End() } // UnprotectSubVolumeSnapshot removes protection from the specified snapshot. @@ -408,5 +408,5 @@ func (fsa *FSAdmin) UnprotectSubVolumeSnapshot(volume, group, subvolume, name st if group != NoGroup { m["group_name"] = group } - return fsa.marshalMgrCommand(m).filterDeprecated().noData().End() + return fsa.marshalMgrCommand(m).FilterDeprecated().NoData().End() } diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go index 573c8c162..455ec495b 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/subvolumegroup.go @@ -48,7 +48,7 @@ func (fsa *FSAdmin) CreateSubVolumeGroup(volume, name string, o *SubVolumeGroupO o = &SubVolumeGroupOptions{} } res := fsa.marshalMgrCommand(o.toFields(volume, name)) - return res.noData().End() + return res.NoData().End() } // ListSubVolumeGroups returns a list of subvolume groups belonging to the @@ -86,7 +86,7 @@ func (fsa *FSAdmin) rmSubVolumeGroup(volume, name string, o commonRmFlags) error "group_name": name, "format": "json", }, o)) - return res.noData().End() + return res.NoData().End() } // SubVolumeGroupPath returns the path to the subvolume from the root of the diff --git a/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go b/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go index 082fa4d8b..cbca3a8e7 100644 --- a/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go +++ b/vendor/github.com/ceph/go-ceph/cephfs/admin/volume.go @@ -43,7 +43,7 @@ func (fsa *FSAdmin) ListFileSystems() ([]FSPoolInfo, error) { func parseFsList(res response) ([]FSPoolInfo, error) { var listing []FSPoolInfo - if err := res.noStatus().unmarshal(&listing).End(); err != nil { + if err := res.NoStatus().Unmarshal(&listing).End(); err != nil { return nil, err } return listing, nil @@ -78,13 +78,8 @@ func parseDumpToIdents(res response) ([]VolumeIdent, error) { if !res.Ok() { return nil, res.End() } - if len(res.status) >= dumpOkLen && res.status[:dumpOkLen] == dumpOkPrefix { - // Unhelpfully, ceph drops a status string on success responses for this - // call. this hacks around that by ignoring its typical prefix - res.status = "" - } var dump fsDump - if err := res.noStatus().unmarshal(&dump).End(); err != nil { + if err := res.FilterPrefix(dumpOkPrefix).NoStatus().Unmarshal(&dump).End(); err != nil { return nil, err } // copy the dump json into the simpler enumeration list @@ -123,15 +118,16 @@ type VolumeStatus struct { func parseVolumeStatus(res response) (*VolumeStatus, error) { var vs VolumeStatus - res = res.noStatus() + res = res.NoStatus() if !res.Ok() { return nil, res.End() } - res = res.unmarshal(&vs) + res = res.Unmarshal(&vs) if !res.Ok() { - if bytes.HasPrefix(res.body, []byte("ceph")) { - res.status = invalidTextualResponse - return nil, NotImplementedError{response: res} + if bytes.HasPrefix(res.Body(), []byte("ceph")) { + return nil, NotImplementedError{ + Response: newResponse(res.Body(), invalidTextualResponse, res.Unwrap()), + } } return nil, res.End() } diff --git a/vendor/github.com/ceph/go-ceph/common/commands/doc.go b/vendor/github.com/ceph/go-ceph/common/commands/doc.go new file mode 100644 index 000000000..f6290aba2 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/common/commands/doc.go @@ -0,0 +1,7 @@ +/* +Package commands provides types and utility functions that are used for +interfacing with the JSON based command infrastructure in Ceph. + +The *rados.Conn type implements many of the interfaces found in this package. +*/ +package commands diff --git a/vendor/github.com/ceph/go-ceph/common/commands/interfaces.go b/vendor/github.com/ceph/go-ceph/common/commands/interfaces.go new file mode 100644 index 000000000..c6350b753 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/common/commands/interfaces.go @@ -0,0 +1,20 @@ +package commands + +// MgrCommander in an interface for the API needed to execute JSON formatted +// commands on the ceph mgr. +type MgrCommander interface { + MgrCommand(buf [][]byte) ([]byte, string, error) +} + +// MonCommander is an interface for the API needed to execute JSON formatted +// commands on the ceph mon(s). +type MonCommander interface { + MonCommand(buf []byte) ([]byte, string, error) +} + +// RadosCommander provides an interface for APIs needed to execute JSON +// formatted commands on the Ceph cluster. +type RadosCommander interface { + MgrCommander + MonCommander +} diff --git a/vendor/github.com/ceph/go-ceph/internal/commands/commands.go b/vendor/github.com/ceph/go-ceph/internal/commands/commands.go new file mode 100644 index 000000000..d0318c659 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/commands/commands.go @@ -0,0 +1,53 @@ +package commands + +import ( + "encoding/json" + + ccom "github.com/ceph/go-ceph/common/commands" + "github.com/ceph/go-ceph/rados" +) + +func validate(m interface{}) error { + if m == nil { + return rados.ErrNotConnected + } + return nil +} + +// RawMgrCommand takes a byte buffer and sends it to the MGR as a command. +// The buffer is expected to contain preformatted JSON. +func RawMgrCommand(m ccom.MgrCommander, buf []byte) Response { + if err := validate(m); err != nil { + return Response{err: err} + } + return NewResponse(m.MgrCommand([][]byte{buf})) +} + +// MarshalMgrCommand takes an generic interface{} value, converts it to JSON +// and sends the json to the MGR as a command. +func MarshalMgrCommand(m ccom.MgrCommander, v interface{}) Response { + b, err := json.Marshal(v) + if err != nil { + return Response{err: err} + } + return RawMgrCommand(m, b) +} + +// RawMonCommand takes a byte buffer and sends it to the MON as a command. +// The buffer is expected to contain preformatted JSON. +func RawMonCommand(m ccom.MonCommander, buf []byte) Response { + if err := validate(m); err != nil { + return Response{err: err} + } + return NewResponse(m.MonCommand(buf)) +} + +// MarshalMonCommand takes an generic interface{} value, converts it to JSON +// and sends the json to the MGR as a command. +func MarshalMonCommand(m ccom.MonCommander, v interface{}) Response { + b, err := json.Marshal(v) + if err != nil { + return Response{err: err} + } + return RawMonCommand(m, b) +} diff --git a/vendor/github.com/ceph/go-ceph/internal/commands/response.go b/vendor/github.com/ceph/go-ceph/internal/commands/response.go new file mode 100644 index 000000000..86a56d3e7 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/commands/response.go @@ -0,0 +1,163 @@ +package commands + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +var ( + // ErrStatusNotEmpty may be returned if a call should not have a status + // string set but one is. + ErrStatusNotEmpty = errors.New("response status not empty") + // ErrBodyNotEmpty may be returned if a call should have an empty body but + // a body value is present. + ErrBodyNotEmpty = errors.New("response body not empty") +) + +const ( + deprecatedSuffix = "call is deprecated and will be removed in a future release" + missingPrefix = "No handler found" + einval = -22 +) + +type cephError interface { + ErrorCode() int +} + +// NotImplementedError error values will be returned in the case that an API +// call is not available in the version of Ceph that is running in the target +// cluster. +type NotImplementedError struct { + Response +} + +// Error implements the error interface. +func (e NotImplementedError) Error() string { + return fmt.Sprintf("API call not implemented server-side: %s", e.status) +} + +// Response encapsulates the data returned by ceph and supports easy processing +// pipelines. +type Response struct { + body []byte + status string + err error +} + +// Ok returns true if the response contains no error. +func (r Response) Ok() bool { + return r.err == nil +} + +// Error implements the error interface. +func (r Response) Error() string { + if r.status == "" { + return r.err.Error() + } + return fmt.Sprintf("%s: %q", r.err, r.status) +} + +// Unwrap returns the error this response contains. +func (r Response) Unwrap() error { + return r.err +} + +// Status returns the status string value. +func (r Response) Status() string { + return r.status +} + +// Body returns the response body as a raw byte-slice. +func (r Response) Body() []byte { + return r.body +} + +// End returns an error if the response contains an error or nil, indicating +// that response is no longer needed for processing. +func (r Response) End() error { + if !r.Ok() { + if ce, ok := r.err.(cephError); ok { + if ce.ErrorCode() == einval && strings.HasPrefix(r.status, missingPrefix) { + return NotImplementedError{Response: r} + } + } + return r + } + return nil +} + +// NoStatus asserts that the input response has no status value. +func (r Response) NoStatus() Response { + if !r.Ok() { + return r + } + if r.status != "" { + return Response{r.body, r.status, ErrStatusNotEmpty} + } + return r +} + +// NoBody asserts that the input response has no body value. +func (r Response) NoBody() Response { + if !r.Ok() { + return r + } + if len(r.body) != 0 { + return Response{r.body, r.status, ErrBodyNotEmpty} + } + return r +} + +// NoData asserts that the input response has no status or body values. +func (r Response) NoData() Response { + return r.NoStatus().NoBody() +} + +// FilterPrefix sets the status value to an empty string if the status +// value contains the given prefix string. +func (r Response) FilterPrefix(p string) Response { + if !r.Ok() { + return r + } + if strings.HasPrefix(r.status, p) { + return Response{r.body, "", r.err} + } + return r +} + +// FilterSuffix sets the status value to an empty string if the status +// value contains the given suffix string. +func (r Response) FilterSuffix(s string) Response { + if !r.Ok() { + return r + } + if strings.HasSuffix(r.status, s) { + return Response{r.body, "", r.err} + } + return r +} + +// FilterDeprecated removes deprecation warnings from the response status. +// Use it when checking the response from calls that may be deprecated in ceph +// if you want those calls to continue working if the warning is present. +func (r Response) FilterDeprecated() Response { + return r.FilterSuffix(deprecatedSuffix) +} + +// Unmarshal data from the response body into v. +func (r Response) Unmarshal(v interface{}) Response { + if !r.Ok() { + return r + } + if err := json.Unmarshal(r.body, v); err != nil { + return Response{body: r.body, err: err} + } + return r +} + +// NewResponse returns a response. +func NewResponse(b []byte, s string, e error) Response { + return Response{b, s, e} +} diff --git a/vendor/github.com/ceph/go-ceph/internal/commands/trace.go b/vendor/github.com/ceph/go-ceph/internal/commands/trace.go new file mode 100644 index 000000000..8d575fd90 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/commands/trace.go @@ -0,0 +1,53 @@ +package commands + +import ( + "fmt" + + ccom "github.com/ceph/go-ceph/common/commands" +) + +// NewTraceCommander is a RadosCommander that wraps a given RadosCommander +// and when commands are executes prints debug level "traces" to the +// standard output. +func NewTraceCommander(c ccom.RadosCommander) ccom.RadosCommander { + return &tracingCommander{c} +} + +// tracingCommander serves two purposes: first, it allows one to trace the +// input and output json when running the tests. It can help with actually +// debugging the tests. Second, it demonstrates the rationale for using an +// interface in FSAdmin. You can layer any sort of debugging, error injection, +// or whatnot between the FSAdmin layer and the RADOS layer. +type tracingCommander struct { + conn ccom.RadosCommander +} + +func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) { + fmt.Println("(MGR Command)") + for i := range buf { + fmt.Println("IN:", string(buf[i])) + } + r, s, err := t.conn.MgrCommand(buf) + fmt.Println("OUT(result):", string(r)) + if s != "" { + fmt.Println("OUT(status):", s) + } + if err != nil { + fmt.Println("OUT(error):", err.Error()) + } + return r, s, err +} + +func (t *tracingCommander) MonCommand(buf []byte) ([]byte, string, error) { + fmt.Println("(MON Command)") + fmt.Println("IN:", string(buf)) + r, s, err := t.conn.MonCommand(buf) + fmt.Println("OUT(result):", string(r)) + if s != "" { + fmt.Println("OUT(status):", s) + } + if err != nil { + fmt.Println("OUT(error):", err.Error()) + } + return r, s, err +} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go b/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go index a9af61a20..309c19dab 100644 --- a/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/aliases.go @@ -8,12 +8,13 @@ typedef void* voidptr; import "C" import ( + "math" "unsafe" ) const ( // MaxIdx is the maximum index on 32 bit systems - MaxIdx = 1<<31 - 1 // 2GB, max int32 value, should be safe + MaxIdx = math.MaxInt32 // 2GB, max int32 value, should be safe // PtrSize is the size of a pointer PtrSize = C.sizeof_voidptr diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/cslice.go b/vendor/github.com/ceph/go-ceph/internal/cutil/cslice.go new file mode 100644 index 000000000..e13473703 --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/cslice.go @@ -0,0 +1,76 @@ +package cutil + +// The following code needs some explanation: +// This creates slices on top of the C memory buffers allocated before in +// order to safely and comfortably use them as arrays. First the void pointer +// is cast to a pointer to an array of the type that will be stored in the +// array. Because the size of an array is a constant, but the real array size +// is dynamic, we just use the biggest possible index value MaxIdx, to make +// sure it's always big enough. (Nothing is allocated by casting, so the size +// can be arbitrarily big.) So, if the array should store items of myType, the +// cast would be (*[MaxIdx]myItem)(myCMemPtr). +// From that array pointer a slice is created with the [start:end:capacity] +// syntax. The capacity must be set explicitly here, because by default it +// would be set to the size of the original array, which is MaxIdx, which +// doesn't reflect reality in this case. This results in definitions like: +// cSlice := (*[MaxIdx]myItem)(myCMemPtr)[:numOfItems:numOfItems] + +////////// CPtr ////////// + +// CPtrCSlice is a C allocated slice of C pointers. +type CPtrCSlice []CPtr + +// NewCPtrCSlice returns a CPtrSlice. +// Similar to CString it must be freed with slice.Free() +func NewCPtrCSlice(size int) CPtrCSlice { + if size == 0 { + return nil + } + cMem := Malloc(SizeT(size) * PtrSize) + cSlice := (*[MaxIdx]CPtr)(cMem)[:size:size] + return cSlice +} + +// Ptr returns a pointer to CPtrSlice +func (v *CPtrCSlice) Ptr() CPtr { + if len(*v) == 0 { + return nil + } + return CPtr(&(*v)[0]) +} + +// Free frees a CPtrSlice +func (v *CPtrCSlice) Free() { + Free(v.Ptr()) + *v = nil +} + +////////// SizeT ////////// + +// SizeTCSlice is a C allocated slice of C.size_t. +type SizeTCSlice []SizeT + +// NewSizeTCSlice returns a SizeTCSlice. +// Similar to CString it must be freed with slice.Free() +func NewSizeTCSlice(size int) SizeTCSlice { + if size == 0 { + return nil + } + cMem := Malloc(SizeT(size) * SizeTSize) + cSlice := (*[MaxIdx]SizeT)(cMem)[:size:size] + return cSlice +} + +// Ptr returns a pointer to SizeTCSlice +func (v *SizeTCSlice) Ptr() CPtr { + if len(*v) == 0 { + return nil + } + return CPtr(&(*v)[0]) +} + +// Free frees a SizeTCSlice +func (v *SizeTCSlice) Free() { + Free(v.Ptr()) + *v = nil +} diff --git a/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go b/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go index 545e0c084..9be6424bd 100644 --- a/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go +++ b/vendor/github.com/ceph/go-ceph/internal/cutil/ptrguard.go @@ -57,6 +57,17 @@ func (v *PtrGuard) Release() { } } +// The uintptrPtr() helper function below assumes that uintptr has the same size +// as a pointer, although in theory it could be larger. Therefore we use this +// constant expression to assert size equality as a safeguard at compile time. +// How it works: the difference of both sizes is converted into an 8 bit value +// and left-bit-shifted by 8. This always creates an overflow error at compile +// time, if the difference of the sizes is not 0. +const _ = uint8(unsafe.Sizeof(uintptr(0))-PtrSize) << 8 // size assert +func uintptrPtr(p *CPtr) *uintptr { + return (*uintptr)(unsafe.Pointer(p)) +} + //go:uintptrescapes // From https://golang.org/src/cmd/compile/internal/gc/lex.go: @@ -69,7 +80,7 @@ func (v *PtrGuard) Release() { // Also see https://golang.org/cmd/compile/#hdr-Compiler_Directives func storeUntilRelease(v *PtrGuard, cPtr *CPtr, goPtr uintptr) { - uip := (*uintptr)(unsafe.Pointer(cPtr)) + uip := uintptrPtr(cPtr) *uip = goPtr // store Go pointer in C memory at c_ptr v.stored.Unlock() // send "stored" signal to main thread -->(1) v.release.Lock() // wait for "release" signal from main thread when diff --git a/vendor/github.com/ceph/go-ceph/rados/ioctx.go b/vendor/github.com/ceph/go-ceph/rados/ioctx.go index b3462e626..f63dafc40 100644 --- a/vendor/github.com/ceph/go-ceph/rados/ioctx.go +++ b/vendor/github.com/ceph/go-ceph/rados/ioctx.go @@ -658,3 +658,32 @@ func (ioctx *IOContext) GetLastVersion() (uint64, error) { 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 +} diff --git a/vendor/github.com/ceph/go-ceph/rados/ioctx_nautilus.go b/vendor/github.com/ceph/go-ceph/rados/ioctx_nautilus.go index f9a168b19..ba4214f59 100644 --- a/vendor/github.com/ceph/go-ceph/rados/ioctx_nautilus.go +++ b/vendor/github.com/ceph/go-ceph/rados/ioctx_nautilus.go @@ -1,4 +1,4 @@ -// +build !luminous,!mimic +// +build nautilus package rados @@ -7,37 +7,28 @@ package rados // import "C" -import ( - "unsafe" - - "github.com/ceph/go-ceph/internal/retry" -) - -// GetNamespace gets the namespace used for objects within this IO context. +// 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: -// int rados_ioctx_get_namespace(rados_ioctx_t io, char *buf, -// unsigned maxlen); -func (ioctx *IOContext) GetNamespace() (string, error) { +// void rados_set_osdmap_full_try(rados_ioctx_t io); +func (ioctx *IOContext) SetPoolFullTry() error { if err := ioctx.validate(); err != nil { - return "", err + 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 + 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 } diff --git a/vendor/github.com/ceph/go-ceph/rados/ioctx_octopus.go b/vendor/github.com/ceph/go-ceph/rados/ioctx_octopus.go new file mode 100644 index 000000000..5cf4d0c8e --- /dev/null +++ b/vendor/github.com/ceph/go-ceph/rados/ioctx_octopus.go @@ -0,0 +1,37 @@ +// +build !nautilus + +package rados + +// #cgo LDFLAGS: -lrados +// #include +// +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 +} diff --git a/vendor/github.com/ceph/go-ceph/rados/omap.go b/vendor/github.com/ceph/go-ceph/rados/omap.go index 79b4c8e40..e71f84b67 100644 --- a/vendor/github.com/ceph/go-ceph/rados/omap.go +++ b/vendor/github.com/ceph/go-ceph/rados/omap.go @@ -4,20 +4,14 @@ package rados #cgo LDFLAGS: -lrados #include #include - -typedef void* voidptr; - */ import "C" import ( "runtime" "unsafe" -) -const ( - ptrSize = C.sizeof_voidptr - sizeTSize = C.sizeof_size_t + "github.com/ceph/go-ceph/internal/cutil" ) // setOmapStep is a write op step. It holds C memory used in the operation. @@ -26,50 +20,44 @@ type setOmapStep struct { withoutUpdate // C arguments - cKeys **C.char - cValues **C.char - cLengths *C.size_t + cKeys cutil.CPtrCSlice + cValues cutil.CPtrCSlice + cLengths cutil.SizeTCSlice cNum C.size_t } func newSetOmapStep(pairs map[string][]byte) *setOmapStep { - maplen := C.size_t(len(pairs)) - cKeys := C.malloc(maplen * ptrSize) - cValues := C.malloc(maplen * ptrSize) - cLengths := C.malloc(maplen * sizeTSize) + maplen := len(pairs) + cKeys := cutil.NewCPtrCSlice(maplen) + cValues := cutil.NewCPtrCSlice(maplen) + cLengths := cutil.NewSizeTCSlice(maplen) sos := &setOmapStep{ - cKeys: (**C.char)(cKeys), - cValues: (**C.char)(cValues), - cLengths: (*C.size_t)(cLengths), - cNum: C.size_t(len(pairs)), + cKeys: cKeys, + cValues: cValues, + cLengths: cLengths, + cNum: C.size_t(maplen), } - sos.add(cKeys) - sos.add(cValues) - sos.add(cLengths) var i uintptr for key, value := range pairs { // key ck := C.CString(key) sos.add(unsafe.Pointer(ck)) - ckp := (**C.char)(unsafe.Pointer(uintptr(cKeys) + i*ptrSize)) - *ckp = ck + cKeys[i] = cutil.CPtr(ck) // value and its length - cvp := (**C.char)(unsafe.Pointer(uintptr(cValues) + i*ptrSize)) - vlen := C.size_t(len(value)) + vlen := cutil.SizeT(len(value)) if vlen > 0 { cv := C.CBytes(value) sos.add(cv) - *cvp = (*C.char)(cv) + cValues[i] = cutil.CPtr(cv) } else { - *cvp = nil + cValues[i] = nil } - clp := (*C.size_t)(unsafe.Pointer(uintptr(cLengths) + i*ptrSize)) - *clp = vlen + cLengths[i] = vlen i++ } @@ -79,9 +67,9 @@ func newSetOmapStep(pairs map[string][]byte) *setOmapStep { } func (sos *setOmapStep) free() { - sos.cKeys = nil - sos.cValues = nil - sos.cLengths = nil + sos.cKeys.Free() + sos.cValues.Free() + sos.cLengths.Free() sos.withRefs.free() } @@ -190,23 +178,21 @@ type removeOmapKeysStep struct { withoutUpdate // arguments: - cKeys **C.char + cKeys cutil.CPtrCSlice cNum C.size_t } func newRemoveOmapKeysStep(keys []string) *removeOmapKeysStep { - cKeys := C.malloc(C.size_t(len(keys)) * ptrSize) + cKeys := cutil.NewCPtrCSlice(len(keys)) roks := &removeOmapKeysStep{ - cKeys: (**C.char)(cKeys), + cKeys: 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)) + cKeys[i] = cutil.CPtr(C.CString(key)) + roks.add(unsafe.Pointer(cKeys[i])) i++ } @@ -215,7 +201,7 @@ func newRemoveOmapKeysStep(keys []string) *removeOmapKeysStep { } func (roks *removeOmapKeysStep) free() { - roks.cKeys = nil + roks.cKeys.Free() roks.withRefs.free() } diff --git a/vendor/github.com/ceph/go-ceph/rados/write_op.go b/vendor/github.com/ceph/go-ceph/rados/write_op.go index 0fab21042..cdb838d0c 100644 --- a/vendor/github.com/ceph/go-ceph/rados/write_op.go +++ b/vendor/github.com/ceph/go-ceph/rados/write_op.go @@ -90,15 +90,15 @@ func (w *WriteOp) Create(exclusive CreateOption) { C.rados_write_op_create(w.op, C.int(exclusive), nil) } -// SetOmap appends the map `pairs` to the omap `oid`. +// 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, + (**C.char)(sos.cKeys.Ptr()), + (**C.char)(sos.cValues.Ptr()), + (*C.size_t)(sos.cLengths.Ptr()), sos.cNum) } @@ -108,7 +108,7 @@ func (w *WriteOp) RmOmapKeys(keys []string) { w.steps = append(w.steps, roks) C.rados_write_op_omap_rm_keys( w.op, - roks.cKeys, + (**C.char)(roks.cKeys.Ptr()), roks.cNum) } diff --git a/vendor/github.com/ceph/go-ceph/rbd/errors.go b/vendor/github.com/ceph/go-ceph/rbd/errors.go index 5bfc09248..34693333f 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/errors.go +++ b/vendor/github.com/ceph/go-ceph/rbd/errors.go @@ -71,6 +71,12 @@ var ( // revive:enable:exported ) +// Public general error +const ( + // ErrNotExist indicates a non-specific missing resource. + ErrNotExist = rbdError(-C.ENOENT) +) + // Private errors: const ( diff --git a/vendor/github.com/ceph/go-ceph/rbd/mirror.go b/vendor/github.com/ceph/go-ceph/rbd/mirror.go index 1087494d0..84f2f47eb 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/mirror.go +++ b/vendor/github.com/ceph/go-ceph/rbd/mirror.go @@ -13,8 +13,10 @@ package rbd import "C" import ( + "fmt" "unsafe" + "github.com/ceph/go-ceph/internal/cutil" "github.com/ceph/go-ceph/internal/retry" "github.com/ceph/go-ceph/rados" ) @@ -31,6 +33,20 @@ const ( MirrorModePool = MirrorMode(C.RBD_MIRROR_MODE_POOL) ) +// String representation of MirrorMode. +func (m MirrorMode) String() string { + switch m { + case MirrorModeDisabled: + return "disabled" + case MirrorModeImage: + return "image" + case MirrorModePool: + return "pool" + default: + return "" + } +} + // ImageMirrorMode is used to indicate the mirroring approach for an RBD image. type ImageMirrorMode int64 @@ -43,6 +59,46 @@ const ( ImageMirrorModeSnapshot = ImageMirrorMode(C.RBD_MIRROR_IMAGE_MODE_SNAPSHOT) ) +// String representation of ImageMirrorMode. +func (imm ImageMirrorMode) String() string { + switch imm { + case ImageMirrorModeJournal: + return "journal" + case ImageMirrorModeSnapshot: + return "snapshot" + default: + return "" + } +} + +// GetMirrorUUID returns a string naming the mirroring uuid for the pool +// associated with the ioctx. +// +// Implements: +// int rbd_mirror_uuid_get(rados_ioctx_t io_ctx, +// char *uuid, size_t *max_len); +func GetMirrorUUID(ioctx *rados.IOContext) (string, error) { + 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_uuid_get( + cephIoctx(ioctx), + (*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 +} + // SetMirrorMode is used to enable or disable pool level mirroring with either // an automatic or per-image behavior. // @@ -181,6 +237,20 @@ const ( MirrorImageDisabled = MirrorImageState(C.RBD_MIRROR_IMAGE_DISABLED) ) +// String representation of MirrorImageState. +func (mis MirrorImageState) String() string { + switch mis { + case MirrorImageDisabling: + return "disabling" + case MirrorImageEnabled: + return "enabled" + case MirrorImageDisabled: + return "disabled" + default: + return "" + } +} + // MirrorImageInfo represents the mirroring status information of a RBD image. type MirrorImageInfo struct { GlobalID string @@ -188,6 +258,14 @@ type MirrorImageInfo struct { Primary bool } +func convertMirrorImageInfo(cInfo *C.rbd_mirror_image_info_t) MirrorImageInfo { + return MirrorImageInfo{ + GlobalID: C.GoString(cInfo.global_id), + State: MirrorImageState(cInfo.state), + Primary: bool(cInfo.primary), + } +} + // GetMirrorImageInfo fetches the mirroring status information of a RBD image. // // Implements: @@ -209,11 +287,7 @@ func (image *Image) GetMirrorImageInfo() (*MirrorImageInfo, error) { return nil, getError(ret) } - mii := MirrorImageInfo{ - GlobalID: C.GoString(cInfo.global_id), - State: MirrorImageState(cInfo.state), - Primary: bool(cInfo.primary), - } + mii := convertMirrorImageInfo(&cInfo) // free C memory allocated by C.rbd_mirror_image_get_info call C.rbd_mirror_image_get_info_cleanup(&cInfo) @@ -233,3 +307,418 @@ func (image *Image) GetImageMirrorMode() (ImageMirrorMode, error) { ret := C.rbd_mirror_image_get_mode(image.image, &mode) return ImageMirrorMode(mode), getError(ret) } + +// MirrorImageStatusState is used to indicate the state of a mirrored image +// within the site status info. +type MirrorImageStatusState int64 + +const ( + // MirrorImageStatusStateUnknown is equivalent to MIRROR_IMAGE_STATUS_STATE_UNKNOWN + MirrorImageStatusStateUnknown = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_UNKNOWN) + // MirrorImageStatusStateError is equivalent to MIRROR_IMAGE_STATUS_STATE_ERROR + MirrorImageStatusStateError = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_ERROR) + // MirrorImageStatusStateSyncing is equivalent to MIRROR_IMAGE_STATUS_STATE_SYNCING + MirrorImageStatusStateSyncing = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_SYNCING) + // MirrorImageStatusStateStartingReplay is equivalent to MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY + MirrorImageStatusStateStartingReplay = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY) + // MirrorImageStatusStateReplaying is equivalent to MIRROR_IMAGE_STATUS_STATE_REPLAYING + MirrorImageStatusStateReplaying = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_REPLAYING) + // MirrorImageStatusStateStoppingReplay is equivalent to MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY + MirrorImageStatusStateStoppingReplay = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY) + // MirrorImageStatusStateStopped is equivalent to MIRROR_IMAGE_STATUS_STATE_STOPPED + MirrorImageStatusStateStopped = MirrorImageStatusState(C.MIRROR_IMAGE_STATUS_STATE_STOPPED) +) + +// String represents the MirrorImageStatusState as a short string. +func (state MirrorImageStatusState) String() (s string) { + switch state { + case MirrorImageStatusStateUnknown: + s = "unknown" + case MirrorImageStatusStateError: + s = "error" + case MirrorImageStatusStateSyncing: + s = "syncing" + case MirrorImageStatusStateStartingReplay: + s = "starting_replay" + case MirrorImageStatusStateReplaying: + s = "replaying" + case MirrorImageStatusStateStoppingReplay: + s = "stopping_replay" + case MirrorImageStatusStateStopped: + s = "stopped" + default: + s = fmt.Sprintf("unknown(%d)", state) + } + return s +} + +// SiteMirrorImageStatus contains information pertaining to the status of +// a mirrored image within a site. +type SiteMirrorImageStatus struct { + MirrorUUID string + State MirrorImageStatusState + Description string + LastUpdate int64 + Up bool +} + +// GlobalMirrorImageStatus contains information pertaining to the global +// status of a mirrored image. It contains general information as well +// as per-site information stored in the SiteStatuses slice. +type GlobalMirrorImageStatus struct { + Name string + Info MirrorImageInfo + SiteStatuses []SiteMirrorImageStatus +} + +// LocalStatus returns one SiteMirrorImageStatus item from the SiteStatuses +// slice that corresponds to the local site's status. If the local status +// is not found than the error ErrNotExist will be returned. +func (gmis GlobalMirrorImageStatus) LocalStatus() (SiteMirrorImageStatus, error) { + var ( + ss SiteMirrorImageStatus + err error = ErrNotExist + ) + for i := range gmis.SiteStatuses { + // I couldn't find it explicitly documented, but a site mirror uuid + // of an empty string indicates that this is the local site. + // This pattern occurs in both the pybind code and ceph c++. + if gmis.SiteStatuses[i].MirrorUUID == "" { + ss = gmis.SiteStatuses[i] + err = nil + break + } + } + return ss, err +} + +type siteArray [cutil.MaxIdx]C.rbd_mirror_image_site_status_t + +// GetGlobalMirrorStatus returns status information pertaining to the state +// of the images's mirroring. +// +// Implements: +// int rbd_mirror_image_get_global_status( +// rbd_image_t image, +// rbd_mirror_image_global_status_t *mirror_image_global_status, +// size_t status_size); +func (image *Image) GetGlobalMirrorStatus() (GlobalMirrorImageStatus, error) { + if err := image.validate(imageIsOpen); err != nil { + return GlobalMirrorImageStatus{}, err + } + + s := C.rbd_mirror_image_global_status_t{} + ret := C.rbd_mirror_image_get_global_status( + image.image, + &s, + C.sizeof_rbd_mirror_image_global_status_t) + if err := getError(ret); err != nil { + return GlobalMirrorImageStatus{}, err + } + defer C.rbd_mirror_image_global_status_cleanup(&s) + + status := newGlobalMirrorImageStatus(&s) + return status, nil +} + +func newGlobalMirrorImageStatus( + s *C.rbd_mirror_image_global_status_t) GlobalMirrorImageStatus { + + status := GlobalMirrorImageStatus{ + Name: C.GoString(s.name), + Info: convertMirrorImageInfo(&s.info), + SiteStatuses: make([]SiteMirrorImageStatus, s.site_statuses_count), + } + // use the "Sven Technique" to treat the C pointer as a go slice temporarily + sscs := (*siteArray)(unsafe.Pointer(s.site_statuses))[:s.site_statuses_count:s.site_statuses_count] + for i := C.uint32_t(0); i < s.site_statuses_count; i++ { + ss := sscs[i] + status.SiteStatuses[i] = SiteMirrorImageStatus{ + MirrorUUID: C.GoString(ss.mirror_uuid), + State: MirrorImageStatusState(ss.state), + Description: C.GoString(ss.description), + LastUpdate: int64(ss.last_update), + Up: bool(ss.up), + } + } + return status +} + +// CreateMirrorSnapshot creates a snapshot for image propagation to mirrors. +// +// Implements: +// int rbd_mirror_image_create_snapshot(rbd_image_t image, +// uint64_t *snap_id); +func (image *Image) CreateMirrorSnapshot() (uint64, error) { + var snapID C.uint64_t + ret := C.rbd_mirror_image_create_snapshot( + image.image, + &snapID) + return uint64(snapID), getError(ret) +} + +// MirrorImageStatusSummary returns a map of images statuses and the count +// of images with said status. +// +// Implements: +// int rbd_mirror_image_status_summary( +// rados_ioctx_t io_ctx, rbd_mirror_image_status_state_t *states, int *counts, +// size_t *maxlen); +func MirrorImageStatusSummary( + ioctx *rados.IOContext) (map[MirrorImageStatusState]uint, error) { + // ideally, we already know the size of the arrays - they should be + // the size of all the values of the rbd_mirror_image_status_state_t + // enum. But the C api doesn't enforce this so we give a little + // wiggle room in case the server returns values outside the enum + // we know about. This is the only case I can think of that we'd + // be able to get -ERANGE. + var ( + cioctx = cephIoctx(ioctx) + err error + cStates []C.rbd_mirror_image_status_state_t + cCounts []C.int + cSize C.size_t + ) + retry.WithSizes(16, 1<<16, func(size int) retry.Hint { + cSize = C.size_t(size) + cStates = make([]C.rbd_mirror_image_status_state_t, cSize) + cCounts = make([]C.int, cSize) + ret := C.rbd_mirror_image_status_summary( + cioctx, + (*C.rbd_mirror_image_status_state_t)(&cStates[0]), + (*C.int)(&cCounts[0]), + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + if err != nil { + return nil, err + } + + m := map[MirrorImageStatusState]uint{} + for i := 0; i < int(cSize); i++ { + s := MirrorImageStatusState(cStates[i]) + m[s] = uint(cCounts[i]) + } + return m, nil +} + +// SetMirrorSiteName sets the site name, used for rbd mirroring, for the ceph +// cluster associated with the provided rados connection. +// +// Implements: +// int rbd_mirror_site_name_set(rados_t cluster, +// const char *name); +func SetMirrorSiteName(conn *rados.Conn, name string) error { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + ret := C.rbd_mirror_site_name_set( + C.rados_t(conn.Cluster()), + cName) + return getError(ret) +} + +// GetMirrorSiteName gets the site name, used for rbd mirroring, for the ceph +// cluster associated with the provided rados connection. +// +// Implements: +// int rbd_mirror_site_name_get(rados_t cluster, +// char *name, size_t *max_len); +func GetMirrorSiteName(conn *rados.Conn) (string, error) { + + var ( + cluster = C.rados_t(conn.Cluster()) + 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_site_name_get( + cluster, + (*C.char)(unsafe.Pointer(&buf[0])), + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + if err != nil { + return "", err + } + // the C code sets the size including null byte + return string(buf[:cSize-1]), nil +} + +// CreateMirrorPeerBootstrapToken returns a token value, representing the +// cluster and pool associated with the given IO context, that can be provided +// to ImportMirrorPeerBootstrapToken in order to set up mirroring between +// pools. +// +// Implements: +// int rbd_mirror_peer_bootstrap_create( +// rados_ioctx_t io_ctx, char *token, size_t *max_len); +func CreateMirrorPeerBootstrapToken(ioctx *rados.IOContext) (string, error) { + var ( + cioctx = cephIoctx(ioctx) + 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_peer_bootstrap_create( + cioctx, + (*C.char)(unsafe.Pointer(&buf[0])), + &cSize) + err = getErrorIfNegative(ret) + return retry.Size(int(cSize)).If(err == errRange) + }) + if err != nil { + return "", err + } + // the C code sets the size including null byte + return string(buf[:cSize-1]), nil +} + +// MirrorPeerDirection is used to indicate what direction data is mirrored. +type MirrorPeerDirection int + +const ( + // MirrorPeerDirectionRx is equivalent to RBD_MIRROR_PEER_DIRECTION_RX + MirrorPeerDirectionRx = MirrorPeerDirection(C.RBD_MIRROR_PEER_DIRECTION_RX) + // MirrorPeerDirectionTx is equivalent to RBD_MIRROR_PEER_DIRECTION_TX + MirrorPeerDirectionTx = MirrorPeerDirection(C.RBD_MIRROR_PEER_DIRECTION_TX) + // MirrorPeerDirectionRxTx is equivalent to RBD_MIRROR_PEER_DIRECTION_RX_TX + MirrorPeerDirectionRxTx = MirrorPeerDirection(C.RBD_MIRROR_PEER_DIRECTION_RX_TX) +) + +// ImportMirrorPeerBootstrapToken applies the provided bootstrap token to the +// pool associated with the IO context to create a mirroring relationship +// between pools. The direction parameter controls if data in the pool is a +// source, destination, or both. +// +// Implements: +// int rbd_mirror_peer_bootstrap_import( +// rados_ioctx_t io_ctx, rbd_mirror_peer_direction_t direction, +// const char *token); +func ImportMirrorPeerBootstrapToken( + ioctx *rados.IOContext, direction MirrorPeerDirection, token string) error { + // instead of taking a length, rbd_mirror_peer_bootstrap_import assumes a + // null terminated "c string". We don't use CString because we don't use + // Go's string type as we don't want to treat the token as something users + // should interpret. If we were doing CString we'd be doing a copy anyway. + cToken := C.CString(token) + defer C.free(unsafe.Pointer(cToken)) + + ret := C.rbd_mirror_peer_bootstrap_import( + cephIoctx(ioctx), + C.rbd_mirror_peer_direction_t(direction), + cToken) + return getError(ret) +} + +// GlobalMirrorImageIDAndStatus values contain an ID string for a RBD image +// and that image's GlobalMirrorImageStatus. +type GlobalMirrorImageIDAndStatus struct { + ID string + Status GlobalMirrorImageStatus +} + +func mirrorImageGlobalStatusList( + ioctx *rados.IOContext, start string, + results []GlobalMirrorImageIDAndStatus) (int, error) { + // this C function is treated like a "batch" iterator. Based on it's + // design it appears expected to call it multiple times to get + // the entire result. + cStart := C.CString(start) + defer C.free(unsafe.Pointer(cStart)) + + var ( + max = C.size_t(len(results)) + length = C.size_t(0) + ids = make([]*C.char, len(results)) + images = make([]C.rbd_mirror_image_global_status_t, len(results)) + ) + ret := C.rbd_mirror_image_global_status_list( + cephIoctx(ioctx), + cStart, + max, + (**C.char)(unsafe.Pointer(&ids[0])), + (*C.rbd_mirror_image_global_status_t)(unsafe.Pointer(&images[0])), + &length) + + for i := 0; i < int(length); i++ { + results[i].ID = C.GoString(ids[i]) + results[i].Status = newGlobalMirrorImageStatus(&images[0]) + } + C.rbd_mirror_image_global_status_list_cleanup( + (**C.char)(unsafe.Pointer(&ids[0])), + (*C.rbd_mirror_image_global_status_t)(unsafe.Pointer(&images[0])), + length) + return int(length), getError(ret) +} + +// statusIterBufSize is intentionally not a constant. The unit tests alter +// this value in order to get more code coverage w/o needing to create +// very many images. +var statusIterBufSize = 64 + +// MirrorImageGlobalStatusIter provide methods for iterating over all +// the GlobalMirrorImageIdAndStatus values in a pool. +type MirrorImageGlobalStatusIter struct { + ioctx *rados.IOContext + + buf []GlobalMirrorImageIDAndStatus + lastID string +} + +// NewMirrorImageGlobalStatusIter creates a new iterator type ready for use. +func NewMirrorImageGlobalStatusIter(ioctx *rados.IOContext) *MirrorImageGlobalStatusIter { + return &MirrorImageGlobalStatusIter{ + ioctx: ioctx, + } +} + +// Next fetches one GlobalMirrorImageIDAndStatus value or a nil value if +// iteration is exhausted. The error return will be non-nil if an underlying +// error fetching more values occurred. +func (iter *MirrorImageGlobalStatusIter) Next() (*GlobalMirrorImageIDAndStatus, error) { + if len(iter.buf) == 0 { + if err := iter.fetch(); err != nil { + return nil, err + } + } + if len(iter.buf) == 0 { + return nil, nil + } + item := iter.buf[0] + iter.lastID = item.ID + iter.buf = iter.buf[1:] + return &item, nil +} + +// Close terminates iteration regardless if iteration was completed and +// frees any associated resources. +func (iter *MirrorImageGlobalStatusIter) Close() error { + iter.buf = nil + iter.lastID = "" + return nil +} + +func (iter *MirrorImageGlobalStatusIter) fetch() error { + iter.buf = nil + items := make([]GlobalMirrorImageIDAndStatus, statusIterBufSize) + n, err := mirrorImageGlobalStatusList( + iter.ioctx, + iter.lastID, + items) + if err != nil { + return err + } + if n > 0 { + iter.buf = items[:n] + } + return nil +} diff --git a/vendor/github.com/ceph/go-ceph/rbd/rbd.go b/vendor/github.com/ceph/go-ceph/rbd/rbd.go index c94fdd470..b0091e942 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/rbd.go +++ b/vendor/github.com/ceph/go-ceph/rbd/rbd.go @@ -531,6 +531,33 @@ func (image *Image) Copy2(dest *Image) error { return getError(C.rbd_copy2(image.image, dest.image)) } +// DeepCopy an rbd image to a new image with specific options. +// +// Implements: +// int rbd_deep_copy(rbd_image_t src, rados_ioctx_t dest_io_ctx, +// const char *destname, rbd_image_options_t dest_opts); +func (image *Image) DeepCopy(ioctx *rados.IOContext, destname string, rio *ImageOptions) error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + if ioctx == nil { + return ErrNoIOContext + } + if destname == "" { + return ErrNoName + } + if rio == nil { + return rbdError(C.EINVAL) + } + + cDestname := C.CString(destname) + defer C.free(unsafe.Pointer(cDestname)) + + ret := C.rbd_deep_copy(image.image, cephIoctx(ioctx), cDestname, + C.rbd_image_options_t(rio.options)) + return getError(ret) +} + // Flatten removes snapshot references from the image. // // Implements: @@ -831,7 +858,7 @@ func (image *Image) WriteSame(ofs, n uint64, data []byte, flags rados.OpFlags) ( ret := C.rbd_writesame(image.image, C.uint64_t(ofs), - C.uint64_t(n), + C.size_t(n), (*C.char)(unsafe.Pointer(&data[0])), C.size_t(len(data)), C.int(flags)) @@ -916,6 +943,22 @@ func (image *Image) GetId() (string, error) { } +// SetSnapshot updates the rbd image (not the Snapshot) such that the snapshot +// is the source of readable data. +// +// Implements: +// int rbd_snap_set(rbd_image_t image, const char *snapname); +func (image *Image) SetSnapshot(snapname string) error { + if err := image.validate(imageIsOpen); err != nil { + return err + } + + c_snapname := C.CString(snapname) + defer C.free(unsafe.Pointer(c_snapname)) + + return getError(C.rbd_snap_set(image.image, c_snapname)) +} + // GetTrashList returns a slice of TrashInfo structs, containing information about all RBD images // currently residing in the trash. func GetTrashList(ioctx *rados.IOContext) ([]TrashInfo, error) { diff --git a/vendor/github.com/ceph/go-ceph/rbd/snapshot.go b/vendor/github.com/ceph/go-ceph/rbd/snapshot.go index 9f10c63c4..97277d72f 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/snapshot.go +++ b/vendor/github.com/ceph/go-ceph/rbd/snapshot.go @@ -149,6 +149,7 @@ func (snapshot *Snapshot) IsProtected() (bool, error) { // Set updates the rbd image (not the Snapshot) such that the snapshot // is the source of readable data. +// This method is deprecated. Refer the SetSnapshot method of the Image type instead. // // Implements: // int rbd_snap_set(rbd_image_t image, const char *snapname); @@ -157,10 +158,7 @@ func (snapshot *Snapshot) Set() error { return err } - c_snapname := C.CString(snapshot.name) - defer C.free(unsafe.Pointer(c_snapname)) - - return getError(C.rbd_snap_set(snapshot.image.image, c_snapname)) + return snapshot.image.SetSnapshot(snapshot.name) } // GetSnapTimestamp returns the timestamp of a snapshot for an image. diff --git a/vendor/github.com/ceph/go-ceph/rbd/snapshot_octopus.go b/vendor/github.com/ceph/go-ceph/rbd/snapshot_octopus.go index 765eb9232..c6ff3854d 100644 --- a/vendor/github.com/ceph/go-ceph/rbd/snapshot_octopus.go +++ b/vendor/github.com/ceph/go-ceph/rbd/snapshot_octopus.go @@ -1,4 +1,4 @@ -// +build octopus +// +build !nautilus package rbd diff --git a/vendor/modules.txt b/vendor/modules.txt index f0832db6e..4f3dfe131 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -46,9 +46,11 @@ github.com/aws/aws-sdk-go/service/sts/stsiface github.com/beorn7/perks/quantile # github.com/blang/semver v3.5.1+incompatible github.com/blang/semver -# github.com/ceph/go-ceph v0.8.0 +# github.com/ceph/go-ceph v0.10.0 github.com/ceph/go-ceph/cephfs/admin +github.com/ceph/go-ceph/common/commands github.com/ceph/go-ceph/internal/callbacks +github.com/ceph/go-ceph/internal/commands github.com/ceph/go-ceph/internal/cutil github.com/ceph/go-ceph/internal/errutil github.com/ceph/go-ceph/internal/retry