]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: make it possible to migrate parent images
authorMykola Golub <mgolub@suse.com>
Mon, 4 Feb 2019 12:09:02 +0000 (12:09 +0000)
committerMykola Golub <mgolub@suse.com>
Tue, 19 Feb 2019 08:43:09 +0000 (08:43 +0000)
Signed-off-by: Mykola Golub <mgolub@suse.com>
src/librbd/api/Migration.cc
src/librbd/api/Migration.h
src/librbd/image/AttachChildRequest.cc
src/test/librbd/test_Migration.cc

index cbae3906af0a2855ce61e9302270326b1adf4cb5..21a71c50f50d0c9ddbcdbe8e7b9d14be249c8810 100644 (file)
 #include "librbd/api/Trash.h"
 #include "librbd/deep_copy/MetadataCopyRequest.h"
 #include "librbd/deep_copy/SnapshotCopyRequest.h"
+#include "librbd/image/AttachChildRequest.h"
+#include "librbd/image/AttachParentRequest.h"
 #include "librbd/image/CloneRequest.h"
 #include "librbd/image/CreateRequest.h"
+#include "librbd/image/DetachChildRequest.h"
+#include "librbd/image/DetachParentRequest.h"
 #include "librbd/image/ListWatchersRequest.h"
 #include "librbd/image/RemoveRequest.h"
 #include "librbd/internal.h"
 #define dout_prefix *_dout << "librbd::Migration: " << __func__ << ": "
 
 namespace librbd {
+
+inline bool operator==(const linked_image_spec_t& rhs,
+                       const linked_image_spec_t& lhs) {
+  bool result = (rhs.pool_id == lhs.pool_id &&
+                 rhs.pool_namespace == lhs.pool_namespace &&
+                 rhs.image_id == lhs.image_id);
+  return result;
+}
+
 namespace api {
 
 using util::create_rados_callback;
@@ -643,7 +656,7 @@ template <typename I>
 int Migration<I>::prepare() {
   ldout(m_cct, 10) << dendl;
 
-  int r = list_snaps();
+  int r = validate_src_snaps();
   if (r < 0) {
     return r;
   }
@@ -672,11 +685,6 @@ int Migration<I>::prepare() {
     return r;
   }
 
-  r = set_state(cls::rbd::MIGRATION_STATE_PREPARED, "");
-  if (r < 0) {
-    return r;
-  }
-
   ldout(m_cct, 10) << "succeeded" << dendl;
 
   return 0;
@@ -768,6 +776,13 @@ int Migration<I>::abort() {
     ldout(m_cct, 1) << "failed to open destination image: " << cpp_strerror(r)
                     << dendl;
   } else {
+    ldout(m_cct, 10) << "relinking children" << dendl;
+
+    r = relink_children(dst_image_ctx, m_src_image_ctx);
+    if (r < 0) {
+      return r;
+    }
+
     ldout(m_cct, 10) << "removing dst image snapshots" << dendl;
 
     BOOST_SCOPE_EXIT_TPL(&dst_image_ctx) {
@@ -786,7 +801,8 @@ int Migration<I>::abort() {
 
     for (auto &snap : snaps) {
       librbd::NoOpProgressContext prog_ctx;
-      int r = snap_remove(dst_image_ctx, snap.name.c_str(), 0, prog_ctx);
+      int r = snap_remove(dst_image_ctx, snap.name.c_str(),
+                          RBD_SNAP_REMOVE_UNPROTECT, prog_ctx);
       if (r < 0) {
         lderr(m_cct) << "failed removing snapshot: " << cpp_strerror(r)
                      << dendl;
@@ -880,7 +896,6 @@ int Migration<I>::commit() {
   }
 
   r = remove_src_image();
-
   if (r < 0) {
     return r;
   }
@@ -957,73 +972,88 @@ int Migration<I>::set_state(cls::rbd::MigrationState state,
 }
 
 template <typename I>
-int Migration<I>::list_snaps(std::vector<librbd::snap_info_t> *snapsptr) {
+int Migration<I>::list_src_snaps(std::vector<librbd::snap_info_t> *snaps) {
   ldout(m_cct, 10) << dendl;
 
-  std::vector<librbd::snap_info_t> snaps;
-
-  int r = snap_list(m_src_image_ctx, snaps);
+  int r = snap_list(m_src_image_ctx, *snaps);
   if (r < 0) {
     lderr(m_cct) << "failed listing snapshots: " << cpp_strerror(r) << dendl;
     return r;
   }
 
-  for (auto &snap : snaps) {
+  for (auto &snap : *snaps) {
     librbd::snap_namespace_type_t namespace_type;
-    r = Snapshot<I>::get_namespace_type(m_src_image_ctx, snap.id, &namespace_type);
+    r = Snapshot<I>::get_namespace_type(m_src_image_ctx, snap.id,
+                                        &namespace_type);
     if (r < 0) {
       lderr(m_cct) << "error getting snap namespace type: " << cpp_strerror(r)
                    << dendl;
       return r;
     }
 
-    if (namespace_type == RBD_SNAP_NAMESPACE_TYPE_GROUP ||
-        namespace_type == RBD_SNAP_NAMESPACE_TYPE_TRASH) {
-      lderr(m_cct) << "image has group or trash snapshot '" << snap.name << "'"
-                   << dendl;
-      return -EBUSY;
-    } else {
-      bool is_protected;
-      r = snap_is_protected(m_src_image_ctx, snap.name.c_str(), &is_protected);
-      if (r < 0) {
-        lderr(m_cct) << "failed retrieving snapshot status: " << cpp_strerror(r)
+    if (namespace_type != RBD_SNAP_NAMESPACE_TYPE_USER) {
+      if (namespace_type == RBD_SNAP_NAMESPACE_TYPE_TRASH) {
+        lderr(m_cct) << "image has snapshots with linked clones that must be "
+                     << "deleted or flattened before the image can be migrated"
                      << dendl;
-        return r;
-      }
-      if (is_protected) {
-        lderr(m_cct) << "image has protected snapshot '" << snap.name << "'"
-                     << dendl;
-        return -EBUSY;
+      } else {
+        lderr(m_cct) << "image has non-user type snapshots "
+                     << "that are not supported by migration" << dendl;
       }
+      return -EBUSY;
+    }
+  }
 
-      RWLock::RLocker l(m_src_image_ctx->snap_lock);
-      cls::rbd::ParentImageSpec parent_spec{m_src_image_ctx->md_ctx.get_id(),
-                                            m_src_image_ctx->md_ctx.get_namespace(),
-                                            m_src_image_ctx->id, snap.id};
-      std::vector<librbd::linked_image_spec_t> child_images;
-      r = api::Image<I>::list_children(m_src_image_ctx, parent_spec, &child_images);
-      if (r < 0) {
-        lderr(m_cct) << "failed listing children: " << cpp_strerror(r)
-                     << dendl;
-        return r;
-      }
+  return 0;
+}
 
-      size_t size = child_images.size();
-      if (size > 0) {
-        lderr(m_cct) << "image has snapshot '" << snap.name << "' with linked clones"
-                     << dendl;
-        return -EBUSY;
-      }
-    }
+template <typename I>
+int Migration<I>::validate_src_snaps() {
+  ldout(m_cct, 10) << dendl;
+
+  std::vector<librbd::snap_info_t> snaps;
+  int r = list_src_snaps(&snaps);
+  if (r < 0) {
+    return r;
+  }
+
+  uint64_t dst_features = 0;
+  r = m_image_options.get(RBD_IMAGE_OPTION_FEATURES, &dst_features);
+  ceph_assert(r == 0);
+
+  if (!m_src_image_ctx->test_features(RBD_FEATURE_LAYERING)) {
+    return 0;
   }
 
-  if (snapsptr != nullptr) {
-    *snapsptr = snaps;
+  for (auto &snap : snaps) {
+    RWLock::RLocker snap_locker(m_src_image_ctx->snap_lock);
+    cls::rbd::ParentImageSpec parent_spec{m_src_image_ctx->md_ctx.get_id(),
+                                          m_src_image_ctx->md_ctx.get_namespace(),
+                                          m_src_image_ctx->id, snap.id};
+    std::vector<librbd::linked_image_spec_t> child_images;
+    r = api::Image<I>::list_children(m_src_image_ctx, parent_spec,
+                                     &child_images);
+    if (r < 0) {
+      lderr(m_cct) << "failed listing children: " << cpp_strerror(r)
+                   << dendl;
+      return r;
+    }
+    if (!child_images.empty()) {
+      ldout(m_cct, 1) << m_src_image_ctx->name << "@" << snap.name
+                      << " has children" << dendl;
+
+      if ((dst_features & RBD_FEATURE_LAYERING) == 0) {
+        lderr(m_cct) << "can't migrate to destination without layering feature: "
+                     << "image has children" << dendl;
+        return -EINVAL;
+      }
+    }
   }
 
   return 0;
 }
 
+
 template <typename I>
 int Migration<I>::set_migration() {
   ldout(m_cct, 10) << dendl;
@@ -1280,6 +1310,23 @@ int Migration<I>::create_dst_image() {
     return r;
   }
 
+  r = set_state(cls::rbd::MIGRATION_STATE_PREPARED, "");
+  if (r < 0) {
+    return r;
+  }
+
+  r = dst_image_ctx->state->refresh();
+  if (r < 0) {
+    lderr(m_cct) << "failed to refresh destination image: " << cpp_strerror(r)
+                 << dendl;
+    return r;
+  }
+
+  r = relink_children(m_src_image_ctx, dst_image_ctx);
+  if (r < 0) {
+    return r;
+  }
+
   return 0;
 }
 
@@ -1450,23 +1497,246 @@ int Migration<I>::enable_mirroring(I *image_ctx, bool was_enabled) {
   return 0;
 }
 
+// When relinking children we should be careful as it my be interrupted
+// at any moment by some reason and we may end up in an inconsistent
+// state, which we have to be able to fix with "migration abort". Below
+// are all possible states during migration (P1 - sourse parent, P2 -
+// destination parent, C - child):
+//
+//   P1  P2    P1  P2    P1  P2    P1  P2
+//   ^\         \  ^      \ /^        /^
+//    \v         v/        v/        v/
+//     C         C         C         C
+//
+//     1         2         3         4
+//
+// (1) and (4) are the initial and the final consistent states. (2)
+// and (3) are intermediate inconsistent states that have to be fixed
+// by relink_children running in "migration abort" mode. For this, it
+// scans P2 for all children attached and relinks (fixes) states (3)
+// and (4) to state (1). Then it scans P1 for remaining children and
+// fixes the states (2).
+
+template <typename I>
+int Migration<I>::relink_children(I *from_image_ctx, I *to_image_ctx) {
+  ldout(m_cct, 10) << dendl;
+
+  std::vector<librbd::snap_info_t> snaps;
+  int r = list_src_snaps(&snaps);
+  if (r < 0) {
+    return r;
+  }
+
+  bool migration_abort = (to_image_ctx == m_src_image_ctx);
+
+  for (auto it = snaps.begin(); it != snaps.end(); it++) {
+    auto &snap = *it;
+    std::vector<librbd::linked_image_spec_t> src_child_images;
+
+    if (from_image_ctx != m_src_image_ctx) {
+      ceph_assert(migration_abort);
+
+      // We run list snaps against the src image to get only those snapshots
+      // that are migrated. If the "from" image is not the src image
+      // (abort migration case), we need to remap snap ids.
+      // Also collect the list of the children currently attached to the
+      // source, so we could make a proper decision later about relinking.
+
+      RWLock::RLocker src_snap_locker(to_image_ctx->snap_lock);
+      cls::rbd::ParentImageSpec src_parent_spec{to_image_ctx->md_ctx.get_id(),
+                                                to_image_ctx->md_ctx.get_namespace(),
+                                                to_image_ctx->id, snap.id};
+      r = api::Image<I>::list_children(to_image_ctx, src_parent_spec,
+                                       &src_child_images);
+      if (r < 0) {
+        lderr(m_cct) << "failed listing children: " << cpp_strerror(r)
+                     << dendl;
+        return r;
+      }
+
+      RWLock::RLocker snap_locker(from_image_ctx->snap_lock);
+      snap.id = from_image_ctx->get_snap_id(cls::rbd::UserSnapshotNamespace(),
+                                            snap.name);
+      if (snap.id == CEPH_NOSNAP) {
+        ldout(m_cct, 5) << "skipping snapshot " << snap.name << dendl;
+        continue;
+      }
+    }
+
+    std::vector<librbd::linked_image_spec_t> child_images;
+    {
+      RWLock::RLocker snap_locker(from_image_ctx->snap_lock);
+      cls::rbd::ParentImageSpec parent_spec{from_image_ctx->md_ctx.get_id(),
+                                            from_image_ctx->md_ctx.get_namespace(),
+                                            from_image_ctx->id, snap.id};
+      r = api::Image<I>::list_children(from_image_ctx, parent_spec,
+                                       &child_images);
+      if (r < 0) {
+        lderr(m_cct) << "failed listing children: " << cpp_strerror(r)
+                     << dendl;
+        return r;
+      }
+    }
+
+    for (auto &child_image : child_images) {
+      r = relink_child(from_image_ctx, to_image_ctx, snap, child_image,
+                       migration_abort, true);
+      if (r < 0) {
+        return r;
+      }
+
+      src_child_images.erase(std::remove(src_child_images.begin(),
+                                         src_child_images.end(), child_image),
+                             src_child_images.end());
+    }
+
+    for (auto &child_image : src_child_images) {
+      r = relink_child(from_image_ctx, to_image_ctx, snap, child_image,
+                       migration_abort, false);
+      if (r < 0) {
+        return r;
+      }
+    }
+  }
+
+  return 0;
+}
+
+template <typename I>
+int Migration<I>::relink_child(I *from_image_ctx, I *to_image_ctx,
+                               const librbd::snap_info_t &from_snap,
+                               const librbd::linked_image_spec_t &child_image,
+                               bool migration_abort, bool reattach_child) {
+  ldout(m_cct, 10) << from_snap.name << " " << child_image.pool_name << "/"
+                   << child_image.pool_namespace << "/"
+                   << child_image.image_name << " (migration_abort="
+                   << migration_abort << ", reattach_child=" << reattach_child
+                   << ")" << dendl;
+
+  librados::snap_t to_snap_id;
+  {
+    RWLock::RLocker snap_locker(to_image_ctx->snap_lock);
+    to_snap_id = to_image_ctx->get_snap_id(cls::rbd::UserSnapshotNamespace(),
+                                             from_snap.name);
+    if (to_snap_id == CEPH_NOSNAP) {
+      lderr(m_cct) << "no snapshot " << from_snap.name << " on destination image"
+                   << dendl;
+      return -ENOENT;
+    }
+  }
+
+  librados::IoCtx child_io_ctx;
+  int r = util::create_ioctx(to_image_ctx->md_ctx,
+                             "child image " + child_image.image_name,
+                             child_image.pool_id, child_image.pool_namespace,
+                             &child_io_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  I *child_image_ctx = I::create("", child_image.image_id, nullptr,
+                                 child_io_ctx, false);
+  r = child_image_ctx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT);
+  if (r < 0) {
+    lderr(m_cct) << "failed to open child image: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+  BOOST_SCOPE_EXIT_TPL(child_image_ctx) {
+    child_image_ctx->state->close();
+  } BOOST_SCOPE_EXIT_END;
+
+  uint32_t clone_format = 1;
+  if (child_image_ctx->test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD)) {
+    clone_format = 2;
+  }
+
+  cls::rbd::ParentImageSpec parent_spec;
+  uint64_t parent_overlap;
+  {
+    RWLock::RLocker snap_locker(child_image_ctx->snap_lock);
+    RWLock::RLocker parent_locker(child_image_ctx->parent_lock);
+
+    // use oldest snapshot or HEAD for parent spec
+    if (!child_image_ctx->snap_info.empty()) {
+      parent_spec = child_image_ctx->snap_info.begin()->second.parent.spec;
+      parent_overlap = child_image_ctx->snap_info.begin()->second.parent.overlap;
+    } else {
+      parent_spec = child_image_ctx->parent_md.spec;
+      parent_overlap = child_image_ctx->parent_md.overlap;
+    }
+  }
+
+  if (migration_abort &&
+      parent_spec.pool_id == to_image_ctx->md_ctx.get_id() &&
+      parent_spec.pool_namespace == to_image_ctx->md_ctx.get_namespace() &&
+      parent_spec.image_id == to_image_ctx->id &&
+      parent_spec.snap_id == to_snap_id) {
+    ldout(m_cct, 10) << "no need for parent re-attach" << dendl;
+  } else {
+    if (parent_spec.pool_id != from_image_ctx->md_ctx.get_id() ||
+        parent_spec.pool_namespace != from_image_ctx->md_ctx.get_namespace() ||
+        parent_spec.image_id != from_image_ctx->id ||
+        parent_spec.snap_id != from_snap.id) {
+      lderr(m_cct) << "parent is not source image: " << parent_spec.pool_id
+                   << "/" << parent_spec.pool_namespace << "/"
+                   << parent_spec.image_id << "@" << parent_spec.snap_id
+                   << dendl;
+      return -ESTALE;
+    }
+
+    parent_spec.pool_id = to_image_ctx->md_ctx.get_id();
+    parent_spec.pool_namespace = to_image_ctx->md_ctx.get_namespace();
+    parent_spec.image_id = to_image_ctx->id;
+    parent_spec.snap_id = to_snap_id;
+
+    C_SaferCond on_reattach_parent;
+    auto reattach_parent_req = image::AttachParentRequest<I>::create(
+      *child_image_ctx, parent_spec, parent_overlap, true, &on_reattach_parent);
+    reattach_parent_req->send();
+    r = on_reattach_parent.wait();
+    if (r < 0) {
+      lderr(m_cct) << "failed to re-attach parent: " << cpp_strerror(r) << dendl;
+      return r;
+    }
+  }
+
+  if (reattach_child) {
+    C_SaferCond on_reattach_child;
+    auto reattach_child_req = image::AttachChildRequest<I>::create(
+      child_image_ctx, to_image_ctx, to_snap_id, from_image_ctx, from_snap.id,
+      clone_format, &on_reattach_child);
+    reattach_child_req->send();
+    r = on_reattach_child.wait();
+    if (r < 0) {
+      lderr(m_cct) << "failed to re-attach child: " << cpp_strerror(r) << dendl;
+      return r;
+    }
+  }
+
+  child_image_ctx->notify_update();
+
+  return 0;
+}
+
 template <typename I>
 int Migration<I>::remove_src_image() {
   ldout(m_cct, 10) << dendl;
 
   std::vector<librbd::snap_info_t> snaps;
-  int r = list_snaps(&snaps);
+  int r = list_src_snaps(&snaps);
   if (r < 0) {
     return r;
   }
 
   for (auto it = snaps.rbegin(); it != snaps.rend(); it++) {
     auto &snap = *it;
+
     librbd::NoOpProgressContext prog_ctx;
-    int r = snap_remove(m_src_image_ctx, snap.name.c_str(), 0, prog_ctx);
+    int r = snap_remove(m_src_image_ctx, snap.name.c_str(),
+                        RBD_SNAP_REMOVE_UNPROTECT, prog_ctx);
     if (r < 0) {
-      lderr(m_cct) << "failed removing snapshot '" << snap.name << "': "
-                   << cpp_strerror(r) << dendl;
+      lderr(m_cct) << "failed removing source image snapshot '" << snap.name
+                   << "': " << cpp_strerror(r) << dendl;
       return r;
     }
   }
index 88c1c959c9ce329219cb6356879e2a83c06ce888..c746b1b997cca3956ebb029d7fb7eb28bc1ce8f9 100644 (file)
@@ -71,7 +71,8 @@ private:
 
   int set_state(cls::rbd::MigrationState state, const std::string &description);
 
-  int list_snaps(std::vector<librbd::snap_info_t> *snaps = nullptr);
+  int list_src_snaps(std::vector<librbd::snap_info_t> *snaps);
+  int validate_src_snaps();
   int disable_mirroring(ImageCtxT *image_ctx, bool *was_enabled);
   int enable_mirroring(ImageCtxT *image_ctx, bool was_enabled);
   int set_migration();
@@ -82,6 +83,7 @@ private:
   int add_group(ImageCtxT *image_ctx, group_info_t &group_info);
   int update_group(ImageCtxT *from_image_ctx, ImageCtxT *to_image_ctx);
   int remove_migration(ImageCtxT *image_ctx);
+  int relink_children(ImageCtxT *from_image_ctx, ImageCtxT *to_image_ctx);
   int remove_src_image();
 
   int v1_set_migration();
@@ -90,6 +92,11 @@ private:
   int v2_unlink_src_image();
   int v1_relink_src_image();
   int v2_relink_src_image();
+
+  int relink_child(ImageCtxT *from_image_ctx, ImageCtxT *to_image_ctx,
+                   const librbd::snap_info_t &src_snap,
+                   const librbd::linked_image_spec_t &child_image,
+                   bool migration_abort, bool reattach_child);
 };
 
 } // namespace api
index 9c95e9f7c4dc59967f0a916c05ac6f514233d070..bedeeca01563720c010b60234b4f83f53b93a5d8 100644 (file)
@@ -82,7 +82,7 @@ void AttachChildRequest<I>::v1_refresh() {
 
   using klass = AttachChildRequest<I>;
   RefreshRequest<I> *req = RefreshRequest<I>::create(
-      *m_image_ctx, false, false,
+      *m_parent_image_ctx, false, false,
       create_context_callback<klass, &klass::handle_v1_refresh>(this));
   req->send();
 }
@@ -93,10 +93,9 @@ void AttachChildRequest<I>::handle_v1_refresh(int r) {
 
   bool snap_protected = false;
   if (r == 0) {
-    m_parent_image_ctx->snap_lock.get_read();
+    RWLock::RLocker snap_locker(m_parent_image_ctx->snap_lock);
     r = m_parent_image_ctx->is_snap_protected(m_parent_snap_id,
                                               &snap_protected);
-    m_parent_image_ctx->snap_lock.put_read();
   }
 
   if (r < 0 || !snap_protected) {
index 78f3388b16f04096946213ddba808cb7a742dfbe..f9ae5c3c274b7db3f73db41ee9dab2ad3f153399 100644 (file)
 #include "librbd/api/Migration.h"
 #include "librbd/api/Mirror.h"
 #include "librbd/api/Namespace.h"
+#include "librbd/image/AttachChildRequest.h"
+#include "librbd/image/AttachParentRequest.h"
 #include "librbd/internal.h"
 #include "librbd/io/ImageRequestWQ.h"
 #include "librbd/io/ReadResult.h"
 
+#include <boost/scope_exit.hpp>
+
 void register_test_migration() {
 }
 
@@ -147,11 +151,17 @@ struct TestMigration : public TestFixture {
   }
 
   void open_image(librados::IoCtx& io_ctx, const std::string &name,
+                  const std::string &id, bool read_only, int flags,
                   librbd::ImageCtx **ictx) {
-    *ictx = new librbd::ImageCtx(name.c_str(), "", nullptr, io_ctx, false);
+    *ictx = new librbd::ImageCtx(name, id, nullptr, io_ctx, read_only);
     m_ictxs.insert(*ictx);
 
-    ASSERT_EQ(0, (*ictx)->state->open(0));
+    ASSERT_EQ(0, (*ictx)->state->open(flags));
+  }
+
+  void open_image(librados::IoCtx& io_ctx, const std::string &name,
+                  librbd::ImageCtx **ictx) {
+    open_image(io_ctx, name, "", false, 0, ictx);
   }
 
   void migration_prepare(librados::IoCtx& dst_io_ctx,
@@ -374,6 +384,52 @@ struct TestMigration : public TestFixture {
     flush();
   }
 
+  template <typename L>
+  void test_migrate_parent(uint32_t clone_format, L&& test) {
+    REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+    std::string prev_clone_format;
+    ASSERT_EQ(0, _rados.conf_get("rbd_default_clone_format",
+                                 prev_clone_format));
+    ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format",
+                                 stringify(clone_format).c_str()));
+    BOOST_SCOPE_EXIT_TPL(&prev_clone_format) {
+      _rados.conf_set("rbd_default_clone_format", prev_clone_format.c_str());
+    } BOOST_SCOPE_EXIT_END;
+
+    write(0, 10, 'A');
+    snap_create("snap1");
+    snap_protect("snap1");
+
+    int order = m_ictx->order;
+    uint64_t features;
+    ASSERT_EQ(0, librbd::get_features(m_ictx, &features));
+    features &= ~RBD_FEATURES_IMPLICIT_ENABLE;
+
+    std::string clone_name = get_temp_image_name();
+    ASSERT_EQ(0, librbd::clone(m_ictx->md_ctx, m_ictx->name.c_str(), "snap1",
+                               m_ioctx, clone_name.c_str(), features, &order,
+                               m_ictx->stripe_unit, m_ictx->stripe_count));
+
+    librbd::ImageCtx *child_ictx;
+    open_image(m_ioctx, clone_name, &child_ictx);
+
+    test(child_ictx);
+
+    ASSERT_EQ(0, child_ictx->state->refresh());
+
+    bufferlist bl;
+    bufferptr ptr(10);
+    bl.push_back(ptr);
+    librbd::io::ReadResult result{&bl};
+    ASSERT_EQ(10, child_ictx->io_work_queue->read(
+                0, 10, librbd::io::ReadResult{result}, 0));
+    bufferlist ref_bl;
+    ref_bl.append(std::string(10, 'A'));
+    ASSERT_TRUE(ref_bl.contents_equal(bl));
+    close_image(child_ictx);
+  }
+
   void test_stress(const std::string &snap_name_prefix = "snap",
                    char start_char = 'A') {
     uint64_t initial_size = m_ictx->size;
@@ -1045,6 +1101,200 @@ TEST_F(TestMigration, SnapTrimBeforePrepare)
   migration_commit(m_ioctx, m_image_name);
 }
 
+TEST_F(TestMigration, CloneV1Parent)
+{
+  const uint32_t CLONE_FORMAT = 1;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *) {
+           migrate(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV2Parent)
+{
+  const uint32_t CLONE_FORMAT = 2;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *) {
+           migrate(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV1ParentAbort)
+{
+  const uint32_t CLONE_FORMAT = 1;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *) {
+           migration_prepare(m_ioctx, m_image_name);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV2ParentAbort)
+{
+  const uint32_t CLONE_FORMAT = 2;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *) {
+           migration_prepare(m_ioctx, m_image_name);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV1ParentAbortFixIncompleteChildReattach)
+{
+  const uint32_t CLONE_FORMAT = 1;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           migration_prepare(m_ioctx, m_image_name);
+           // Attach the child to both source and destination
+           // to emulate a crash when re-attaching the child
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond;
+           auto req = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], nullptr, 0,
+               CLONE_FORMAT, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV1ParentAbortFixParentReattach)
+{
+  const uint32_t CLONE_FORMAT = 1;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           migration_prepare(m_ioctx, m_image_name);
+           // Re-attach the child back to the source to emulate a crash
+           // after the parent reattach but before the child reattach
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond;
+           auto req = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], m_ictx,
+               m_ictx->snaps[0], CLONE_FORMAT, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV1ParentAbortRelinkNotNeeded)
+{
+  const uint32_t CLONE_FORMAT = 1;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           auto parent_spec = child_ictx->parent_md.spec;
+           parent_spec.image_id = m_ictx->id;
+           parent_spec.snap_id = m_ictx->snaps[0];
+           auto parent_overlap = child_ictx->parent_md.overlap;
+           migration_prepare(m_ioctx, m_image_name);
+           // Relink the child back to emulate a crash
+           // before relinking the child
+           C_SaferCond cond;
+           auto req = librbd::image::AttachParentRequest<>::create(
+               *child_ictx, parent_spec, parent_overlap, true, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond1;
+           auto req1 = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], m_ictx,
+               m_ictx->snaps[0], CLONE_FORMAT, &cond1);
+           req1->send();
+           ASSERT_EQ(0, cond1.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV2ParentAbortFixIncompleteChildReattach)
+{
+  const uint32_t CLONE_FORMAT = 2;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           migration_prepare(m_ioctx, m_image_name);
+           // Attach the child to both source and destination
+           // to emulate a crash when re-attaching the child
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond;
+           auto req = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], nullptr, 0,
+               CLONE_FORMAT, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV2ParentAbortFixParentReattach)
+{
+  const uint32_t CLONE_FORMAT = 2;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           migration_prepare(m_ioctx, m_image_name);
+           // Re-attach the child back to the source to emulate a crash
+           // after the parent reattach but before the child reattach
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond;
+           auto req = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], m_ictx,
+               m_ictx->snaps[0], CLONE_FORMAT, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
+TEST_F(TestMigration, CloneV2ParentAbortRelinkNotNeeded)
+{
+  const uint32_t CLONE_FORMAT = 2;
+  test_migrate_parent(
+      CLONE_FORMAT, [this](librbd::ImageCtx *child_ictx) {
+           auto src_image_id = m_ictx->id;
+           auto parent_spec = child_ictx->parent_md.spec;
+           parent_spec.image_id = m_ictx->id;
+           parent_spec.snap_id = m_ictx->snaps[0];
+           auto parent_overlap = child_ictx->parent_md.overlap;
+           migration_prepare(m_ioctx, m_image_name);
+           // Relink the child back to emulate a crash
+           // before relinking the child
+           C_SaferCond cond;
+           auto req = librbd::image::AttachParentRequest<>::create(
+               *child_ictx, parent_spec, parent_overlap, true, &cond);
+           req->send();
+           ASSERT_EQ(0, cond.wait());
+           librbd::ImageCtx *src_ictx;
+           open_image(m_ioctx, "", src_image_id, false,
+                      librbd::OPEN_FLAG_IGNORE_MIGRATING, &src_ictx);
+           C_SaferCond cond1;
+           auto req1 = librbd::image::AttachChildRequest<>::create(
+               child_ictx, src_ictx, src_ictx->snaps[0], m_ictx,
+               m_ictx->snaps[0], CLONE_FORMAT, &cond1);
+           req1->send();
+           ASSERT_EQ(0, cond1.wait());
+           close_image(src_ictx);
+           migration_abort(m_ioctx, m_image_name);
+         });
+}
+
 TEST_F(TestMigration, StressNoMigrate)
 {
   test_stress();