rbd: add support for CloneImageByID()

RBD image groups can be used to create consistent snapshots of all
images that are part of the group. The new rbd_clone4() API makes it
possible to create a new RBD image from a single snapshot that was
created as part of the group snapshot.

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos 2024-06-21 11:25:37 +02:00 committed by mergify[bot]
parent 0fc95cf6fe
commit ee25db94c6
4 changed files with 208 additions and 1 deletions

View File

@ -1956,6 +1956,12 @@
"comment": "GetSnapGroupNamespace returns the SnapGroupNamespace of the snapshot which\nis part of a group. The caller should make sure that the snapshot ID passed\nin this function belongs to a snapshot that was taken as part of a group\nsnapshot.\n\nImplements:\n\n\t\tint rbd_snap_get_group_namespace(rbd_image_t image, uint64_t snap_id,\n\t rbd_snap_group_namespace_t *group_snap,\n\t size_t group_snap_size)\n",
"added_in_version": "v0.27.0",
"expected_stable_version": "v0.29.0"
},
{
"name": "CloneImageByID",
"comment": "CloneImageByID creates a clone of the image from a snapshot with the given\nID in the provided io-context with the given name and image options.\n\nImplements:\n\n\tint rbd_clone4(rados_ioctx_t p_ioctx, const char *p_name,\n\t uint64_t p_snap_id, rados_ioctx_t c_ioctx,\n\t const char *c_name, rbd_image_options_t c_opts);\n",
"added_in_version": "$NEXT_RELEASE",
"expected_stable_version": "$NEXT_RELEASE_STABLE"
}
]
},
@ -2259,4 +2265,4 @@
}
]
}
}
}

View File

@ -31,6 +31,7 @@ WriteOp.Exec | $NEXT_RELEASE | $NEXT_RELEASE_STABLE |
Name | Added in Version | Expected Stable Version |
---- | ---------------- | ----------------------- |
Image.GetSnapGroupNamespace | v0.27.0 | v0.29.0 |
CloneImageByID | $NEXT_RELEASE | $NEXT_RELEASE_STABLE |
### Deprecated APIs

46
rbd/clone_image_by_id.go Normal file
View File

@ -0,0 +1,46 @@
//go:build !(nautilus || octopus || pacific || quincy || reef) && ceph_preview
package rbd
// #cgo LDFLAGS: -lrbd
// #include <errno.h>
// #include <stdlib.h>
// #include <rados/librados.h>
// #include <rbd/librbd.h>
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/rados"
)
// CloneImageByID creates a clone of the image from a snapshot with the given
// ID in the provided io-context with the given name and image options.
//
// Implements:
//
// int rbd_clone4(rados_ioctx_t p_ioctx, const char *p_name,
// uint64_t p_snap_id, rados_ioctx_t c_ioctx,
// const char *c_name, rbd_image_options_t c_opts);
func CloneImageByID(ioctx *rados.IOContext, parentName string, snapID uint64,
destctx *rados.IOContext, name string, rio *ImageOptions) error {
if rio == nil {
return rbdError(C.EINVAL)
}
cParentName := C.CString(parentName)
defer C.free(unsafe.Pointer(cParentName))
cCloneName := C.CString(name)
defer C.free(unsafe.Pointer(cCloneName))
ret := C.rbd_clone4(
cephIoctx(ioctx),
cParentName,
C.uint64_t(snapID),
cephIoctx(destctx),
cCloneName,
C.rbd_image_options_t(rio.options))
return getError(ret)
}

View File

@ -0,0 +1,154 @@
//go:build !(nautilus || octopus || pacific || quincy || reef) && ceph_preview
package rbd
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCloneImageByID(t *testing.T) {
// tests are done as subtests to avoid creating pools, images, etc
// over and over again.
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()
// create a group, some images, and add images to the group
gname := "snapme"
err = GroupCreate(ioctx, gname)
assert.NoError(t, err)
defer func() {
assert.NoError(t, GroupRemove(ioctx, gname))
}()
options := NewRbdImageOptions()
assert.NoError(t,
options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))
defer options.Destroy()
name1 := GetUUID()
err = CreateImage(ioctx, name1, testImageSize, options)
require.NoError(t, err)
defer func() {
assert.NoError(t, RemoveImage(ioctx, name1))
}()
name2 := GetUUID()
err = CreateImage(ioctx, name2, testImageSize, options)
require.NoError(t, err)
defer func() {
assert.NoError(t, RemoveImage(ioctx, name2))
}()
err = GroupImageAdd(ioctx, gname, ioctx, name1)
assert.NoError(t, err)
defer func() {
assert.NoError(t, GroupImageRemove(ioctx, gname, ioctx, name1))
}()
err = GroupImageAdd(ioctx, gname, ioctx, name2)
assert.NoError(t, err)
defer func() {
assert.NoError(t, GroupImageRemove(ioctx, gname, ioctx, name2))
}()
t.Run("CloneFromSnapshot", func(t *testing.T) {
cloneName := "child"
optionsClone := NewRbdImageOptions()
defer optionsClone.Destroy()
err := optionsClone.SetUint64(ImageOptionCloneFormat, 2)
assert.NoError(t, err)
// Get the snapID
img, err := OpenImage(ioctx, name1, NoSnapshot)
assert.NoError(t, err)
defer img.Close()
snapName := "mysnap"
snapshot, err := img.CreateSnapshot(snapName)
assert.NoError(t, err)
defer func() {
assert.NoError(t, snapshot.Remove())
}()
snapInfos, err := img.GetSnapshotNames()
assert.NoError(t, err)
require.Equal(t, 1, len(snapInfos))
snapID := snapInfos[0].Id
// Create a clone of the image using the snapshot.
err = CloneImageByID(ioctx, name1, snapID, ioctx, cloneName, optionsClone)
assert.NoError(t, err)
defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }()
imgNew, err := OpenImage(ioctx, cloneName, NoSnapshot)
defer func() {
assert.NoError(t, imgNew.Close())
}()
assert.NoError(t, err)
parentInfo, err := imgNew.GetParent()
assert.NoError(t, err)
assert.Equal(t, parentInfo.Image.ImageName, name1)
assert.Equal(t, parentInfo.Image.PoolName, poolname)
assert.False(t, parentInfo.Image.Trash)
assert.Equal(t, parentInfo.Snap.SnapName, snapName)
assert.Equal(t, parentInfo.Snap.ID, snapID)
})
t.Run("CloneFromGroupSnap", func(t *testing.T) {
err := GroupSnapCreate(ioctx, gname, "groupsnap")
assert.NoError(t, err)
cloneName := "img-clone"
optionsClone := NewRbdImageOptions()
defer optionsClone.Destroy()
err = optionsClone.SetUint64(ImageOptionCloneFormat, 2)
assert.NoError(t, err)
// Get the snapID of the image
img, err := OpenImageReadOnly(ioctx, name1, NoSnapshot)
assert.NoError(t, err)
defer img.Close()
snapInfos, err := img.GetSnapshotNames()
assert.NoError(t, err)
require.Equal(t, 1, len(snapInfos))
snapID := snapInfos[0].Id
// Create a clone of the image using the snapshot.
err = CloneImageByID(ioctx, name1, snapID, ioctx, cloneName, optionsClone)
assert.NoError(t, err)
defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }()
imgNew, err := OpenImage(ioctx, cloneName, NoSnapshot)
defer func() {
assert.NoError(t, imgNew.Close())
}()
assert.NoError(t, err)
parentInfo, err := imgNew.GetParent()
assert.NoError(t, err)
assert.Equal(t, parentInfo.Image.ImageName, name1)
assert.Equal(t, parentInfo.Snap.ID, snapID)
assert.Equal(t, parentInfo.Image.PoolName, poolname)
assert.False(t, parentInfo.Image.Trash)
err = GroupSnapRemove(ioctx, gname, "groupsnap")
assert.NoError(t, err)
})
}