summaryrefslogtreecommitdiffstats
path: root/src/librbd/api/Migration.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/librbd/api/Migration.cc
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librbd/api/Migration.cc')
-rw-r--r--src/librbd/api/Migration.cc2126
1 files changed, 2126 insertions, 0 deletions
diff --git a/src/librbd/api/Migration.cc b/src/librbd/api/Migration.cc
new file mode 100644
index 000000000..957c872ac
--- /dev/null
+++ b/src/librbd/api/Migration.cc
@@ -0,0 +1,2126 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/api/Migration.h"
+#include "include/rados/librados.hpp"
+#include "include/stringify.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "common/Cond.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/AsioEngine.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/Utils.h"
+#include "librbd/api/Config.h"
+#include "librbd/api/Group.h"
+#include "librbd/api/Image.h"
+#include "librbd/api/Snapshot.h"
+#include "librbd/api/Trash.h"
+#include "librbd/deep_copy/Handler.h"
+#include "librbd/deep_copy/ImageCopyRequest.h"
+#include "librbd/deep_copy/MetadataCopyRequest.h"
+#include "librbd/deep_copy/SnapshotCopyRequest.h"
+#include "librbd/exclusive_lock/Policy.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/image/Types.h"
+#include "librbd/internal.h"
+#include "librbd/migration/FormatInterface.h"
+#include "librbd/migration/OpenSourceImageRequest.h"
+#include "librbd/migration/NativeFormat.h"
+#include "librbd/mirror/DisableRequest.h"
+#include "librbd/mirror/EnableRequest.h"
+
+#include <boost/scope_exit.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#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;
+
+namespace {
+
+class MigrationProgressContext : public ProgressContext {
+public:
+ MigrationProgressContext(librados::IoCtx& io_ctx,
+ const std::string &header_oid,
+ cls::rbd::MigrationState state,
+ ProgressContext *prog_ctx)
+ : m_io_ctx(io_ctx), m_header_oid(header_oid), m_state(state),
+ m_prog_ctx(prog_ctx), m_cct(reinterpret_cast<CephContext*>(io_ctx.cct())),
+ m_lock(ceph::make_mutex(
+ util::unique_lock_name("librbd::api::MigrationProgressContext",
+ this))) {
+ ceph_assert(m_prog_ctx != nullptr);
+ }
+
+ ~MigrationProgressContext() {
+ wait_for_in_flight_updates();
+ }
+
+ int update_progress(uint64_t offset, uint64_t total) override {
+ ldout(m_cct, 20) << "offset=" << offset << ", total=" << total << dendl;
+
+ m_prog_ctx->update_progress(offset, total);
+
+ std::string description = stringify(offset * 100 / total) + "% complete";
+
+ send_state_description_update(description);
+
+ return 0;
+ }
+
+private:
+ librados::IoCtx& m_io_ctx;
+ std::string m_header_oid;
+ cls::rbd::MigrationState m_state;
+ ProgressContext *m_prog_ctx;
+
+ CephContext* m_cct;
+ mutable ceph::mutex m_lock;
+ ceph::condition_variable m_cond;
+ std::string m_state_description;
+ bool m_pending_update = false;
+ int m_in_flight_state_updates = 0;
+
+ void send_state_description_update(const std::string &description) {
+ std::lock_guard locker{m_lock};
+
+ if (description == m_state_description) {
+ return;
+ }
+
+ m_state_description = description;
+
+ if (m_in_flight_state_updates > 0) {
+ m_pending_update = true;
+ return;
+ }
+
+ set_state_description();
+ }
+
+ void set_state_description() {
+ ldout(m_cct, 20) << "state_description=" << m_state_description << dendl;
+
+ ceph_assert(ceph_mutex_is_locked(m_lock));
+
+ librados::ObjectWriteOperation op;
+ cls_client::migration_set_state(&op, m_state, m_state_description);
+
+ using klass = MigrationProgressContext;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_set_state_description>(this);
+ int r = m_io_ctx.aio_operate(m_header_oid, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+
+ m_in_flight_state_updates++;
+ }
+
+ void handle_set_state_description(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ std::lock_guard locker{m_lock};
+
+ m_in_flight_state_updates--;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to update migration state: " << cpp_strerror(r)
+ << dendl;
+ } else if (m_pending_update) {
+ set_state_description();
+ m_pending_update = false;
+ } else {
+ m_cond.notify_all();
+ }
+ }
+
+ void wait_for_in_flight_updates() {
+ std::unique_lock locker{m_lock};
+
+ ldout(m_cct, 20) << "m_in_flight_state_updates="
+ << m_in_flight_state_updates << dendl;
+ m_pending_update = false;
+ m_cond.wait(locker, [this] { return m_in_flight_state_updates <= 0; });
+ }
+};
+
+int trash_search(librados::IoCtx &io_ctx, rbd_trash_image_source_t source,
+ const std::string &image_name, std::string *image_id) {
+ std::vector<trash_image_info_t> entries;
+
+ int r = Trash<>::list(io_ctx, entries, false);
+ if (r < 0) {
+ return r;
+ }
+
+ for (auto &entry : entries) {
+ if (entry.source == source && entry.name == image_name) {
+ *image_id = entry.id;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+template <typename I>
+int open_images(librados::IoCtx& io_ctx, const std::string &image_name,
+ I **src_image_ctx, I **dst_image_ctx,
+ cls::rbd::MigrationSpec* src_migration_spec,
+ cls::rbd::MigrationSpec* dst_migration_spec,
+ bool skip_open_dst_image) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ *src_image_ctx = nullptr;
+ *dst_image_ctx = nullptr;
+
+ ldout(cct, 10) << "trying to open image by name " << io_ctx.get_pool_name()
+ << "/" << image_name << dendl;
+ auto image_ctx = I::create(image_name, "", nullptr, io_ctx, false);
+ int r = image_ctx->state->open(OPEN_FLAG_IGNORE_MIGRATING);
+ if (r == -ENOENT) {
+ // presume user passed the source image so we need to search the trash
+ ldout(cct, 10) << "Source image is not found. Trying trash" << dendl;
+
+ std::string src_image_id;
+ r = trash_search(io_ctx, RBD_TRASH_IMAGE_SOURCE_MIGRATION, image_name,
+ &src_image_id);
+ if (r < 0) {
+ lderr(cct) << "failed to determine image id: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ ldout(cct, 10) << "source image id from trash: " << src_image_id << dendl;
+ image_ctx = I::create(image_name, src_image_id, nullptr, io_ctx, false);
+ r = image_ctx->state->open(OPEN_FLAG_IGNORE_MIGRATING);
+ }
+
+ if (r < 0) {
+ if (r != -ENOENT) {
+ lderr(cct) << "failed to open image: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ image_ctx = nullptr;
+ }
+
+ BOOST_SCOPE_EXIT_TPL(&r, &image_ctx, src_image_ctx, dst_image_ctx) {
+ if (r != 0) {
+ if (*src_image_ctx != nullptr) {
+ (*src_image_ctx)->state->close();
+ }
+ if (*dst_image_ctx != nullptr) {
+ (*dst_image_ctx)->state->close();
+ }
+ if (image_ctx != nullptr) {
+ image_ctx->state->close();
+ }
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ // The opened image is either a source or destination
+ cls::rbd::MigrationSpec migration_spec;
+ r = cls_client::migration_get(&image_ctx->md_ctx, image_ctx->header_oid,
+ &migration_spec);
+ if (r < 0) {
+ lderr(cct) << "failed retrieving migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ ldout(cct, 10) << "migration spec: " << migration_spec << dendl;
+ if (migration_spec.header_type == cls::rbd::MIGRATION_HEADER_TYPE_SRC) {
+ ldout(cct, 10) << "the source image is opened" << dendl;
+ *src_image_ctx = image_ctx;
+ *src_migration_spec = migration_spec;
+ image_ctx = nullptr;
+ } else if (migration_spec.header_type ==
+ cls::rbd::MIGRATION_HEADER_TYPE_DST) {
+ ldout(cct, 10) << "the destination image is opened" << dendl;
+ std::string image_id = image_ctx->id;
+ image_ctx->state->close();
+ image_ctx = I::create(image_name, image_id, nullptr, io_ctx, false);
+
+ if (!skip_open_dst_image) {
+ ldout(cct, 10) << "re-opening the destination image" << dendl;
+ r = image_ctx->state->open(0);
+ if (r < 0) {
+ image_ctx = nullptr;
+ lderr(cct) << "failed to re-open destination image: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+ }
+
+ *dst_image_ctx = image_ctx;
+ *dst_migration_spec = migration_spec;
+ image_ctx = nullptr;
+ } else {
+ lderr(cct) << "unexpected migration header type: "
+ << migration_spec.header_type << dendl;
+ r = -EINVAL;
+ return r;
+ }
+
+ // attempt to open the other (paired) image
+ I** other_image_ctx = nullptr;
+ std::string other_image_type;
+ std::string other_image_name;
+ std::string other_image_id;
+ cls::rbd::MigrationSpec* other_migration_spec = nullptr;
+ librados::IoCtx other_io_ctx;
+
+ int flags = OPEN_FLAG_IGNORE_MIGRATING;
+ if (*src_image_ctx == nullptr &&
+ dst_migration_spec->source_spec.empty()) {
+ r = util::create_ioctx(io_ctx, "source image", migration_spec.pool_id,
+ migration_spec.pool_namespace, &other_io_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ other_image_type = "source";
+ other_image_ctx = src_image_ctx;
+ other_migration_spec = src_migration_spec;
+ other_image_name = migration_spec.image_name;
+ other_image_id = migration_spec.image_id;
+
+ if (other_image_id.empty()) {
+ ldout(cct, 20) << "trying to open v1 image by name "
+ << other_io_ctx.get_pool_name() << "/"
+ << other_image_name << dendl;
+ flags |= OPEN_FLAG_OLD_FORMAT;
+ } else {
+ ldout(cct, 20) << "trying to open v2 image by id "
+ << other_io_ctx.get_pool_name() << "/"
+ << other_image_id << dendl;
+ }
+
+ *src_image_ctx = I::create(other_image_name, other_image_id, nullptr,
+ other_io_ctx, false);
+ } else if (*dst_image_ctx == nullptr) {
+ r = util::create_ioctx(io_ctx, "destination image", migration_spec.pool_id,
+ migration_spec.pool_namespace, &other_io_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ other_image_name = migration_spec.image_name;
+ if (skip_open_dst_image) {
+ other_image_id = migration_spec.image_id;
+ } else {
+ other_image_type = "destination";
+ other_image_ctx = dst_image_ctx;
+ other_migration_spec = dst_migration_spec;
+ other_image_id = migration_spec.image_id;
+ }
+
+ *dst_image_ctx = I::create(other_image_name, other_image_id, nullptr,
+ other_io_ctx, false);
+ }
+
+ if (other_image_ctx != nullptr) {
+ r = (*other_image_ctx)->state->open(flags);
+ if (r < 0) {
+ lderr(cct) << "failed to open " << other_image_type << " image "
+ << other_io_ctx.get_pool_name()
+ << "/" << (other_image_id.empty() ?
+ other_image_name : other_image_id)
+ << ": " << cpp_strerror(r) << dendl;
+ *other_image_ctx = nullptr;
+ return r;
+ }
+
+ r = cls_client::migration_get(&(*other_image_ctx)->md_ctx,
+ (*other_image_ctx)->header_oid,
+ other_migration_spec);
+ if (r < 0) {
+ lderr(cct) << "failed retrieving migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ ldout(cct, 20) << other_image_type << " migration spec: "
+ << *other_migration_spec << dendl;
+ }
+
+ if (!skip_open_dst_image) {
+ // legacy clients will only store status in the source images
+ if (dst_migration_spec->source_spec.empty()) {
+ dst_migration_spec->state = migration_spec.state;
+ dst_migration_spec->state_description =
+ migration_spec.state_description;
+ }
+ }
+
+ return 0;
+}
+
+class SteppedProgressContext : public ProgressContext {
+public:
+ SteppedProgressContext(ProgressContext* progress_ctx, size_t total_steps)
+ : m_progress_ctx(progress_ctx), m_total_steps(total_steps) {
+ }
+
+ void next_step() {
+ ceph_assert(m_current_step < m_total_steps);
+ ++m_current_step;
+ }
+
+ int update_progress(uint64_t object_number,
+ uint64_t object_count) override {
+ return m_progress_ctx->update_progress(
+ object_number + (object_count * (m_current_step - 1)),
+ object_count * m_total_steps);
+ }
+
+private:
+ ProgressContext* m_progress_ctx;
+ size_t m_total_steps;
+ size_t m_current_step = 1;
+};
+
+} // anonymous namespace
+
+template <typename I>
+int Migration<I>::prepare(librados::IoCtx& io_ctx,
+ const std::string &image_name,
+ librados::IoCtx& dest_io_ctx,
+ const std::string &dest_image_name_,
+ ImageOptions& opts) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ std::string dest_image_name = dest_image_name_.empty() ? image_name :
+ dest_image_name_;
+
+ ldout(cct, 10) << io_ctx.get_pool_name() << "/" << image_name << " -> "
+ << dest_io_ctx.get_pool_name() << "/" << dest_image_name
+ << ", opts=" << opts << dendl;
+
+ auto src_image_ctx = I::create(image_name, "", nullptr, io_ctx, false);
+ int r = src_image_ctx->state->open(0);
+ if (r < 0) {
+ lderr(cct) << "failed to open image: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ BOOST_SCOPE_EXIT_TPL(src_image_ctx) {
+ src_image_ctx->state->close();
+ } BOOST_SCOPE_EXIT_END;
+
+ std::list<obj_watch_t> watchers;
+ int flags = librbd::image::LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
+ librbd::image::LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES;
+ C_SaferCond on_list_watchers;
+ auto list_watchers_request = librbd::image::ListWatchersRequest<I>::create(
+ *src_image_ctx, flags, &watchers, &on_list_watchers);
+ list_watchers_request->send();
+ r = on_list_watchers.wait();
+ if (r < 0) {
+ lderr(cct) << "failed listing watchers:" << cpp_strerror(r) << dendl;
+ return r;
+ }
+ if (!watchers.empty()) {
+ lderr(cct) << "image has watchers - not migrating" << dendl;
+ return -EBUSY;
+ }
+
+ uint64_t format = 2;
+ if (opts.get(RBD_IMAGE_OPTION_FORMAT, &format) != 0) {
+ opts.set(RBD_IMAGE_OPTION_FORMAT, format);
+ }
+ if (format != 2) {
+ lderr(cct) << "unsupported destination image format: " << format << dendl;
+ return -EINVAL;
+ }
+
+ uint64_t features;
+ {
+ std::shared_lock image_locker{src_image_ctx->image_lock};
+ features = src_image_ctx->features;
+ }
+ opts.get(RBD_IMAGE_OPTION_FEATURES, &features);
+ if ((features & ~RBD_FEATURES_ALL) != 0) {
+ lderr(cct) << "librbd does not support requested features" << dendl;
+ return -ENOSYS;
+ }
+ opts.set(RBD_IMAGE_OPTION_FEATURES, features);
+
+ uint64_t order = src_image_ctx->order;
+ if (opts.get(RBD_IMAGE_OPTION_ORDER, &order) != 0) {
+ opts.set(RBD_IMAGE_OPTION_ORDER, order);
+ }
+ r = image::CreateRequest<I>::validate_order(cct, order);
+ if (r < 0) {
+ return r;
+ }
+
+ uint64_t stripe_unit = src_image_ctx->stripe_unit;
+ if (opts.get(RBD_IMAGE_OPTION_STRIPE_UNIT, &stripe_unit) != 0) {
+ opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit);
+ }
+ uint64_t stripe_count = src_image_ctx->stripe_count;
+ if (opts.get(RBD_IMAGE_OPTION_STRIPE_COUNT, &stripe_count) != 0) {
+ opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count);
+ }
+
+ uint64_t flatten = 0;
+ if (opts.get(RBD_IMAGE_OPTION_FLATTEN, &flatten) == 0) {
+ opts.unset(RBD_IMAGE_OPTION_FLATTEN);
+ }
+
+ ldout(cct, 20) << "updated opts=" << opts << dendl;
+
+ auto dst_image_ctx = I::create(
+ dest_image_name, util::generate_image_id(dest_io_ctx), nullptr,
+ dest_io_ctx, false);
+ src_image_ctx->image_lock.lock_shared();
+ cls::rbd::MigrationSpec dst_migration_spec{
+ cls::rbd::MIGRATION_HEADER_TYPE_DST,
+ src_image_ctx->md_ctx.get_id(), src_image_ctx->md_ctx.get_namespace(),
+ src_image_ctx->name, src_image_ctx->id, "", {}, 0, false,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, flatten > 0,
+ cls::rbd::MIGRATION_STATE_PREPARING, ""};
+ src_image_ctx->image_lock.unlock_shared();
+
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, nullptr);
+ r = migration.prepare();
+
+ return r;
+}
+
+template <typename I>
+int Migration<I>::prepare_import(
+ const std::string& source_spec, librados::IoCtx& dest_io_ctx,
+ const std::string &dest_image_name, ImageOptions& opts) {
+ if (source_spec.empty() || !dest_io_ctx.is_valid() ||
+ dest_image_name.empty()) {
+ return -EINVAL;
+ }
+
+ auto cct = reinterpret_cast<CephContext *>(dest_io_ctx.cct());
+ ldout(cct, 10) << source_spec << " -> "
+ << dest_io_ctx.get_pool_name() << "/"
+ << dest_image_name << ", opts=" << opts << dendl;
+
+ I* src_image_ctx = nullptr;
+ C_SaferCond open_ctx;
+ auto req = migration::OpenSourceImageRequest<I>::create(
+ dest_io_ctx, nullptr, CEPH_NOSNAP,
+ {-1, "", "", "", source_spec, {}, 0, false}, &src_image_ctx, &open_ctx);
+ req->send();
+
+ int r = open_ctx.wait();
+ if (r < 0) {
+ lderr(cct) << "failed to open source image: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ auto asio_engine = src_image_ctx->asio_engine;
+ BOOST_SCOPE_EXIT_TPL(src_image_ctx) {
+ src_image_ctx->state->close();
+ } BOOST_SCOPE_EXIT_END;
+
+ uint64_t image_format = 2;
+ if (opts.get(RBD_IMAGE_OPTION_FORMAT, &image_format) != 0) {
+ opts.set(RBD_IMAGE_OPTION_FORMAT, image_format);
+ }
+ if (image_format != 2) {
+ lderr(cct) << "unsupported destination image format: " << image_format
+ << dendl;
+ return -EINVAL;
+ }
+
+ ldout(cct, 20) << "updated opts=" << opts << dendl;
+
+ // use json-spirit to clean-up json formatting
+ json_spirit::mObject source_spec_object;
+ json_spirit::mValue json_root;
+ if(json_spirit::read(source_spec, json_root)) {
+ try {
+ source_spec_object = json_root.get_obj();
+ } catch (std::runtime_error&) {
+ lderr(cct) << "failed to clean source spec" << dendl;
+ return -EINVAL;
+ }
+ }
+
+ auto dst_image_ctx = I::create(
+ dest_image_name, util::generate_image_id(dest_io_ctx), nullptr,
+ dest_io_ctx, false);
+ cls::rbd::MigrationSpec dst_migration_spec{
+ cls::rbd::MIGRATION_HEADER_TYPE_DST, -1, "", "", "",
+ json_spirit::write(source_spec_object), {},
+ 0, false, cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, true,
+ cls::rbd::MIGRATION_STATE_PREPARING, ""};
+
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, nullptr);
+ return migration.prepare_import();
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::execute(librados::IoCtx& io_ctx,
+ const std::string &image_name,
+ ProgressContext &prog_ctx) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ ldout(cct, 10) << io_ctx.get_pool_name() << "/" << image_name << dendl;
+
+ I *src_image_ctx;
+ I *dst_image_ctx;
+ cls::rbd::MigrationSpec src_migration_spec;
+ cls::rbd::MigrationSpec dst_migration_spec;
+ int r = open_images(io_ctx, image_name, &src_image_ctx, &dst_image_ctx,
+ &src_migration_spec, &dst_migration_spec, false);
+ if (r < 0) {
+ return r;
+ }
+
+ // ensure the destination loads the migration info
+ dst_image_ctx->ignore_migrating = false;
+ r = dst_image_ctx->state->refresh();
+ if (r < 0) {
+ lderr(cct) << "failed to refresh destination image: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ BOOST_SCOPE_EXIT_TPL(src_image_ctx, dst_image_ctx) {
+ dst_image_ctx->state->close();
+ if (src_image_ctx != nullptr) {
+ src_image_ctx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ if (dst_migration_spec.state != cls::rbd::MIGRATION_STATE_PREPARED &&
+ dst_migration_spec.state != cls::rbd::MIGRATION_STATE_EXECUTING) {
+ lderr(cct) << "current migration state is '" << dst_migration_spec.state
+ << "' (should be 'prepared')" << dendl;
+ return -EINVAL;
+ }
+
+ ldout(cct, 5) << "migrating ";
+ if (!dst_migration_spec.source_spec.empty()) {
+ *_dout << dst_migration_spec.source_spec;
+ } else {
+ *_dout << src_image_ctx->md_ctx.get_pool_name() << "/"
+ << src_image_ctx->name;
+ }
+ *_dout << " -> " << dst_image_ctx->md_ctx.get_pool_name() << "/"
+ << dst_image_ctx->name << dendl;
+
+ ImageOptions opts;
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, &prog_ctx);
+ r = migration.execute();
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::abort(librados::IoCtx& io_ctx, const std::string &image_name,
+ ProgressContext &prog_ctx) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ ldout(cct, 10) << io_ctx.get_pool_name() << "/" << image_name << dendl;
+
+ I *src_image_ctx;
+ I *dst_image_ctx;
+ cls::rbd::MigrationSpec src_migration_spec;
+ cls::rbd::MigrationSpec dst_migration_spec;
+ int r = open_images(io_ctx, image_name, &src_image_ctx, &dst_image_ctx,
+ &src_migration_spec, &dst_migration_spec, true);
+ if (r < 0) {
+ return r;
+ }
+
+ ldout(cct, 5) << "canceling incomplete migration ";
+ if (!dst_migration_spec.source_spec.empty()) {
+ *_dout << dst_migration_spec.source_spec;
+ } else {
+ *_dout << src_image_ctx->md_ctx.get_pool_name() << "/"
+ << src_image_ctx->name;
+ }
+ *_dout << " -> " << dst_image_ctx->md_ctx.get_pool_name() << "/"
+ << dst_image_ctx->name << dendl;
+
+ ImageOptions opts;
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, &prog_ctx);
+ r = migration.abort();
+
+ if (src_image_ctx != nullptr) {
+ src_image_ctx->state->close();
+ }
+
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::commit(librados::IoCtx& io_ctx,
+ const std::string &image_name,
+ ProgressContext &prog_ctx) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ ldout(cct, 10) << io_ctx.get_pool_name() << "/" << image_name << dendl;
+
+ I *src_image_ctx;
+ I *dst_image_ctx;
+ cls::rbd::MigrationSpec src_migration_spec;
+ cls::rbd::MigrationSpec dst_migration_spec;
+ int r = open_images(io_ctx, image_name, &src_image_ctx, &dst_image_ctx,
+ &src_migration_spec, &dst_migration_spec, false);
+ if (r < 0) {
+ return r;
+ }
+
+ if (dst_migration_spec.state != cls::rbd::MIGRATION_STATE_EXECUTED) {
+ lderr(cct) << "current migration state is '" << dst_migration_spec.state
+ << "' (should be 'executed')" << dendl;
+ dst_image_ctx->state->close();
+ if (src_image_ctx != nullptr) {
+ src_image_ctx->state->close();
+ }
+ return -EINVAL;
+ }
+
+ // ensure the destination loads the migration info
+ dst_image_ctx->ignore_migrating = false;
+ r = dst_image_ctx->state->refresh();
+ if (r < 0) {
+ lderr(cct) << "failed to refresh destination image: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ ldout(cct, 5) << "migrating ";
+ if (!dst_migration_spec.source_spec.empty()) {
+ *_dout << dst_migration_spec.source_spec;
+ } else {
+ *_dout << src_image_ctx->md_ctx.get_pool_name() << "/"
+ << src_image_ctx->name;
+ }
+ *_dout << " -> " << dst_image_ctx->md_ctx.get_pool_name() << "/"
+ << dst_image_ctx->name << dendl;
+
+ ImageOptions opts;
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, &prog_ctx);
+ r = migration.commit();
+
+ // image_ctx is closed in commit when removing src image
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::status(librados::IoCtx& io_ctx,
+ const std::string &image_name,
+ image_migration_status_t *status) {
+ CephContext* cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+
+ ldout(cct, 10) << io_ctx.get_pool_name() << "/" << image_name << dendl;
+
+ I *src_image_ctx;
+ I *dst_image_ctx;
+ cls::rbd::MigrationSpec src_migration_spec;
+ cls::rbd::MigrationSpec dst_migration_spec;
+ int r = open_images(io_ctx, image_name, &src_image_ctx, &dst_image_ctx,
+ &src_migration_spec, &dst_migration_spec, false);
+ if (r < 0) {
+ return r;
+ }
+
+ ldout(cct, 5) << "migrating ";
+ if (!dst_migration_spec.source_spec.empty()) {
+ *_dout << dst_migration_spec.source_spec;
+ } else {
+ *_dout << src_image_ctx->md_ctx.get_pool_name() << "/"
+ << src_image_ctx->name;
+ }
+ *_dout << " -> " << dst_image_ctx->md_ctx.get_pool_name() << "/"
+ << dst_image_ctx->name << dendl;
+
+ ImageOptions opts;
+ Migration migration(src_image_ctx, dst_image_ctx, dst_migration_spec,
+ opts, nullptr);
+ r = migration.status(status);
+
+ dst_image_ctx->state->close();
+ if (src_image_ctx != nullptr) {
+ src_image_ctx->state->close();
+ }
+
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::get_source_spec(I* image_ctx, std::string* source_spec) {
+ auto cct = image_ctx->cct;
+ ldout(cct, 10) << dendl;
+
+ image_ctx->image_lock.lock_shared();
+ auto migration_info = image_ctx->migration_info;
+ image_ctx->image_lock.unlock_shared();
+
+ if (migration_info.empty()) {
+ // attempt to directly read the spec in case the state is EXECUTED
+ cls::rbd::MigrationSpec migration_spec;
+ int r = cls_client::migration_get(&image_ctx->md_ctx, image_ctx->header_oid,
+ &migration_spec);
+ if (r == -ENOENT) {
+ return r;
+ } else if (r < 0) {
+ lderr(cct) << "failed retrieving migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ migration_info = {
+ migration_spec.pool_id, migration_spec.pool_namespace,
+ migration_spec.image_name, migration_spec.image_id,
+ migration_spec.source_spec, {}, 0, false};
+ }
+
+ if (!migration_info.source_spec.empty()) {
+ *source_spec = migration_info.source_spec;
+ } else {
+ // legacy migration source
+ *source_spec = migration::NativeFormat<I>::build_source_spec(
+ migration_info.pool_id,
+ migration_info.pool_namespace,
+ migration_info.image_name,
+ migration_info.image_id);
+ }
+
+ return 0;
+}
+
+template <typename I>
+Migration<I>::Migration(ImageCtx* src_image_ctx,
+ ImageCtx* dst_image_ctx,
+ const cls::rbd::MigrationSpec& dst_migration_spec,
+ ImageOptions& opts, ProgressContext *prog_ctx)
+ : m_cct(dst_image_ctx->cct),
+ m_src_image_ctx(src_image_ctx), m_dst_image_ctx(dst_image_ctx),
+ m_dst_io_ctx(dst_image_ctx->md_ctx), m_dst_image_name(dst_image_ctx->name),
+ m_dst_image_id(dst_image_ctx->id),
+ m_dst_header_oid(util::header_name(m_dst_image_id)),
+ m_image_options(opts), m_flatten(dst_migration_spec.flatten),
+ m_mirroring(dst_migration_spec.mirroring),
+ m_mirror_image_mode(dst_migration_spec.mirror_image_mode),
+ m_prog_ctx(prog_ctx),
+ m_src_migration_spec(cls::rbd::MIGRATION_HEADER_TYPE_SRC,
+ m_dst_io_ctx.get_id(), m_dst_io_ctx.get_namespace(),
+ m_dst_image_name, m_dst_image_id, "", {}, 0,
+ m_mirroring, m_mirror_image_mode, m_flatten,
+ dst_migration_spec.state,
+ dst_migration_spec.state_description),
+ m_dst_migration_spec(dst_migration_spec) {
+ m_dst_io_ctx.dup(dst_image_ctx->md_ctx);
+}
+
+template <typename I>
+int Migration<I>::prepare() {
+ ldout(m_cct, 10) << dendl;
+
+ BOOST_SCOPE_EXIT_TPL(&m_dst_image_ctx) {
+ if (m_dst_image_ctx != nullptr) {
+ m_dst_image_ctx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ int r = validate_src_snaps(m_src_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = disable_mirroring(m_src_image_ctx, &m_mirroring, &m_mirror_image_mode);
+ if (r < 0) {
+ return r;
+ }
+
+ r = unlink_src_image(m_src_image_ctx);
+ if (r < 0) {
+ enable_mirroring(m_src_image_ctx, m_mirroring, m_mirror_image_mode);
+ return r;
+ }
+
+ r = set_src_migration(m_src_image_ctx);
+ if (r < 0) {
+ relink_src_image(m_src_image_ctx);
+ enable_mirroring(m_src_image_ctx, m_mirroring, m_mirror_image_mode);
+ return r;
+ }
+
+ r = create_dst_image(&m_dst_image_ctx);
+ if (r < 0) {
+ abort();
+ return r;
+ }
+
+ ldout(m_cct, 10) << "succeeded" << dendl;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::prepare_import() {
+ ldout(m_cct, 10) << dendl;
+
+ BOOST_SCOPE_EXIT_TPL(&m_dst_image_ctx) {
+ if (m_dst_image_ctx != nullptr) {
+ m_dst_image_ctx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ int r = create_dst_image(&m_dst_image_ctx);
+ if (r < 0) {
+ abort();
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::execute() {
+ ldout(m_cct, 10) << dendl;
+
+ int r = set_state(cls::rbd::MIGRATION_STATE_EXECUTING, "");
+ if (r < 0) {
+ return r;
+ }
+
+ {
+ MigrationProgressContext dst_prog_ctx(
+ m_dst_image_ctx->md_ctx, m_dst_image_ctx->header_oid,
+ cls::rbd::MIGRATION_STATE_EXECUTING, m_prog_ctx);
+ std::optional<MigrationProgressContext> src_prog_ctx;
+ if (m_src_image_ctx != nullptr) {
+ src_prog_ctx.emplace(m_src_image_ctx->md_ctx, m_src_image_ctx->header_oid,
+ cls::rbd::MIGRATION_STATE_EXECUTING, &dst_prog_ctx);
+ }
+
+ while (true) {
+ r = m_dst_image_ctx->operations->migrate(
+ *(src_prog_ctx ? &src_prog_ctx.value() : &dst_prog_ctx));
+ if (r == -EROFS) {
+ std::shared_lock owner_locker{m_dst_image_ctx->owner_lock};
+ if (m_dst_image_ctx->exclusive_lock != nullptr &&
+ !m_dst_image_ctx->exclusive_lock->accept_ops()) {
+ ldout(m_cct, 5) << "lost exclusive lock, retrying remote" << dendl;
+ continue;
+ }
+ }
+ break;
+ }
+ }
+
+ if (r < 0) {
+ lderr(m_cct) << "migration failed: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ r = set_state(cls::rbd::MIGRATION_STATE_EXECUTED, "");
+ if (r < 0) {
+ return r;
+ }
+
+ m_dst_image_ctx->notify_update();
+
+ ldout(m_cct, 10) << "succeeded" << dendl;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::abort() {
+ ldout(m_cct, 10) << dendl;
+
+ int r;
+ if (m_src_image_ctx != nullptr) {
+ m_src_image_ctx->owner_lock.lock_shared();
+ if (m_src_image_ctx->exclusive_lock != nullptr &&
+ !m_src_image_ctx->exclusive_lock->is_lock_owner()) {
+ C_SaferCond ctx;
+ m_src_image_ctx->exclusive_lock->acquire_lock(&ctx);
+ m_src_image_ctx->owner_lock.unlock_shared();
+ r = ctx.wait();
+ if (r < 0) {
+ lderr(m_cct) << "error acquiring exclusive lock: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+ } else {
+ m_src_image_ctx->owner_lock.unlock_shared();
+ }
+ }
+
+ group_info_t group_info;
+ group_info.pool = -1;
+
+ r = m_dst_image_ctx->state->open(OPEN_FLAG_IGNORE_MIGRATING);
+ if (r < 0) {
+ ldout(m_cct, 1) << "failed to open destination image: " << cpp_strerror(r)
+ << dendl;
+ m_dst_image_ctx = nullptr;
+ } else {
+ BOOST_SCOPE_EXIT_TPL(&m_dst_image_ctx) {
+ if (m_dst_image_ctx != nullptr) {
+ m_dst_image_ctx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ std::list<obj_watch_t> watchers;
+ int flags = librbd::image::LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
+ librbd::image::LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES;
+ C_SaferCond on_list_watchers;
+ auto list_watchers_request = librbd::image::ListWatchersRequest<I>::create(
+ *m_dst_image_ctx, flags, &watchers, &on_list_watchers);
+ list_watchers_request->send();
+ r = on_list_watchers.wait();
+ if (r < 0) {
+ lderr(m_cct) << "failed listing watchers:" << cpp_strerror(r) << dendl;
+ return r;
+ }
+ if (!watchers.empty()) {
+ lderr(m_cct) << "image has watchers - cannot abort migration" << dendl;
+ return -EBUSY;
+ }
+
+ // ensure destination image is now read-only
+ r = set_state(cls::rbd::MIGRATION_STATE_ABORTING, "");
+ if (r < 0) {
+ return r;
+ }
+
+ SteppedProgressContext progress_ctx(
+ m_prog_ctx, (m_src_image_ctx != nullptr ? 2 : 1));
+ if (m_src_image_ctx != nullptr) {
+ // copy dst HEAD -> src HEAD
+ revert_data(m_dst_image_ctx, m_src_image_ctx, &progress_ctx);
+ progress_ctx.next_step();
+
+ ldout(m_cct, 10) << "relinking children" << dendl;
+ r = relink_children(m_dst_image_ctx, m_src_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+ }
+
+ ldout(m_cct, 10) << "removing dst image snapshots" << dendl;
+ std::vector<librbd::snap_info_t> snaps;
+ r = Snapshot<I>::list(m_dst_image_ctx, snaps);
+ if (r < 0) {
+ lderr(m_cct) << "failed listing snapshots: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ for (auto &snap : snaps) {
+ librbd::NoOpProgressContext prog_ctx;
+ int r = Snapshot<I>::remove(m_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;
+ return r;
+ }
+ }
+
+ ldout(m_cct, 10) << "removing group" << dendl;
+
+ r = remove_group(m_dst_image_ctx, &group_info);
+ if (r < 0 && r != -ENOENT) {
+ return r;
+ }
+
+ ldout(m_cct, 10) << "removing dst image" << dendl;
+
+ ceph_assert(m_dst_image_ctx->ignore_migrating);
+
+ auto asio_engine = m_dst_image_ctx->asio_engine;
+ librados::IoCtx dst_io_ctx(m_dst_image_ctx->md_ctx);
+
+ C_SaferCond on_remove;
+ auto req = librbd::image::RemoveRequest<>::create(
+ dst_io_ctx, m_dst_image_ctx, false, false, progress_ctx,
+ asio_engine->get_work_queue(), &on_remove);
+ req->send();
+ r = on_remove.wait();
+
+ m_dst_image_ctx = nullptr;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed removing destination image '"
+ << dst_io_ctx.get_pool_name() << "/" << m_dst_image_name
+ << " (" << m_dst_image_id << ")': " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+ }
+
+ if (m_src_image_ctx != nullptr) {
+ r = relink_src_image(m_src_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = add_group(m_src_image_ctx, group_info);
+ if (r < 0) {
+ return r;
+ }
+
+ r = remove_migration(m_src_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = enable_mirroring(m_src_image_ctx, m_mirroring, m_mirror_image_mode);
+ if (r < 0) {
+ return r;
+ }
+ }
+
+ ldout(m_cct, 10) << "succeeded" << dendl;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::commit() {
+ ldout(m_cct, 10) << dendl;
+
+ BOOST_SCOPE_EXIT_TPL(&m_dst_image_ctx, &m_src_image_ctx) {
+ m_dst_image_ctx->state->close();
+ if (m_src_image_ctx != nullptr) {
+ m_src_image_ctx->state->close();
+ }
+ } BOOST_SCOPE_EXIT_END;
+
+ int r = remove_migration(m_dst_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ if (m_src_image_ctx != nullptr) {
+ r = remove_src_image(&m_src_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+ }
+
+ r = enable_mirroring(m_dst_image_ctx, m_mirroring, m_mirror_image_mode);
+ if (r < 0) {
+ return r;
+ }
+
+ ldout(m_cct, 10) << "succeeded" << dendl;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::status(image_migration_status_t *status) {
+ ldout(m_cct, 10) << dendl;
+
+ status->source_pool_id = m_dst_migration_spec.pool_id;
+ status->source_pool_namespace = m_dst_migration_spec.pool_namespace;
+ status->source_image_name = m_dst_migration_spec.image_name;
+ status->source_image_id = m_dst_migration_spec.image_id;
+ status->dest_pool_id = m_src_migration_spec.pool_id;
+ status->dest_pool_namespace = m_src_migration_spec.pool_namespace;
+ status->dest_image_name = m_src_migration_spec.image_name;
+ status->dest_image_id = m_src_migration_spec.image_id;
+
+ switch (m_src_migration_spec.state) {
+ case cls::rbd::MIGRATION_STATE_ERROR:
+ status->state = RBD_IMAGE_MIGRATION_STATE_ERROR;
+ break;
+ case cls::rbd::MIGRATION_STATE_PREPARING:
+ status->state = RBD_IMAGE_MIGRATION_STATE_PREPARING;
+ break;
+ case cls::rbd::MIGRATION_STATE_PREPARED:
+ status->state = RBD_IMAGE_MIGRATION_STATE_PREPARED;
+ break;
+ case cls::rbd::MIGRATION_STATE_EXECUTING:
+ status->state = RBD_IMAGE_MIGRATION_STATE_EXECUTING;
+ break;
+ case cls::rbd::MIGRATION_STATE_EXECUTED:
+ status->state = RBD_IMAGE_MIGRATION_STATE_EXECUTED;
+ break;
+ default:
+ status->state = RBD_IMAGE_MIGRATION_STATE_UNKNOWN;
+ break;
+ }
+
+ status->state_description = m_src_migration_spec.state_description;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::set_state(I* image_ctx, const std::string& image_description,
+ cls::rbd::MigrationState state,
+ const std::string &description) {
+ int r = cls_client::migration_set_state(&image_ctx->md_ctx,
+ image_ctx->header_oid,
+ state, description);
+ if (r < 0) {
+ lderr(m_cct) << "failed to set " << image_description << " "
+ << "migration header: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::set_state(cls::rbd::MigrationState state,
+ const std::string &description) {
+ int r;
+ if (m_src_image_ctx != nullptr) {
+ r = set_state(m_src_image_ctx, "source", state, description);
+ if (r < 0) {
+ return r;
+ }
+ }
+
+ r = set_state(m_dst_image_ctx, "destination", state, description);
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::list_src_snaps(I* image_ctx,
+ std::vector<librbd::snap_info_t> *snaps) {
+ ldout(m_cct, 10) << dendl;
+
+ int r = Snapshot<I>::list(image_ctx, *snaps);
+ if (r < 0) {
+ lderr(m_cct) << "failed listing snapshots: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ for (auto &snap : *snaps) {
+ librbd::snap_namespace_type_t namespace_type;
+ r = Snapshot<I>::get_namespace_type(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_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;
+ } else {
+ lderr(m_cct) << "image has non-user type snapshots "
+ << "that are not supported by migration" << dendl;
+ }
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::validate_src_snaps(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ std::vector<librbd::snap_info_t> snaps;
+ int r = list_src_snaps(image_ctx, &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 (!image_ctx->test_features(RBD_FEATURE_LAYERING)) {
+ return 0;
+ }
+
+ for (auto &snap : snaps) {
+ std::shared_lock image_locker{image_ctx->image_lock};
+ cls::rbd::ParentImageSpec parent_spec{image_ctx->md_ctx.get_id(),
+ image_ctx->md_ctx.get_namespace(),
+ image_ctx->id, snap.id};
+ std::vector<librbd::linked_image_spec_t> child_images;
+ r = api::Image<I>::list_children(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) << 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_src_migration(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ image_ctx->ignore_migrating = true;
+
+ int r = cls_client::migration_set(&image_ctx->md_ctx, image_ctx->header_oid,
+ m_src_migration_spec);
+ if (r < 0) {
+ lderr(m_cct) << "failed to set source migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ image_ctx->notify_update();
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::remove_migration(I *image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ int r;
+
+ r = cls_client::migration_remove(&image_ctx->md_ctx, image_ctx->header_oid);
+ if (r == -ENOENT) {
+ r = 0;
+ }
+ if (r < 0) {
+ lderr(m_cct) << "failed removing migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ image_ctx->notify_update();
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::unlink_src_image(I* image_ctx) {
+ if (image_ctx->old_format) {
+ return v1_unlink_src_image(image_ctx);
+ } else {
+ return v2_unlink_src_image(image_ctx);
+ }
+}
+
+template <typename I>
+int Migration<I>::v1_unlink_src_image(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ std::shared_lock image_locker{image_ctx->image_lock};
+ int r = tmap_rm(image_ctx->md_ctx, image_ctx->name);
+ if (r < 0) {
+ lderr(m_cct) << "failed removing " << image_ctx->name << " from tmap: "
+ << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::v2_unlink_src_image(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ image_ctx->owner_lock.lock_shared();
+ if (image_ctx->exclusive_lock != nullptr &&
+ image_ctx->exclusive_lock->is_lock_owner()) {
+ C_SaferCond ctx;
+ image_ctx->exclusive_lock->release_lock(&ctx);
+ image_ctx->owner_lock.unlock_shared();
+ int r = ctx.wait();
+ if (r < 0) {
+ lderr(m_cct) << "error releasing exclusive lock: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+ } else {
+ image_ctx->owner_lock.unlock_shared();
+ }
+
+ int r = Trash<I>::move(image_ctx->md_ctx, RBD_TRASH_IMAGE_SOURCE_MIGRATION,
+ image_ctx->name, 0);
+ if (r < 0) {
+ lderr(m_cct) << "failed moving image to trash: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::relink_src_image(I* image_ctx) {
+ if (image_ctx->old_format) {
+ return v1_relink_src_image(image_ctx);
+ } else {
+ return v2_relink_src_image(image_ctx);
+ }
+}
+
+template <typename I>
+int Migration<I>::v1_relink_src_image(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ std::shared_lock image_locker{image_ctx->image_lock};
+ int r = tmap_set(image_ctx->md_ctx, image_ctx->name);
+ if (r < 0) {
+ lderr(m_cct) << "failed adding " << image_ctx->name << " to tmap: "
+ << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::v2_relink_src_image(I* image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ std::shared_lock image_locker{image_ctx->image_lock};
+ int r = Trash<I>::restore(image_ctx->md_ctx,
+ {cls::rbd::TRASH_IMAGE_SOURCE_MIGRATION},
+ image_ctx->id, image_ctx->name);
+ if (r < 0) {
+ lderr(m_cct) << "failed restoring image from trash: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::create_dst_image(I** image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ uint64_t size;
+ cls::rbd::ParentImageSpec parent_spec;
+ {
+ std::shared_lock image_locker{m_src_image_ctx->image_lock};
+ size = m_src_image_ctx->size;
+
+ // use oldest snapshot or HEAD for parent spec
+ if (!m_src_image_ctx->snap_info.empty()) {
+ parent_spec = m_src_image_ctx->snap_info.begin()->second.parent.spec;
+ } else {
+ parent_spec = m_src_image_ctx->parent_md.spec;
+ }
+ }
+
+ ConfigProxy config{m_cct->_conf};
+ api::Config<I>::apply_pool_overrides(m_dst_io_ctx, &config);
+
+ uint64_t mirror_image_mode;
+ if (m_image_options.get(RBD_IMAGE_OPTION_MIRROR_IMAGE_MODE,
+ &mirror_image_mode) == 0) {
+ m_mirroring = true;
+ m_mirror_image_mode = static_cast<cls::rbd::MirrorImageMode>(
+ mirror_image_mode);
+ m_image_options.unset(RBD_IMAGE_OPTION_MIRROR_IMAGE_MODE);
+ }
+
+ int r;
+ C_SaferCond on_create;
+ librados::IoCtx parent_io_ctx;
+ if (parent_spec.pool_id == -1) {
+ auto *req = image::CreateRequest<I>::create(
+ config, m_dst_io_ctx, m_dst_image_name, m_dst_image_id, size,
+ m_image_options, image::CREATE_FLAG_SKIP_MIRROR_ENABLE,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ m_src_image_ctx->op_work_queue, &on_create);
+ req->send();
+ } else {
+ r = util::create_ioctx(m_src_image_ctx->md_ctx, "parent image",
+ parent_spec.pool_id, parent_spec.pool_namespace,
+ &parent_io_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ auto *req = image::CloneRequest<I>::create(
+ config, parent_io_ctx, parent_spec.image_id, "", {}, parent_spec.snap_id,
+ m_dst_io_ctx, m_dst_image_name, m_dst_image_id, m_image_options,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "",
+ m_src_image_ctx->op_work_queue, &on_create);
+ req->send();
+ }
+
+ r = on_create.wait();
+ if (r < 0) {
+ lderr(m_cct) << "header creation failed: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ auto dst_image_ctx = *image_ctx;
+ dst_image_ctx->id = m_dst_image_id;
+ *image_ctx = nullptr; // prevent prepare from cleaning up the ImageCtx
+
+ r = dst_image_ctx->state->open(OPEN_FLAG_IGNORE_MIGRATING);
+ if (r < 0) {
+ lderr(m_cct) << "failed to open newly created header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ BOOST_SCOPE_EXIT_TPL(dst_image_ctx) {
+ dst_image_ctx->state->close();
+ } BOOST_SCOPE_EXIT_END;
+
+ {
+ std::shared_lock owner_locker{dst_image_ctx->owner_lock};
+ r = dst_image_ctx->operations->prepare_image_update(
+ exclusive_lock::OPERATION_REQUEST_TYPE_GENERAL, true);
+ if (r < 0) {
+ lderr(m_cct) << "cannot obtain exclusive lock" << dendl;
+ return r;
+ }
+ if (dst_image_ctx->exclusive_lock != nullptr) {
+ dst_image_ctx->exclusive_lock->block_requests(0);
+ }
+ }
+
+ SnapSeqs snap_seqs;
+
+ C_SaferCond on_snapshot_copy;
+ auto snapshot_copy_req = librbd::deep_copy::SnapshotCopyRequest<I>::create(
+ m_src_image_ctx, dst_image_ctx, 0, CEPH_NOSNAP, 0, m_flatten,
+ m_src_image_ctx->op_work_queue, &snap_seqs, &on_snapshot_copy);
+ snapshot_copy_req->send();
+ r = on_snapshot_copy.wait();
+ if (r < 0) {
+ lderr(m_cct) << "failed to copy snapshots: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ if (!m_src_image_ctx->header_oid.empty()) {
+ C_SaferCond on_metadata_copy;
+ auto metadata_copy_req = librbd::deep_copy::MetadataCopyRequest<I>::create(
+ m_src_image_ctx, dst_image_ctx, &on_metadata_copy);
+ metadata_copy_req->send();
+ r = on_metadata_copy.wait();
+ if (r < 0) {
+ lderr(m_cct) << "failed to copy metadata: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ }
+
+ m_dst_migration_spec.snap_seqs = snap_seqs;
+ m_dst_migration_spec.overlap = size;
+ m_dst_migration_spec.mirroring = m_mirroring;
+ m_dst_migration_spec.mirror_image_mode = m_mirror_image_mode;
+ m_dst_migration_spec.flatten = m_flatten;
+ r = cls_client::migration_set(&m_dst_io_ctx, m_dst_header_oid,
+ m_dst_migration_spec);
+ if (r < 0) {
+ lderr(m_cct) << "failed to set migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ if (m_dst_migration_spec.source_spec.empty()) {
+ r = update_group(m_src_image_ctx, dst_image_ctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = set_state(m_src_image_ctx, "source",
+ cls::rbd::MIGRATION_STATE_PREPARED, "");
+ if (r < 0) {
+ return r;
+ }
+ }
+
+ r = set_state(dst_image_ctx, "destination",
+ cls::rbd::MIGRATION_STATE_PREPARED, "");
+ if (r < 0) {
+ return r;
+ }
+
+ if (m_dst_migration_spec.source_spec.empty()) {
+ 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;
+}
+
+template <typename I>
+int Migration<I>::remove_group(I *image_ctx, group_info_t *group_info) {
+ int r = librbd::api::Group<I>::image_get_group(image_ctx, group_info);
+ if (r < 0) {
+ lderr(m_cct) << "failed to get image group: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ if (group_info->pool == -1) {
+ return -ENOENT;
+ }
+
+ ceph_assert(!image_ctx->id.empty());
+
+ ldout(m_cct, 10) << dendl;
+
+ IoCtx group_ioctx;
+ r = util::create_ioctx(image_ctx->md_ctx, "group", group_info->pool, {},
+ &group_ioctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = librbd::api::Group<I>::image_remove_by_id(group_ioctx,
+ group_info->name.c_str(),
+ image_ctx->md_ctx,
+ image_ctx->id.c_str());
+ if (r < 0) {
+ lderr(m_cct) << "failed to remove image from group: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::add_group(I *image_ctx, group_info_t &group_info) {
+ if (group_info.pool == -1) {
+ return 0;
+ }
+
+ ldout(m_cct, 10) << dendl;
+
+ IoCtx group_ioctx;
+ int r = util::create_ioctx(image_ctx->md_ctx, "group", group_info.pool, {},
+ &group_ioctx);
+ if (r < 0) {
+ return r;
+ }
+
+ r = librbd::api::Group<I>::image_add(group_ioctx, group_info.name.c_str(),
+ image_ctx->md_ctx,
+ image_ctx->name.c_str());
+ if (r < 0) {
+ lderr(m_cct) << "failed to add image to group: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::update_group(I *from_image_ctx, I *to_image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ group_info_t group_info;
+
+ int r = remove_group(from_image_ctx, &group_info);
+ if (r < 0) {
+ return r == -ENOENT ? 0 : r;
+ }
+
+ r = add_group(to_image_ctx, group_info);
+ if (r < 0) {
+ return r;
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::disable_mirroring(
+ I *image_ctx, bool *was_enabled,
+ cls::rbd::MirrorImageMode *mirror_image_mode) {
+ *was_enabled = false;
+
+ cls::rbd::MirrorImage mirror_image;
+ int r = cls_client::mirror_image_get(&image_ctx->md_ctx, image_ctx->id,
+ &mirror_image);
+ if (r == -ENOENT) {
+ ldout(m_cct, 10) << "mirroring is not enabled for this image" << dendl;
+ return 0;
+ }
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to retrieve mirror image: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ if (mirror_image.state == cls::rbd::MIRROR_IMAGE_STATE_ENABLED) {
+ *was_enabled = true;
+ *mirror_image_mode = mirror_image.mode;
+ }
+
+ ldout(m_cct, 10) << dendl;
+
+ C_SaferCond ctx;
+ auto req = mirror::DisableRequest<I>::create(image_ctx, false, true, &ctx);
+ req->send();
+ r = ctx.wait();
+ if (r < 0) {
+ lderr(m_cct) << "failed to disable mirroring: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ m_src_migration_spec.mirroring = true;
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::enable_mirroring(
+ I *image_ctx, bool was_enabled,
+ cls::rbd::MirrorImageMode mirror_image_mode) {
+ cls::rbd::MirrorMode mirror_mode;
+ int r = cls_client::mirror_mode_get(&image_ctx->md_ctx, &mirror_mode);
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to retrieve mirror mode: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ if (mirror_mode == cls::rbd::MIRROR_MODE_DISABLED) {
+ ldout(m_cct, 10) << "mirroring is not enabled for destination pool"
+ << dendl;
+ return 0;
+ }
+ if (mirror_mode == cls::rbd::MIRROR_MODE_IMAGE && !was_enabled) {
+ ldout(m_cct, 10) << "mirroring is not enabled for image" << dendl;
+ return 0;
+ }
+
+ ldout(m_cct, 10) << dendl;
+
+ C_SaferCond ctx;
+ auto req = mirror::EnableRequest<I>::create(
+ image_ctx, mirror_image_mode, "", false, &ctx);
+ req->send();
+ r = ctx.wait();
+ if (r < 0) {
+ lderr(m_cct) << "failed to enable mirroring: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ 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;
+
+ bool migration_abort = (to_image_ctx == m_src_image_ctx);
+
+ std::vector<librbd::snap_info_t> snaps;
+ int r = list_src_snaps(
+ migration_abort ? to_image_ctx : from_image_ctx, &snaps);
+ if (r < 0) {
+ return r;
+ }
+
+ 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.
+
+ std::shared_lock src_image_locker{to_image_ctx->image_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;
+ }
+
+ std::shared_lock image_locker{from_image_ctx->image_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;
+ {
+ std::shared_lock image_locker{from_image_ctx->image_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;
+ {
+ std::shared_lock image_locker{to_image_ctx->image_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;
+ {
+ std::shared_lock image_locker{child_image_ctx->image_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(I** image_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ auto src_image_ctx = *image_ctx;
+
+ std::vector<librbd::snap_info_t> snaps;
+ int r = list_src_snaps(src_image_ctx, &snaps);
+ if (r < 0) {
+ return r;
+ }
+
+ for (auto it = snaps.rbegin(); it != snaps.rend(); it++) {
+ auto &snap = *it;
+
+ librbd::NoOpProgressContext prog_ctx;
+ int r = Snapshot<I>::remove(src_image_ctx, snap.name.c_str(),
+ RBD_SNAP_REMOVE_UNPROTECT, prog_ctx);
+ if (r < 0) {
+ lderr(m_cct) << "failed removing source image snapshot '" << snap.name
+ << "': " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ }
+
+ ceph_assert(src_image_ctx->ignore_migrating);
+
+ auto asio_engine = src_image_ctx->asio_engine;
+ auto src_image_id = src_image_ctx->id;
+ librados::IoCtx src_io_ctx(src_image_ctx->md_ctx);
+
+ C_SaferCond on_remove;
+ auto req = librbd::image::RemoveRequest<I>::create(
+ src_io_ctx, src_image_ctx, false, true, *m_prog_ctx,
+ asio_engine->get_work_queue(), &on_remove);
+ req->send();
+ r = on_remove.wait();
+
+ *image_ctx = nullptr;
+
+ // For old format image it will return -ENOENT due to expected
+ // tmap_rm failure at the end.
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed removing source image: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ if (!src_image_id.empty()) {
+ r = cls_client::trash_remove(&src_io_ctx, src_image_id);
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing image " << src_image_id
+ << " from rbd_trash object" << dendl;
+ }
+ }
+
+ return 0;
+}
+
+template <typename I>
+int Migration<I>::revert_data(I* src_image_ctx, I* dst_image_ctx,
+ ProgressContext* prog_ctx) {
+ ldout(m_cct, 10) << dendl;
+
+ cls::rbd::MigrationSpec migration_spec;
+ int r = cls_client::migration_get(&src_image_ctx->md_ctx,
+ src_image_ctx->header_oid,
+ &migration_spec);
+
+ if (r < 0) {
+ lderr(m_cct) << "failed retrieving migration header: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ if (migration_spec.header_type != cls::rbd::MIGRATION_HEADER_TYPE_DST) {
+ lderr(m_cct) << "unexpected migration header type: "
+ << migration_spec.header_type << dendl;
+ return -EINVAL;
+ }
+
+ uint64_t src_snap_id_start = 0;
+ uint64_t src_snap_id_end = CEPH_NOSNAP;
+ uint64_t dst_snap_id_start = 0;
+ if (!migration_spec.snap_seqs.empty()) {
+ src_snap_id_start = migration_spec.snap_seqs.rbegin()->second;
+ }
+
+ // we only care about the HEAD revision so only add a single mapping to
+ // represent the most recent state
+ SnapSeqs snap_seqs;
+ snap_seqs[CEPH_NOSNAP] = CEPH_NOSNAP;
+
+ ldout(m_cct, 20) << "src_snap_id_start=" << src_snap_id_start << ", "
+ << "src_snap_id_end=" << src_snap_id_end << ", "
+ << "dst_snap_id_start=" << dst_snap_id_start << ", "
+ << "snap_seqs=" << snap_seqs << dendl;
+
+ C_SaferCond ctx;
+ deep_copy::ProgressHandler progress_handler(prog_ctx);
+ auto request = deep_copy::ImageCopyRequest<I>::create(
+ src_image_ctx, dst_image_ctx, src_snap_id_start, src_snap_id_end,
+ dst_snap_id_start, false, {}, snap_seqs, &progress_handler, &ctx);
+ request->send();
+
+ r = ctx.wait();
+ if (r < 0) {
+ lderr(m_cct) << "error reverting destination image data blocks back to "
+ << "source image: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+} // namespace api
+} // namespace librbd
+
+template class librbd::api::Migration<librbd::ImageCtx>;