]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: add rbd_clone4() API to take parent snapshot by ID
authorIlya Dryomov <idryomov@gmail.com>
Fri, 24 May 2024 10:06:09 +0000 (12:06 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 13 Jun 2024 12:08:46 +0000 (14:08 +0200)
Allow cloning from non-user snapshots -- namely snapshots in group
and mirror namespaces.  The motivation is to provide a building block
for cloning new groups from group snapshots ("rbd group snap create").
Otherwise, group snapshots as they are today can be used only for
rolling back the group as a whole, which is very limiting.

While at it, there doesn't seem to be anything wrong with making it
possible to clone from mirror snapshots as well.

Snapshots in a trash namespace can't be cloned from since they are
considered to be deleted.

Cloning from non-user snapshots is limited to clone v2 just because
protecting/unprotecting is limited to snapshots in a user namespace.
This happens to simplify some invariants.

Fixes: https://tracker.ceph.com/issues/64662
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
src/include/rbd/librbd.h
src/include/rbd/librbd.hpp
src/librbd/internal.cc
src/librbd/internal.h
src/librbd/librbd.cc
src/pybind/rbd/c_rbd.pxd
src/pybind/rbd/mock_rbd.pxi
src/pybind/rbd/rbd.pyx
src/test/pybind/test_rbd.py
src/test/rbd_mirror/test_ImageDeleter.cc
src/tracing/librbd.tp

index e94a72a719b34eca7044cef983976b68c3ff2979..ce84b3ef7493ecf2fcde730e6496149e49264946 100644 (file)
@@ -479,6 +479,9 @@ CEPH_RBD_API int rbd_clone2(rados_ioctx_t p_ioctx, const char *p_name,
 CEPH_RBD_API int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
                            const char *p_snapname, rados_ioctx_t c_ioctx,
                            const char *c_name, rbd_image_options_t c_opts);
+CEPH_RBD_API 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);
 CEPH_RBD_API int rbd_remove(rados_ioctx_t io, const char *name);
 CEPH_RBD_API int rbd_remove_with_progress(rados_ioctx_t io, const char *name,
                                          librbd_progress_fn_t cb,
index 9224c850c55951545e6a0fe6fd5c09dd338f0932..cb6770ceb82e9cc152a73af317a13a6e47b1a59b 100644 (file)
@@ -294,6 +294,8 @@ public:
             int *c_order, uint64_t stripe_unit, int stripe_count);
   int clone3(IoCtx& p_ioctx, const char *p_name, const char *p_snapname,
             IoCtx& c_ioctx, const char *c_name, ImageOptions& opts);
+  int clone4(IoCtx& p_ioctx, const char *p_name, uint64_t p_snap_id,
+            IoCtx& c_ioctx, const char *c_name, ImageOptions& opts);
   int remove(IoCtx& io_ctx, const char *name);
   int remove_with_progress(IoCtx& io_ctx, const char *name, ProgressContext& pctx);
   int rename(IoCtx& src_io_ctx, const char *srcname, const char *destname);
index 33b9dcfa5afa2fd650be18fa8be4b2ed40d46e2d..dd674f3a9497c19a83d0274fd1e217157f76d14d 100644 (file)
@@ -716,28 +716,40 @@ int validate_pool(IoCtx &io_ctx, CephContext *cct) {
     opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit);
     opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count);
 
-    int r = clone(p_ioctx, nullptr, p_name, p_snap_name, c_ioctx, nullptr,
-                  c_name, opts, "", "");
+    int r = clone(p_ioctx, nullptr, p_name, CEPH_NOSNAP, p_snap_name,
+                  c_ioctx, nullptr, c_name, opts, "", "");
     opts.get(RBD_IMAGE_OPTION_ORDER, &order);
     *c_order = order;
     return r;
   }
 
   int clone(IoCtx& p_ioctx, const char *p_id, const char *p_name,
-            const char *p_snap_name, IoCtx& c_ioctx, const char *c_id,
-            const char *c_name, ImageOptions& c_opts,
+            uint64_t p_snap_id, const char *p_snap_name, IoCtx& c_ioctx,
+            const char *c_id, const char *c_name, ImageOptions& c_opts,
             const std::string &non_primary_global_image_id,
             const std::string &primary_mirror_uuid)
   {
     CephContext *cct = (CephContext *)p_ioctx.cct();
+    ldout(cct, 10) << __func__
+                   << " p_id=" << (p_id ?: "")
+                   << ", p_name=" << (p_name ?: "")
+                   << ", p_snap_id=" << p_snap_id
+                   << ", p_snap_name=" << (p_snap_name ?: "")
+                   << ", c_id=" << (c_id ?: "")
+                   << ", c_name=" << c_name
+                   << ", c_opts=" << c_opts
+                   << ", non_primary_global_image_id=" << non_primary_global_image_id
+                   << ", primary_mirror_uuid=" << primary_mirror_uuid
+                   << dendl;
 
     if (((p_id == nullptr) ^ (p_name == nullptr)) == 0) {
       lderr(cct) << "must specify either parent image id or parent image name"
                  << dendl;
       return -EINVAL;
     }
-    if (p_snap_name == nullptr) {
-      lderr(cct) << "image to be cloned must be a snapshot" << dendl;
+    if (((p_snap_id == CEPH_NOSNAP) ^ (p_snap_name == nullptr)) == 0) {
+      lderr(cct) << "must specify either parent snap id or parent snap name"
+                 << dendl;
       return -EINVAL;
     }
 
@@ -770,10 +782,8 @@ int validate_pool(IoCtx &io_ctx, CephContext *cct) {
       clone_id = c_id;
     }
 
-    ldout(cct, 10) << __func__ << " "
-                  << "c_name=" << c_name << ", "
-                  << "c_id= " << clone_id << ", "
-                  << "c_opts=" << c_opts << dendl;
+    ldout(cct, 10) << __func__ << " parent_id=" << parent_id
+                  << ", clone_id=" << clone_id << dendl;
 
     ConfigProxy config{reinterpret_cast<CephContext *>(c_ioctx.cct())->_conf};
     api::Config<>::apply_pool_overrides(c_ioctx, &config);
@@ -782,8 +792,8 @@ int validate_pool(IoCtx &io_ctx, CephContext *cct) {
 
     C_SaferCond cond;
     auto *req = image::CloneRequest<>::create(
-      config, p_ioctx, parent_id, p_snap_name,
-      {cls::rbd::UserSnapshotNamespace{}}, CEPH_NOSNAP, c_ioctx, c_name,
+      config, p_ioctx, parent_id, (p_snap_name ?: ""),
+      {cls::rbd::UserSnapshotNamespace{}}, p_snap_id, c_ioctx, c_name,
       clone_id, c_opts, cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
       non_primary_global_image_id, primary_mirror_uuid,
       asio_engine.get_work_queue(), &cond);
index 65e9a9d18fe689bcd7756745ba172243c8b7a314..77a64137ddb0d65c45af710e52240c0967bea478 100644 (file)
@@ -77,8 +77,8 @@ namespace librbd {
            uint64_t features, int *c_order,
            uint64_t stripe_unit, int stripe_count);
   int clone(IoCtx& p_ioctx, const char *p_id, const char *p_name,
-            const char *p_snap_name, IoCtx& c_ioctx, const char *c_id,
-            const char *c_name, ImageOptions& c_opts,
+            uint64_t p_snap_id, const char *p_snap_name, IoCtx& c_ioctx,
+            const char *c_id, const char *c_name, ImageOptions& c_opts,
             const std::string &non_primary_global_image_id,
             const std::string &primary_mirror_uuid);
   int rename(librados::IoCtx& io_ctx, const char *srcname, const char *dstname);
index 2fccdf5abb46afd31e1c254d1afecafa823bb351..dfbf9f879c91c408e038d43c73d8573c37782ff9 100644 (file)
@@ -778,12 +778,26 @@ namespace librbd {
   {
     TracepointProvider::initialize<tracepoint_traits>(get_cct(p_ioctx));
     tracepoint(librbd, clone3_enter, p_ioctx.get_pool_name().c_str(), p_ioctx.get_id(), p_name, p_snap_name, c_ioctx.get_pool_name().c_str(), c_ioctx.get_id(), c_name, c_opts.opts);
-    int r = librbd::clone(p_ioctx, nullptr, p_name, p_snap_name, c_ioctx,
-                          nullptr, c_name, c_opts, "", "");
+    int r = librbd::clone(p_ioctx, nullptr, p_name, CEPH_NOSNAP, p_snap_name,
+                          c_ioctx, nullptr, c_name, c_opts, "", "");
     tracepoint(librbd, clone3_exit, r);
     return r;
   }
 
+  int RBD::clone4(IoCtx& p_ioctx, const char *p_name, uint64_t p_snap_id,
+                 IoCtx& c_ioctx, const char *c_name, ImageOptions& c_opts)
+  {
+    TracepointProvider::initialize<tracepoint_traits>(get_cct(p_ioctx));
+    tracepoint(librbd, clone4_enter, p_ioctx.get_pool_name().c_str(),
+               p_ioctx.get_id(), p_name, p_snap_id,
+               c_ioctx.get_pool_name().c_str(), c_ioctx.get_id(), c_name,
+               c_opts.opts);
+    int r = librbd::clone(p_ioctx, nullptr, p_name, p_snap_id, nullptr,
+                          c_ioctx, nullptr, c_name, c_opts, "", "");
+    tracepoint(librbd, clone4_exit, r);
+    return r;
+  }
+
   int RBD::remove(IoCtx& io_ctx, const char *name)
   {
     TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));
@@ -3978,12 +3992,30 @@ extern "C" int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
   TracepointProvider::initialize<tracepoint_traits>(get_cct(p_ioc));
   tracepoint(librbd, clone3_enter, p_ioc.get_pool_name().c_str(), p_ioc.get_id(), p_name, p_snap_name, c_ioc.get_pool_name().c_str(), c_ioc.get_id(), c_name, c_opts);
   librbd::ImageOptions c_opts_(c_opts);
-  int r = librbd::clone(p_ioc, nullptr, p_name, p_snap_name, c_ioc, nullptr,
-                        c_name, c_opts_, "", "");
+  int r = librbd::clone(p_ioc, nullptr, p_name, CEPH_NOSNAP, p_snap_name,
+                        c_ioc, nullptr, c_name, c_opts_, "", "");
   tracepoint(librbd, clone3_exit, r);
   return r;
 }
 
+extern "C" 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)
+{
+  librados::IoCtx p_ioc, c_ioc;
+  librados::IoCtx::from_rados_ioctx_t(p_ioctx, p_ioc);
+  librados::IoCtx::from_rados_ioctx_t(c_ioctx, c_ioc);
+  TracepointProvider::initialize<tracepoint_traits>(get_cct(p_ioc));
+  tracepoint(librbd, clone4_enter, p_ioc.get_pool_name().c_str(),
+             p_ioc.get_id(), p_name, p_snap_id, c_ioc.get_pool_name().c_str(),
+             c_ioc.get_id(), c_name, c_opts);
+  librbd::ImageOptions c_opts_(c_opts);
+  int r = librbd::clone(p_ioc, nullptr, p_name, p_snap_id, nullptr,
+                        c_ioc, nullptr, c_name, c_opts_, "", "");
+  tracepoint(librbd, clone4_exit, r);
+  return r;
+}
+
 extern "C" int rbd_remove(rados_ioctx_t p, const char *name)
 {
   librados::IoCtx io_ctx;
index e7d1958bf31cf3c5a0b5eeedd640c70a084f009f..61f7bff389fc2bb8809386d97591d1fc152c4f5c 100644 (file)
@@ -329,6 +329,9 @@ cdef extern from "rbd/librbd.h" nogil:
     int rbd_clone3(rados_ioctx_t p_ioctx, const char *p_name,
                    const char *p_snapname, rados_ioctx_t c_ioctx,
                    const char *c_name, rbd_image_options_t c_opts)
+    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)
     int rbd_remove_with_progress(rados_ioctx_t io, const char *name,
                                  librbd_progress_fn_t cb, void *cbdata)
     int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname,
index dc0d499664e1567cfa3fb1ff0c0ea4619c24ae70..d88c136d3d6b10732b87c5760ba673bfa1183370 100644 (file)
@@ -350,6 +350,10 @@ cdef nogil:
                    const char *p_snapname, rados_ioctx_t c_ioctx,
                    const char *c_name, rbd_image_options_t c_opts):
         pass
+    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):
+        pass
     int rbd_remove_with_progress(rados_ioctx_t io, const char *name,
                                  librbd_progress_fn_t cb, void *cbdata):
         pass
index 6650b08c935be45e6a801c7419b8b728c9f91fdb..7902ded17e8fd2000e7e0d88263afca34284d287 100644 (file)
@@ -632,7 +632,7 @@ class RBD(object):
         if ret < 0:
             raise make_ex(ret, 'error creating image')
 
-    def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name,
+    def clone(self, p_ioctx, p_name, p_snapshot, c_ioctx, c_name,
               features=None, order=None, stripe_unit=None, stripe_count=None,
               data_pool=None, clone_format=None):
         """
@@ -642,7 +642,7 @@ class RBD(object):
         :type ioctx: :class:`rados.Ioctx`
         :param p_name: the parent image name
         :type name: str
-        :param p_snapname: the parent image snapshot name
+        :param p_snapshot: the parent image snapshot name or id
         :type name: str
         :param c_ioctx: the child context that represents the new clone
         :type ioctx: :class:`rados.Ioctx`
@@ -666,7 +666,6 @@ class RBD(object):
         :raises: :class:`FunctionNotSupported`
         :raises: :class:`ArgumentOutOfRange`
         """
-        p_snapname = cstr(p_snapname, 'p_snapname')
         p_name = cstr(p_name, 'p_name')
         c_name = cstr(c_name, 'c_name')
         data_pool = cstr(data_pool, 'data_pool', opt=True)
@@ -674,9 +673,18 @@ class RBD(object):
             rados_ioctx_t _p_ioctx = convert_ioctx(p_ioctx)
             rados_ioctx_t _c_ioctx = convert_ioctx(c_ioctx)
             char *_p_name = p_name
-            char *_p_snapname = p_snapname
+            char *_p_snap_name
+            uint64_t _p_snap_id
             char *_c_name = c_name
             rbd_image_options_t opts
+        if isinstance(p_snapshot, str):
+            p_snap_name = cstr(p_snapshot, 'p_snapshot')
+            _p_snap_name = p_snap_name
+        elif isinstance(p_snapshot, int):
+            p_snap_name = None
+            _p_snap_id = p_snapshot
+        else:
+            raise TypeError("p_snapshot must be a string or an integer")
 
         rbd_image_options_create(&opts)
         try:
@@ -698,9 +706,14 @@ class RBD(object):
             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)
+            if p_snap_name is not None:
+                with nogil:
+                    ret = rbd_clone3(_p_ioctx, _p_name, _p_snap_name,
+                                     _c_ioctx, _c_name, opts)
+            else:
+                with nogil:
+                    ret = rbd_clone4(_p_ioctx, _p_name, _p_snap_id,
+                                     _c_ioctx, _c_name, opts)
         finally:
             rbd_image_options_destroy(opts)
         if ret < 0:
index 3d8cefdb43ff4d26ad366bc756d8f1d43b91c3b8..deaf04585964669327c19ede216496d4c13ec1ad 100644 (file)
@@ -15,6 +15,7 @@ from assertions import (assert_equal as eq, assert_raises, assert_not_equal,
                         assert_greater_equal)
 from datetime import datetime, timedelta
 from rados import (Rados,
+                   LIBRADOS_SNAP_HEAD,
                    LIBRADOS_OP_FLAG_FADVISE_DONTNEED,
                    LIBRADOS_OP_FLAG_FADVISE_NOCACHE,
                    LIBRADOS_OP_FLAG_FADVISE_RANDOM)
@@ -33,6 +34,7 @@ from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
                  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_GROUP,
                  RBD_SNAP_NAMESPACE_TYPE_TRASH,
                  RBD_SNAP_NAMESPACE_TYPE_MIRROR,
                  RBD_IMAGE_MIGRATION_STATE_PREPARED, RBD_CONFIG_SOURCE_CONFIG,
@@ -1800,6 +1802,67 @@ class TestClone(object):
 
         # unprotect, remove parent snap happen in cleanup, and should succeed
 
+    def test_clone_by_snap_id(self):
+        clone_name2 = get_temp_image_name()
+        assert_raises(TypeError, self.rbd.clone, ioctx, image_name,
+                      None, ioctx, clone_name2, features)
+        assert_raises(TypeError, self.rbd.clone, ioctx, image_name,
+                      1.0, ioctx, clone_name2, features)
+        assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
+                      LIBRADOS_SNAP_HEAD, ioctx, clone_name2, features)
+
+        self.image.create_snap('snap2')
+        snap_id = self.image.snap_get_id('snap2')
+        self.image.remove_snap('snap2')
+        assert_raises(ImageNotFound, self.image.snap_get_trash_namespace,
+                      snap_id)
+        assert_raises(ImageNotFound, self.rbd.clone, ioctx, image_name,
+                      snap_id, ioctx, clone_name2, features, clone_format=1)
+        assert_raises(ImageNotFound, self.rbd.clone, ioctx, image_name,
+                      snap_id, ioctx, clone_name2, features, clone_format=2)
+
+        snap_id = self.image.snap_get_id('snap1')
+        self.rbd.clone(ioctx, image_name, snap_id, ioctx, clone_name2,
+                       features, clone_format=1)
+        with Image(ioctx, clone_name2) as clone2:
+            assert clone2.parent_info() == self.clone.parent_info()
+            assert clone2.op_features() == 0
+        self.rbd.remove(ioctx, clone_name2)
+        self.rbd.clone(ioctx, image_name, snap_id, ioctx, clone_name2,
+                       features, clone_format=2)
+        with Image(ioctx, clone_name2) as clone2:
+            assert clone2.parent_info() == self.clone.parent_info()
+            assert clone2.op_features() == RBD_OPERATION_FEATURE_CLONE_CHILD
+        self.rbd.remove(ioctx, clone_name2)
+
+        self.image.create_snap('snap2')
+        snap_id = self.image.snap_get_id('snap2')
+        assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
+                      snap_id, ioctx, clone_name2, features, clone_format=1)
+        self.rbd.clone(ioctx, image_name, snap_id, ioctx, clone_name2,
+                       features, clone_format=2)
+        with Image(ioctx, clone_name2) as clone2:
+            clone2_parent_info = clone2.parent_info()
+            clone_parent_info = self.clone.parent_info()
+            assert clone2_parent_info[0] == clone_parent_info[0]
+            assert clone2_parent_info[1] == clone_parent_info[1]
+            assert clone2_parent_info[2] == 'snap2'
+            assert clone_parent_info[2] == 'snap1'
+
+        self.image.remove_snap('snap2')
+        trash_snap = self.image.snap_get_trash_namespace(snap_id)
+        assert trash_snap == {
+            'original_name' : 'snap2'
+            }
+        clone_name3 = get_temp_image_name()
+        assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
+                      snap_id, ioctx, clone_name3, features, clone_format=1)
+        assert_raises(ImageNotFound, self.rbd.clone, ioctx, image_name,
+                      snap_id, ioctx, clone_name3, features, clone_format=2)
+        self.rbd.remove(ioctx, clone_name2)
+        assert_raises(ImageNotFound, self.image.snap_get_trash_namespace,
+                      snap_id)
+
     def test_stat(self):
         image_info = self.image.stat()
         clone_info = self.clone.stat()
@@ -2820,7 +2883,7 @@ class TestGroups(object):
         eq([snap_name], [snap['name'] for snap in self.group.list_snaps()])
 
         for snap in self.image.list_snaps():
-            eq(rbd.RBD_SNAP_NAMESPACE_TYPE_GROUP, snap['namespace'])
+            eq(RBD_SNAP_NAMESPACE_TYPE_GROUP, snap['namespace'])
             info = snap['group']
             eq(group_name, info['group_name'])
             eq(snap_name, info['group_snap_name'])
@@ -2885,6 +2948,107 @@ class TestGroups(object):
         self.group.remove_snap(new_snap_name)
         eq([], list(self.group.list_snaps()))
 
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_group_snap_clone(self):
+        data = rand_data(256)
+        with Image(ioctx, image_name) as image:
+            image.write(data, 0)
+
+        self.group.add_image(ioctx, image_name)
+        self.group.create_snap(snap_name)
+        assert [s['name'] for s in self.group.list_snaps()] == [snap_name]
+        image_snaps = list(self.image.list_snaps())
+        assert [s['namespace'] for s in image_snaps] == [RBD_SNAP_NAMESPACE_TYPE_GROUP]
+        image_snap_name = image_snaps[0]['name']
+        image_snap_id = image_snaps[0]['id']
+        assert image_snaps[0]['group'] == {
+            'pool' : ioctx.get_pool_id(),
+            'name' : group_name,
+            'snap_name' : snap_name,
+            }
+
+        clone_name = get_temp_image_name()
+        assert_raises(ImageNotFound, self.rbd.clone, ioctx, image_name,
+                      image_snap_name, ioctx, clone_name, features, clone_format=1)
+        assert_raises(InvalidArgument, self.rbd.clone, ioctx, image_name,
+                      image_snap_id, ioctx, clone_name, features, clone_format=1)
+        assert_raises(ImageNotFound, self.rbd.clone, ioctx, image_name,
+                      image_snap_name, ioctx, clone_name, features, clone_format=2)
+        self.rbd.clone(ioctx, image_name, image_snap_id, ioctx, clone_name,
+                       features, clone_format=2)
+        with Image(ioctx, clone_name) as clone:
+            parent_spec = clone.get_parent_image_spec()
+            assert parent_spec['pool_name'] == pool_name
+            assert parent_spec['image_name'] == image_name
+            assert parent_spec['snap_namespace_type'] == RBD_SNAP_NAMESPACE_TYPE_GROUP
+            assert parent_spec['snap_name'] == image_snap_name
+            assert parent_spec['snap_id'] == image_snap_id
+            read = clone.read(0, 256)
+            assert read == data
+
+        self.group.remove_snap(snap_name)
+        assert list(self.group.list_snaps()) == []
+        image_snaps = list(self.image.list_snaps())
+        assert [s['namespace'] for s in image_snaps] == [RBD_SNAP_NAMESPACE_TYPE_TRASH]
+        trash_image_snap_name = image_snaps[0]['name']
+        assert image_snaps[0]['id'] == image_snap_id
+        assert image_snaps[0]['trash'] == {
+            'original_name' : image_snap_name
+            }
+        assert trash_image_snap_name != image_snap_name
+
+        with Image(ioctx, clone_name) as clone:
+            parent_spec = clone.get_parent_image_spec()
+            assert parent_spec['pool_name'] == pool_name
+            assert parent_spec['image_name'] == image_name
+            assert parent_spec['snap_namespace_type'] == RBD_SNAP_NAMESPACE_TYPE_TRASH
+            assert parent_spec['snap_name'] == trash_image_snap_name
+            assert parent_spec['snap_id'] == image_snap_id
+            read = clone.read(0, 256)
+            assert read == data
+
+        self.rbd.remove(ioctx, clone_name)
+        assert list(self.image.list_snaps()) == []
+
+    @require_features([RBD_FEATURE_LAYERING])
+    def test_group_snap_clone_flatten(self):
+        data = rand_data(256)
+        with Image(ioctx, image_name) as image:
+            image.write(data, 0)
+
+        self.group.add_image(ioctx, image_name)
+        self.group.create_snap(snap_name)
+        assert [s['name'] for s in self.group.list_snaps()] == [snap_name]
+        image_snaps = list(self.image.list_snaps())
+        assert [s['namespace'] for s in image_snaps] == [RBD_SNAP_NAMESPACE_TYPE_GROUP]
+        image_snap_id = image_snaps[0]['id']
+
+        clone_name = get_temp_image_name()
+        self.rbd.clone(ioctx, image_name, image_snap_id, ioctx, clone_name,
+                       features, clone_format=2)
+        self.group.remove_snap(snap_name)
+        assert list(self.group.list_snaps()) == []
+        image_snaps = list(self.image.list_snaps())
+        assert [s['namespace'] for s in image_snaps] == [RBD_SNAP_NAMESPACE_TYPE_TRASH]
+        assert image_snaps[0]['id'] == image_snap_id
+
+        with Image(ioctx, clone_name) as clone:
+            parent_spec = clone.get_parent_image_spec()
+            assert parent_spec['pool_id'] == ioctx.get_pool_id()
+            assert parent_spec['image_id'] == self.image.id()
+            assert parent_spec['snap_id'] == image_snap_id
+            read = clone.read(0, 256)
+            assert read == data
+            clone.flatten()
+
+        assert list(self.image.list_snaps()) == []
+        with Image(ioctx, clone_name) as clone:
+            assert_raises(ImageNotFound, clone.get_parent_image_spec)
+            read = clone.read(0, 256)
+            assert read == data
+
+        self.rbd.remove(ioctx, clone_name)
+
     def test_group_snap_rollback(self):
         eq([], list(self.group.list_images()))
         self.group.add_image(ioctx, image_name)
index 5fa5d6db51231c078f8725d0ba765e0b4619df01..6b5993591fd070aa03f712d365b6a6c7878ae0a6 100644 (file)
@@ -202,7 +202,7 @@ public:
     librbd::ImageOptions clone_opts;
     clone_opts.set(RBD_IMAGE_OPTION_FEATURES, ictx->features);
     EXPECT_EQ(0, librbd::clone(m_local_io_ctx, m_local_image_id.c_str(),
-                               nullptr, "snap1", m_local_io_ctx,
+                               nullptr, CEPH_NOSNAP, "snap1", m_local_io_ctx,
                                clone_id.c_str(), "clone1", clone_opts,
                                GLOBAL_CLONE_IMAGE_ID, m_remote_mirror_uuid));
 
index b2624d5b184a3905ef2156c876529e1bc13dd86d..5b7f2b31ff30c9eda97ba84efe6330dd1e083a95 100644 (file)
@@ -1481,6 +1481,36 @@ TRACEPOINT_EVENT(librbd, clone3_exit,
     )
 )
 
+TRACEPOINT_EVENT(librbd, clone4_enter,
+    TP_ARGS(
+        const char*, parent_pool_name,
+        uint64_t, parent_pool_id,
+        const char*, parent_name,
+        uint64_t, parent_snap_id,
+        const char*, child_pool_name,
+        uint64_t, child_pool_id,
+        const char*, child_name,
+        void*, opts),
+    TP_FIELDS(
+        ctf_string(parent_pool_name, parent_pool_name)
+        ctf_integer(uint64_t, parent_pool_id, parent_pool_id)
+        ctf_string(parent_name, parent_name)
+        ctf_integer(uint64_t, parent_snap_id, parent_snap_id)
+        ctf_string(child_pool_name, child_pool_name)
+        ctf_integer(uint64_t, child_pool_id, child_pool_id)
+        ctf_string(child_name, child_name)
+        ctf_integer_hex(void*, opts, opts)
+    )
+)
+
+TRACEPOINT_EVENT(librbd, clone4_exit,
+    TP_ARGS(
+        int, retval),
+    TP_FIELDS(
+        ctf_integer(int, retval, retval)
+    )
+)
+
 TRACEPOINT_EVENT(librbd, flatten_enter,
     TP_ARGS(
         void*, imagectx,