From: Mykola Golub Date: Mon, 4 Feb 2019 12:09:02 +0000 (+0000) Subject: librbd: make it possible to migrate parent images X-Git-Tag: v14.1.0~56^2~8 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=2b2838d7466c97d32acbad569a9c1d449d4b76e0;p=ceph.git librbd: make it possible to migrate parent images Signed-off-by: Mykola Golub --- diff --git a/src/librbd/api/Migration.cc b/src/librbd/api/Migration.cc index cbae3906af0a2..21a71c50f50d0 100644 --- a/src/librbd/api/Migration.cc +++ b/src/librbd/api/Migration.cc @@ -19,8 +19,12 @@ #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" @@ -35,6 +39,15 @@ #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 int Migration::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::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::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::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::commit() { } r = remove_src_image(); - if (r < 0) { return r; } @@ -957,73 +972,88 @@ int Migration::set_state(cls::rbd::MigrationState state, } template -int Migration::list_snaps(std::vector *snapsptr) { +int Migration::list_src_snaps(std::vector *snaps) { ldout(m_cct, 10) << dendl; - std::vector 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::get_namespace_type(m_src_image_ctx, snap.id, &namespace_type); + r = Snapshot::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 child_images; - r = api::Image::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 +int Migration::validate_src_snaps() { + ldout(m_cct, 10) << dendl; + + std::vector 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 child_images; + r = api::Image::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 int Migration::set_migration() { ldout(m_cct, 10) << dendl; @@ -1280,6 +1310,23 @@ int Migration::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::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 +int Migration::relink_children(I *from_image_ctx, I *to_image_ctx) { + ldout(m_cct, 10) << dendl; + + std::vector 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 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::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 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::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 +int Migration::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::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::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 int Migration::remove_src_image() { ldout(m_cct, 10) << dendl; std::vector 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; } } diff --git a/src/librbd/api/Migration.h b/src/librbd/api/Migration.h index 88c1c959c9ce3..c746b1b997cca 100644 --- a/src/librbd/api/Migration.h +++ b/src/librbd/api/Migration.h @@ -71,7 +71,8 @@ private: int set_state(cls::rbd::MigrationState state, const std::string &description); - int list_snaps(std::vector *snaps = nullptr); + int list_src_snaps(std::vector *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 diff --git a/src/librbd/image/AttachChildRequest.cc b/src/librbd/image/AttachChildRequest.cc index 9c95e9f7c4dc5..bedeeca015637 100644 --- a/src/librbd/image/AttachChildRequest.cc +++ b/src/librbd/image/AttachChildRequest.cc @@ -82,7 +82,7 @@ void AttachChildRequest::v1_refresh() { using klass = AttachChildRequest; RefreshRequest *req = RefreshRequest::create( - *m_image_ctx, false, false, + *m_parent_image_ctx, false, false, create_context_callback(this)); req->send(); } @@ -93,10 +93,9 @@ void AttachChildRequest::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) { diff --git a/src/test/librbd/test_Migration.cc b/src/test/librbd/test_Migration.cc index 78f3388b16f04..f9ae5c3c274b7 100644 --- a/src/test/librbd/test_Migration.cc +++ b/src/test/librbd/test_Migration.cc @@ -11,10 +11,14 @@ #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 + 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 + 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();