#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;
int Migration<I>::prepare() {
ldout(m_cct, 10) << dendl;
- int r = list_snaps();
+ int r = validate_src_snaps();
if (r < 0) {
return r;
}
return r;
}
- r = set_state(cls::rbd::MIGRATION_STATE_PREPARED, "");
- if (r < 0) {
- return r;
- }
-
ldout(m_cct, 10) << "succeeded" << dendl;
return 0;
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) {
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;
}
r = remove_src_image();
-
if (r < 0) {
return r;
}
}
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;
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;
}
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;
}
}
#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() {
}
}
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,
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;
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();