From 39f207ff65a4eb10f67a2d4a8a4af1bad0d2e412 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Wed, 1 May 2024 15:49:47 +0200 Subject: [PATCH] pybind/rbd: expose RBD_IMAGE_OPTION_FLATTEN option It takes effect with deep_copy() and migration_prepare(). Fixes: https://tracker.ceph.com/issues/65624 Signed-off-by: Ilya Dryomov --- PendingReleaseNotes | 2 ++ src/pybind/rbd/c_rbd.pxd | 1 + src/pybind/rbd/mock_rbd.pxi | 1 + src/pybind/rbd/rbd.pyx | 13 ++++++-- src/test/pybind/test_rbd.py | 63 +++++++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/PendingReleaseNotes b/PendingReleaseNotes index ce499bed0769f..1cf3d25b1081b 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -179,6 +179,8 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config * RBD: `RBD_IMAGE_OPTION_CLONE_FORMAT` option has been exposed in Python bindings via `clone_format` optional parameter to `clone`, `deep_copy` and `migration_prepare` methods. +* RBD: `RBD_IMAGE_OPTION_FLATTEN` option has been exposed in Python bindings via + `flatten` optional parameter to `deep_copy` and `migration_prepare` methods. >=18.0.0 diff --git a/src/pybind/rbd/c_rbd.pxd b/src/pybind/rbd/c_rbd.pxd index 5ece401bf1d36..c451baac58eef 100644 --- a/src/pybind/rbd/c_rbd.pxd +++ b/src/pybind/rbd/c_rbd.pxd @@ -45,6 +45,7 @@ cdef extern from "rbd/librbd.h" nogil: _RBD_IMAGE_OPTION_STRIPE_UNIT "RBD_IMAGE_OPTION_STRIPE_UNIT" _RBD_IMAGE_OPTION_STRIPE_COUNT "RBD_IMAGE_OPTION_STRIPE_COUNT" _RBD_IMAGE_OPTION_DATA_POOL "RBD_IMAGE_OPTION_DATA_POOL" + _RBD_IMAGE_OPTION_FLATTEN "RBD_IMAGE_OPTION_FLATTEN" _RBD_IMAGE_OPTION_CLONE_FORMAT "RBD_IMAGE_OPTION_CLONE_FORMAT" RBD_MAX_BLOCK_NAME_SIZE diff --git a/src/pybind/rbd/mock_rbd.pxi b/src/pybind/rbd/mock_rbd.pxi index 68802e674a00b..15f2c78e3b1bd 100644 --- a/src/pybind/rbd/mock_rbd.pxi +++ b/src/pybind/rbd/mock_rbd.pxi @@ -49,6 +49,7 @@ cdef nogil: _RBD_IMAGE_OPTION_STRIPE_UNIT "RBD_IMAGE_OPTION_STRIPE_UNIT" _RBD_IMAGE_OPTION_STRIPE_COUNT "RBD_IMAGE_OPTION_STRIPE_COUNT" _RBD_IMAGE_OPTION_DATA_POOL "RBD_IMAGE_OPTION_DATA_POOL" + _RBD_IMAGE_OPTION_FLATTEN "RBD_IMAGE_OPTION_FLATTEN" _RBD_IMAGE_OPTION_CLONE_FORMAT "RBD_IMAGE_OPTION_CLONE_FORMAT" RBD_MAX_BLOCK_NAME_SIZE diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx index d31d11738d59e..e8b7e607c4043 100644 --- a/src/pybind/rbd/rbd.pyx +++ b/src/pybind/rbd/rbd.pyx @@ -115,6 +115,7 @@ RBD_IMAGE_OPTION_ORDER = _RBD_IMAGE_OPTION_ORDER RBD_IMAGE_OPTION_STRIPE_UNIT = _RBD_IMAGE_OPTION_STRIPE_UNIT RBD_IMAGE_OPTION_STRIPE_COUNT = _RBD_IMAGE_OPTION_STRIPE_COUNT RBD_IMAGE_OPTION_DATA_POOL = _RBD_IMAGE_OPTION_DATA_POOL +RBD_IMAGE_OPTION_FLATTEN = _RBD_IMAGE_OPTION_FLATTEN RBD_IMAGE_OPTION_CLONE_FORMAT = _RBD_IMAGE_OPTION_CLONE_FORMAT RBD_SNAP_NAMESPACE_TYPE_USER = _RBD_SNAP_NAMESPACE_TYPE_USER @@ -962,7 +963,7 @@ class RBD(object): def migration_prepare(self, ioctx, image_name, dest_ioctx, dest_image_name, features=None, order=None, stripe_unit=None, stripe_count=None, - data_pool=None, clone_format=None): + data_pool=None, clone_format=None, flatten=False): """ Prepare an RBD image migration. @@ -987,6 +988,9 @@ class RBD(object): :param clone_format: if the source image is a clone, which clone format to use for the destination image :type clone_format: int + :param flatten: if the source image is a clone, whether to flatten the + destination image or make it a clone of the same parent + :type flatten: bool :raises: :class:`TypeError` :raises: :class:`InvalidArgument` :raises: :class:`ImageExists` @@ -1022,6 +1026,7 @@ class RBD(object): if clone_format is not None: rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_CLONE_FORMAT, clone_format) + rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FLATTEN, flatten) with nogil: ret = rbd_migration_prepare(_ioctx, _image_name, _dest_ioctx, _dest_image_name, opts) @@ -3451,7 +3456,7 @@ cdef class Image(object): @requires_not_closed def deep_copy(self, dest_ioctx, dest_name, features=None, order=None, stripe_unit=None, stripe_count=None, data_pool=None, - clone_format=None): + clone_format=None, flatten=False): """ Deep copy the image to another location. @@ -3472,6 +3477,9 @@ cdef class Image(object): :param clone_format: if the source image is a clone, which clone format to use for the destination image :type clone_format: int + :param flatten: if the source image is a clone, whether to flatten the + destination image or make it a clone of the same parent + :type flatten: bool :raises: :class:`TypeError` :raises: :class:`InvalidArgument` :raises: :class:`ImageExists` @@ -3505,6 +3513,7 @@ cdef class Image(object): if clone_format is not None: rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_CLONE_FORMAT, clone_format) + rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_FLATTEN, flatten) with nogil: ret = rbd_deep_copy(self.image, _dest_ioctx, _dest_name, opts) finally: diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 1bd492bd2d4b6..55a5fc5722fab 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -949,6 +949,66 @@ def test_deep_copy_clone_v2_to_v2(self): self.rbd.remove(ioctx, clone_name) self.image.remove_snap('snap1') + @require_features([RBD_FEATURE_LAYERING]) + def test_deep_copy_clone_v1_flatten(self): + self.image.write(b'a' * 256, 0) + self.image.create_snap('snap1') + self.image.write(b'b' * 256, 0) + self.image.protect_snap('snap1') + clone_name = get_temp_image_name() + dst_name = get_temp_image_name() + self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name, features, + clone_format=1) + with Image(ioctx, clone_name) as child: + eq(0, child.op_features()) + child.create_snap('snap1') + child.deep_copy(ioctx, dst_name, features=features, + order=self.image.stat()['order'], + stripe_unit=self.image.stripe_unit(), + stripe_count=self.image.stripe_count(), + flatten=True) + child.remove_snap('snap1') + + with Image(ioctx, dst_name) as copy: + copy_data = copy.read(0, 256) + eq(b'a' * 256, copy_data) + assert_raises(ImageNotFound, copy.parent_id) + eq(0, copy.op_features()) + copy.remove_snap('snap1') + self.rbd.remove(ioctx, dst_name) + self.rbd.remove(ioctx, clone_name) + self.image.unprotect_snap('snap1') + self.image.remove_snap('snap1') + + @require_features([RBD_FEATURE_LAYERING]) + def test_deep_copy_clone_v2_flatten(self): + self.image.write(b'a' * 256, 0) + self.image.create_snap('snap1') + self.image.write(b'b' * 256, 0) + clone_name = get_temp_image_name() + dst_name = get_temp_image_name() + self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name, features, + clone_format=2) + with Image(ioctx, clone_name) as child: + eq(RBD_OPERATION_FEATURE_CLONE_CHILD, child.op_features()) + child.create_snap('snap1') + child.deep_copy(ioctx, dst_name, features=features, + order=self.image.stat()['order'], + stripe_unit=self.image.stripe_unit(), + stripe_count=self.image.stripe_count(), + flatten=True) + child.remove_snap('snap1') + + with Image(ioctx, dst_name) as copy: + copy_data = copy.read(0, 256) + eq(b'a' * 256, copy_data) + assert_raises(ImageNotFound, copy.parent_id) + eq(0, copy.op_features()) + copy.remove_snap('snap1') + self.rbd.remove(ioctx, dst_name) + self.rbd.remove(ioctx, clone_name) + self.image.remove_snap('snap1') + def test_create_snap(self): global ioctx self.image.create_snap('snap1') @@ -2975,6 +3035,9 @@ def test_migration_clone_v2_to_v2(self): image.remove_snap('snap1') remove_image() + # TODO: add test_migration_clone_v{1,2}_flatten tests once + # https://tracker.ceph.com/issues/65743 is addressed + def test_migration_import(self): create_image() with Image(ioctx, image_name) as image: