Repository: ceph/go-ceph
Pull request #561 (2021-08-19 06:22:20)

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 <rar@redhat.com>


<!--
Thank you for opening a pull request. Please provide:

- A clear summary of your changes

- Descriptive and succinct commit messages with the format:
  """
  [topic]: [short description]

  [Longer description]

  Signed-off-by: [Your Name] <[your email]>
  """

  Topic will generally be the go ceph package dir you are working in.

- Ensure checklist items listed below are accounted for
-->

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code



Patch:
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
+}


Test patch:
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)
+	})
+}