Skip to content

Commit

Permalink
rbd: add support for CloneImageByID()
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
nixpanic committed Jun 25, 2024
1 parent 0fc95cf commit c65868f
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 1 deletion.
8 changes: 7 additions & 1 deletion docs/api-status.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
},
Expand Down Expand Up @@ -2259,4 +2265,4 @@
}
]
}
}
}
1 change: 1 addition & 0 deletions docs/api-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 46 additions & 0 deletions rbd/clone_image_by_id.go
Original file line number Diff line number Diff line change
@@ -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)
}
154 changes: 154 additions & 0 deletions rbd/clone_image_by_id_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}

0 comments on commit c65868f

Please sign in to comment.