rbd: add MirrorImageStatusSummary implementing rbd_mirror_image_status_summary

The MirrorImageStatusSummary returns a map of image mirroring states to
the number of images in those states or an error.
Tests of both the basic conditions and actual mirrored images are added.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2021-04-19 10:31:22 -04:00 committed by mergify[bot]
parent c45fa95b32
commit d37951f7cd
2 changed files with 170 additions and 0 deletions

View File

@ -421,3 +421,49 @@ func (image *Image) CreateMirrorSnapshot() (uint64, error) {
&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
}

View File

@ -463,3 +463,127 @@ func TestGetGlobalMirrorStatusMirroredPool(t *testing.T) {
}
})
}
func TestMirrorImageStatusSummary(t *testing.T) {
t.Run("ioctxNil", func(t *testing.T) {
assert.Panics(t, func() {
MirrorImageStatusSummary(nil)
})
})
t.Run("emptyPool", func(t *testing.T) {
conn := radosConnect(t)
poolName := GetUUID()
err := conn.MakePool(poolName)
require.NoError(t, err)
defer func() {
assert.NoError(t, conn.DeletePool(poolName))
conn.Shutdown()
}()
ioctx, err := conn.OpenIOContext(poolName)
assert.NoError(t, err)
defer func() {
ioctx.Destroy()
}()
ssum, err := MirrorImageStatusSummary(ioctx)
assert.NoError(t, err)
assert.Len(t, ssum, 0)
})
t.Run("mirroredPool", testMirrorImageStatusSummaryMirroredPool)
}
func testMirrorImageStatusSummaryMirroredPool(t *testing.T) {
mconfig := mirrorConfig()
if mconfig == "" {
t.Skip("no mirror config env var set")
}
conn := radosConnect(t)
// this test assumes the rbd pool already exists and is mirrored
// this must be set up previously by the CI or manually
poolName := "rbd"
ioctx, err := conn.OpenIOContext(poolName)
assert.NoError(t, err)
defer func() {
ioctx.Destroy()
}()
imgBase := GetUUID()
imgName1 := imgBase + "a"
imgName2 := imgBase + "b"
imgName3 := imgBase + "c"
imgName4 := imgBase + "d"
options := NewRbdImageOptions()
assert.NoError(t, options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
for _, n := range []string{imgName1, imgName2, imgName3, imgName4} {
err = CreateImage(ioctx, n, testImageSize, options)
require.NoError(t, err)
defer func(n string) {
err = RemoveImage(ioctx, n)
assert.NoError(t, err)
}(n)
}
mkMirror := func(n string) {
img, err := OpenImage(ioctx, n, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
}()
err = img.MirrorEnable(ImageMirrorModeSnapshot)
assert.NoError(t, err)
mid, err := img.CreateMirrorSnapshot()
assert.NoError(t, err)
assert.NotEqual(t, 0, mid)
}
checkMirror := func(n string) {
img, err := OpenImage(ioctx, n, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
}()
// wait for site statuses to get updated
for i := 0; i < 30; i++ {
gms, err := img.GetGlobalMirrorStatus()
assert.NoError(t, err)
if len(gms.SiteStatuses) > 1 {
break
}
time.Sleep(time.Second)
}
}
for _, n := range []string{imgName1, imgName3} {
mkMirror(n)
}
for _, n := range []string{imgName1, imgName3} {
checkMirror(n)
}
ssum, err := MirrorImageStatusSummary(ioctx)
assert.NoError(t, err)
if assert.Len(t, ssum, 1) {
assert.Contains(t, ssum, MirrorImageStatusStateReplaying)
assert.GreaterOrEqual(t, ssum[MirrorImageStatusStateReplaying], uint(2))
}
// immediately going for status right after enabling mirroring and not
// waiting for things to settle should give us one unknown status
mkMirror(imgName2)
ssum, err = MirrorImageStatusSummary(ioctx)
assert.NoError(t, err)
if assert.Len(t, ssum, 2) {
assert.Contains(t, ssum, MirrorImageStatusStateReplaying)
assert.GreaterOrEqual(t, ssum[MirrorImageStatusStateReplaying], uint(2))
assert.Contains(t, ssum, MirrorImageStatusStateUnknown)
assert.GreaterOrEqual(t, ssum[MirrorImageStatusStateUnknown], uint(1))
}
}