Repository: ceph/go-ceph
Pull request #168 (2020-01-30 15:51:15)

rbd: add CloneImage as a wrapper for rbd_clone3 func
Change is based off of the branch tracked by PR #146 . Please review that PR first.

Add a new CloneImage that makes use of rbd_clone3 and thus behaves like
the new CreateImage function.

## 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: updated.

Patch:
diff --git a/rbd/rbd.go b/rbd/rbd.go
index eab4109..9d54e86 100644
--- a/rbd/rbd.go
+++ b/rbd/rbd.go
@@ -1457,3 +1457,47 @@ func RemoveImage(ioctx *rados.IOContext, name string) error {
 	defer C.free(unsafe.Pointer(c_name))
 	return getError(C.rbd_remove(C.rados_ioctx_t(ioctx.Pointer()), c_name))
 }
+
+// CloneImage creates a clone of the image from the named snapshot in the
+// provided io-context with the given name and image options.
+//
+// Implements:
+//   int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
+//                  const char *p_snapname, rados_ioctx_t c_ioctx,
+//                  const char *c_name, rbd_image_options_t c_opts);
+func CloneImage(ioctx *rados.IOContext, parentName, snapName string,
+	destctx *rados.IOContext, name string, rio *RbdImageOptions) error {
+
+	if rio == nil {
+		return RBDError(C.EINVAL)
+	}
+
+	cParentName := C.CString(parentName)
+	defer C.free(unsafe.Pointer(cParentName))
+	cParentSnapName := C.CString(snapName)
+	defer C.free(unsafe.Pointer(cParentSnapName))
+	cCloneName := C.CString(name)
+	defer C.free(unsafe.Pointer(cCloneName))
+
+	ret := C.rbd_clone3(
+		C.rados_ioctx_t(ioctx.Pointer()),
+		cParentName,
+		cParentSnapName,
+		C.rados_ioctx_t(destctx.Pointer()),
+		cCloneName,
+		C.rbd_image_options_t(rio.options))
+	return getError(ret)
+}
+
+// CloneFromImage creates a clone of the image from the named snapshot in the
+// provided io-context with the given name and image options.
+// This function is a convenience wrapper around CloneImage to support cloning
+// from an existing Image.
+func CloneFromImage(parent *Image, snapName string,
+	destctx *rados.IOContext, name string, rio *RbdImageOptions) error {
+
+	if err := parent.validate(imageNeedsIOContext); err != nil {
+		return err
+	}
+	return CloneImage(parent.ioctx, parent.name, snapName, destctx, name, rio)
+}


Test patch:
diff --git a/rbd/rbd_test.go b/rbd/rbd_test.go
index a55eb5e..d644170 100644
--- a/rbd/rbd_test.go
+++ b/rbd/rbd_test.go
@@ -1413,6 +1413,112 @@ func TestRemoveImage(t *testing.T) {
 	conn.Shutdown()
 }
 
+func TestCloneImage(t *testing.T) {
+	conn := radosConnect(t)
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	assert.NoError(t, err)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+
+	imageName := "parent1"
+	snapName := "snap1"
+	cloneName := "clone1"
+	options := NewRbdImageOptions()
+	defer options.Destroy()
+	err = options.SetUint64(RbdImageOptionOrder, uint64(testImageOrder))
+	assert.NoError(t, err)
+	err = options.SetUint64(RbdImageOptionFeatures, 1)
+	assert.NoError(t, err)
+	err = CreateImage(ioctx, imageName, testImageSize, options)
+	assert.NoError(t, err)
+
+	image, err := OpenImage(ioctx, imageName, NoSnapshot)
+	assert.NoError(t, err)
+
+	snapshot, err := image.CreateSnapshot(snapName)
+	assert.NoError(t, err)
+	err = snapshot.Protect()
+	assert.NoError(t, err)
+
+	t.Run("cloneImage", func(t *testing.T) {
+		imageNames, err := GetImageNames(ioctx)
+		assert.NoError(t, err)
+		assert.Contains(t, imageNames, imageName)
+		assert.NotContains(t, imageNames, cloneName)
+
+		options := NewRbdImageOptions()
+		defer options.Destroy()
+		err = options.SetUint64(RbdImageOptionFormat, uint64(2))
+		assert.NoError(t, err)
+		err = CloneImage(ioctx, imageName, snapName, ioctx, cloneName, options)
+		assert.NoError(t, err)
+
+		imageNames, err = GetImageNames(ioctx)
+		assert.NoError(t, err)
+		assert.Contains(t, imageNames, imageName)
+		assert.Contains(t, imageNames, cloneName)
+
+		err = RemoveImage(ioctx, cloneName)
+		assert.NoError(t, err)
+	})
+
+	t.Run("cloneFromImage", func(t *testing.T) {
+		imageNames, err := GetImageNames(ioctx)
+		assert.NoError(t, err)
+		assert.Contains(t, imageNames, imageName)
+		assert.NotContains(t, imageNames, cloneName)
+
+		options := NewRbdImageOptions()
+		defer options.Destroy()
+		err = options.SetUint64(RbdImageOptionFormat, uint64(2))
+		assert.NoError(t, err)
+		err = CloneFromImage(image, snapName, ioctx, cloneName, options)
+		assert.NoError(t, err)
+
+		imageNames, err = GetImageNames(ioctx)
+		assert.NoError(t, err)
+		assert.Contains(t, imageNames, imageName)
+		assert.Contains(t, imageNames, cloneName)
+
+		err = RemoveImage(ioctx, cloneName)
+		assert.NoError(t, err)
+	})
+
+	t.Run("cloneFromImageInvalidCtx", func(t *testing.T) {
+		options := NewRbdImageOptions()
+		defer options.Destroy()
+		err = options.SetUint64(RbdImageOptionFormat, uint64(2))
+		assert.NoError(t, err)
+		badImage := GetImage(nil, image.name)
+		err = CloneFromImage(badImage, snapName, ioctx, cloneName, options)
+		assert.Error(t, err)
+	})
+
+	t.Run("cloneImageNilOpts", func(t *testing.T) {
+		err = CloneImage(ioctx, imageName, snapName, ioctx, cloneName, nil)
+		assert.Error(t, err)
+	})
+
+	err = snapshot.Unprotect()
+	assert.NoError(t, err)
+
+	err = snapshot.Remove()
+	assert.NoError(t, err)
+
+	err = image.Close()
+	assert.NoError(t, err)
+
+	err = image.Remove()
+	assert.NoError(t, err)
+
+	ioctx.Destroy()
+	conn.DeletePool(poolname)
+	conn.Shutdown()
+}
+
 // quickCreate creates an image similar to Create but uses CreateImage.
 // If possible, avoid using this function for new code/tests. It mainly exists
 // to help with refactoring of existing tests.