Repository: ceph/go-ceph
Pull request #432 (2021-01-12 15:46:29)

rbd: Add initial group management functions
These are the simplest "rbd global" functions that allow managing groups w/o interacting also with images.

Add GroupCreate implementing rbd_group_create
Add GroupRemove implementing rbd_group_remove
Add GroupRename implementing rbd_group_rename
Add GroupList implementing rbd_group_list

Naming was <noun><verb> to better match other functions in rbd
like NamespaceCreate, etc. even though I don't prefer that
ordering ;-).

Fixes: #416


## 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**
> 

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


Patch:
diff --git a/rbd/group.go b/rbd/group.go
new file mode 100644
index 0000000..7838458
--- /dev/null
+++ b/rbd/group.go
@@ -0,0 +1,86 @@
+package rbd
+
+/*
+#cgo LDFLAGS: -lrbd
+#include <stdlib.h>
+#include <rbd/librbd.h>
+*/
+import "C"
+
+import (
+	"unsafe"
+
+	"github.com/ceph/go-ceph/internal/cutil"
+	"github.com/ceph/go-ceph/internal/retry"
+	"github.com/ceph/go-ceph/rados"
+)
+
+// GroupCreate is used to create an image group.
+//
+// Implements:
+//  int rbd_group_create(rados_ioctx_t p, const char *name);
+func GroupCreate(ioctx *rados.IOContext, name string) error {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	ret := C.rbd_group_create(cephIoctx(ioctx), cName)
+	return getError(ret)
+}
+
+// GroupRemove is used to remove an image group.
+//
+// Implements:
+//  int rbd_group_remove(rados_ioctx_t p, const char *name);
+func GroupRemove(ioctx *rados.IOContext, name string) error {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	ret := C.rbd_group_remove(cephIoctx(ioctx), cName)
+	return getError(ret)
+}
+
+// GroupRename will rename an existing image group.
+//
+// Implements:
+//  int rbd_group_rename(rados_ioctx_t p, const char *src_name,
+//                       const char *dest_name);
+func GroupRename(ioctx *rados.IOContext, src, dest string) error {
+	cSrc := C.CString(src)
+	defer C.free(unsafe.Pointer(cSrc))
+	cDest := C.CString(dest)
+	defer C.free(unsafe.Pointer(cDest))
+
+	ret := C.rbd_group_rename(cephIoctx(ioctx), cSrc, cDest)
+	return getError(ret)
+}
+
+// GroupList returns a slice of image group names.
+//
+// Implements:
+//  int rbd_group_list(rados_ioctx_t p, char *names, size_t *size);
+func GroupList(ioctx *rados.IOContext) ([]string, error) {
+	var (
+		buf []byte
+		err error
+		ret C.int
+	)
+	retry.WithSizes(1024, 262144, func(size int) retry.Hint {
+		cSize := C.size_t(size)
+		buf = make([]byte, cSize)
+		ret = C.rbd_group_list(
+			cephIoctx(ioctx),
+			(*C.char)(unsafe.Pointer(&buf[0])),
+			&cSize)
+		err = getErrorIfNegative(ret)
+		return retry.Size(int(cSize)).If(err == errRange)
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	// cSize is not set to the expected size when it is sufficiently large
+	// but ret will be set to the size in a non-error condition.
+	groups := cutil.SplitBuffer(buf[:ret])
+	return groups, nil
+}


Test patch:
diff --git a/rbd/group_test.go b/rbd/group_test.go
new file mode 100644
index 0000000..c4c9f9e
--- /dev/null
+++ b/rbd/group_test.go
@@ -0,0 +1,115 @@
+package rbd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGroupCreateRemove(t *testing.T) {
+	conn := radosConnect(t)
+	require.NotNil(t, conn)
+	defer conn.Shutdown()
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	require.NoError(t, err)
+	defer conn.DeletePool(poolname)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+	defer ioctx.Destroy()
+
+	err = GroupCreate(ioctx, "group1")
+	assert.NoError(t, err)
+
+	err = GroupRemove(ioctx, "group1")
+	assert.NoError(t, err)
+
+	err = GroupRemove(ioctx, "group2")
+	assert.NoError(t, err)
+
+	err = GroupCreate(ioctx, "group2")
+	assert.NoError(t, err)
+	err = GroupCreate(ioctx, "group")
+	assert.NoError(t, err)
+
+	err = GroupRemove(ioctx, "group2")
+	assert.NoError(t, err)
+}
+
+func TestGroupRename(t *testing.T) {
+	conn := radosConnect(t)
+	require.NotNil(t, conn)
+	defer conn.Shutdown()
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	require.NoError(t, err)
+	defer conn.DeletePool(poolname)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+	defer ioctx.Destroy()
+
+	err = GroupCreate(ioctx, "group1")
+	assert.NoError(t, err)
+
+	err = GroupRename(ioctx, "group1", "club1")
+	assert.NoError(t, err)
+
+	err = GroupRemove(ioctx, "club1")
+	assert.NoError(t, err)
+
+	// unlike remove, rename does return an error if the src name
+	// doesn't exist
+	err = GroupRename(ioctx, "club1", "nowhere")
+	assert.Error(t, err)
+}
+
+func TestGroupList(t *testing.T) {
+	conn := radosConnect(t)
+	require.NotNil(t, conn)
+	defer conn.Shutdown()
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	require.NoError(t, err)
+	defer conn.DeletePool(poolname)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+	defer ioctx.Destroy()
+
+	err = GroupCreate(ioctx, "uno")
+	assert.NoError(t, err)
+	err = GroupCreate(ioctx, "dos")
+	assert.NoError(t, err)
+	err = GroupCreate(ioctx, "tres")
+	assert.NoError(t, err)
+
+	l, err := GroupList(ioctx)
+	assert.NoError(t, err)
+	if assert.Len(t, l, 3) {
+		assert.Contains(t, l, "uno")
+		assert.Contains(t, l, "dos")
+		assert.Contains(t, l, "tres")
+	}
+
+	err = GroupRemove(ioctx, "uno")
+	assert.NoError(t, err)
+	err = GroupRemove(ioctx, "dos")
+	assert.NoError(t, err)
+	err = GroupRemove(ioctx, "tres")
+	assert.NoError(t, err)
+
+	l, err = GroupList(ioctx)
+	assert.NoError(t, err)
+	assert.Len(t, l, 0)
+
+	// test that GroupList panics if passed a nil ioctx
+	assert.Panics(t, func() {
+		GroupList(nil)
+	})
+}