Repository: ceph/go-ceph
Pull request #523 (2021-06-28 13:16:16)

rbd: implement bindings for rbd_mirror_image_info_list
This change adds an iterator type to fetch infos for all images in a pool with `rbd_mirror_image_info_list()`. It uses the same interface as `MirrorImageGlobalStatusIter`.

Resolves: #482

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code


phlogistonjohn: @mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch has been successfully rebased**
> 

phlogistonjohn: @nixpanic or @agarwal-mudit PTAL when you have a moment. Other PRs depend on this work.
ansiwen: @mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch has been successfully rebased**
> 

phlogistonjohn: @Mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch has been successfully rebased**
> 


Patch:
diff --git a/rbd/mirror.go b/rbd/mirror.go
index 84f2f47..f6032cf 100644
--- a/rbd/mirror.go
+++ b/rbd/mirror.go
@@ -50,6 +50,16 @@ func (m MirrorMode) String() string {
 // ImageMirrorMode is used to indicate the mirroring approach for an RBD image.
 type ImageMirrorMode int64
 
+// ImageMirrorModeFilter is a ImageMirrorMode or nil for no filtering
+type ImageMirrorModeFilter interface {
+	mode() ImageMirrorMode
+}
+
+// Mode returns the ImageMirrorMode
+func (imm ImageMirrorMode) mode() ImageMirrorMode {
+	return imm
+}
+
 const (
 	// ImageMirrorModeJournal uses journaling to propagate RBD images between
 	// ceph clusters.
@@ -71,12 +81,12 @@ func (imm ImageMirrorMode) String() string {
 	}
 }
 
-//  GetMirrorUUID returns a string naming the mirroring uuid for the pool
-//  associated with the ioctx.
+// 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);
+// 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
@@ -645,8 +655,8 @@ func mirrorImageGlobalStatusList(
 		cephIoctx(ioctx),
 		cStart,
 		max,
-		(**C.char)(unsafe.Pointer(&ids[0])),
-		(*C.rbd_mirror_image_global_status_t)(unsafe.Pointer(&images[0])),
+		&ids[0],
+		&images[0],
 		&length)
 
 	for i := 0; i < int(length); i++ {
@@ -654,16 +664,16 @@ func mirrorImageGlobalStatusList(
 		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])),
+		&ids[0],
+		&images[0],
 		length)
 	return int(length), getError(ret)
 }
 
-// statusIterBufSize is intentionally not a constant. The unit tests alter
+// iterBufSize 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
+var iterBufSize = 64
 
 // MirrorImageGlobalStatusIter provide methods for iterating over all
 // the GlobalMirrorImageIdAndStatus values in a pool.
@@ -709,7 +719,7 @@ func (iter *MirrorImageGlobalStatusIter) Close() error {
 
 func (iter *MirrorImageGlobalStatusIter) fetch() error {
 	iter.buf = nil
-	items := make([]GlobalMirrorImageIDAndStatus, statusIterBufSize)
+	items := make([]GlobalMirrorImageIDAndStatus, iterBufSize)
 	n, err := mirrorImageGlobalStatusList(
 		iter.ioctx,
 		iter.lastID,
@@ -722,3 +732,105 @@ func (iter *MirrorImageGlobalStatusIter) fetch() error {
 	}
 	return nil
 }
+
+// MirrorImageInfoItem contains an ID string for a RBD image and that image's
+// ImageMirrorMode and MirrorImageInfo.
+type MirrorImageInfoItem struct {
+	ID   string
+	Mode ImageMirrorMode
+	Info MirrorImageInfo
+}
+
+func mirrorImageInfoList(ioctx *rados.IOContext, start string,
+	modeFilter ImageMirrorModeFilter, results []MirrorImageInfoItem) (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))
+		modes         = make([]C.rbd_mirror_image_mode_t, len(results))
+		infos         = make([]C.rbd_mirror_image_info_t, len(results))
+		modeFilterPtr *C.rbd_mirror_image_mode_t
+	)
+	if modeFilter != nil {
+		cMode := C.rbd_mirror_image_mode_t(modeFilter.mode())
+		modeFilterPtr = &cMode
+	}
+	ret := C.rbd_mirror_image_info_list(
+		cephIoctx(ioctx),
+		modeFilterPtr,
+		cStart,
+		max,
+		&ids[0],
+		&modes[0],
+		&infos[0],
+		&length,
+	)
+
+	for i := 0; i < int(length); i++ {
+		results[i].ID = C.GoString(ids[i])
+		results[i].Mode = ImageMirrorMode(modes[i])
+		results[i].Info = convertMirrorImageInfo(&infos[i])
+	}
+	C.rbd_mirror_image_info_list_cleanup(
+		&ids[0],
+		&infos[0],
+		length)
+	return int(length), getError(ret)
+}
+
+// MirrorImageInfoIter provide methods for iterating over all
+// the MirrorImageInfoItem values in a pool.
+type MirrorImageInfoIter struct {
+	ioctx *rados.IOContext
+
+	modeFilter ImageMirrorModeFilter
+	buf        []MirrorImageInfoItem
+	lastID     string
+}
+
+// NewMirrorImageInfoIter creates a new iterator ready for use.
+func NewMirrorImageInfoIter(ioctx *rados.IOContext, modeFilter ImageMirrorModeFilter) *MirrorImageInfoIter {
+	return &MirrorImageInfoIter{
+		ioctx:      ioctx,
+		modeFilter: modeFilter,
+	}
+}
+
+// Next fetches one MirrorImageInfoItem 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 *MirrorImageInfoIter) Next() (*MirrorImageInfoItem, 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 *MirrorImageInfoIter) fetch() error {
+	iter.buf = nil
+	items := make([]MirrorImageInfoItem, iterBufSize)
+	n, err := mirrorImageInfoList(
+		iter.ioctx,
+		iter.lastID,
+		iter.modeFilter,
+		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 ad52901..78358b3 100644
--- a/rbd/mirror_test.go
+++ b/rbd/mirror_test.go
@@ -780,13 +780,13 @@ func TestMirrorBootstrapToken(t *testing.T) {
 	})
 }
 
-func TestMirrorImageGlobalStatusIter(t *testing.T) {
+func TestMirrorImageLists(t *testing.T) {
 	defer func(x int) {
-		statusIterBufSize = x
-	}(statusIterBufSize)
+		iterBufSize = x
+	}(iterBufSize)
 	// shrink the buffer size in order to trigger more of the
 	// retry logic in the iter type
-	statusIterBufSize = 4
+	iterBufSize = 4
 
 	conn := radosConnect(t)
 	poolName := GetUUID()
@@ -811,7 +811,7 @@ func TestMirrorImageGlobalStatusIter(t *testing.T) {
 	options := NewRbdImageOptions()
 	assert.NoError(t, options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
 
-	for i := 0; i < 7; i++ {
+	for i := 0; i < 5; i++ {
 		name := fmt.Sprintf("%s%d", imgName, i)
 		err = CreateImage(ioctx, name, testImageSize, options)
 		require.NoError(t, err)
@@ -822,11 +822,29 @@ func TestMirrorImageGlobalStatusIter(t *testing.T) {
 		require.NoError(t, img.Close())
 	}
 
-	t.Run("ioctxNil", func(t *testing.T) {
+	for i := 5; i < 10; 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(ImageMirrorModeJournal)
+		assert.NoError(t, err)
+		require.NoError(t, img.Close())
+	}
+
+	t.Run("statusIterIoctxNil", func(t *testing.T) {
 		iter := NewMirrorImageGlobalStatusIter(nil)
-		defer iter.Close()
+		defer iter.Close() //nolint:errcheck
 		assert.Panics(t, func() {
-			iter.Next()
+			iter.Next() //nolint:errcheck
+		})
+	})
+
+	t.Run("infoIterIoctxNil", func(t *testing.T) {
+		iter := NewMirrorImageInfoIter(nil, nil)
+		assert.Panics(t, func() {
+			iter.Next() //nolint:errcheck
 		})
 	})
 
@@ -841,7 +859,7 @@ func TestMirrorImageGlobalStatusIter(t *testing.T) {
 			}
 			lst = append(lst, istatus)
 		}
-		assert.Len(t, lst, 7)
+		assert.Len(t, lst, 10)
 		gms := lst[0].Status
 		assert.NoError(t, err)
 		assert.NotEqual(t, "", gms.Name)
@@ -860,4 +878,50 @@ func TestMirrorImageGlobalStatusIter(t *testing.T) {
 			assert.Equal(t, ss, ls)
 		}
 	})
+
+	t.Run("getInfo", func(t *testing.T) {
+		get := func(iter *MirrorImageInfoIter) []*MirrorImageInfoItem {
+			lst := []*MirrorImageInfoItem{}
+			for {
+				info, err := iter.Next()
+				assert.NoError(t, err)
+				if info == nil {
+					break
+				}
+				lst = append(lst, info)
+			}
+			return lst
+		}
+		iter := NewMirrorImageInfoIter(ioctx, nil)
+		lst := get(iter)
+		assert.Len(t, lst, 10)
+		item := lst[0]
+		assert.NotEqual(t, "", item.ID)
+		assert.NotEqual(t, "", item.Info.GlobalID)
+		assert.Equal(t, item.Info.State, MirrorImageEnabled)
+		assert.Equal(t, item.Info.Primary, true)
+		for i := 1; i < 10; i++ {
+			assert.NotEqual(t, lst[i-1].ID, lst[i].ID)
+		}
+
+		iter = NewMirrorImageInfoIter(ioctx, ImageMirrorModeJournal)
+		lst = get(iter)
+		assert.Len(t, lst, 5)
+		item = lst[0]
+		assert.Equal(t, item.Mode, ImageMirrorModeJournal)
+		assert.NotEqual(t, "", item.ID)
+		assert.NotEqual(t, "", item.Info.GlobalID)
+		assert.Equal(t, item.Info.State, MirrorImageEnabled)
+		assert.Equal(t, item.Info.Primary, true)
+
+		iter = NewMirrorImageInfoIter(ioctx, ImageMirrorModeSnapshot)
+		lst = get(iter)
+		assert.Len(t, lst, 5)
+		item = lst[0]
+		assert.Equal(t, item.Mode, ImageMirrorModeSnapshot)
+		assert.NotEqual(t, "", item.ID)
+		assert.NotEqual(t, "", item.Info.GlobalID)
+		assert.Equal(t, item.Info.State, MirrorImageEnabled)
+		assert.Equal(t, item.Info.Primary, true)
+	})
 }