]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind/rbd: expose RBD_IMAGE_OPTION_CLONE_FORMAT option
authorIlya Dryomov <idryomov@gmail.com>
Sun, 28 Apr 2024 17:19:22 +0000 (19:19 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Mon, 6 May 2024 08:13:15 +0000 (10:13 +0200)
It takes effect with clone(), deep_copy() and migration_prepare().

Fixes: https://tracker.ceph.com/issues/65624
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
PendingReleaseNotes
src/pybind/rbd/c_rbd.pxd
src/pybind/rbd/mock_rbd.pxi
src/pybind/rbd/rbd.pyx
src/test/pybind/test_rbd.py

index 82d56bfbb605d5597c3a12be9c739fbec1ccf65e..ce499bed0769f3578cf775828b5b4c4d85a5147b 100644 (file)
@@ -168,7 +168,6 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
   default json format produces a rather massive output in large clusters and
   isn't scalable. So we have removed the 'network_ping_times' section from
   the output. Details in the tracker: https://tracker.ceph.com/issues/57460
-
 * CephFS: The `subvolume snapshot clone` command now depends on the config option
   `snapshot_clone_no_wait` which is used to reject the clone operation when
   all the cloner threads are busy. This config option is enabled by default which means 
@@ -177,6 +176,9 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
    `ceph config get mgr mgr/volumes/snapshot_clone_no_wait`
   and it can be disabled by using:
    `ceph config set mgr mgr/volumes/snapshot_clone_no_wait false`
+* 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.
 
 >=18.0.0
 
index bda23bbc4735f87298c46d02c8efe72c26caef39..5ece401bf1d369f92896e0cc3ae86cfe9f64989b 100644 (file)
@@ -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_CLONE_FORMAT "RBD_IMAGE_OPTION_CLONE_FORMAT"
 
         RBD_MAX_BLOCK_NAME_SIZE
         RBD_MAX_IMAGE_NAME_SIZE
index 364f965fbad43f2c96c7c3f71276faf80fdad00a..68802e674a00b389a858d69f8e22171ec04ef4df 100644 (file)
@@ -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_CLONE_FORMAT "RBD_IMAGE_OPTION_CLONE_FORMAT"
 
         RBD_MAX_BLOCK_NAME_SIZE
         RBD_MAX_IMAGE_NAME_SIZE
index f59ba23f0fe21063f5a13a6d9abd2c00b1f8fb19..d31d11738d59e75e08b383739a8ed321c4d89c2b 100644 (file)
@@ -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_CLONE_FORMAT = _RBD_IMAGE_OPTION_CLONE_FORMAT
 
 RBD_SNAP_NAMESPACE_TYPE_USER = _RBD_SNAP_NAMESPACE_TYPE_USER
 RBD_SNAP_NAMESPACE_TYPE_GROUP = _RBD_SNAP_NAMESPACE_TYPE_GROUP
@@ -632,7 +633,7 @@ class RBD(object):
 
     def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name,
               features=None, order=None, stripe_unit=None, stripe_count=None,
-              data_pool=None):
+              data_pool=None, clone_format=None):
         """
         Clone a parent rbd snapshot into a COW sparse child.
 
@@ -656,6 +657,8 @@ class RBD(object):
         :type stripe_count: int
         :param data_pool: optional separate pool for data blocks
         :type data_pool: str
+        :param clone_format: 1 (requires a protected snapshot), 2 (requires mimic+ clients)
+        :type clone_format: int
         :raises: :class:`TypeError`
         :raises: :class:`InvalidArgument`
         :raises: :class:`ImageExists`
@@ -691,6 +694,9 @@ class RBD(object):
             if data_pool is not None:
                 rbd_image_options_set_string(opts, RBD_IMAGE_OPTION_DATA_POOL,
                                              data_pool)
+            if clone_format is not None:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_CLONE_FORMAT,
+                                             clone_format)
             with nogil:
                 ret = rbd_clone3(_p_ioctx, _p_name, _p_snapname,
                                  _c_ioctx, _c_name, opts)
@@ -956,7 +962,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):
+                          data_pool=None, clone_format=None):
         """
         Prepare an RBD image migration.
 
@@ -978,6 +984,9 @@ class RBD(object):
         :type stripe_count: int
         :param data_pool: optional separate pool for data blocks
         :type data_pool: str
+        :param clone_format: if the source image is a clone, which clone format
+                             to use for the destination image
+        :type clone_format: int
         :raises: :class:`TypeError`
         :raises: :class:`InvalidArgument`
         :raises: :class:`ImageExists`
@@ -1010,6 +1019,9 @@ class RBD(object):
             if data_pool is not None:
                 rbd_image_options_set_string(opts, RBD_IMAGE_OPTION_DATA_POOL,
                                              data_pool)
+            if clone_format is not None:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_CLONE_FORMAT,
+                                             clone_format)
             with nogil:
                 ret = rbd_migration_prepare(_ioctx, _image_name, _dest_ioctx,
                                             _dest_image_name, opts)
@@ -3438,7 +3450,8 @@ 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):
+                  stripe_unit=None, stripe_count=None, data_pool=None,
+                  clone_format=None):
         """
         Deep copy the image to another location.
 
@@ -3456,6 +3469,9 @@ cdef class Image(object):
         :type stripe_count: int
         :param data_pool: optional separate pool for data blocks
         :type data_pool: str
+        :param clone_format: if the source image is a clone, which clone format
+                             to use for the destination image
+        :type clone_format: int
         :raises: :class:`TypeError`
         :raises: :class:`InvalidArgument`
         :raises: :class:`ImageExists`
@@ -3486,6 +3502,9 @@ cdef class Image(object):
             if data_pool is not None:
                 rbd_image_options_set_string(opts, RBD_IMAGE_OPTION_DATA_POOL,
                                              data_pool)
+            if clone_format is not None:
+                rbd_image_options_set_uint64(opts, RBD_IMAGE_OPTION_CLONE_FORMAT,
+                                             clone_format)
             with nogil:
                 ret = rbd_deep_copy(self.image, _dest_ioctx, _dest_name, opts)
         finally:
index df47b0d2976be816b5598475959e5841768299ba..1bd492bd2d4b6701a0676ff7842454113a7a9255 100644 (file)
@@ -32,6 +32,7 @@ from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
                  RBD_MIRROR_IMAGE_DISABLED, MIRROR_IMAGE_STATUS_STATE_UNKNOWN,
                  RBD_MIRROR_IMAGE_MODE_JOURNAL, RBD_MIRROR_IMAGE_MODE_SNAPSHOT,
                  RBD_LOCK_MODE_EXCLUSIVE, RBD_OPERATION_FEATURE_GROUP,
+                 RBD_OPERATION_FEATURE_CLONE_CHILD,
                  RBD_SNAP_NAMESPACE_TYPE_TRASH,
                  RBD_SNAP_NAMESPACE_TYPE_MIRROR,
                  RBD_IMAGE_MIGRATION_STATE_PREPARED, RBD_CONFIG_SOURCE_CONFIG,
@@ -827,34 +828,127 @@ class TestImage(object):
         self.rbd.remove(ioctx, dst_name)
 
     @require_features([RBD_FEATURE_LAYERING])
-    def test_deep_copy_clone(self):
-        global ioctx
-        global features
+    def test_deep_copy_clone_v1_to_v1(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)
+        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(),
-                            data_pool=None)
+                            clone_format=1)
             child.remove_snap('snap1')
 
         with Image(ioctx, dst_name) as copy:
             copy_data = copy.read(0, 256)
             eq(b'a' * 256, copy_data)
+            eq(self.image.id(), 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_v1_to_v2(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(),
+                            clone_format=2)
+            child.remove_snap('snap1')
+
+        with Image(ioctx, dst_name) as copy:
+            copy_data = copy.read(0, 256)
+            eq(b'a' * 256, copy_data)
+            eq(self.image.id(), copy.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, 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_to_v1(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=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(),
+                            clone_format=1)
+            child.remove_snap('snap1')
+
+        with Image(ioctx, dst_name) as copy:
+            copy_data = copy.read(0, 256)
+            eq(b'a' * 256, copy_data)
+            eq(self.image.id(), 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_to_v2(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(),
+                            clone_format=2)
+            child.remove_snap('snap1')
+
+        with Image(ioctx, dst_name) as copy:
+            copy_data = copy.read(0, 256)
+            eq(b'a' * 256, copy_data)
+            eq(self.image.id(), copy.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, 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')
@@ -1562,15 +1656,29 @@ class TestClone(object):
         image.close()
         RBD().remove(ioctx, image_name)
 
+    def test_clone_format(self):
+        clone_name2 = get_temp_image_name()
+        self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+                       features, clone_format=1)
+        with Image(ioctx, clone_name2) as clone2:
+            eq(0, clone2.op_features())
+        self.rbd.remove(ioctx, clone_name2)
+
+        self.rbd.clone(ioctx, image_name, 'snap1', ioctx, clone_name2,
+                       features, clone_format=2)
+        with Image(ioctx, clone_name2) as clone2:
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, clone2.op_features())
+        self.rbd.remove(ioctx, clone_name2)
 
     def test_unprotected(self):
         self.image.create_snap('snap2')
-        global features
         clone_name2 = get_temp_image_name()
-        rados.conf_set("rbd_default_clone_format", "1")
+        # clone format 1 requires a protected snapshot
         assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
-                      'snap2', ioctx, clone_name2, features)
-        rados.conf_set("rbd_default_clone_format", "auto")
+                      'snap2', ioctx, clone_name2, features, clone_format=1)
+        self.rbd.clone(ioctx, image_name, 'snap2', ioctx, clone_name2,
+                       features, clone_format=2)
+        self.rbd.remove(ioctx, clone_name2)
         self.image.remove_snap('snap2')
 
     def test_unprotect_with_children(self):
@@ -2753,6 +2861,120 @@ class TestMigration(object):
         RBD().migration_commit(ioctx, image_name)
         remove_image()
 
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_migration_clone_v1_to_v1(self):
+        create_image()
+        with Image(ioctx, image_name) as image:
+            image_id = image.id()
+            image.create_snap('snap1')
+            image.protect_snap('snap1')
+        clone_name = get_temp_image_name()
+        RBD().clone(ioctx, image_name, 'snap1', ioctx, clone_name, features,
+                    clone_format=1)
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(0, clone.op_features())
+
+        RBD().migration_prepare(ioctx, clone_name, ioctx, clone_name, features=63,
+                                order=23, stripe_unit=1<<23, stripe_count=1,
+                                clone_format=1)
+        RBD().migration_execute(ioctx, clone_name)
+        RBD().migration_commit(ioctx, clone_name)
+
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(0, clone.op_features())
+        RBD().remove(ioctx, clone_name)
+        with Image(ioctx, image_name) as image:
+            image.unprotect_snap('snap1')
+            image.remove_snap('snap1')
+        remove_image()
+
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_migration_clone_v1_to_v2(self):
+        create_image()
+        with Image(ioctx, image_name) as image:
+            image_id = image.id()
+            image.create_snap('snap1')
+            image.protect_snap('snap1')
+        clone_name = get_temp_image_name()
+        RBD().clone(ioctx, image_name, 'snap1', ioctx, clone_name, features,
+                    clone_format=1)
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(0, clone.op_features())
+
+        RBD().migration_prepare(ioctx, clone_name, ioctx, clone_name, features=63,
+                                order=23, stripe_unit=1<<23, stripe_count=1,
+                                clone_format=2)
+        RBD().migration_execute(ioctx, clone_name)
+        RBD().migration_commit(ioctx, clone_name)
+
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, clone.op_features())
+        RBD().remove(ioctx, clone_name)
+        with Image(ioctx, image_name) as image:
+            image.unprotect_snap('snap1')
+            image.remove_snap('snap1')
+        remove_image()
+
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_migration_clone_v2_to_v1(self):
+        create_image()
+        with Image(ioctx, image_name) as image:
+            image_id = image.id()
+            image.create_snap('snap1')
+            image.protect_snap('snap1')
+        clone_name = get_temp_image_name()
+        RBD().clone(ioctx, image_name, 'snap1', ioctx, clone_name, features,
+                    clone_format=2)
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, clone.op_features())
+
+        RBD().migration_prepare(ioctx, clone_name, ioctx, clone_name, features=63,
+                                order=23, stripe_unit=1<<23, stripe_count=1,
+                                clone_format=1)
+        RBD().migration_execute(ioctx, clone_name)
+        RBD().migration_commit(ioctx, clone_name)
+
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(0, clone.op_features())
+        RBD().remove(ioctx, clone_name)
+        with Image(ioctx, image_name) as image:
+            image.unprotect_snap('snap1')
+            image.remove_snap('snap1')
+        remove_image()
+
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_migration_clone_v2_to_v2(self):
+        create_image()
+        with Image(ioctx, image_name) as image:
+            image_id = image.id()
+            image.create_snap('snap1')
+        clone_name = get_temp_image_name()
+        RBD().clone(ioctx, image_name, 'snap1', ioctx, clone_name, features,
+                    clone_format=2)
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, clone.op_features())
+
+        RBD().migration_prepare(ioctx, clone_name, ioctx, clone_name, features=63,
+                                order=23, stripe_unit=1<<23, stripe_count=1,
+                                clone_format=2)
+        RBD().migration_execute(ioctx, clone_name)
+        RBD().migration_commit(ioctx, clone_name)
+
+        with Image(ioctx, clone_name) as clone:
+            eq(image_id, clone.parent_id())
+            eq(RBD_OPERATION_FEATURE_CLONE_CHILD, clone.op_features())
+        RBD().remove(ioctx, clone_name)
+        with Image(ioctx, image_name) as image:
+            image.remove_snap('snap1')
+        remove_image()
+
     def test_migration_import(self):
         create_image()
         with Image(ioctx, image_name) as image: