diff --git a/docs/api-status.json b/docs/api-status.json index 5e69c99..f3794b8 100644 --- a/docs/api-status.json +++ b/docs/api-status.json @@ -1867,6 +1867,12 @@ "comment": "SetMirrorPeerSiteDirection sets the direction of a mirror peer site.\n", "added_in_version": "v0.21.0", "expected_stable_version": "v0.23.0" + }, + { + "name": "Image.SparsifyWithProgress", + "comment": "SparsifyWithProgress makes an image sparse by deallocating runs of zeros.\nThe sparseSize value will be used to find runs of zeros and must be\na power of two no less than 4096 and no larger than the image size.\nThe given progress callback will be called to report on the progress\nof sparse. The operation will be aborted if the progress callback returns\na non-zero value.\n\nImplements:\n\n\tint rbd_sparsify_with_progress(rbd_image_t image, size_t sparse_size,\n\t\t\t\t\t\t\t\t librbd_progress_fn_t cb, void *cbdata);\n", + "added_in_version": "$NEXT_RELEASE", + "expected_stable_version": "$NEXT_RELEASE_STABLE" } ] }, diff --git a/docs/api-status.md b/docs/api-status.md index ec399af..6d5c30c 100644 --- a/docs/api-status.md +++ b/docs/api-status.md @@ -63,6 +63,7 @@ ListMirrorPeerSite | v0.21.0 | v0.23.0 | SetMirrorPeerSiteClientName | v0.21.0 | v0.23.0 | SetMirrorPeerSiteName | v0.21.0 | v0.23.0 | SetMirrorPeerSiteDirection | v0.21.0 | v0.23.0 | +Image.SparsifyWithProgress | $NEXT_RELEASE | $NEXT_RELEASE_STABLE | ### Deprecated APIs diff --git a/rbd/rbd_sparsify.go b/rbd/rbd_sparsify.go new file mode 100644 index 0000000..d1cb812 --- /dev/null +++ b/rbd/rbd_sparsify.go @@ -0,0 +1,82 @@ +//go:build !nautilus && ceph_preview +// +build !nautilus,ceph_preview + +package rbd + +/* +#cgo LDFLAGS: -lrbd +#include +#include +#include + +extern int sparsifyCallback(uint64_t, uint64_t, uintptr_t); + +// inline wrapper to cast uintptr_t to void* +static inline int wrap_rbd_sparsify_with_progress( + rbd_image_t image, size_t sparse_size, uintptr_t arg) { + return rbd_sparsify_with_progress( + image, sparse_size, (librbd_progress_fn_t)sparsifyCallback, (void*)arg); +}; +*/ +import "C" + +import ( + "github.com/ceph/go-ceph/internal/callbacks" +) + +// SparsifyCallback defines the function signature needed for the +// SparsifyWithProgress callback. +// +// This callback will be called by SparsifyWithProgress when it +// wishes to report progress on sparse. +type SparsifyCallback func(uint64, uint64, interface{}) int + +var sparsifyCallbacks = callbacks.New() + +type sparsifyCallbackCtx struct { + callback SparsifyCallback + data interface{} +} + +// SparsifyWithProgress makes an image sparse by deallocating runs of zeros. +// The sparseSize value will be used to find runs of zeros and must be +// a power of two no less than 4096 and no larger than the image size. +// The given progress callback will be called to report on the progress +// of sparse. The operation will be aborted if the progress callback returns +// a non-zero value. +// +// Implements: +// +// int rbd_sparsify_with_progress(rbd_image_t image, size_t sparse_size, +// librbd_progress_fn_t cb, void *cbdata); +func (image *Image) SparsifyWithProgress( + sparseSize uint, cb SparsifyCallback, data interface{}) error { + // the provided callback must be a real function + if cb == nil { + return rbdError(C.EINVAL) + } + + if err := image.validate(imageIsOpen); err != nil { + return err + } + + ctx := sparsifyCallbackCtx{ + callback: cb, + data: data, + } + cbIndex := sparsifyCallbacks.Add(ctx) + defer diffIterateCallbacks.Remove(cbIndex) + + ret := C.wrap_rbd_sparsify_with_progress(image.image, C.size_t(sparseSize), C.uintptr_t(cbIndex)) + + return getError(ret) +} + +//export sparsifyCallback +func sparsifyCallback( + offset, total C.uint64_t, index uintptr) C.int { + + v := sparsifyCallbacks.Lookup(index) + ctx := v.(sparsifyCallbackCtx) + return C.int(ctx.callback(uint64(offset), uint64(total), ctx.data)) +} diff --git a/rbd/rbd_sparsify_test.go b/rbd/rbd_sparsify_test.go new file mode 100644 index 0000000..b7ec545 --- /dev/null +++ b/rbd/rbd_sparsify_test.go @@ -0,0 +1,94 @@ +//go:build !nautilus && ceph_preview +// +build !nautilus,ceph_preview + +package rbd + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSparsifyWithProgress(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() + + name := GetUUID() + err = quickCreate(ioctx, name, testImageSize, testImageOrder) + require.NoError(t, err) + defer func() { assert.NoError(t, RemoveImage(ioctx, name)) }() + + t.Run("valid", func(t *testing.T) { + img, err := OpenImage(ioctx, name, NoSnapshot) + assert.NoError(t, err) + defer func() { assert.NoError(t, img.Close()) }() + + cc := 0 + cb := func(offset, total uint64, v interface{}) int { + cc++ + val := v.(int) + assert.Equal(t, 0, val) + assert.Equal(t, uint64(1), total) + return 0 + } + + err = img.SparsifyWithProgress(4096, cb, 0) + assert.NoError(t, err) + assert.GreaterOrEqual(t, cc, 1) + }) + + t.Run("negativeReturnValue", func(t *testing.T) { + img, err := OpenImage(ioctx, name, NoSnapshot) + assert.NoError(t, err) + defer func() { assert.NoError(t, img.Close()) }() + + cc := 0 + cb := func(offset, total uint64, v interface{}) int { + cc++ + val := v.(int) + assert.Equal(t, 0, val) + assert.Equal(t, uint64(1), total) + return -1 + } + + err = img.SparsifyWithProgress(4096, cb, 0) + assert.Error(t, err) + }) + + t.Run("closedImage", func(t *testing.T) { + img, err := OpenImage(ioctx, name, NoSnapshot) + assert.NoError(t, err) + assert.NoError(t, img.Close()) + + cc := 0 + cb := func(offset, total uint64, v interface{}) int { + cc++ + val := v.(int) + assert.Equal(t, 0, val) + assert.Equal(t, uint64(1), total) + return 0 + } + + err = img.SparsifyWithProgress(4096, cb, 0) + assert.Error(t, err) + }) + + t.Run("invalidValue", func(t *testing.T) { + img, err := OpenImage(ioctx, name, NoSnapshot) + assert.NoError(t, err) + defer func() { assert.NoError(t, img.Close()) }() + + err = img.SparsifyWithProgress(4096, nil, nil) + assert.Error(t, err) + }) +}