Repository: ceph/go-ceph
Pull request #314 (2020-06-25 12:17:02)

rbd: snapshot namespace related changes
1. Added following macros required by snapshot namespace functions:
    a. RBD_SNAP_NAMESPACE_TYPE_USER 
    b. RBD_SNAP_NAMESPACE_TYPE_GROUP 
    c. RBD_SNAP_NAMESPACE_TYPE_TRASH
2. Wrapper to following snapshot namespace functions:
   a.  rbd_snap_get_namespace_type() - To get the namespace to which snapshot belongs to.
   b.  rbd_snap_get_trash_namespace() -  To get more information about the snapshot which is in Trash.

Fixes: https://github.com/ceph/go-ceph/issues/300

Signed-off-by: Mudit Agarwal <muagarwa@redhat.com>
  
## Checklist
- [ ] Added tests for features and functional changes
- [ ] Public functions and types are documented
- [ ] Standard formatting is applied to Go code


agarwal-mudit: @nixpanic PTAL
agarwal-mudit: @nixpanic PTAL
agarwal-mudit: > just minor things, looks good to go once these are addressed

Forgot to address the comments, fixed now.

Also, this should not create any conflict because its a new file.

Patch:
diff --git a/rbd/options_mimic.go b/rbd/options_mimic.go
index 5ea1c5b..94f05bb 100644
--- a/rbd/options_mimic.go
+++ b/rbd/options_mimic.go
@@ -9,4 +9,8 @@ const (
 	// ImageOptionFlatten is the representation of RBD_IMAGE_OPTION_FLATTEN
 	// from librbd
 	ImageOptionFlatten = C.RBD_IMAGE_OPTION_FLATTEN
+
+	// ImageOptionCloneFormat is the representation of
+	// RBD_IMAGE_OPTION_CLONE_FORMAT from librbd
+	ImageOptionCloneFormat = C.RBD_IMAGE_OPTION_CLONE_FORMAT
 )
diff --git a/rbd/options_nautilus.go b/rbd/options_nautilus.go
deleted file mode 100644
index e1d13c2..0000000
--- a/rbd/options_nautilus.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build !luminous,!mimic
-
-package rbd
-
-// #include <rbd/librbd.h>
-import "C"
-
-const (
-	// ImageOptionCloneFormat is the representation of
-	// RBD_IMAGE_OPTION_CLONE_FORMAT from librbd
-	ImageOptionCloneFormat = C.RBD_IMAGE_OPTION_CLONE_FORMAT
-)
diff --git a/rbd/snapshot_namespace.go b/rbd/snapshot_namespace.go
new file mode 100644
index 0000000..a9e8d17
--- /dev/null
+++ b/rbd/snapshot_namespace.go
@@ -0,0 +1,80 @@
+// +build !luminous
+//
+// Ceph Mimic introduced rbd_snap_get_namespace_type().
+
+package rbd
+
+// #cgo LDFLAGS: -lrbd
+// #include <rbd/librbd.h>
+import "C"
+
+import (
+	"unsafe"
+
+	"github.com/ceph/go-ceph/internal/retry"
+)
+
+// SnapNamespaceType indicates the namespace to which the snapshot belongs to.
+type SnapNamespaceType C.rbd_snap_namespace_type_t
+
+const (
+	// SnapNamespaceTypeUser indicates that the snapshot belongs to user namespace.
+	SnapNamespaceTypeUser = SnapNamespaceType(C.RBD_SNAP_NAMESPACE_TYPE_USER)
+
+	// SnapNamespaceTypeGroup indicates that the snapshot belongs to group namespace.
+	// Such snapshots will have associated group information.
+	SnapNamespaceTypeGroup = SnapNamespaceType(C.RBD_SNAP_NAMESPACE_TYPE_GROUP)
+
+	// SnapNamespaceTypeTrash indicates that the snapshot belongs to trash namespace.
+	SnapNamespaceTypeTrash = SnapNamespaceType(C.RBD_SNAP_NAMESPACE_TYPE_TRASH)
+)
+
+// GetSnapNamespaceType gets the type of namespace to which the snapshot belongs to,
+// returns error on failure.
+//
+// Implements:
+//  int rbd_snap_get_namespace_type(rbd_image_t image, uint64_t snap_id, rbd_snap_namespace_type_t *namespace_type)
+func (image *Image) GetSnapNamespaceType(snapID uint64) (SnapNamespaceType, error) {
+	var nsType SnapNamespaceType
+
+	if err := image.validate(imageIsOpen); err != nil {
+		return nsType, err
+	}
+
+	ret := C.rbd_snap_get_namespace_type(image.image,
+		C.uint64_t(snapID),
+		(*C.rbd_snap_namespace_type_t)(&nsType))
+	return nsType, getError(ret)
+}
+
+// GetSnapTrashNamespace returns the original name of the snapshot which was
+// moved to the Trash. The caller should make sure that the snapshot ID passed in this
+// function belongs to a snapshot already in the Trash.
+//
+// Implements:
+//  int rbd_snap_get_trash_namespace(rbd_image_t image, uint64_t snap_id, char *original_name, size_t max_length)
+func (image *Image) GetSnapTrashNamespace(snapID uint64) (string, error) {
+	if err := image.validate(imageIsOpen); err != nil {
+		return "", err
+	}
+
+	var (
+		buf []byte
+		err error
+	)
+	retry.WithSizes(4096, 262144, func(length int) retry.Hint {
+		cLength := C.size_t(length)
+		buf = make([]byte, cLength)
+		ret := C.rbd_snap_get_trash_namespace(image.image,
+			C.uint64_t(snapID),
+			(*C.char)(unsafe.Pointer(&buf[0])),
+			cLength)
+		err = getError(ret)
+		return retry.Size(int(cLength)).If(err == errRange)
+	})
+
+	if err != nil {
+		return "", err
+	}
+	return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
+}


Test patch:
diff --git a/rbd/snapshot_namespace_test.go b/rbd/snapshot_namespace_test.go
new file mode 100644
index 0000000..fa2f543
--- /dev/null
+++ b/rbd/snapshot_namespace_test.go
@@ -0,0 +1,200 @@
+// +build !luminous
+
+package rbd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetSnapNamespaceType(t *testing.T) {
+	conn := radosConnect(t)
+	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()
+
+	imageName := "parent"
+	snapName := "mySnap"
+	options := NewRbdImageOptions()
+	defer options.Destroy()
+	err = options.SetUint64(ImageOptionOrder, uint64(testImageOrder))
+	assert.NoError(t, err)
+	err = options.SetUint64(ImageOptionFeatures, 1)
+	assert.NoError(t, err)
+
+	err = CreateImage(ioctx, imageName, testImageSize, options)
+	assert.NoError(t, err)
+
+	img, err := OpenImage(ioctx, imageName, NoSnapshot)
+	assert.NoError(t, err)
+
+	defer func() {
+		assert.NoError(t, img.Close())
+		assert.NoError(t, img.Remove())
+	}()
+
+	snapshot, err := img.CreateSnapshot(snapName)
+	assert.NoError(t, err)
+
+	snapInfoList, err := img.GetSnapshotNames()
+	assert.NoError(t, err)
+
+	snapInfo := snapInfoList[0]
+	assert.Equal(t, snapInfo.Name, snapName)
+
+	t.Run("SnapNamespaceTypeInvalidArgs", func(t *testing.T) {
+		validImageName := GetUUID()
+		validImg, err := Create(ioctx, validImageName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, validImg.Remove())
+		}()
+
+		validImg = GetImage(ioctx, validImageName)
+		// Closed image and a snapshot ID which doesn't belong to this image.
+		_, err = validImg.GetSnapNamespaceType(snapInfo.Id)
+		assert.Error(t, err)
+
+		// Open image but invalid snap ID.
+		validImg, err = OpenImage(ioctx, validImageName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, validImg.Close())
+		}()
+
+		_, err = validImg.GetSnapNamespaceType(uint64(22))
+		assert.Error(t, err)
+
+		// With non-existing image.
+		invalidImageName := GetUUID()
+		invalidImg := GetImage(ioctx, invalidImageName)
+		_, err = invalidImg.GetSnapNamespaceType(snapInfo.Id)
+		assert.Error(t, err)
+	})
+
+	t.Run("SnapNamespaceTypeUser", func(t *testing.T) {
+		nsType, err := img.GetSnapNamespaceType(snapInfo.Id)
+		assert.NoError(t, err)
+		assert.Equal(t, nsType, SnapNamespaceTypeUser)
+	})
+
+	t.Run("SnapNamespaceTypeTrash", func(t *testing.T) {
+		cloneName := "myClone"
+		optionsClone := NewRbdImageOptions()
+		defer optionsClone.Destroy()
+		err := optionsClone.SetUint64(ImageOptionCloneFormat, 2)
+		assert.NoError(t, err)
+
+		// Create a clone of the image using the same snapshot.
+		err = CloneImage(ioctx, imageName, snapName, ioctx, cloneName, optionsClone)
+		assert.NoError(t, err)
+		defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }()
+
+		// Once clone is created, remove the snapshot.
+		err = snapshot.Remove()
+		assert.NoError(t, err)
+
+		// Snapshot would move to the trash because linked clone is still there.
+		nsType, err := img.GetSnapNamespaceType(snapInfo.Id)
+		assert.NoError(t, err)
+		assert.Equal(t, nsType, SnapNamespaceTypeTrash)
+	})
+}
+
+func TestGetSnapTrashNamespace(t *testing.T) {
+	conn := radosConnect(t)
+	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()
+
+	imageName := "parent"
+	snapName := "mySnap"
+	cloneName := "myClone"
+	options := NewRbdImageOptions()
+	defer options.Destroy()
+	err = options.SetUint64(ImageOptionOrder, uint64(testImageOrder))
+	assert.NoError(t, err)
+	err = options.SetUint64(ImageOptionFeatures, 1)
+	assert.NoError(t, err)
+
+	err = CreateImage(ioctx, imageName, testImageSize, options)
+	assert.NoError(t, err)
+
+	img, err := OpenImage(ioctx, imageName, NoSnapshot)
+	assert.NoError(t, err)
+	defer func() {
+		assert.NoError(t, img.Close())
+		assert.NoError(t, img.Remove())
+	}()
+
+	snapshot, err := img.CreateSnapshot(snapName)
+	assert.NoError(t, err)
+
+	optionsClone := NewRbdImageOptions()
+	defer optionsClone.Destroy()
+	err = optionsClone.SetUint64(ImageOptionCloneFormat, 2)
+	assert.NoError(t, err)
+
+	// Create a clone of the image using the same snapshot.
+	err = CloneImage(ioctx, imageName, snapName, ioctx, cloneName, optionsClone)
+	assert.NoError(t, err)
+	defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }()
+
+	// Check the name of the snapshot.
+	snapInfoList, err := img.GetSnapshotNames()
+	assert.NoError(t, err)
+	snapInfo := snapInfoList[0]
+	assert.Equal(t, snapInfo.Name, snapName)
+
+	// Remove the snapshot.
+	err = snapshot.Remove()
+	assert.NoError(t, err)
+
+	// Snapshot would move to the trash because linked clone is still there.
+	nsType, err := img.GetSnapNamespaceType(snapInfo.Id)
+	assert.NoError(t, err)
+	assert.Equal(t, nsType, SnapNamespaceTypeTrash)
+
+	// Get the snap info again, name would have changed.
+	newSnapInfoList, err := img.GetSnapshotNames()
+	assert.NoError(t, err)
+	newSnapInfo := newSnapInfoList[0]
+	assert.NotEqual(t, newSnapInfo.Name, snapName)
+	// ID would have remained same.
+	assert.Equal(t, snapInfo.Id, newSnapInfo.Id)
+
+	// Get the original name.
+	origSnapName, err := img.GetSnapTrashNamespace(newSnapInfo.Id)
+	assert.NoError(t, err)
+	assert.Equal(t, snapName, origSnapName)
+
+	invalidSnapID := uint64(22)
+
+	t.Run("InvalidSnapID", func(t *testing.T) {
+		_, err := img.GetSnapTrashNamespace(invalidSnapID)
+		assert.Error(t, err)
+	})
+
+	t.Run("InvalidImage", func(t *testing.T) {
+		invalidImgName := GetUUID()
+		invalidImg := GetImage(ioctx, invalidImgName)
+		_, err := invalidImg.GetSnapTrashNamespace(invalidSnapID)
+		assert.Error(t, err)
+	})
+}