From a32355cba2d6d321960e1e74d806cf9084dfb8ec Mon Sep 17 00:00:00 2001 From: Rakshith R Date: Tue, 17 Aug 2021 17:22:24 +0530 Subject: [PATCH] rbd: implement binding for rbd_mirror_image_instance_id_list This commit adds MirrorImageInstanceIDList and MirrorImageInstanceIDIter with necessary helper functions and tests. Fixes: #483 Signed-off-by: Rakshith R --- rbd/mirror.go | 129 +++++++++++++++++++++++++++++++++++++++++++++ rbd/mirror_test.go | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) diff --git a/rbd/mirror.go b/rbd/mirror.go index e9dfc56..05c5998 100644 --- a/rbd/mirror.go +++ b/rbd/mirror.go @@ -908,3 +908,132 @@ func (iter *MirrorImageInfoIter) fetch() error { } return nil } + +// MirrorImageInstanceIDItem contains an ID string for a RBD image and +// its corresponding mirrored image's Instance ID. +type MirrorImageInstanceIDItem struct { + ID string + InstanceID string +} + +// MirrorImageInstanceIDList returns a slice of MirrorImageInstanceIDItem. If +// the length of the returned slice equals max, the next chunk of the list can +// be obtained by setting start to the ID of the last item of the returned slice. +// If max is 0 a slice of all items is returned. +// +// Implements: +// int rbd_mirror_image_instance_id_list( +// rados_ioctx_t io_ctx, +// const char *start_id, +// size_t max, char **image_ids, +// char **instance_ids, +// size_t *len) +func MirrorImageInstanceIDList( + ioctx *rados.IOContext, start string, + max int) ([]MirrorImageInstanceIDItem, error) { + var ( + result []MirrorImageInstanceIDItem + fetchAll bool + ) + if max <= 0 { + max = iterBufSize + fetchAll = true + } + chunk := make([]MirrorImageInstanceIDItem, max) + for { + length, err := mirrorImageInstanceIDList(ioctx, start, chunk) + if err != nil { + return nil, err + } + result = append(result, chunk[:length]...) + if !fetchAll || length < max { + break + } + start = chunk[length-1].ID + } + return result, nil +} + +func mirrorImageInstanceIDList(ioctx *rados.IOContext, start string, + results []MirrorImageInstanceIDItem) (int, error) { + + 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)) + instance_ids = make([]*C.char, len(results)) + ) + ret := C.rbd_mirror_image_instance_id_list( + cephIoctx(ioctx), + cStart, + max, + &ids[0], + &instance_ids[0], + &length, + ) + if err := getError(ret); err != nil { + return 0, err + } + for i := 0; i < int(length); i++ { + results[i].ID = C.GoString(ids[i]) + results[i].InstanceID = C.GoString(instance_ids[i]) + } + C.rbd_mirror_image_instance_id_list_cleanup( + &ids[0], + &instance_ids[0], + length) + return int(length), getError(ret) +} + +// MirrorImageInstanceIDIter provide methods for iterating over all +// the MirrorImageInstanceIDItem values in a pool. +type MirrorImageInstanceIDIter struct { + ioctx *rados.IOContext + + buf []MirrorImageInstanceIDItem + lastID string +} + +// NewMirrorImageInstanceIDIter creates a new iterator ready for use. +func NewMirrorImageInstanceIDIter(ioctx *rados.IOContext) *MirrorImageInstanceIDIter { + return &MirrorImageInstanceIDIter{ + ioctx: ioctx, + } +} + +// Next fetches one MirrorImageInstanceIDItem 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 *MirrorImageInstanceIDIter) Next() (*MirrorImageInstanceIDItem, error) { + if len(iter.buf) == 0 { + if err := iter.fetch(); err != nil { + return nil, err + } + if len(iter.buf) == 0 { + return nil, nil + } + iter.lastID = iter.buf[len(iter.buf)-1].ID + } + item := iter.buf[0] + iter.buf = iter.buf[1:] + return &item, nil +} + +func (iter *MirrorImageInstanceIDIter) fetch() error { + iter.buf = nil + items := make([]MirrorImageInstanceIDItem, iterBufSize) + n, err := mirrorImageInstanceIDList( + iter.ioctx, + iter.lastID, + items) + if err != nil { + return err + } + if n > 0 { + iter.buf = items[:n] + } + return nil +} diff --git a/rbd/mirror_test.go b/rbd/mirror_test.go index 063b88c..7672271 100644 --- a/rbd/mirror_test.go +++ b/rbd/mirror_test.go @@ -971,3 +971,130 @@ func TestMirrorImageLists(t *testing.T) { assert.Len(t, lst, 7) }) } + +func TestMirrorImageInstanceIDLists(t *testing.T) { + defer func(x int) { + iterBufSize = x + }(iterBufSize) + // shrink the buffer size in order to trigger more of the + // retry logic in the iter type + iterBufSize = 4 + + t.Run("instanceIDIterIoctxNil", func(t *testing.T) { + iter := NewMirrorImageInstanceIDIter(nil) + assert.Panics(t, func() { + iter.Next() //nolint:errcheck + }) + }) + + mconfig := mirrorConfig() + if mconfig == "" { + t.Skip("no mirror config env var set") + } + + conn := radosConnect(t) + defer conn.Shutdown() + + poolName := GetUUID() + err := conn.MakePool(poolName) + require.NoError(t, err) + defer func() { + assert.NoError(t, conn.DeletePool(poolName)) + }() + + ioctx, err := conn.OpenIOContext(poolName) + assert.NoError(t, err) + defer func() { + ioctx.Destroy() + }() + + err = SetMirrorMode(ioctx, MirrorModeImage) + require.NoError(t, err) + + token, err := CreateMirrorPeerBootstrapToken(ioctx) + assert.NoError(t, err) + assert.GreaterOrEqual(t, len(token), 4) + + conn2 := radosConnectConfig(t, mconfig) + defer conn2.Shutdown() + + err = conn2.MakePool(poolName) + require.NoError(t, err) + defer func() { + assert.NoError(t, conn2.DeletePool(poolName)) + }() + + ioctx2, err := conn2.OpenIOContext(poolName) + assert.NoError(t, err) + defer func() { + ioctx2.Destroy() + }() + + err = SetMirrorMode(ioctx2, MirrorModeImage) + require.NoError(t, err) + + err = ImportMirrorPeerBootstrapToken( + ioctx2, MirrorPeerDirectionRxTx, token) + assert.NoError(t, err) + + imgName := GetUUID() + options := NewRbdImageOptions() + assert.NoError(t, options.SetUint64(ImageOptionOrder, uint64(testImageOrder))) + + for i := 0; i < 5; i++ { + name := fmt.Sprintf("%s%d", imgName, i) + err = CreateImage(ioctx, name, testImageSize, options) + require.NoError(t, err) + img, err := OpenImage(ioctx, name, NoSnapshot) + assert.NoError(t, err) + err = img.MirrorEnable(ImageMirrorModeSnapshot) + assert.NoError(t, err) + require.NoError(t, img.Close()) + } + + // wait for all the images to be mirrored + for i := 0; i < 30; i++ { + lst, err := MirrorImageInstanceIDList(ioctx, "", 0) + assert.NoError(t, err) + if len(lst) == 5 { + break + } + time.Sleep(time.Second) + } + + t.Run("getInstanceID", func(t *testing.T) { + lst := []*MirrorImageInstanceIDItem{} + iter := NewMirrorImageInstanceIDIter(ioctx) + for { + istatus, err := iter.Next() + assert.NoError(t, err) + if istatus == nil { + break + } + lst = append(lst, istatus) + } + assert.Len(t, lst, 5) + assert.NoError(t, err) + }) + + t.Run("getInstanceIDSlice", func(t *testing.T) { + lst, err := MirrorImageInstanceIDList(ioctx, "", 0) + fmt.Print(lst) + assert.NoError(t, err) + assert.Len(t, lst, 5) + for i := 1; i < len(lst); i++ { + assert.NotEqual(t, lst[i-1].ID, lst[i].ID) + } + for i := 1; i <= iterBufSize; i++ { + lst, err := MirrorImageInstanceIDList(ioctx, "", i) + assert.NoError(t, err) + assert.Len(t, lst, i) + } + lst, err = MirrorImageInstanceIDList(ioctx, "", 3) + assert.NoError(t, err) + assert.Len(t, lst, 3) + lst, err = MirrorImageInstanceIDList(ioctx, lst[2].ID, 0) + assert.NoError(t, err) + assert.Len(t, lst, 2) + }) +}