rbd: add ListMetadata implementing rbd_metadata_list

The function is named ListMetadata but returns a map of all
the metadata assigned to the rbd image. It is named ListMetadata to
match the C librbd api call.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-10-22 13:22:09 -04:00 committed by mergify[bot]
parent 6bc6c7c3b1
commit e7791c9e06
2 changed files with 94 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
@ -84,3 +85,65 @@ func (image *Image) RemoveMetadata(key string) error {
return nil
}
// ListMetadata returns a map containing all metadata assigned to the RBD image.
//
// Implements:
// int rbd_metadata_list(rbd_image_t image, const char *start, uint64_t max,
// char *keys, size_t *key_len, char *values, size_t *vals_len);
func (image *Image) ListMetadata() (map[string]string, error) {
if err := image.validate(imageIsOpen); err != nil {
return nil, err
}
var (
err error
keysbuf []byte
keysSize C.size_t
valsbuf []byte
valsSize C.size_t
)
retry.WithSizes(4096, 262144, func(size int) retry.Hint {
keysbuf = make([]byte, size)
keysSize = C.size_t(size)
valsbuf = make([]byte, size)
valsSize = C.size_t(size)
// the rbd_metadata_list function can use a start point and a limit.
// we do not use it and prefer our retry helper and just allocating
// buffers large enough to take all the keys and values
ret := C.rbd_metadata_list(
image.image,
(*C.char)(unsafe.Pointer(&empty[0])), // always start at the beginning (no paging)
0, // fetch all key-value pairs
(*C.char)(unsafe.Pointer(&keysbuf[0])),
&keysSize,
(*C.char)(unsafe.Pointer(&valsbuf[0])),
&valsSize)
err = getError(ret)
nextSize := valsSize
if keysSize > nextSize {
nextSize = keysSize
}
return retry.Size(int(nextSize)).If(err == errRange)
})
if err != nil {
return nil, err
}
m := map[string]string{}
keys := cutil.SplitBuffer(keysbuf[:keysSize])
vals := cutil.SplitBuffer(valsbuf[:valsSize])
if len(keys) != len(vals) {
// this should not happen (famous last words)
return nil, errRange
}
for i := range keys {
m[keys[i]] = vals[i]
}
return m, nil
}
// rather than allocate memory every time that ListMetadata is called,
// define a static byte slice to stand in for the C "empty string"
var empty = []byte{0}

View File

@ -39,6 +39,9 @@ func TestImageMetadata(t *testing.T) {
value, err = image.GetMetadata(metadataKey)
assert.Equal(t, "", value)
assert.Equal(t, err, ErrImageNotOpen)
// check ListMetadata for unopened image
_, err = image.ListMetadata()
assert.Equal(t, err, ErrImageNotOpen)
image, err = OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
@ -50,6 +53,13 @@ func TestImageMetadata(t *testing.T) {
value, err = image.GetMetadata(metadataKey)
assert.NoError(t, err)
assert.Equal(t, metadataValue, value)
// List metadata
mm, err := image.ListMetadata()
assert.NoError(t, err)
assert.Len(t, mm, 1)
if assert.Contains(t, mm, metadataKey) {
assert.Equal(t, mm[metadataKey], metadataValue)
}
// Remove the metadata key
err = image.RemoveMetadata(metadataKey)
assert.NoError(t, err)
@ -58,6 +68,27 @@ func TestImageMetadata(t *testing.T) {
assert.Equal(t, "", value)
assert.Error(t, err)
// Set some additional metadata values
m1 := map[string]string{
metadataKey: metadataValue,
"k2": "himalayas",
"matterhorn": "alps",
"aconcagua": "andes",
}
for k, v := range m1 {
err = image.SetMetadata(k, v)
assert.NoError(t, err)
}
// Get the metadata value
value, err = image.GetMetadata(metadataKey)
assert.NoError(t, err)
assert.Equal(t, metadataValue, value)
// List metadata
mm, err = image.ListMetadata()
assert.NoError(t, err)
assert.Len(t, mm, 4)
assert.Equal(t, m1, mm)
err = image.Close()
assert.NoError(t, err)
err = image.Remove()