summaryrefslogtreecommitdiffstats
path: root/src/librbd/image
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/librbd/image/AttachChildRequest.cc261
-rw-r--r--src/librbd/image/AttachChildRequest.h105
-rw-r--r--src/librbd/image/AttachParentRequest.cc90
-rw-r--r--src/librbd/image/AttachParentRequest.h79
-rw-r--r--src/librbd/image/CloneRequest.cc607
-rw-r--r--src/librbd/image/CloneRequest.h181
-rw-r--r--src/librbd/image/CloseRequest.cc350
-rw-r--r--src/librbd/image/CloseRequest.h127
-rw-r--r--src/librbd/image/CreateRequest.cc835
-rw-r--r--src/librbd/image/CreateRequest.h191
-rw-r--r--src/librbd/image/DetachChildRequest.cc392
-rw-r--r--src/librbd/image/DetachChildRequest.h119
-rw-r--r--src/librbd/image/DetachParentRequest.cc81
-rw-r--r--src/librbd/image/DetachParentRequest.h66
-rw-r--r--src/librbd/image/GetMetadataRequest.cc121
-rw-r--r--src/librbd/image/GetMetadataRequest.h83
-rw-r--r--src/librbd/image/ListWatchersRequest.cc174
-rw-r--r--src/librbd/image/ListWatchersRequest.h82
-rw-r--r--src/librbd/image/OpenRequest.cc727
-rw-r--r--src/librbd/image/OpenRequest.h149
-rw-r--r--src/librbd/image/PreRemoveRequest.cc348
-rw-r--r--src/librbd/image/PreRemoveRequest.h100
-rw-r--r--src/librbd/image/RefreshParentRequest.cc244
-rw-r--r--src/librbd/image/RefreshParentRequest.h109
-rw-r--r--src/librbd/image/RefreshRequest.cc1575
-rw-r--r--src/librbd/image/RefreshRequest.h275
-rw-r--r--src/librbd/image/RemoveRequest.cc617
-rw-r--r--src/librbd/image/RemoveRequest.h197
-rw-r--r--src/librbd/image/SetFlagsRequest.cc78
-rw-r--r--src/librbd/image/SetFlagsRequest.h61
-rw-r--r--src/librbd/image/SetSnapRequest.cc368
-rw-r--r--src/librbd/image/SetSnapRequest.h118
-rw-r--r--src/librbd/image/TypeTraits.h21
-rw-r--r--src/librbd/image/Types.h20
-rw-r--r--src/librbd/image/ValidatePoolRequest.cc234
-rw-r--r--src/librbd/image/ValidatePoolRequest.h93
-rw-r--r--src/librbd/image_watcher/NotifyLockOwner.cc96
-rw-r--r--src/librbd/image_watcher/NotifyLockOwner.h50
38 files changed, 9424 insertions, 0 deletions
diff --git a/src/librbd/image/AttachChildRequest.cc b/src/librbd/image/AttachChildRequest.cc
new file mode 100644
index 000000000..2f74191ed
--- /dev/null
+++ b/src/librbd/image/AttachChildRequest.cc
@@ -0,0 +1,261 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/AttachChildRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/image/RefreshRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::AttachChildRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+AttachChildRequest<I>::AttachChildRequest(
+ I *image_ctx, I *parent_image_ctx, const librados::snap_t &parent_snap_id,
+ I *old_parent_image_ctx, const librados::snap_t &old_parent_snap_id,
+ uint32_t clone_format, Context* on_finish)
+ : m_image_ctx(image_ctx), m_parent_image_ctx(parent_image_ctx),
+ m_parent_snap_id(parent_snap_id),
+ m_old_parent_image_ctx(old_parent_image_ctx),
+ m_old_parent_snap_id(old_parent_snap_id), m_clone_format(clone_format),
+ m_on_finish(on_finish), m_cct(m_image_ctx->cct) {
+}
+
+template <typename I>
+void AttachChildRequest<I>::send() {
+ if (m_clone_format == 1) {
+ v1_add_child();
+ } else {
+ v2_set_op_feature();
+ }
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_add_child() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::add_child(&op, {m_parent_image_ctx->md_ctx.get_id(), "",
+ m_parent_image_ctx->id,
+ m_parent_snap_id}, m_image_ctx->id);
+
+ using klass = AttachChildRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_v1_add_child>(this);
+ int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_add_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ if (r == -EEXIST && m_old_parent_image_ctx != nullptr) {
+ ldout(m_cct, 5) << "child already exists" << dendl;
+ } else {
+ lderr(m_cct) << "couldn't add child: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+ }
+
+ v1_refresh();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_refresh() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = AttachChildRequest<I>;
+ RefreshRequest<I> *req = RefreshRequest<I>::create(
+ *m_parent_image_ctx, false, false,
+ create_context_callback<klass, &klass::handle_v1_refresh>(this));
+ req->send();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_refresh(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ bool snap_protected = false;
+ if (r == 0) {
+ std::shared_lock image_locker{m_parent_image_ctx->image_lock};
+ r = m_parent_image_ctx->is_snap_protected(m_parent_snap_id,
+ &snap_protected);
+ }
+
+ if (r < 0 || !snap_protected) {
+ lderr(m_cct) << "validate protected failed" << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ v1_remove_child_from_old_parent();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v1_remove_child_from_old_parent() {
+ if (m_old_parent_image_ctx == nullptr) {
+ finish(0);
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::remove_child(&op, {m_old_parent_image_ctx->md_ctx.get_id(),
+ m_old_parent_image_ctx->md_ctx.get_namespace(),
+ m_old_parent_image_ctx->id,
+ m_old_parent_snap_id}, m_image_ctx->id);
+
+ using klass = AttachChildRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v1_remove_child_from_old_parent>(this);
+ int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v1_remove_child_from_old_parent(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "couldn't remove child: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_set_op_feature() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::op_features_set(&op, RBD_OPERATION_FEATURE_CLONE_CHILD,
+ RBD_OPERATION_FEATURE_CLONE_CHILD);
+
+ using klass = AttachChildRequest<I>;
+ auto aio_comp = create_rados_callback<
+ klass, &klass::handle_v2_set_op_feature>(this);
+ int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, aio_comp,
+ &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_set_op_feature(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to enable clone v2: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ v2_child_attach();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_child_attach() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::child_attach(&op, m_parent_snap_id,
+ {m_image_ctx->md_ctx.get_id(),
+ m_image_ctx->md_ctx.get_namespace(),
+ m_image_ctx->id});
+
+ using klass = AttachChildRequest<I>;
+ auto aio_comp = create_rados_callback<
+ klass, &klass::handle_v2_child_attach>(this);
+ int r = m_parent_image_ctx->md_ctx.aio_operate(m_parent_image_ctx->header_oid,
+ aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_child_attach(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ if (r == -EEXIST && m_old_parent_image_ctx != nullptr) {
+ ldout(m_cct, 5) << "child already exists" << dendl;
+ } else {
+ lderr(m_cct) << "failed to attach child image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+ }
+
+ v2_child_detach_from_old_parent();
+}
+
+template <typename I>
+void AttachChildRequest<I>::v2_child_detach_from_old_parent() {
+ if (m_old_parent_image_ctx == nullptr) {
+ finish(0);
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::child_detach(&op, m_old_parent_snap_id,
+ {m_image_ctx->md_ctx.get_id(),
+ m_image_ctx->md_ctx.get_namespace(),
+ m_image_ctx->id});
+
+ using klass = AttachChildRequest<I>;
+ auto aio_comp = create_rados_callback<
+ klass, &klass::handle_v2_child_detach_from_old_parent>(this);
+ int r = m_old_parent_image_ctx->md_ctx.aio_operate(
+ m_old_parent_image_ctx->header_oid, aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::handle_v2_child_detach_from_old_parent(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to detach child image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void AttachChildRequest<I>::finish(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::AttachChildRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/AttachChildRequest.h b/src/librbd/image/AttachChildRequest.h
new file mode 100644
index 000000000..a40afaf54
--- /dev/null
+++ b/src/librbd/image/AttachChildRequest.h
@@ -0,0 +1,105 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
+
+#include "include/common_fwd.h"
+#include "include/int_types.h"
+#include "include/rados/librados.hpp"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class AttachChildRequest {
+public:
+ static AttachChildRequest* create(ImageCtxT *image_ctx,
+ ImageCtxT *parent_image_ctx,
+ const librados::snap_t &parent_snap_id,
+ ImageCtxT *old_parent_image_ctx,
+ const librados::snap_t &old_parent_snap_id,
+ uint32_t clone_format,
+ Context* on_finish) {
+ return new AttachChildRequest(image_ctx, parent_image_ctx, parent_snap_id,
+ old_parent_image_ctx, old_parent_snap_id,
+ clone_format, on_finish);
+ }
+
+ AttachChildRequest(ImageCtxT *image_ctx,
+ ImageCtxT *parent_image_ctx,
+ const librados::snap_t &parent_snap_id,
+ ImageCtxT *old_parent_image_ctx,
+ const librados::snap_t &old_parent_snap_id,
+ uint32_t clone_format,
+ Context* on_finish);
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * (clone v1) | (clone v2)
+ * /----------------/ \---------------\
+ * | |
+ * v v
+ * V1 ADD CHILD V2 SET CLONE
+ * | |
+ * v v
+ * V1 VALIDATE PROTECTED V2 ATTACH CHILD
+ * | |
+ * | v
+ * V1 REMOVE CHILD FROM OLD PARENT V2 DETACH CHILD FROM OLD PARENT
+ * | |
+ * \----------------\ /---------------/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT *m_image_ctx;
+ ImageCtxT *m_parent_image_ctx;
+ librados::snap_t m_parent_snap_id;
+ ImageCtxT *m_old_parent_image_ctx;
+ librados::snap_t m_old_parent_snap_id;
+ uint32_t m_clone_format;
+ Context* m_on_finish;
+
+ CephContext *m_cct;
+
+ void v1_add_child();
+ void handle_v1_add_child(int r);
+
+ void v1_refresh();
+ void handle_v1_refresh(int r);
+
+ void v1_remove_child_from_old_parent();
+ void handle_v1_remove_child_from_old_parent(int r);
+
+ void v2_set_op_feature();
+ void handle_v2_set_op_feature(int r);
+
+ void v2_child_attach();
+ void handle_v2_child_attach(int r);
+
+ void v2_child_detach_from_old_parent();
+ void handle_v2_child_detach_from_old_parent(int r);
+
+ void finish(int r);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::AttachChildRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
diff --git a/src/librbd/image/AttachParentRequest.cc b/src/librbd/image/AttachParentRequest.cc
new file mode 100644
index 000000000..d0c35b6a9
--- /dev/null
+++ b/src/librbd/image/AttachParentRequest.cc
@@ -0,0 +1,90 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/AttachParentRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::AttachParentRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_rados_callback;
+
+template <typename I>
+void AttachParentRequest<I>::send() {
+ attach_parent();
+}
+
+template <typename I>
+void AttachParentRequest<I>::attach_parent() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "parent_image_spec=" << m_parent_image_spec << dendl;
+
+ librados::ObjectWriteOperation op;
+ if (!m_legacy_parent) {
+ librbd::cls_client::parent_attach(&op, m_parent_image_spec,
+ m_parent_overlap, m_reattach);
+ } else {
+ librbd::cls_client::set_parent(&op, m_parent_image_spec, m_parent_overlap);
+ }
+
+ auto aio_comp = create_rados_callback<
+ AttachParentRequest<I>,
+ &AttachParentRequest<I>::handle_attach_parent>(this);
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void AttachParentRequest<I>::handle_attach_parent(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ if (!m_legacy_parent && r == -EOPNOTSUPP && !m_reattach) {
+ if (m_parent_image_spec.pool_namespace ==
+ m_image_ctx.md_ctx.get_namespace()) {
+ m_parent_image_spec.pool_namespace = "";
+ }
+ if (m_parent_image_spec.pool_namespace.empty()) {
+ ldout(cct, 10) << "retrying using legacy parent method" << dendl;
+ m_legacy_parent = true;
+ attach_parent();
+ return;
+ }
+
+ // namespaces require newer OSDs
+ r = -EXDEV;
+ }
+
+ if (r < 0) {
+ lderr(cct) << "attach parent encountered an error: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void AttachParentRequest<I>::finish(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::AttachParentRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/AttachParentRequest.h b/src/librbd/image/AttachParentRequest.h
new file mode 100644
index 000000000..482e03273
--- /dev/null
+++ b/src/librbd/image/AttachParentRequest.h
@@ -0,0 +1,79 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_ATTACH_PARENT_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_ATTACH_PARENT_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "librbd/Types.h"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class AttachParentRequest {
+public:
+ static AttachParentRequest* create(ImageCtxT& image_ctx,
+ const cls::rbd::ParentImageSpec& pspec,
+ uint64_t parent_overlap,
+ bool reattach,
+ Context* on_finish) {
+ return new AttachParentRequest(image_ctx, pspec, parent_overlap, reattach,
+ on_finish);
+ }
+
+ AttachParentRequest(ImageCtxT& image_ctx,
+ const cls::rbd::ParentImageSpec& pspec,
+ uint64_t parent_overlap, bool reattach,
+ Context* on_finish)
+ : m_image_ctx(image_ctx), m_parent_image_spec(pspec),
+ m_parent_overlap(parent_overlap), m_reattach(reattach),
+ m_on_finish(on_finish) {
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * | * * * * * *
+ * | * * -EOPNOTSUPP
+ * v v *
+ * ATTACH_PARENT * * *
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT& m_image_ctx;
+ cls::rbd::ParentImageSpec m_parent_image_spec;
+ uint64_t m_parent_overlap;
+ bool m_reattach;
+ Context* m_on_finish;
+
+ bool m_legacy_parent = false;
+
+ void attach_parent();
+ void handle_attach_parent(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::AttachParentRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_ATTACH_PARENT_REQUEST_H
diff --git a/src/librbd/image/CloneRequest.cc b/src/librbd/image/CloneRequest.cc
new file mode 100644
index 000000000..7a955f064
--- /dev/null
+++ b/src/librbd/image/CloneRequest.cc
@@ -0,0 +1,607 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "cls/rbd/cls_rbd_client.h"
+#include "cls/rbd/cls_rbd_types.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "include/ceph_assert.h"
+#include "librbd/ImageState.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+#include "librbd/deep_copy/MetadataCopyRequest.h"
+#include "librbd/image/AttachChildRequest.h"
+#include "librbd/image/AttachParentRequest.h"
+#include "librbd/image/CloneRequest.h"
+#include "librbd/image/CreateRequest.h"
+#include "librbd/image/RemoveRequest.h"
+#include "librbd/image/Types.h"
+#include "librbd/mirror/EnableRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::CloneRequest: " << this << " " \
+ << __func__ << ": "
+
+#define MAX_KEYS 64
+
+namespace librbd {
+namespace image {
+
+using util::create_rados_callback;
+using util::create_context_callback;
+using util::create_async_context_callback;
+
+template <typename I>
+CloneRequest<I>::CloneRequest(
+ ConfigProxy& config,
+ IoCtx& parent_io_ctx,
+ const std::string& parent_image_id,
+ const std::string& parent_snap_name,
+ const cls::rbd::SnapshotNamespace& parent_snap_namespace,
+ uint64_t parent_snap_id,
+ IoCtx &c_ioctx,
+ const std::string &c_name,
+ const std::string &c_id,
+ ImageOptions c_options,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue, Context *on_finish)
+ : m_config(config), m_parent_io_ctx(parent_io_ctx),
+ m_parent_image_id(parent_image_id), m_parent_snap_name(parent_snap_name),
+ m_parent_snap_namespace(parent_snap_namespace),
+ m_parent_snap_id(parent_snap_id), m_ioctx(c_ioctx), m_name(c_name),
+ m_id(c_id), m_opts(c_options), m_mirror_image_mode(mirror_image_mode),
+ m_non_primary_global_image_id(non_primary_global_image_id),
+ m_primary_mirror_uuid(primary_mirror_uuid),
+ m_op_work_queue(op_work_queue), m_on_finish(on_finish),
+ m_use_p_features(true) {
+
+ m_cct = reinterpret_cast<CephContext *>(m_ioctx.cct());
+
+ bool default_format_set;
+ m_opts.is_set(RBD_IMAGE_OPTION_FORMAT, &default_format_set);
+ if (!default_format_set) {
+ m_opts.set(RBD_IMAGE_OPTION_FORMAT, static_cast<uint64_t>(2));
+ }
+
+ ldout(m_cct, 20) << "parent_pool_id=" << parent_io_ctx.get_id() << ", "
+ << "parent_image_id=" << parent_image_id << ", "
+ << "parent_snap=" << parent_snap_name << "/"
+ << parent_snap_id << " clone to "
+ << "pool_id=" << m_ioctx.get_id() << ", "
+ << "name=" << m_name << ", "
+ << "opts=" << m_opts << dendl;
+}
+
+template <typename I>
+void CloneRequest<I>::send() {
+ ldout(m_cct, 20) << dendl;
+ validate_options();
+}
+
+template <typename I>
+void CloneRequest<I>::validate_options() {
+ ldout(m_cct, 20) << dendl;
+
+ uint64_t format = 0;
+ m_opts.get(RBD_IMAGE_OPTION_FORMAT, &format);
+ if (format < 2) {
+ lderr(m_cct) << "format 2 or later required for clone" << dendl;
+ complete(-EINVAL);
+ return;
+ }
+
+ if (m_opts.get(RBD_IMAGE_OPTION_FEATURES, &m_features) == 0) {
+ if (m_features & ~RBD_FEATURES_ALL) {
+ lderr(m_cct) << "librbd does not support requested features" << dendl;
+ complete(-ENOSYS);
+ return;
+ }
+ m_use_p_features = false;
+ }
+
+ if (m_opts.get(RBD_IMAGE_OPTION_CLONE_FORMAT, &m_clone_format) < 0) {
+ std::string default_clone_format = m_config.get_val<std::string>(
+ "rbd_default_clone_format");
+ if (default_clone_format == "1") {
+ m_clone_format = 1;
+ } else if (default_clone_format == "auto") {
+ librados::Rados rados(m_ioctx);
+ int8_t min_compat_client;
+ int8_t require_min_compat_client;
+ int r = rados.get_min_compatible_client(&min_compat_client,
+ &require_min_compat_client);
+ if (r < 0) {
+ complete(r);
+ return;
+ }
+ if (std::max(min_compat_client, require_min_compat_client) <
+ CEPH_RELEASE_MIMIC) {
+ m_clone_format = 1;
+ }
+ }
+ }
+
+ if (m_clone_format == 1 &&
+ m_parent_io_ctx.get_namespace() != m_ioctx.get_namespace()) {
+ ldout(m_cct, 1) << "clone v2 required for cross-namespace clones" << dendl;
+ complete(-EXDEV);
+ return;
+ }
+
+ open_parent();
+}
+
+template <typename I>
+void CloneRequest<I>::open_parent() {
+ ldout(m_cct, 20) << dendl;
+ ceph_assert(m_parent_snap_name.empty() ^ (m_parent_snap_id == CEPH_NOSNAP));
+
+ if (m_parent_snap_id != CEPH_NOSNAP) {
+ m_parent_image_ctx = I::create("", m_parent_image_id, m_parent_snap_id,
+ m_parent_io_ctx, true);
+ } else {
+ m_parent_image_ctx = I::create("", m_parent_image_id,
+ m_parent_snap_name.c_str(),
+ m_parent_io_ctx,
+ true);
+ m_parent_image_ctx->snap_namespace = m_parent_snap_namespace;
+ }
+
+ Context *ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_open_parent>(this);
+ m_parent_image_ctx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT, ctx);
+}
+
+template <typename I>
+void CloneRequest<I>::handle_open_parent(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ m_parent_image_ctx = nullptr;
+
+ lderr(m_cct) << "failed to open parent image: " << cpp_strerror(r) << dendl;
+ complete(r);
+ return;
+ }
+
+ m_parent_snap_id = m_parent_image_ctx->snap_id;
+ m_pspec = {m_parent_io_ctx.get_id(), m_parent_io_ctx.get_namespace(),
+ m_parent_image_id, m_parent_snap_id};
+ validate_parent();
+}
+
+template <typename I>
+void CloneRequest<I>::validate_parent() {
+ ldout(m_cct, 20) << dendl;
+
+ if (m_parent_image_ctx->operations_disabled) {
+ lderr(m_cct) << "image operations disabled due to unsupported op features"
+ << dendl;
+ m_r_saved = -EROFS;
+ close_parent();
+ return;
+ }
+
+ if (m_parent_image_ctx->snap_id == CEPH_NOSNAP) {
+ lderr(m_cct) << "image to be cloned must be a snapshot" << dendl;
+ m_r_saved = -EINVAL;
+ close_parent();
+ return;
+ }
+
+ if (m_parent_image_ctx->old_format) {
+ lderr(m_cct) << "parent image must be in new format" << dendl;
+ m_r_saved = -EINVAL;
+ close_parent();
+ return;
+ }
+
+ m_parent_image_ctx->image_lock.lock_shared();
+ uint64_t p_features = m_parent_image_ctx->features;
+ m_size = m_parent_image_ctx->get_image_size(m_parent_image_ctx->snap_id);
+
+ bool snap_protected;
+ int r = m_parent_image_ctx->is_snap_protected(m_parent_image_ctx->snap_id, &snap_protected);
+ m_parent_image_ctx->image_lock.unlock_shared();
+
+ if ((p_features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) {
+ lderr(m_cct) << "parent image must support layering" << dendl;
+ m_r_saved = -ENOSYS;
+ close_parent();
+ return;
+ }
+ if (m_use_p_features) {
+ m_features = p_features;
+ }
+
+ if (r < 0) {
+ lderr(m_cct) << "unable to locate parent's snapshot" << dendl;
+ m_r_saved = r;
+ close_parent();
+ return;
+ }
+
+ if (m_clone_format == 1 && !snap_protected) {
+ lderr(m_cct) << "parent snapshot must be protected" << dendl;
+ m_r_saved = -EINVAL;
+ close_parent();
+ return;
+ }
+
+ validate_child();
+}
+
+template <typename I>
+void CloneRequest<I>::validate_child() {
+ ldout(m_cct, 15) << dendl;
+
+ if ((m_features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) {
+ lderr(m_cct) << "cloning image must support layering" << dendl;
+ m_r_saved = -ENOSYS;
+ close_parent();
+ return;
+ }
+
+ using klass = CloneRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_validate_child>(this);
+
+ librados::ObjectReadOperation op;
+ op.stat(NULL, NULL, NULL);
+
+ int r = m_ioctx.aio_operate(util::old_header_name(m_name), comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_validate_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r != -ENOENT) {
+ lderr(m_cct) << "rbd image " << m_name << " already exists" << dendl;
+ m_r_saved = r;
+ close_parent();
+ return;
+ }
+
+ create_child();
+}
+
+template <typename I>
+void CloneRequest<I>::create_child() {
+ ldout(m_cct, 15) << dendl;
+
+ uint64_t order = m_parent_image_ctx->order;
+ if (m_opts.get(RBD_IMAGE_OPTION_ORDER, &order) != 0) {
+ m_opts.set(RBD_IMAGE_OPTION_ORDER, order);
+ }
+ m_opts.set(RBD_IMAGE_OPTION_FEATURES, m_features);
+
+ uint64_t stripe_unit = m_parent_image_ctx->stripe_unit;
+ if (m_opts.get(RBD_IMAGE_OPTION_STRIPE_UNIT, &stripe_unit) != 0) {
+ m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit);
+ }
+
+ uint64_t stripe_count = m_parent_image_ctx->stripe_count;
+ if (m_opts.get(RBD_IMAGE_OPTION_STRIPE_COUNT, &stripe_count) != 0) {
+ m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count);
+ }
+
+ using klass = CloneRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_create_child>(this);
+
+ auto req = CreateRequest<I>::create(
+ m_config, m_ioctx, m_name, m_id, m_size, m_opts,
+ image::CREATE_FLAG_SKIP_MIRROR_ENABLE,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, m_non_primary_global_image_id,
+ m_primary_mirror_uuid, m_op_work_queue, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_create_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == -EBADF) {
+ ldout(m_cct, 5) << "image id already in-use" << dendl;
+ complete(r);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "error creating child: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ close_parent();
+ return;
+ }
+ open_child();
+}
+
+template <typename I>
+void CloneRequest<I>::open_child() {
+ ldout(m_cct, 15) << dendl;
+
+ m_imctx = I::create(m_name, "", nullptr, m_ioctx, false);
+
+ using klass = CloneRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_open_child>(this);
+
+ uint64_t flags = OPEN_FLAG_SKIP_OPEN_PARENT;
+ if ((m_features & RBD_FEATURE_MIGRATING) != 0) {
+ flags |= OPEN_FLAG_IGNORE_MIGRATING;
+ }
+
+ m_imctx->state->open(flags, ctx);
+}
+
+template <typename I>
+void CloneRequest<I>::handle_open_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ m_imctx = nullptr;
+
+ lderr(m_cct) << "Error opening new image: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ remove_child();
+ return;
+ }
+
+ attach_parent();
+}
+
+template <typename I>
+void CloneRequest<I>::attach_parent() {
+ ldout(m_cct, 15) << dendl;
+
+ auto ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_attach_parent>(this);
+ auto req = AttachParentRequest<I>::create(
+ *m_imctx, m_pspec, m_size, false, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_attach_parent(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to attach parent: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ close_child();
+ return;
+ }
+
+ attach_child();
+}
+
+template <typename I>
+void CloneRequest<I>::attach_child() {
+ ldout(m_cct, 15) << dendl;
+
+ auto ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_attach_child>(this);
+ auto req = AttachChildRequest<I>::create(
+ m_imctx, m_parent_image_ctx, m_parent_image_ctx->snap_id, nullptr, 0,
+ m_clone_format, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_attach_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to attach parent: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ close_child();
+ return;
+ }
+
+ copy_metadata();
+}
+
+template <typename I>
+void CloneRequest<I>::copy_metadata() {
+ ldout(m_cct, 15) << dendl;
+
+ auto ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_copy_metadata>(this);
+ auto req = deep_copy::MetadataCopyRequest<I>::create(
+ m_parent_image_ctx, m_imctx, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_copy_metadata(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to copy metadata: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ close_child();
+ return;
+ }
+
+ get_mirror_mode();
+}
+
+template <typename I>
+void CloneRequest<I>::get_mirror_mode() {
+ ldout(m_cct, 15) << dendl;
+
+ uint64_t mirror_image_mode;
+ if (!m_non_primary_global_image_id.empty()) {
+ enable_mirror();
+ return;
+ } else if (m_opts.get(RBD_IMAGE_OPTION_MIRROR_IMAGE_MODE,
+ &mirror_image_mode) == 0) {
+ m_mirror_image_mode = static_cast<cls::rbd::MirrorImageMode>(
+ mirror_image_mode);
+ enable_mirror();
+ return;
+ } else if (!m_imctx->test_features(RBD_FEATURE_JOURNALING)) {
+ close_child();
+ return;
+ }
+
+ librados::ObjectReadOperation op;
+ cls_client::mirror_mode_get_start(&op);
+
+ using klass = CloneRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_get_mirror_mode>(this);
+ m_out_bl.clear();
+ m_imctx->md_ctx.aio_operate(RBD_MIRRORING,
+ comp, &op, &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_get_mirror_mode(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == 0) {
+ auto it = m_out_bl.cbegin();
+ r = cls_client::mirror_mode_get_finish(&it, &m_mirror_mode);
+ }
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to retrieve mirror mode: " << cpp_strerror(r)
+ << dendl;
+
+ m_r_saved = r;
+ } else if (m_mirror_mode == cls::rbd::MIRROR_MODE_POOL) {
+ m_mirror_image_mode = cls::rbd::MIRROR_IMAGE_MODE_JOURNAL;
+ enable_mirror();
+ return;
+ }
+
+ close_child();
+}
+
+template <typename I>
+void CloneRequest<I>::enable_mirror() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CloneRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_enable_mirror>(this);
+ auto req = mirror::EnableRequest<I>::create(
+ m_imctx, m_mirror_image_mode, m_non_primary_global_image_id, true, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_enable_mirror(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to enable mirroring: " << cpp_strerror(r)
+ << dendl;
+ m_r_saved = r;
+ }
+ close_child();
+}
+
+template <typename I>
+void CloneRequest<I>::close_child() {
+ ldout(m_cct, 15) << dendl;
+
+ ceph_assert(m_imctx != nullptr);
+
+ auto ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_close_child>(this);
+ m_imctx->state->close(ctx);
+}
+
+template <typename I>
+void CloneRequest<I>::handle_close_child(int r) {
+ ldout(m_cct, 15) << dendl;
+
+ m_imctx = nullptr;
+
+ if (r < 0) {
+ lderr(m_cct) << "couldn't close image: " << cpp_strerror(r) << dendl;
+ if (m_r_saved == 0) {
+ m_r_saved = r;
+ }
+ }
+
+ if (m_r_saved < 0) {
+ remove_child();
+ return;
+ }
+
+ close_parent();
+}
+
+template <typename I>
+void CloneRequest<I>::remove_child() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CloneRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_remove_child>(this);
+
+ auto req = librbd::image::RemoveRequest<I>::create(
+ m_ioctx, m_name, m_id, false, false, m_no_op, m_op_work_queue, ctx);
+ req->send();
+}
+
+template <typename I>
+void CloneRequest<I>::handle_remove_child(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "Error removing failed clone: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ close_parent();
+}
+
+template <typename I>
+void CloneRequest<I>::close_parent() {
+ ldout(m_cct, 20) << dendl;
+ ceph_assert(m_parent_image_ctx != nullptr);
+
+ auto ctx = create_context_callback<
+ CloneRequest<I>, &CloneRequest<I>::handle_close_parent>(this);
+ m_parent_image_ctx->state->close(ctx);
+}
+
+template <typename I>
+void CloneRequest<I>::handle_close_parent(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ m_parent_image_ctx = nullptr;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to close parent image: "
+ << cpp_strerror(r) << dendl;
+ if (m_r_saved == 0) {
+ m_r_saved = r;
+ }
+ }
+
+ complete(m_r_saved);
+}
+
+template <typename I>
+void CloneRequest<I>::complete(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} //namespace image
+} //namespace librbd
+
+template class librbd::image::CloneRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/CloneRequest.h b/src/librbd/image/CloneRequest.h
new file mode 100644
index 000000000..35d9cab17
--- /dev/null
+++ b/src/librbd/image/CloneRequest.h
@@ -0,0 +1,181 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_CLONE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_CLONE_REQUEST_H
+
+#include "cls/rbd/cls_rbd_types.h"
+#include "common/config_fwd.h"
+#include "librbd/internal.h"
+#include "include/rbd/librbd.hpp"
+
+class Context;
+
+using librados::IoCtx;
+
+namespace librbd {
+
+namespace asio { struct ContextWQ; }
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class CloneRequest {
+public:
+ static CloneRequest *create(
+ ConfigProxy& config, IoCtx& parent_io_ctx,
+ const std::string& parent_image_id,
+ const std::string& parent_snap_name,
+ const cls::rbd::SnapshotNamespace& parent_snap_namespace,
+ uint64_t parent_snap_id,
+ IoCtx &c_ioctx, const std::string &c_name,
+ const std::string &c_id, ImageOptions c_options,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue, Context *on_finish) {
+ return new CloneRequest(config, parent_io_ctx, parent_image_id,
+ parent_snap_name, parent_snap_namespace,
+ parent_snap_id, c_ioctx, c_name, c_id, c_options,
+ mirror_image_mode, non_primary_global_image_id,
+ primary_mirror_uuid, op_work_queue, on_finish);
+ }
+
+ CloneRequest(ConfigProxy& config, IoCtx& parent_io_ctx,
+ const std::string& parent_image_id,
+ const std::string& parent_snap_name,
+ const cls::rbd::SnapshotNamespace& parent_snap_namespace,
+ uint64_t parent_snap_id,
+ IoCtx &c_ioctx, const std::string &c_name,
+ const std::string &c_id, ImageOptions c_options,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue, Context *on_finish);
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * OPEN PARENT
+ * |
+ * v
+ * VALIDATE CHILD <finish>
+ * | ^
+ * v |
+ * CREATE CHILD * * * * * * * * * > CLOSE PARENT
+ * | ^
+ * v |
+ * OPEN CHILD * * * * * * * * * * > REMOVE CHILD
+ * | ^
+ * v |
+ * ATTACH PARENT * * * * * * * * > CLOSE CHILD
+ * | ^
+ * v *
+ * ATTACH CHILD * * * * * * * * * * * *
+ * | *
+ * v *
+ * COPY META DATA * * * * * * * * * * ^
+ * | *
+ * v (skip if not needed) *
+ * GET MIRROR MODE * * * * * * * * * ^
+ * | *
+ * v (skip if not needed) *
+ * SET MIRROR ENABLED * * * * * * * * *
+ * |
+ * v
+ * CLOSE CHILD
+ * |
+ * v
+ * CLOSE PARENT
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ConfigProxy& m_config;
+ IoCtx &m_parent_io_ctx;
+ std::string m_parent_image_id;
+ std::string m_parent_snap_name;
+ cls::rbd::SnapshotNamespace m_parent_snap_namespace;
+ uint64_t m_parent_snap_id;
+ ImageCtxT *m_parent_image_ctx;
+
+ IoCtx &m_ioctx;
+ std::string m_name;
+ std::string m_id;
+ ImageOptions m_opts;
+ cls::rbd::ParentImageSpec m_pspec;
+ ImageCtxT *m_imctx;
+ cls::rbd::MirrorMode m_mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+ cls::rbd::MirrorImageMode m_mirror_image_mode;
+ const std::string m_non_primary_global_image_id;
+ const std::string m_primary_mirror_uuid;
+ NoOpProgressContext m_no_op;
+ asio::ContextWQ *m_op_work_queue;
+ Context *m_on_finish;
+
+ CephContext *m_cct;
+ uint64_t m_clone_format = 2;
+ bool m_use_p_features;
+ uint64_t m_features;
+ bufferlist m_out_bl;
+ uint64_t m_size;
+ int m_r_saved = 0;
+
+ void validate_options();
+
+ void open_parent();
+ void handle_open_parent(int r);
+
+ void validate_parent();
+
+ void validate_child();
+ void handle_validate_child(int r);
+
+ void create_child();
+ void handle_create_child(int r);
+
+ void open_child();
+ void handle_open_child(int r);
+
+ void attach_parent();
+ void handle_attach_parent(int r);
+
+ void attach_child();
+ void handle_attach_child(int r);
+
+ void copy_metadata();
+ void handle_copy_metadata(int r);
+
+ void get_mirror_mode();
+ void handle_get_mirror_mode(int r);
+
+ void enable_mirror();
+ void handle_enable_mirror(int r);
+
+ void close_child();
+ void handle_close_child(int r);
+
+ void remove_child();
+ void handle_remove_child(int r);
+
+ void close_parent();
+ void handle_close_parent(int r);
+
+ void complete(int r);
+};
+
+} //namespace image
+} //namespace librbd
+
+extern template class librbd::image::CloneRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_CLONE_REQUEST_H
diff --git a/src/librbd/image/CloseRequest.cc b/src/librbd/image/CloseRequest.cc
new file mode 100644
index 000000000..7293687f5
--- /dev/null
+++ b/src/librbd/image/CloseRequest.cc
@@ -0,0 +1,350 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/CloseRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ConfigWatcher.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatcher.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::CloseRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_async_context_callback;
+using util::create_context_callback;
+
+template <typename I>
+CloseRequest<I>::CloseRequest(I *image_ctx, Context *on_finish)
+ : m_image_ctx(image_ctx), m_on_finish(on_finish), m_error_result(0),
+ m_exclusive_lock(nullptr) {
+ ceph_assert(image_ctx != nullptr);
+}
+
+template <typename I>
+void CloseRequest<I>::send() {
+ if (m_image_ctx->config_watcher != nullptr) {
+ m_image_ctx->config_watcher->shut_down();
+
+ delete m_image_ctx->config_watcher;
+ m_image_ctx->config_watcher = nullptr;
+ }
+
+ send_block_image_watcher();
+}
+
+template <typename I>
+void CloseRequest<I>::send_block_image_watcher() {
+ if (m_image_ctx->image_watcher == nullptr) {
+ send_shut_down_update_watchers();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // prevent incoming requests from our peers
+ m_image_ctx->image_watcher->block_notifies(create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_block_image_watcher>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_block_image_watcher(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ send_shut_down_update_watchers();
+}
+
+template <typename I>
+void CloseRequest<I>::send_shut_down_update_watchers() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->state->shut_down_update_watchers(create_async_context_callback(
+ *m_image_ctx, create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_shut_down_update_watchers>(this)));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_shut_down_update_watchers(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "failed to shut down update watchers: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ send_flush();
+}
+
+template <typename I>
+void CloseRequest<I>::send_flush() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ std::shared_lock owner_locker{m_image_ctx->owner_lock};
+ auto ctx = create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_flush>(this);
+ auto aio_comp = io::AioCompletion::create_and_start(ctx, m_image_ctx,
+ io::AIO_TYPE_FLUSH);
+ auto req = io::ImageDispatchSpec::create_flush(
+ *m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
+ io::FLUSH_SOURCE_SHUTDOWN, {});
+ req->send();
+}
+
+template <typename I>
+void CloseRequest<I>::handle_flush(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "failed to flush IO: " << cpp_strerror(r) << dendl;
+ }
+
+ send_shut_down_exclusive_lock();
+}
+
+template <typename I>
+void CloseRequest<I>::send_shut_down_exclusive_lock() {
+ {
+ std::unique_lock owner_locker{m_image_ctx->owner_lock};
+ m_exclusive_lock = m_image_ctx->exclusive_lock;
+
+ // if reading a snapshot -- possible object map is open
+ std::unique_lock image_locker{m_image_ctx->image_lock};
+ if (m_exclusive_lock == nullptr && m_image_ctx->object_map) {
+ m_image_ctx->object_map->put();
+ m_image_ctx->object_map = nullptr;
+ }
+ }
+
+ if (m_exclusive_lock == nullptr) {
+ send_unregister_image_watcher();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // in-flight IO will be flushed and in-flight requests will be canceled
+ // before releasing lock
+ m_exclusive_lock->shut_down(create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_shut_down_exclusive_lock>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_shut_down_exclusive_lock(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ {
+ std::shared_lock owner_locker{m_image_ctx->owner_lock};
+ ceph_assert(m_image_ctx->exclusive_lock == nullptr);
+
+ // object map and journal closed during exclusive lock shutdown
+ std::shared_lock image_locker{m_image_ctx->image_lock};
+ ceph_assert(m_image_ctx->journal == nullptr);
+ ceph_assert(m_image_ctx->object_map == nullptr);
+ }
+
+ m_exclusive_lock->put();
+ m_exclusive_lock = nullptr;
+
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "failed to shut down exclusive lock: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ send_unregister_image_watcher();
+}
+
+template <typename I>
+void CloseRequest<I>::send_unregister_image_watcher() {
+ if (m_image_ctx->image_watcher == nullptr) {
+ send_flush_readahead();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->image_watcher->unregister_watch(create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_unregister_image_watcher>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_unregister_image_watcher(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "failed to unregister image watcher: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ send_flush_readahead();
+}
+
+template <typename I>
+void CloseRequest<I>::send_flush_readahead() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->readahead.wait_for_pending(create_async_context_callback(
+ *m_image_ctx, create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_flush_readahead>(this)));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_flush_readahead(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ send_shut_down_image_dispatcher();
+}
+
+template <typename I>
+void CloseRequest<I>::send_shut_down_image_dispatcher() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->io_image_dispatcher->shut_down(create_context_callback<
+ CloseRequest<I>,
+ &CloseRequest<I>::handle_shut_down_image_dispatcher>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_shut_down_image_dispatcher(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "failed to shut down image dispatcher: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ send_shut_down_object_dispatcher();
+}
+
+template <typename I>
+void CloseRequest<I>::send_shut_down_object_dispatcher() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->io_object_dispatcher->shut_down(create_context_callback<
+ CloseRequest<I>,
+ &CloseRequest<I>::handle_shut_down_object_dispatcher>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_shut_down_object_dispatcher(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "failed to shut down object dispatcher: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ send_flush_op_work_queue();
+}
+
+template <typename I>
+void CloseRequest<I>::send_flush_op_work_queue() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->op_work_queue->queue(create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_flush_op_work_queue>(this), 0);
+}
+
+template <typename I>
+void CloseRequest<I>::handle_flush_op_work_queue(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+ send_close_parent();
+}
+
+template <typename I>
+void CloseRequest<I>::send_close_parent() {
+ if (m_image_ctx->parent == nullptr) {
+ send_flush_image_watcher();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->parent->state->close(create_async_context_callback(
+ *m_image_ctx, create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_close_parent>(this)));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_close_parent(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ m_image_ctx->parent = nullptr;
+ save_result(r);
+ if (r < 0) {
+ lderr(cct) << "error closing parent image: " << cpp_strerror(r) << dendl;
+ }
+ send_flush_image_watcher();
+}
+
+template <typename I>
+void CloseRequest<I>::send_flush_image_watcher() {
+ if (m_image_ctx->image_watcher == nullptr) {
+ finish();
+ return;
+ }
+
+ m_image_ctx->image_watcher->flush(create_context_callback<
+ CloseRequest<I>, &CloseRequest<I>::handle_flush_image_watcher>(this));
+}
+
+template <typename I>
+void CloseRequest<I>::handle_flush_image_watcher(int r) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "error flushing image watcher: " << cpp_strerror(r) << dendl;
+ }
+ save_result(r);
+ finish();
+}
+
+template <typename I>
+void CloseRequest<I>::finish() {
+ m_image_ctx->shutdown();
+ m_on_finish->complete(m_error_result);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::CloseRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/CloseRequest.h b/src/librbd/image/CloseRequest.h
new file mode 100644
index 000000000..ee298aa9d
--- /dev/null
+++ b/src/librbd/image/CloseRequest.h
@@ -0,0 +1,127 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_CLOSE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_CLOSE_REQUEST_H
+
+#include "librbd/ImageCtx.h"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class CloseRequest {
+public:
+ static CloseRequest *create(ImageCtxT *image_ctx, Context *on_finish) {
+ return new CloseRequest(image_ctx, on_finish);
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * BLOCK_IMAGE_WATCHER (skip if R/O)
+ * |
+ * v
+ * SHUT_DOWN_UPDATE_WATCHERS
+ * |
+ * v
+ * FLUSH
+ * |
+ * v (skip if disabled)
+ * SHUT_DOWN_EXCLUSIVE_LOCK
+ * |
+ * v
+ * UNREGISTER_IMAGE_WATCHER (skip if R/O)
+ * |
+ * v
+ * FLUSH_READAHEAD
+ * |
+ * v
+ * SHUT_DOWN_IMAGE_DISPATCHER
+ * |
+ * v
+ * SHUT_DOWN_OBJECT_DISPATCHER
+ * |
+ * v
+ * FLUSH_OP_WORK_QUEUE
+ * |
+ * v (skip if no parent)
+ * CLOSE_PARENT
+ * |
+ * v
+ * FLUSH_IMAGE_WATCHER
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ CloseRequest(ImageCtxT *image_ctx, Context *on_finish);
+
+ ImageCtxT *m_image_ctx;
+ Context *m_on_finish;
+
+ int m_error_result;
+
+ decltype(m_image_ctx->exclusive_lock) m_exclusive_lock;
+
+ void send_block_image_watcher();
+ void handle_block_image_watcher(int r);
+
+ void send_shut_down_update_watchers();
+ void handle_shut_down_update_watchers(int r);
+
+ void send_flush();
+ void handle_flush(int r);
+
+ void send_shut_down_exclusive_lock();
+ void handle_shut_down_exclusive_lock(int r);
+
+ void send_unregister_image_watcher();
+ void handle_unregister_image_watcher(int r);
+
+ void send_flush_readahead();
+ void handle_flush_readahead(int r);
+
+ void send_shut_down_image_dispatcher();
+ void handle_shut_down_image_dispatcher(int r);
+
+ void send_shut_down_object_dispatcher();
+ void handle_shut_down_object_dispatcher(int r);
+
+ void send_flush_op_work_queue();
+ void handle_flush_op_work_queue(int r);
+
+ void send_close_parent();
+ void handle_close_parent(int r);
+
+ void send_flush_image_watcher();
+ void handle_flush_image_watcher(int r);
+
+ void finish();
+
+ void save_result(int result) {
+ if (m_error_result == 0 && result < 0) {
+ m_error_result = result;
+ }
+ }
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::CloseRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_CLOSE_REQUEST_H
diff --git a/src/librbd/image/CreateRequest.cc b/src/librbd/image/CreateRequest.cc
new file mode 100644
index 000000000..71fb9cf26
--- /dev/null
+++ b/src/librbd/image/CreateRequest.cc
@@ -0,0 +1,835 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/CreateRequest.h"
+#include "include/ceph_assert.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "common/ceph_context.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "osdc/Striper.h"
+#include "librbd/Features.h"
+#include "librbd/Journal.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+#include "librbd/image/Types.h"
+#include "librbd/image/ValidatePoolRequest.h"
+#include "librbd/journal/CreateRequest.h"
+#include "librbd/journal/RemoveRequest.h"
+#include "librbd/journal/TypeTraits.h"
+#include "librbd/mirror/EnableRequest.h"
+#include "journal/Journaler.h"
+
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::CreateRequest: " << __func__ \
+ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_rados_callback;
+using util::create_context_callback;
+
+namespace {
+
+int validate_features(CephContext *cct, uint64_t features) {
+ if (features & ~RBD_FEATURES_ALL) {
+ lderr(cct) << "librbd does not support requested features." << dendl;
+ return -ENOSYS;
+ }
+ if ((features & RBD_FEATURES_INTERNAL) != 0) {
+ lderr(cct) << "cannot use internally controlled features" << dendl;
+ return -EINVAL;
+ }
+ if ((features & RBD_FEATURE_FAST_DIFF) != 0 &&
+ (features & RBD_FEATURE_OBJECT_MAP) == 0) {
+ lderr(cct) << "cannot use fast diff without object map" << dendl;
+ return -EINVAL;
+ }
+ if ((features & RBD_FEATURE_OBJECT_MAP) != 0 &&
+ (features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) {
+ lderr(cct) << "cannot use object map without exclusive lock" << dendl;
+ return -EINVAL;
+ }
+ if ((features & RBD_FEATURE_JOURNALING) != 0 &&
+ (features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) {
+ lderr(cct) << "cannot use journaling without exclusive lock" << dendl;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int validate_striping(CephContext *cct, uint8_t order, uint64_t stripe_unit,
+ uint64_t stripe_count) {
+ if ((stripe_unit && !stripe_count) ||
+ (!stripe_unit && stripe_count)) {
+ lderr(cct) << "must specify both (or neither) of stripe-unit and "
+ << "stripe-count" << dendl;
+ return -EINVAL;
+ } else if (stripe_unit && ((1ull << order) % stripe_unit || stripe_unit > (1ull << order))) {
+ lderr(cct) << "stripe unit is not a factor of the object size" << dendl;
+ return -EINVAL;
+ } else if (stripe_unit != 0 && stripe_unit < 512) {
+ lderr(cct) << "stripe unit must be at least 512 bytes" << dendl;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+bool validate_layout(CephContext *cct, uint64_t size, file_layout_t &layout) {
+ if (!librbd::ObjectMap<>::is_compatible(layout, size)) {
+ lderr(cct) << "image size not compatible with object map" << dendl;
+ return false;
+ }
+
+ return true;
+}
+
+int get_image_option(const ImageOptions &image_options, int option,
+ uint8_t *value) {
+ uint64_t large_value;
+ int r = image_options.get(option, &large_value);
+ if (r < 0) {
+ return r;
+ }
+ *value = static_cast<uint8_t>(large_value);
+ return 0;
+}
+
+} // anonymous namespace
+
+template<typename I>
+int CreateRequest<I>::validate_order(CephContext *cct, uint8_t order) {
+ if (order > 25 || order < 12) {
+ lderr(cct) << "order must be in the range [12, 25]" << dendl;
+ return -EDOM;
+ }
+ return 0;
+}
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::CreateRequest: " << this << " " \
+ << __func__ << ": "
+
+template<typename I>
+CreateRequest<I>::CreateRequest(const ConfigProxy& config, IoCtx &ioctx,
+ const std::string &image_name,
+ const std::string &image_id, uint64_t size,
+ const ImageOptions &image_options,
+ uint32_t create_flags,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue,
+ Context *on_finish)
+ : m_config(config), m_image_name(image_name), m_image_id(image_id),
+ m_size(size), m_create_flags(create_flags),
+ m_mirror_image_mode(mirror_image_mode),
+ m_non_primary_global_image_id(non_primary_global_image_id),
+ m_primary_mirror_uuid(primary_mirror_uuid),
+ m_op_work_queue(op_work_queue), m_on_finish(on_finish) {
+
+ m_io_ctx.dup(ioctx);
+ m_cct = reinterpret_cast<CephContext *>(m_io_ctx.cct());
+
+ m_id_obj = util::id_obj_name(m_image_name);
+ m_header_obj = util::header_name(m_image_id);
+ m_objmap_name = ObjectMap<>::object_map_name(m_image_id, CEPH_NOSNAP);
+ if (!non_primary_global_image_id.empty() &&
+ (m_create_flags & CREATE_FLAG_MIRROR_ENABLE_MASK) == 0) {
+ m_create_flags |= CREATE_FLAG_FORCE_MIRROR_ENABLE;
+ }
+
+ if (image_options.get(RBD_IMAGE_OPTION_FEATURES, &m_features) != 0) {
+ m_features = librbd::rbd_features_from_string(
+ m_config.get_val<std::string>("rbd_default_features"), nullptr);
+ m_negotiate_features = true;
+ }
+
+ uint64_t features_clear = 0;
+ uint64_t features_set = 0;
+ image_options.get(RBD_IMAGE_OPTION_FEATURES_CLEAR, &features_clear);
+ image_options.get(RBD_IMAGE_OPTION_FEATURES_SET, &features_set);
+
+ uint64_t features_conflict = features_clear & features_set;
+ features_clear &= ~features_conflict;
+ features_set &= ~features_conflict;
+ m_features |= features_set;
+ m_features &= ~features_clear;
+
+ m_features &= ~RBD_FEATURES_IMPLICIT_ENABLE;
+ if ((m_features & RBD_FEATURE_OBJECT_MAP) == RBD_FEATURE_OBJECT_MAP) {
+ m_features |= RBD_FEATURE_FAST_DIFF;
+ }
+
+ if (image_options.get(RBD_IMAGE_OPTION_STRIPE_UNIT, &m_stripe_unit) != 0 ||
+ m_stripe_unit == 0) {
+ m_stripe_unit = m_config.get_val<Option::size_t>("rbd_default_stripe_unit");
+ }
+ if (image_options.get(RBD_IMAGE_OPTION_STRIPE_COUNT, &m_stripe_count) != 0 ||
+ m_stripe_count == 0) {
+ m_stripe_count = m_config.get_val<uint64_t>("rbd_default_stripe_count");
+ }
+ if (get_image_option(image_options, RBD_IMAGE_OPTION_ORDER, &m_order) != 0 ||
+ m_order == 0) {
+ m_order = config.get_val<uint64_t>("rbd_default_order");
+ }
+ if (get_image_option(image_options, RBD_IMAGE_OPTION_JOURNAL_ORDER,
+ &m_journal_order) != 0) {
+ m_journal_order = m_config.get_val<uint64_t>("rbd_journal_order");
+ }
+ if (get_image_option(image_options, RBD_IMAGE_OPTION_JOURNAL_SPLAY_WIDTH,
+ &m_journal_splay_width) != 0) {
+ m_journal_splay_width = m_config.get_val<uint64_t>(
+ "rbd_journal_splay_width");
+ }
+ if (image_options.get(RBD_IMAGE_OPTION_JOURNAL_POOL, &m_journal_pool) != 0) {
+ m_journal_pool = m_config.get_val<std::string>("rbd_journal_pool");
+ }
+ if (image_options.get(RBD_IMAGE_OPTION_DATA_POOL, &m_data_pool) != 0) {
+ m_data_pool = m_config.get_val<std::string>("rbd_default_data_pool");
+ }
+
+ m_layout.object_size = 1ull << m_order;
+ if (m_stripe_unit == 0 || m_stripe_count == 0) {
+ m_layout.stripe_unit = m_layout.object_size;
+ m_layout.stripe_count = 1;
+ } else {
+ m_layout.stripe_unit = m_stripe_unit;
+ m_layout.stripe_count = m_stripe_count;
+ }
+
+ if (!m_data_pool.empty() && m_data_pool != ioctx.get_pool_name()) {
+ m_features |= RBD_FEATURE_DATA_POOL;
+ } else {
+ m_data_pool.clear();
+ }
+
+ if ((m_stripe_unit != 0 && m_stripe_unit != (1ULL << m_order)) ||
+ (m_stripe_count != 0 && m_stripe_count != 1)) {
+ m_features |= RBD_FEATURE_STRIPINGV2;
+ }
+
+ ldout(m_cct, 10) << "name=" << m_image_name << ", "
+ << "id=" << m_image_id << ", "
+ << "size=" << m_size << ", "
+ << "features=" << m_features << ", "
+ << "order=" << (uint64_t)m_order << ", "
+ << "stripe_unit=" << m_stripe_unit << ", "
+ << "stripe_count=" << m_stripe_count << ", "
+ << "journal_order=" << (uint64_t)m_journal_order << ", "
+ << "journal_splay_width="
+ << (uint64_t)m_journal_splay_width << ", "
+ << "journal_pool=" << m_journal_pool << ", "
+ << "data_pool=" << m_data_pool << dendl;
+}
+
+template<typename I>
+void CreateRequest<I>::send() {
+ ldout(m_cct, 20) << dendl;
+
+ int r = validate_features(m_cct, m_features);
+ if (r < 0) {
+ complete(r);
+ return;
+ }
+
+ r = validate_order(m_cct, m_order);
+ if (r < 0) {
+ complete(r);
+ return;
+ }
+
+ r = validate_striping(m_cct, m_order, m_stripe_unit, m_stripe_count);
+ if (r < 0) {
+ complete(r);
+ return;
+ }
+
+ if (((m_features & RBD_FEATURE_OBJECT_MAP) != 0) &&
+ (!validate_layout(m_cct, m_size, m_layout))) {
+ complete(-EINVAL);
+ return;
+ }
+
+ validate_data_pool();
+}
+
+template <typename I>
+void CreateRequest<I>::validate_data_pool() {
+ m_data_io_ctx = m_io_ctx;
+ if ((m_features & RBD_FEATURE_DATA_POOL) != 0) {
+ librados::Rados rados(m_io_ctx);
+ int r = rados.ioctx_create(m_data_pool.c_str(), m_data_io_ctx);
+ if (r < 0) {
+ lderr(m_cct) << "data pool " << m_data_pool << " does not exist" << dendl;
+ complete(r);
+ return;
+ }
+ m_data_pool_id = m_data_io_ctx.get_id();
+ m_data_io_ctx.set_namespace(m_io_ctx.get_namespace());
+ }
+
+ if (!m_config.get_val<bool>("rbd_validate_pool")) {
+ add_image_to_directory();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ auto ctx = create_context_callback<
+ CreateRequest<I>, &CreateRequest<I>::handle_validate_data_pool>(this);
+ auto req = ValidatePoolRequest<I>::create(m_data_io_ctx, ctx);
+ req->send();
+}
+
+template <typename I>
+void CreateRequest<I>::handle_validate_data_pool(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == -EINVAL) {
+ lderr(m_cct) << "pool does not support RBD images" << dendl;
+ complete(r);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "failed to validate pool: " << cpp_strerror(r) << dendl;
+ complete(r);
+ return;
+ }
+
+ add_image_to_directory();
+}
+
+template<typename I>
+void CreateRequest<I>::add_image_to_directory() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ if (!m_io_ctx.get_namespace().empty()) {
+ cls_client::dir_state_assert(&op, cls::rbd::DIRECTORY_STATE_READY);
+ }
+ cls_client::dir_add_image(&op, m_image_name, m_image_id);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_add_image_to_directory>(this);
+ int r = m_io_ctx.aio_operate(RBD_DIRECTORY, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_add_image_to_directory(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == -EEXIST) {
+ ldout(m_cct, 5) << "directory entry for image " << m_image_name
+ << " already exists" << dendl;
+ complete(r);
+ return;
+ } else if (!m_io_ctx.get_namespace().empty() && r == -ENOENT) {
+ ldout(m_cct, 5) << "namespace " << m_io_ctx.get_namespace()
+ << " does not exist" << dendl;
+ complete(r);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "error adding image to directory: " << cpp_strerror(r)
+ << dendl;
+ complete(r);
+ return;
+ }
+
+ create_id_object();
+}
+
+template<typename I>
+void CreateRequest<I>::create_id_object() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ op.create(true);
+ cls_client::set_id(&op, m_image_id);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_create_id_object>(this);
+ int r = m_io_ctx.aio_operate(m_id_obj, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_create_id_object(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == -EEXIST) {
+ ldout(m_cct, 5) << "id object for " << m_image_name << " already exists"
+ << dendl;
+ m_r_saved = r;
+ remove_from_dir();
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "error creating RBD id object: " << cpp_strerror(r)
+ << dendl;
+ m_r_saved = r;
+ remove_from_dir();
+ return;
+ }
+
+ negotiate_features();
+}
+
+template<typename I>
+void CreateRequest<I>::negotiate_features() {
+ if (!m_negotiate_features) {
+ create_image();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_all_features_start(&op);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_negotiate_features>(this);
+
+ m_outbl.clear();
+ int r = m_io_ctx.aio_operate(RBD_DIRECTORY, comp, &op, &m_outbl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_negotiate_features(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ uint64_t all_features;
+ if (r >= 0) {
+ auto it = m_outbl.cbegin();
+ r = cls_client::get_all_features_finish(&it, &all_features);
+ }
+ if (r < 0) {
+ ldout(m_cct, 10) << "error retrieving server supported features set: "
+ << cpp_strerror(r) << dendl;
+ } else if ((m_features & all_features) != m_features) {
+ m_features &= all_features;
+ ldout(m_cct, 10) << "limiting default features set to server supported: "
+ << m_features << dendl;
+ }
+
+ create_image();
+}
+
+template<typename I>
+void CreateRequest<I>::create_image() {
+ ldout(m_cct, 15) << dendl;
+ ceph_assert(m_data_pool.empty() || m_data_pool_id != -1);
+
+ std::ostringstream oss;
+ oss << RBD_DATA_PREFIX;
+ if (m_data_pool_id != -1) {
+ oss << stringify(m_io_ctx.get_id()) << ".";
+ }
+ oss << m_image_id;
+ if (oss.str().length() > RBD_MAX_BLOCK_NAME_PREFIX_LENGTH) {
+ lderr(m_cct) << "object prefix '" << oss.str() << "' too large" << dendl;
+ m_r_saved = -EINVAL;
+ remove_id_object();
+ return;
+ }
+
+ librados::ObjectWriteOperation op;
+ op.create(true);
+ cls_client::create_image(&op, m_size, m_order, m_features, oss.str(),
+ m_data_pool_id);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_create_image>(this);
+ int r = m_io_ctx.aio_operate(m_header_obj, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_create_image(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r == -EEXIST) {
+ ldout(m_cct, 5) << "image id already in-use" << dendl;
+ complete(-EBADF);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "error writing header: " << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ remove_id_object();
+ return;
+ }
+
+ set_stripe_unit_count();
+}
+
+template<typename I>
+void CreateRequest<I>::set_stripe_unit_count() {
+ if ((!m_stripe_unit && !m_stripe_count) ||
+ ((m_stripe_count == 1) && (m_stripe_unit == (1ull << m_order)))) {
+ object_map_resize();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::set_stripe_unit_count(&op, m_stripe_unit, m_stripe_count);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_set_stripe_unit_count>(this);
+ int r = m_io_ctx.aio_operate(m_header_obj, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_set_stripe_unit_count(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error setting stripe unit/count: "
+ << cpp_strerror(r) << dendl;
+ m_r_saved = r;
+ remove_header_object();
+ return;
+ }
+
+ object_map_resize();
+}
+
+template<typename I>
+void CreateRequest<I>::object_map_resize() {
+ if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0) {
+ fetch_mirror_mode();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::object_map_resize(&op, Striper::get_num_objects(m_layout, m_size),
+ OBJECT_NONEXISTENT);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_object_map_resize>(this);
+ int r = m_io_ctx.aio_operate(m_objmap_name, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_object_map_resize(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error creating initial object map: "
+ << cpp_strerror(r) << dendl;
+
+ m_r_saved = r;
+ remove_header_object();
+ return;
+ }
+
+ fetch_mirror_mode();
+}
+
+template<typename I>
+void CreateRequest<I>::fetch_mirror_mode() {
+ if ((m_features & RBD_FEATURE_JOURNALING) == 0) {
+ mirror_image_enable();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::mirror_mode_get_start(&op);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_fetch_mirror_mode>(this);
+ m_outbl.clear();
+ int r = m_io_ctx.aio_operate(RBD_MIRRORING, comp, &op, &m_outbl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_fetch_mirror_mode(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if ((r < 0) && (r != -ENOENT)) {
+ lderr(m_cct) << "failed to retrieve mirror mode: " << cpp_strerror(r)
+ << dendl;
+
+ m_r_saved = r;
+ remove_object_map();
+ return;
+ }
+
+ m_mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+ if (r == 0) {
+ auto it = m_outbl.cbegin();
+ r = cls_client::mirror_mode_get_finish(&it, &m_mirror_mode);
+ if (r < 0) {
+ lderr(m_cct) << "Failed to retrieve mirror mode" << dendl;
+
+ m_r_saved = r;
+ remove_object_map();
+ return;
+ }
+ }
+
+ journal_create();
+}
+
+template<typename I>
+void CreateRequest<I>::journal_create() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CreateRequest<I>;
+ Context *ctx = create_context_callback<klass, &klass::handle_journal_create>(
+ this);
+
+ // only link to remote primary mirror uuid if in journal-based
+ // mirroring mode
+ bool use_primary_mirror_uuid = (
+ !m_non_primary_global_image_id.empty() &&
+ m_mirror_image_mode == cls::rbd::MIRROR_IMAGE_MODE_JOURNAL);
+
+ librbd::journal::TagData tag_data;
+ tag_data.mirror_uuid = (use_primary_mirror_uuid ? m_primary_mirror_uuid :
+ librbd::Journal<I>::LOCAL_MIRROR_UUID);
+
+ typename journal::TypeTraits<I>::ContextWQ* context_wq;
+ Journal<>::get_work_queue(m_cct, &context_wq);
+
+ auto req = librbd::journal::CreateRequest<I>::create(
+ m_io_ctx, m_image_id, m_journal_order, m_journal_splay_width,
+ m_journal_pool, cls::journal::Tag::TAG_CLASS_NEW, tag_data,
+ librbd::Journal<I>::IMAGE_CLIENT_ID, context_wq, ctx);
+ req->send();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_journal_create(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error creating journal: " << cpp_strerror(r)
+ << dendl;
+
+ m_r_saved = r;
+ remove_object_map();
+ return;
+ }
+
+ mirror_image_enable();
+}
+
+template<typename I>
+void CreateRequest<I>::mirror_image_enable() {
+ auto mirror_enable_flag = (m_create_flags & CREATE_FLAG_MIRROR_ENABLE_MASK);
+
+ if ((m_mirror_mode != cls::rbd::MIRROR_MODE_POOL &&
+ mirror_enable_flag != CREATE_FLAG_FORCE_MIRROR_ENABLE) ||
+ (mirror_enable_flag == CREATE_FLAG_SKIP_MIRROR_ENABLE)) {
+ complete(0);
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+ auto ctx = create_context_callback<
+ CreateRequest<I>, &CreateRequest<I>::handle_mirror_image_enable>(this);
+
+ auto req = mirror::EnableRequest<I>::create(
+ m_io_ctx, m_image_id, m_mirror_image_mode,
+ m_non_primary_global_image_id, true, m_op_work_queue, ctx);
+ req->send();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_mirror_image_enable(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "cannot enable mirroring: " << cpp_strerror(r)
+ << dendl;
+
+ m_r_saved = r;
+ journal_remove();
+ return;
+ }
+
+ complete(0);
+}
+
+template<typename I>
+void CreateRequest<I>::complete(int r) {
+ ldout(m_cct, 10) << "r=" << r << dendl;
+
+ m_data_io_ctx.close();
+ auto on_finish = m_on_finish;
+ delete this;
+ on_finish->complete(r);
+}
+
+// cleanup
+template<typename I>
+void CreateRequest<I>::journal_remove() {
+ if ((m_features & RBD_FEATURE_JOURNALING) == 0) {
+ remove_object_map();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CreateRequest<I>;
+ Context *ctx = create_context_callback<klass, &klass::handle_journal_remove>(
+ this);
+
+ typename journal::TypeTraits<I>::ContextWQ* context_wq;
+ Journal<>::get_work_queue(m_cct, &context_wq);
+
+ librbd::journal::RemoveRequest<I> *req =
+ librbd::journal::RemoveRequest<I>::create(
+ m_io_ctx, m_image_id, librbd::Journal<I>::IMAGE_CLIENT_ID, context_wq,
+ ctx);
+ req->send();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_journal_remove(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error cleaning up journal after creation failed: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ remove_object_map();
+}
+
+template<typename I>
+void CreateRequest<I>::remove_object_map() {
+ if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0) {
+ remove_header_object();
+ return;
+ }
+
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_remove_object_map>(this);
+ int r = m_io_ctx.aio_remove(m_objmap_name, comp);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_remove_object_map(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error cleaning up object map after creation failed: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ remove_header_object();
+}
+
+template<typename I>
+void CreateRequest<I>::remove_header_object() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_remove_header_object>(this);
+ int r = m_io_ctx.aio_remove(m_header_obj, comp);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_remove_header_object(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error cleaning up image header after creation failed: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ remove_id_object();
+}
+
+template<typename I>
+void CreateRequest<I>::remove_id_object() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_remove_id_object>(this);
+ int r = m_io_ctx.aio_remove(m_id_obj, comp);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_remove_id_object(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error cleaning up id object after creation failed: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ remove_from_dir();
+}
+
+template<typename I>
+void CreateRequest<I>::remove_from_dir() {
+ ldout(m_cct, 15) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::dir_remove_image(&op, m_image_name, m_image_id);
+
+ using klass = CreateRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_remove_from_dir>(this);
+ int r = m_io_ctx.aio_operate(RBD_DIRECTORY, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template<typename I>
+void CreateRequest<I>::handle_remove_from_dir(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error cleaning up image from rbd_directory object "
+ << "after creation failed: " << cpp_strerror(r) << dendl;
+ }
+
+ complete(m_r_saved);
+}
+
+} //namespace image
+} //namespace librbd
+
+template class librbd::image::CreateRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/CreateRequest.h b/src/librbd/image/CreateRequest.h
new file mode 100644
index 000000000..9cb0eec7c
--- /dev/null
+++ b/src/librbd/image/CreateRequest.h
@@ -0,0 +1,191 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_CREATE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_CREATE_REQUEST_H
+
+#include "common/config_fwd.h"
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "include/rbd/librbd.hpp"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/ImageCtx.h"
+
+class Context;
+
+using librados::IoCtx;
+
+namespace journal { class Journaler; }
+
+namespace librbd {
+
+namespace asio { struct ContextWQ; }
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class CreateRequest {
+public:
+ static CreateRequest *create(const ConfigProxy& config, IoCtx &ioctx,
+ const std::string &image_name,
+ const std::string &image_id, uint64_t size,
+ const ImageOptions &image_options,
+ uint32_t create_flags,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue,
+ Context *on_finish) {
+ return new CreateRequest(config, ioctx, image_name, image_id, size,
+ image_options, create_flags,
+ mirror_image_mode, non_primary_global_image_id,
+ primary_mirror_uuid, op_work_queue, on_finish);
+ }
+
+ static int validate_order(CephContext *cct, uint8_t order);
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start> . . . . > . . . . .
+ * | .
+ * v .
+ * VALIDATE DATA POOL v (pool validation
+ * | . disabled)
+ * v .
+ * (error: bottom up) ADD IMAGE TO DIRECTORY < . . . .
+ * _______<_______ |
+ * | | v
+ * | | CREATE ID OBJECT
+ * | | / |
+ * | REMOVE FROM DIR <-------/ v
+ * | | NEGOTIATE FEATURES (when using default features)
+ * | | |
+ * | | v (stripingv2 disabled)
+ * | | CREATE IMAGE. . . . > . . . .
+ * v | / | .
+ * | REMOVE ID OBJ <---------/ v .
+ * | | SET STRIPE UNIT COUNT .
+ * | | / | \ . . . . . > . . . .
+ * | REMOVE HEADER OBJ<------/ v /. (object-map
+ * | |\ OBJECT MAP RESIZE . . < . . * v disabled)
+ * | | \ / | \ . . . . . > . . . .
+ * | | *<-----------/ v /. (journaling
+ * | | FETCH MIRROR MODE. . < . . * v disabled)
+ * | | / | .
+ * | REMOVE OBJECT MAP<--------/ v .
+ * | |\ JOURNAL CREATE .
+ * | | \ / | .
+ * v | *<------------/ v .
+ * | | MIRROR IMAGE ENABLE .
+ * | | / | .
+ * | JOURNAL REMOVE*<-------/ | .
+ * | v .
+ * |_____________>___________________<finish> . . . . < . . . .
+ *
+ * @endverbatim
+ */
+
+ CreateRequest(const ConfigProxy& config, IoCtx &ioctx,
+ const std::string &image_name,
+ const std::string &image_id, uint64_t size,
+ const ImageOptions &image_options,
+ uint32_t create_flags,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ asio::ContextWQ *op_work_queue, Context *on_finish);
+
+ const ConfigProxy& m_config;
+ IoCtx m_io_ctx;
+ IoCtx m_data_io_ctx;
+ std::string m_image_name;
+ std::string m_image_id;
+ uint64_t m_size;
+ uint8_t m_order = 0;
+ uint64_t m_features = 0;
+ uint64_t m_stripe_unit = 0;
+ uint64_t m_stripe_count = 0;
+ uint8_t m_journal_order = 0;
+ uint8_t m_journal_splay_width = 0;
+ std::string m_journal_pool;
+ std::string m_data_pool;
+ int64_t m_data_pool_id = -1;
+ uint32_t m_create_flags;
+ cls::rbd::MirrorImageMode m_mirror_image_mode;
+ const std::string m_non_primary_global_image_id;
+ const std::string m_primary_mirror_uuid;
+ bool m_negotiate_features = false;
+
+ asio::ContextWQ *m_op_work_queue;
+ Context *m_on_finish;
+
+ CephContext *m_cct;
+ int m_r_saved = 0; // used to return actual error after cleanup
+ file_layout_t m_layout;
+ std::string m_id_obj, m_header_obj, m_objmap_name;
+
+ bufferlist m_outbl;
+ cls::rbd::MirrorMode m_mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+ cls::rbd::MirrorImage m_mirror_image_internal;
+
+ void validate_data_pool();
+ void handle_validate_data_pool(int r);
+
+ void add_image_to_directory();
+ void handle_add_image_to_directory(int r);
+
+ void create_id_object();
+ void handle_create_id_object(int r);
+
+ void negotiate_features();
+ void handle_negotiate_features(int r);
+
+ void create_image();
+ void handle_create_image(int r);
+
+ void set_stripe_unit_count();
+ void handle_set_stripe_unit_count(int r);
+
+ void object_map_resize();
+ void handle_object_map_resize(int r);
+
+ void fetch_mirror_mode();
+ void handle_fetch_mirror_mode(int r);
+
+ void journal_create();
+ void handle_journal_create(int r);
+
+ void mirror_image_enable();
+ void handle_mirror_image_enable(int r);
+
+ void complete(int r);
+
+ // cleanup
+ void journal_remove();
+ void handle_journal_remove(int r);
+
+ void remove_object_map();
+ void handle_remove_object_map(int r);
+
+ void remove_header_object();
+ void handle_remove_header_object(int r);
+
+ void remove_id_object();
+ void handle_remove_id_object(int r);
+
+ void remove_from_dir();
+ void handle_remove_from_dir(int r);
+
+};
+
+} //namespace image
+} //namespace librbd
+
+extern template class librbd::image::CreateRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_CREATE_REQUEST_H
diff --git a/src/librbd/image/DetachChildRequest.cc b/src/librbd/image/DetachChildRequest.cc
new file mode 100644
index 000000000..ab39dbcd7
--- /dev/null
+++ b/src/librbd/image/DetachChildRequest.cc
@@ -0,0 +1,392 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/DetachChildRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+#include "librbd/journal/DisabledPolicy.h"
+#include "librbd/trash/RemoveRequest.h"
+#include <string>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::DetachChildRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+DetachChildRequest<I>::~DetachChildRequest() {
+ ceph_assert(m_parent_image_ctx == nullptr);
+}
+
+template <typename I>
+void DetachChildRequest<I>::send() {
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+
+ // use oldest snapshot or HEAD for parent spec
+ if (!m_image_ctx.snap_info.empty()) {
+ m_parent_spec = m_image_ctx.snap_info.begin()->second.parent.spec;
+ } else {
+ m_parent_spec = m_image_ctx.parent_md.spec;
+ }
+ }
+
+ if (m_parent_spec.pool_id == -1) {
+ // ignore potential race with parent disappearing
+ m_image_ctx.op_work_queue->queue(create_context_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::finish>(this), 0);
+ return;
+ } else if (!m_image_ctx.test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD)) {
+ clone_v1_remove_child();
+ return;
+ }
+
+ clone_v2_child_detach();
+}
+
+template <typename I>
+void DetachChildRequest<I>::clone_v2_child_detach() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::child_detach(&op, m_parent_spec.snap_id,
+ {m_image_ctx.md_ctx.get_id(),
+ m_image_ctx.md_ctx.get_namespace(),
+ m_image_ctx.id});
+
+ int r = util::create_ioctx(m_image_ctx.md_ctx, "parent image",
+ m_parent_spec.pool_id,
+ m_parent_spec.pool_namespace, &m_parent_io_ctx);
+ if (r < 0) {
+ if (r == -ENOENT) {
+ r = 0;
+ }
+ finish(r);
+ return;
+ }
+
+ m_parent_header_name = util::header_name(m_parent_spec.image_id);
+
+ auto aio_comp = create_rados_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_child_detach>(this);
+ r = m_parent_io_ctx.aio_operate(m_parent_header_name, aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void DetachChildRequest<I>::handle_clone_v2_child_detach(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(cct) << "error detaching child from parent: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ clone_v2_get_snapshot();
+}
+
+template <typename I>
+void DetachChildRequest<I>::clone_v2_get_snapshot() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::snapshot_get_start(&op, m_parent_spec.snap_id);
+
+ m_out_bl.clear();
+ auto aio_comp = create_rados_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_get_snapshot>(this);
+ int r = m_parent_io_ctx.aio_operate(m_parent_header_name, aio_comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void DetachChildRequest<I>::handle_clone_v2_get_snapshot(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ bool remove_snapshot = false;
+ if (r == 0) {
+ cls::rbd::SnapshotInfo snap_info;
+ auto it = m_out_bl.cbegin();
+ r = cls_client::snapshot_get_finish(&it, &snap_info);
+ if (r == 0) {
+ m_parent_snap_namespace = snap_info.snapshot_namespace;
+ m_parent_snap_name = snap_info.name;
+
+ if (cls::rbd::get_snap_namespace_type(m_parent_snap_namespace) ==
+ cls::rbd::SNAPSHOT_NAMESPACE_TYPE_TRASH &&
+ snap_info.child_count == 0) {
+ // snapshot is in trash w/ zero children, so remove it
+ remove_snapshot = true;
+ }
+ }
+ }
+
+ if (r < 0 && r != -ENOENT) {
+ ldout(cct, 5) << "failed to retrieve snapshot: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ if (!remove_snapshot) {
+ finish(0);
+ return;
+ }
+
+ clone_v2_open_parent();
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_open_parent() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ m_parent_image_ctx = I::create("", m_parent_spec.image_id, nullptr,
+ m_parent_io_ctx, false);
+
+ // ensure non-primary images can be modified
+ m_parent_image_ctx->read_only_mask &= ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+
+ auto ctx = create_context_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_open_parent>(this);
+ m_parent_image_ctx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT, ctx);
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_open_parent(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ ldout(cct, 5) << "failed to open parent for read/write: "
+ << cpp_strerror(r) << dendl;
+ m_parent_image_ctx = nullptr;
+ finish(0);
+ return;
+ }
+
+ // do not attempt to open the parent journal when removing the trash
+ // snapshot, because the parent may be not promoted
+ if (m_parent_image_ctx->test_features(RBD_FEATURE_JOURNALING)) {
+ std::unique_lock image_locker{m_parent_image_ctx->image_lock};
+ m_parent_image_ctx->set_journal_policy(new journal::DisabledPolicy());
+ }
+
+ // disallow any proxied maintenance operations
+ {
+ std::shared_lock owner_locker{m_parent_image_ctx->owner_lock};
+ if (m_parent_image_ctx->exclusive_lock != nullptr) {
+ m_parent_image_ctx->exclusive_lock->block_requests(0);
+ }
+ }
+
+ clone_v2_remove_snapshot();
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_remove_snapshot() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ auto ctx = create_context_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_remove_snapshot>(this);
+ m_parent_image_ctx->operations->snap_remove(m_parent_snap_namespace,
+ m_parent_snap_name, ctx);
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_remove_snapshot(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ ldout(cct, 5) << "failed to remove trashed clone snapshot: "
+ << cpp_strerror(r) << dendl;
+ clone_v2_close_parent();
+ return;
+ }
+
+ if (m_parent_image_ctx->snaps.empty()) {
+ clone_v2_get_parent_trash_entry();
+ } else {
+ clone_v2_close_parent();
+ }
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_get_parent_trash_entry() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::trash_get_start(&op, m_parent_image_ctx->id);
+
+ m_out_bl.clear();
+ auto aio_comp = create_rados_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_get_parent_trash_entry>(this);
+ int r = m_parent_io_ctx.aio_operate(RBD_TRASH, aio_comp, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_get_parent_trash_entry(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ ldout(cct, 5) << "failed to get parent trash entry: " << cpp_strerror(r)
+ << dendl;
+ clone_v2_close_parent();
+ return;
+ }
+
+ bool in_trash = false;
+
+ if (r == 0) {
+ cls::rbd::TrashImageSpec trash_spec;
+ auto it = m_out_bl.cbegin();
+ r = cls_client::trash_get_finish(&it, &trash_spec);
+
+ if (r == 0 &&
+ trash_spec.source == cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT &&
+ trash_spec.state == cls::rbd::TRASH_IMAGE_STATE_NORMAL &&
+ trash_spec.deferment_end_time <= ceph_clock_now()) {
+ in_trash = true;
+ }
+ }
+
+ if (in_trash) {
+ clone_v2_remove_parent_from_trash();
+ } else {
+ clone_v2_close_parent();
+ }
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_remove_parent_from_trash() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ auto ctx = create_context_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_remove_parent_from_trash>(this);
+ auto req = librbd::trash::RemoveRequest<I>::create(
+ m_parent_io_ctx, m_parent_image_ctx, m_image_ctx.op_work_queue, false,
+ m_no_op, ctx);
+ req->send();
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_remove_parent_from_trash(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ ldout(cct, 5) << "failed to remove parent image:" << cpp_strerror(r)
+ << dendl;
+ }
+
+ m_parent_image_ctx = nullptr;
+ finish(0);
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v2_close_parent() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ auto ctx = create_context_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v2_close_parent>(this);
+ m_parent_image_ctx->state->close(ctx);
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v2_close_parent(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ ldout(cct, 5) << "failed to close parent image:" << cpp_strerror(r)
+ << dendl;
+ }
+
+ m_parent_image_ctx = nullptr;
+ finish(0);
+}
+
+template<typename I>
+void DetachChildRequest<I>::clone_v1_remove_child() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ m_parent_spec.pool_namespace = "";
+
+ librados::ObjectWriteOperation op;
+ librbd::cls_client::remove_child(&op, m_parent_spec, m_image_ctx.id);
+
+ auto aio_comp = create_rados_callback<
+ DetachChildRequest<I>,
+ &DetachChildRequest<I>::handle_clone_v1_remove_child>(this);
+ int r = m_image_ctx.md_ctx.aio_operate(RBD_CHILDREN, aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template<typename I>
+void DetachChildRequest<I>::handle_clone_v1_remove_child(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r == -ENOENT) {
+ r = 0;
+ } else if (r < 0) {
+ lderr(cct) << "failed to remove child from children list: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void DetachChildRequest<I>::finish(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::DetachChildRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/DetachChildRequest.h b/src/librbd/image/DetachChildRequest.h
new file mode 100644
index 000000000..646b7ec62
--- /dev/null
+++ b/src/librbd/image/DetachChildRequest.h
@@ -0,0 +1,119 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_DETACH_CHILD_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_DETACH_CHILD_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "librbd/Types.h"
+#include "librbd/internal.h"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class DetachChildRequest {
+public:
+ static DetachChildRequest* create(ImageCtxT& image_ctx, Context* on_finish) {
+ return new DetachChildRequest(image_ctx, on_finish);
+ }
+
+ DetachChildRequest(ImageCtxT& image_ctx, Context* on_finish)
+ : m_image_ctx(image_ctx), m_on_finish(on_finish) {
+ }
+ ~DetachChildRequest();
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * (v1) | (v2)
+ * /--------------/ \--------------\
+ * | |
+ * v v
+ * REMOVE_CHILD CHILD_DETACH
+ * | |
+ * | v
+ * | GET_SNAPSHOT
+ * | (snapshot in-use) . |
+ * |/. . . . . . . . . . . . . . . |
+ * | v
+ * | OPEN_PARENT
+ * | |
+ * | v (has more children)
+ * | REMOVE_SNAPSHOT ---------------\
+ * | | |
+ * | v (noent) |
+ * | (auto-delete when GET_PARENT_TRASH_ENTRY . . . .\|
+ * | last child detached) | |
+ * | v v
+ * | REMOVE_PARENT_FROM_TRASH CLOSE_PARENT
+ * | | |
+ * |/------------------------------/--------------------------/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT& m_image_ctx;
+ Context* m_on_finish;
+
+ librados::IoCtx m_parent_io_ctx;
+ cls::rbd::ParentImageSpec m_parent_spec;
+ std::string m_parent_header_name;
+
+ cls::rbd::SnapshotNamespace m_parent_snap_namespace;
+ std::string m_parent_snap_name;
+
+ ImageCtxT* m_parent_image_ctx = nullptr;
+
+ ceph::bufferlist m_out_bl;
+ NoOpProgressContext m_no_op;
+
+ void clone_v2_child_detach();
+ void handle_clone_v2_child_detach(int r);
+
+ void clone_v2_get_snapshot();
+ void handle_clone_v2_get_snapshot(int r);
+
+ void clone_v2_open_parent();
+ void handle_clone_v2_open_parent(int r);
+
+ void clone_v2_remove_snapshot();
+ void handle_clone_v2_remove_snapshot(int r);
+
+ void clone_v2_get_parent_trash_entry();
+ void handle_clone_v2_get_parent_trash_entry(int r);
+
+ void clone_v2_remove_parent_from_trash();
+ void handle_clone_v2_remove_parent_from_trash(int r);
+
+ void clone_v2_close_parent();
+ void handle_clone_v2_close_parent(int r);
+
+ void clone_v1_remove_child();
+ void handle_clone_v1_remove_child(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::DetachChildRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_DETACH_CHILD_REQUEST_H
diff --git a/src/librbd/image/DetachParentRequest.cc b/src/librbd/image/DetachParentRequest.cc
new file mode 100644
index 000000000..74b1b0f67
--- /dev/null
+++ b/src/librbd/image/DetachParentRequest.cc
@@ -0,0 +1,81 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/DetachParentRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::DetachParentRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+void DetachParentRequest<I>::send() {
+ detach_parent();
+}
+
+template <typename I>
+void DetachParentRequest<I>::detach_parent() {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ librados::ObjectWriteOperation op;
+ if (!m_legacy_parent) {
+ librbd::cls_client::parent_detach(&op);
+ } else {
+ librbd::cls_client::remove_parent(&op);
+ }
+
+ auto aio_comp = create_rados_callback<
+ DetachParentRequest<I>,
+ &DetachParentRequest<I>::handle_detach_parent>(this);
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, aio_comp, &op);
+ ceph_assert(r == 0);
+ aio_comp->release();
+}
+
+template <typename I>
+void DetachParentRequest<I>::handle_detach_parent(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << dendl;
+
+ if (!m_legacy_parent && r == -EOPNOTSUPP) {
+ ldout(cct, 10) << "retrying using legacy parent method" << dendl;
+ m_legacy_parent = true;
+ detach_parent();
+ return;
+ }
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(cct) << "detach parent encountered an error: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void DetachParentRequest<I>::finish(int r) {
+ auto cct = m_image_ctx.cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::DetachParentRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/DetachParentRequest.h b/src/librbd/image/DetachParentRequest.h
new file mode 100644
index 000000000..17c86aaac
--- /dev/null
+++ b/src/librbd/image/DetachParentRequest.h
@@ -0,0 +1,66 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_DETACH_PARENT_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_DETACH_PARENT_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/rados/librados.hpp"
+#include "librbd/Types.h"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class DetachParentRequest {
+public:
+ static DetachParentRequest* create(ImageCtxT& image_ctx, Context* on_finish) {
+ return new DetachParentRequest(image_ctx, on_finish);
+ }
+
+ DetachParentRequest(ImageCtxT& image_ctx, Context* on_finish)
+ : m_image_ctx(image_ctx), m_on_finish(on_finish) {
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * | * * * * * *
+ * | * * -EOPNOTSUPP
+ * v v *
+ * DETACH_PARENT * * *
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT& m_image_ctx;
+ Context* m_on_finish;
+
+ bool m_legacy_parent = false;
+
+ void detach_parent();
+ void handle_detach_parent(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::DetachParentRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_DETACH_PARENT_REQUEST_H
diff --git a/src/librbd/image/GetMetadataRequest.cc b/src/librbd/image/GetMetadataRequest.cc
new file mode 100644
index 000000000..1410c9005
--- /dev/null
+++ b/src/librbd/image/GetMetadataRequest.cc
@@ -0,0 +1,121 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/GetMetadataRequest.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "include/ceph_assert.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include <boost/algorithm/string/predicate.hpp>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::GetMetadataRequest: " \
+ << this << " " << __func__ << ": "
+
+#define MAX_KEYS 64U
+
+namespace librbd {
+namespace image {
+namespace {
+
+static const std::string INTERNAL_KEY_PREFIX{".rbd"};
+
+} // anonymous namespace
+
+using util::create_rados_callback;
+
+template <typename I>
+GetMetadataRequest<I>::GetMetadataRequest(
+ IoCtx &io_ctx, const std::string &oid, bool filter_internal,
+ const std::string& filter_key_prefix, const std::string& last_key,
+ uint32_t max_results, KeyValues* key_values, Context *on_finish)
+ : m_io_ctx(io_ctx), m_oid(oid), m_filter_internal(filter_internal),
+ m_filter_key_prefix(filter_key_prefix), m_last_key(last_key),
+ m_max_results(max_results), m_key_values(key_values),
+ m_on_finish(on_finish),
+ m_cct(reinterpret_cast<CephContext*>(m_io_ctx.cct())) {
+}
+
+template <typename I>
+void GetMetadataRequest<I>::send() {
+ metadata_list();
+}
+
+template <typename I>
+void GetMetadataRequest<I>::metadata_list() {
+ ldout(m_cct, 15) << "start_key=" << m_last_key << dendl;
+
+ m_expected_results = MAX_KEYS;
+ if (m_max_results > 0) {
+ m_expected_results = std::min<uint32_t>(
+ m_expected_results, m_max_results - m_key_values->size());
+ }
+
+ librados::ObjectReadOperation op;
+ cls_client::metadata_list_start(&op, m_last_key, m_expected_results);
+
+ auto aio_comp = create_rados_callback<
+ GetMetadataRequest<I>, &GetMetadataRequest<I>::handle_metadata_list>(this);
+ m_out_bl.clear();
+ m_io_ctx.aio_operate(m_oid, aio_comp, &op, &m_out_bl);
+ aio_comp->release();
+}
+
+template <typename I>
+void GetMetadataRequest<I>::handle_metadata_list(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ KeyValues metadata;
+ if (r == 0) {
+ auto it = m_out_bl.cbegin();
+ r = cls_client::metadata_list_finish(&it, &metadata);
+ }
+
+ if (r == -ENOENT || r == -EOPNOTSUPP) {
+ finish(0);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "failed to retrieve image metadata: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ for (auto it = metadata.begin(); it != metadata.end(); ++it) {
+ if (m_filter_internal &&
+ boost::starts_with(it->first, INTERNAL_KEY_PREFIX)) {
+ continue;
+ } else if (!m_filter_key_prefix.empty() &&
+ !boost::starts_with(it->first, m_filter_key_prefix)) {
+ continue;
+ }
+ m_key_values->insert({it->first, std::move(it->second)});
+ }
+ if (!metadata.empty()) {
+ m_last_key = metadata.rbegin()->first;
+ }
+
+ if (metadata.size() == m_expected_results &&
+ (m_max_results == 0 || m_key_values->size() < m_max_results)) {
+ metadata_list();
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void GetMetadataRequest<I>::finish(int r) {
+ ldout(m_cct, 15) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::GetMetadataRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/GetMetadataRequest.h b/src/librbd/image/GetMetadataRequest.h
new file mode 100644
index 000000000..08fc2de71
--- /dev/null
+++ b/src/librbd/image/GetMetadataRequest.h
@@ -0,0 +1,83 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_GET_METADATA_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_GET_METADATA_REQUEST_H
+
+#include "include/common_fwd.h"
+#include "include/rados/librados.hpp"
+#include "include/rbd/librbd.hpp"
+#include <string>
+#include <map>
+
+class Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class GetMetadataRequest {
+public:
+ typedef std::map<std::string, bufferlist> KeyValues;
+
+ static GetMetadataRequest* create(
+ IoCtx &io_ctx, const std::string &oid, bool filter_internal,
+ const std::string& filter_key_prefix, const std::string& last_key,
+ uint32_t max_results, KeyValues* key_values, Context *on_finish) {
+ return new GetMetadataRequest(io_ctx, oid, filter_internal,
+ filter_key_prefix, last_key, max_results,
+ key_values, on_finish);
+ }
+
+ GetMetadataRequest(
+ IoCtx &io_ctx, const std::string &oid, bool filter_internal,
+ const std::string& filter_key_prefix, const std::string& last_key,
+ uint32_t max_results, KeyValues* key_values, Context *on_finish);
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * | /-------\
+ * | | |
+ * v v |
+ * METADATA_LIST ---/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+ librados::IoCtx m_io_ctx;
+ std::string m_oid;
+ bool m_filter_internal;
+ std::string m_filter_key_prefix;
+ std::string m_last_key;
+ uint32_t m_max_results;
+ KeyValues* m_key_values;
+ Context* m_on_finish;
+
+ CephContext* m_cct;
+ bufferlist m_out_bl;
+ uint32_t m_expected_results = 0;
+
+ void metadata_list();
+ void handle_metadata_list(int r);
+
+ void finish(int r);
+
+};
+
+} //namespace image
+} //namespace librbd
+
+extern template class librbd::image::GetMetadataRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_GET_METADATA_REQUEST_H
diff --git a/src/librbd/image/ListWatchersRequest.cc b/src/librbd/image/ListWatchersRequest.cc
new file mode 100644
index 000000000..7ccbd136f
--- /dev/null
+++ b/src/librbd/image/ListWatchersRequest.cc
@@ -0,0 +1,174 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "ListWatchersRequest.h"
+#include "common/RWLock.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/Utils.h"
+
+#include <algorithm>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::ListWatchersRequest: " << this \
+ << " " << __func__ << ": "
+
+static std::ostream& operator<<(std::ostream& os, const obj_watch_t& watch) {
+ os << "{addr=" << watch.addr << ", "
+ << "watcher_id=" << watch.watcher_id << ", "
+ << "cookie=" << watch.cookie << "}";
+ return os;
+}
+
+namespace librbd {
+namespace image {
+
+using librados::IoCtx;
+using util::create_rados_callback;
+
+template<typename I>
+ListWatchersRequest<I>::ListWatchersRequest(I &image_ctx, int flags,
+ std::list<obj_watch_t> *watchers,
+ Context *on_finish)
+ : m_image_ctx(image_ctx), m_flags(flags), m_watchers(watchers),
+ m_on_finish(on_finish), m_cct(m_image_ctx.cct) {
+ ceph_assert((m_flags & LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES) == 0 ||
+ (m_flags & LIST_WATCHERS_MIRROR_INSTANCES_ONLY) == 0);
+}
+
+template<typename I>
+void ListWatchersRequest<I>::send() {
+ ldout(m_cct, 20) << dendl;
+
+ list_image_watchers();
+}
+
+template<typename I>
+void ListWatchersRequest<I>::list_image_watchers() {
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectReadOperation op;
+ op.list_watchers(&m_object_watchers, &m_ret_val);
+
+ using klass = ListWatchersRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_list_image_watchers>(this);
+
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid,
+ rados_completion, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void ListWatchersRequest<I>::handle_list_image_watchers(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r == 0 && m_ret_val < 0) {
+ r = m_ret_val;
+ }
+ if (r < 0) {
+ lderr(m_cct) << "error listing image watchers: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ ldout(m_cct, 20) << "object_watchers=" << m_object_watchers << dendl;
+ list_mirror_watchers();
+}
+
+template<typename I>
+void ListWatchersRequest<I>::list_mirror_watchers() {
+ if ((m_object_watchers.empty()) ||
+ (m_flags & (LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES |
+ LIST_WATCHERS_MIRROR_INSTANCES_ONLY)) == 0) {
+ finish(0);
+ return;
+ }
+
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectReadOperation op;
+ op.list_watchers(&m_mirror_watchers, &m_ret_val);
+
+ using klass = ListWatchersRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_list_mirror_watchers>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(RBD_MIRRORING, rados_completion,
+ &op, &m_out_bl);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void ListWatchersRequest<I>::handle_list_mirror_watchers(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r == 0 && m_ret_val < 0) {
+ r = m_ret_val;
+ }
+ if (r < 0 && r != -ENOENT) {
+ ldout(m_cct, 1) << "error listing mirror watchers: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ ldout(m_cct, 20) << "mirror_watchers=" << m_mirror_watchers << dendl;
+ finish(0);
+}
+
+template<typename I>
+void ListWatchersRequest<I>::finish(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r == 0) {
+ m_watchers->clear();
+
+ if (m_object_watchers.size() > 0) {
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ uint64_t watch_handle = m_image_ctx.image_watcher != nullptr ?
+ m_image_ctx.image_watcher->get_watch_handle() : 0;
+
+ for (auto &w : m_object_watchers) {
+ if ((m_flags & LIST_WATCHERS_FILTER_OUT_MY_INSTANCE) != 0) {
+ if (w.cookie == watch_handle) {
+ ldout(m_cct, 20) << "filtering out my instance: " << w << dendl;
+ continue;
+ }
+ }
+ auto it = std::find_if(m_mirror_watchers.begin(),
+ m_mirror_watchers.end(),
+ [w] (obj_watch_t &watcher) {
+ return (strncmp(w.addr, watcher.addr,
+ sizeof(w.addr)) == 0);
+ });
+ if ((m_flags & LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES) != 0) {
+ if (it != m_mirror_watchers.end()) {
+ ldout(m_cct, 20) << "filtering out mirror instance: " << w << dendl;
+ continue;
+ }
+ } else if ((m_flags & LIST_WATCHERS_MIRROR_INSTANCES_ONLY) != 0) {
+ if (it == m_mirror_watchers.end()) {
+ ldout(m_cct, 20) << "filtering out non-mirror instance: " << w
+ << dendl;
+ continue;
+ }
+ }
+ m_watchers->push_back(w);
+ }
+ }
+ }
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::ListWatchersRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/ListWatchersRequest.h b/src/librbd/image/ListWatchersRequest.h
new file mode 100644
index 000000000..2c77254a7
--- /dev/null
+++ b/src/librbd/image/ListWatchersRequest.h
@@ -0,0 +1,82 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_LIST_WATCHERS_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_LIST_WATCHERS_REQUEST_H
+
+#include "include/rados/rados_types.hpp"
+
+#include <list>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+enum {
+ LIST_WATCHERS_FILTER_OUT_MY_INSTANCE = 1 << 0,
+ LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES = 1 << 1,
+ LIST_WATCHERS_MIRROR_INSTANCES_ONLY = 1 << 3,
+};
+
+template<typename ImageCtxT = ImageCtx>
+class ListWatchersRequest {
+public:
+ static ListWatchersRequest *create(ImageCtxT &image_ctx, int flags,
+ std::list<obj_watch_t> *watchers,
+ Context *on_finish) {
+ return new ListWatchersRequest(image_ctx, flags, watchers, on_finish);
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * LIST_IMAGE_WATCHERS
+ * |
+ * v
+ * LIST_MIRROR_WATCHERS (skip if not needed)
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ListWatchersRequest(ImageCtxT &image_ctx, int flags, std::list<obj_watch_t> *watchers,
+ Context *on_finish);
+
+ ImageCtxT& m_image_ctx;
+ int m_flags;
+ std::list<obj_watch_t> *m_watchers;
+ Context *m_on_finish;
+
+ CephContext *m_cct;
+ int m_ret_val;
+ bufferlist m_out_bl;
+ std::list<obj_watch_t> m_object_watchers;
+ std::list<obj_watch_t> m_mirror_watchers;
+
+ void list_image_watchers();
+ void handle_list_image_watchers(int r);
+
+ void list_mirror_watchers();
+ void handle_list_mirror_watchers(int r);
+
+ void finish(int r);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::ListWatchersRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_LIST_WATCHERS_REQUEST_H
diff --git a/src/librbd/image/OpenRequest.cc b/src/librbd/image/OpenRequest.cc
new file mode 100644
index 000000000..70008d712
--- /dev/null
+++ b/src/librbd/image/OpenRequest.cc
@@ -0,0 +1,727 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/OpenRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ConfigWatcher.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/PluginRegistry.h"
+#include "librbd/Utils.h"
+#include "librbd/cache/ObjectCacherObjectDispatch.h"
+#include "librbd/cache/WriteAroundObjectDispatch.h"
+#include "librbd/image/CloseRequest.h"
+#include "librbd/image/RefreshRequest.h"
+#include "librbd/image/SetSnapRequest.h"
+#include "librbd/io/SimpleSchedulerObjectDispatch.h"
+#include <boost/algorithm/string/predicate.hpp>
+#include "include/ceph_assert.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::OpenRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+OpenRequest<I>::OpenRequest(I *image_ctx, uint64_t flags,
+ Context *on_finish)
+ : m_image_ctx(image_ctx),
+ m_skip_open_parent_image(flags & OPEN_FLAG_SKIP_OPEN_PARENT),
+ m_on_finish(on_finish), m_error_result(0) {
+ if ((flags & OPEN_FLAG_OLD_FORMAT) != 0) {
+ m_image_ctx->old_format = true;
+ }
+ if ((flags & OPEN_FLAG_IGNORE_MIGRATING) != 0) {
+ m_image_ctx->ignore_migrating = true;
+ }
+}
+
+template <typename I>
+void OpenRequest<I>::send() {
+ if (m_image_ctx->old_format) {
+ send_v1_detect_header();
+ } else {
+ send_v2_detect_header();
+ }
+}
+
+template <typename I>
+void OpenRequest<I>::send_v1_detect_header() {
+ librados::ObjectReadOperation op;
+ op.stat(NULL, NULL, NULL);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_v1_detect_header>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(util::old_header_name(m_image_ctx->name),
+ comp, &op, &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v1_detect_header(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ if (*result != -ENOENT) {
+ lderr(cct) << "failed to stat image header: " << cpp_strerror(*result)
+ << dendl;
+ }
+ send_close_image(*result);
+ } else {
+ ldout(cct, 1) << "RBD image format 1 is deprecated. "
+ << "Please copy this image to image format 2." << dendl;
+
+ m_image_ctx->old_format = true;
+ m_image_ctx->header_oid = util::old_header_name(m_image_ctx->name);
+ m_image_ctx->apply_metadata({}, true);
+
+ send_refresh();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_detect_header() {
+ if (m_image_ctx->id.empty()) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ op.stat(NULL, NULL, NULL);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_v2_detect_header>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(util::id_obj_name(m_image_ctx->name),
+ comp, &op, &m_out_bl);
+ comp->release();
+ } else {
+ send_v2_get_name();
+ }
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_detect_header(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result == -ENOENT) {
+ send_v1_detect_header();
+ } else if (*result < 0) {
+ lderr(cct) << "failed to stat v2 image header: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ } else {
+ m_image_ctx->old_format = false;
+ send_v2_get_id();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_id() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_id_start(&op);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_v2_get_id>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(util::id_obj_name(m_image_ctx->name),
+ comp, &op, &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_id(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::get_id_finish(&it, &m_image_ctx->id);
+ }
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve image id: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ } else {
+ send_v2_get_initial_metadata();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_name() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::dir_get_name_start(&op, m_image_ctx->id);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_name>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(RBD_DIRECTORY, comp, &op, &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_name(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::dir_get_name_finish(&it, &m_image_ctx->name);
+ }
+ if (*result < 0 && *result != -ENOENT) {
+ lderr(cct) << "failed to retrieve name: "
+ << cpp_strerror(*result) << dendl;
+ send_close_image(*result);
+ } else if (*result == -ENOENT) {
+ // image does not exist in directory, look in the trash bin
+ ldout(cct, 10) << "image id " << m_image_ctx->id << " does not exist in "
+ << "rbd directory, searching in rbd trash..." << dendl;
+ send_v2_get_name_from_trash();
+ } else {
+ send_v2_get_initial_metadata();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_name_from_trash() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::trash_get_start(&op, m_image_ctx->id);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_name_from_trash>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(RBD_TRASH, comp, &op, &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_name_from_trash(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ cls::rbd::TrashImageSpec trash_spec;
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::trash_get_finish(&it, &trash_spec);
+ m_image_ctx->name = trash_spec.name;
+ }
+ if (*result < 0) {
+ if (*result == -EOPNOTSUPP) {
+ *result = -ENOENT;
+ }
+ if (*result == -ENOENT) {
+ ldout(cct, 5) << "failed to retrieve name for image id "
+ << m_image_ctx->id << dendl;
+ } else {
+ lderr(cct) << "failed to retrieve name from trash: "
+ << cpp_strerror(*result) << dendl;
+ }
+ send_close_image(*result);
+ } else {
+ send_v2_get_initial_metadata();
+ }
+
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_initial_metadata() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->old_format = false;
+ m_image_ctx->header_oid = util::header_name(m_image_ctx->id);
+
+ librados::ObjectReadOperation op;
+ cls_client::get_size_start(&op, CEPH_NOSNAP);
+ cls_client::get_object_prefix_start(&op);
+ cls_client::get_features_start(&op, true);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_initial_metadata>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_initial_metadata(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ auto it = m_out_bl.cbegin();
+ if (*result >= 0) {
+ uint64_t size;
+ *result = cls_client::get_size_finish(&it, &size, &m_image_ctx->order);
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_object_prefix_finish(&it,
+ &m_image_ctx->object_prefix);
+ }
+
+ if (*result >= 0) {
+ uint64_t incompatible_features;
+ *result = cls_client::get_features_finish(&it, &m_image_ctx->features,
+ &incompatible_features);
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve initial metadata: "
+ << cpp_strerror(*result) << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ if (m_image_ctx->test_features(RBD_FEATURE_STRIPINGV2)) {
+ send_v2_get_stripe_unit_count();
+ } else {
+ send_v2_get_create_timestamp();
+ }
+
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_stripe_unit_count() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_stripe_unit_count_start(&op);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_stripe_unit_count>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_stripe_unit_count(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::get_stripe_unit_count_finish(
+ &it, &m_image_ctx->stripe_unit, &m_image_ctx->stripe_count);
+ }
+
+ if (*result == -ENOEXEC || *result == -EINVAL) {
+ *result = 0;
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to read striping metadata: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ send_v2_get_create_timestamp();
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_create_timestamp() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_create_timestamp_start(&op);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_create_timestamp>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_create_timestamp(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::get_create_timestamp_finish(&it,
+ &m_image_ctx->create_timestamp);
+ }
+ if (*result < 0 && *result != -EOPNOTSUPP) {
+ lderr(cct) << "failed to retrieve create_timestamp: "
+ << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ send_v2_get_access_modify_timestamp();
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_access_modify_timestamp() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_access_timestamp_start(&op);
+ cls_client::get_modify_timestamp_start(&op);
+ //TODO: merge w/ create timestamp query after luminous EOLed
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_access_modify_timestamp>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_access_modify_timestamp(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::get_access_timestamp_finish(&it,
+ &m_image_ctx->access_timestamp);
+ if (*result == 0)
+ *result = cls_client::get_modify_timestamp_finish(&it,
+ &m_image_ctx->modify_timestamp);
+ }
+ if (*result < 0 && *result != -EOPNOTSUPP) {
+ lderr(cct) << "failed to retrieve access/modify_timestamp: "
+ << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ send_v2_get_data_pool();
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_v2_get_data_pool() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::get_data_pool_start(&op);
+
+ using klass = OpenRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_data_pool>(this);
+ m_out_bl.clear();
+ m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_v2_get_data_pool(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ int64_t data_pool_id = -1;
+ if (*result == 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::get_data_pool_finish(&it, &data_pool_id);
+ } else if (*result == -EOPNOTSUPP) {
+ *result = 0;
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to read data pool: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ if (data_pool_id != -1) {
+ *result = util::create_ioctx(m_image_ctx->md_ctx, "data pool", data_pool_id,
+ {}, &m_image_ctx->data_ctx);
+ if (*result < 0) {
+ if (*result != -ENOENT) {
+ send_close_image(*result);
+ return nullptr;
+ }
+ m_image_ctx->data_ctx.close();
+ } else {
+ m_image_ctx->rebuild_data_io_context();
+ }
+ } else {
+ data_pool_id = m_image_ctx->md_ctx.get_id();
+ }
+
+ m_image_ctx->init_layout(data_pool_id);
+ send_refresh();
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_refresh() {
+ m_image_ctx->init();
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_image_ctx->config_watcher = ConfigWatcher<I>::create(*m_image_ctx);
+ m_image_ctx->config_watcher->init();
+
+ using klass = OpenRequest<I>;
+ RefreshRequest<I> *req = RefreshRequest<I>::create(
+ *m_image_ctx, false, m_skip_open_parent_image,
+ create_context_callback<klass, &klass::handle_refresh>(this));
+ req->send();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_refresh(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to refresh image: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ send_init_plugin_registry();
+ return nullptr;
+}
+
+template <typename I>
+void OpenRequest<I>::send_init_plugin_registry() {
+ CephContext *cct = m_image_ctx->cct;
+
+ auto plugins = m_image_ctx->config.template get_val<std::string>(
+ "rbd_plugins");
+ ldout(cct, 10) << __func__ << ": plugins=" << plugins << dendl;
+
+ auto ctx = create_context_callback<
+ OpenRequest<I>, &OpenRequest<I>::handle_init_plugin_registry>(this);
+ m_image_ctx->plugin_registry->init(plugins, ctx);
+}
+
+template <typename I>
+Context* OpenRequest<I>::handle_init_plugin_registry(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to initialize plugin registry: "
+ << cpp_strerror(*result) << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ return send_init_cache(result);
+}
+
+template <typename I>
+Context *OpenRequest<I>::send_init_cache(int *result) {
+ if (!m_image_ctx->cache || m_image_ctx->child != nullptr ||
+ !m_image_ctx->data_ctx.is_valid()) {
+ return send_register_watch(result);
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ size_t max_dirty = m_image_ctx->config.template get_val<Option::size_t>(
+ "rbd_cache_max_dirty");
+ auto writethrough_until_flush = m_image_ctx->config.template get_val<bool>(
+ "rbd_cache_writethrough_until_flush");
+ auto cache_policy = m_image_ctx->config.template get_val<std::string>(
+ "rbd_cache_policy");
+ if (cache_policy == "writearound") {
+ auto cache = cache::WriteAroundObjectDispatch<I>::create(
+ m_image_ctx, max_dirty, writethrough_until_flush);
+ cache->init();
+
+ m_image_ctx->readahead.set_max_readahead_size(0);
+ } else if (cache_policy == "writethrough" || cache_policy == "writeback") {
+ if (cache_policy == "writethrough") {
+ max_dirty = 0;
+ }
+
+ auto cache = cache::ObjectCacherObjectDispatch<I>::create(
+ m_image_ctx, max_dirty, writethrough_until_flush);
+ cache->init();
+
+ // readahead requires the object cacher cache
+ m_image_ctx->readahead.set_trigger_requests(
+ m_image_ctx->config.template get_val<uint64_t>("rbd_readahead_trigger_requests"));
+ m_image_ctx->readahead.set_max_readahead_size(
+ m_image_ctx->config.template get_val<Option::size_t>("rbd_readahead_max_bytes"));
+ }
+ return send_register_watch(result);
+}
+
+template <typename I>
+Context *OpenRequest<I>::send_register_watch(int *result) {
+ if ((m_image_ctx->read_only_flags & IMAGE_READ_ONLY_FLAG_USER) != 0U) {
+ return send_set_snap(result);
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ using klass = OpenRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_register_watch>(this);
+ m_image_ctx->register_watch(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_register_watch(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result == -EPERM) {
+ ldout(cct, 5) << "user does not have write permission" << dendl;
+ send_close_image(*result);
+ return nullptr;
+ } else if (*result < 0) {
+ lderr(cct) << "failed to register watch: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ return send_set_snap(result);
+}
+
+template <typename I>
+Context *OpenRequest<I>::send_set_snap(int *result) {
+ if (m_image_ctx->snap_name.empty() &&
+ m_image_ctx->open_snap_id == CEPH_NOSNAP) {
+ *result = 0;
+ return finalize(*result);
+ }
+
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ uint64_t snap_id = CEPH_NOSNAP;
+ std::swap(m_image_ctx->open_snap_id, snap_id);
+ if (snap_id == CEPH_NOSNAP) {
+ std::shared_lock image_locker{m_image_ctx->image_lock};
+ snap_id = m_image_ctx->get_snap_id(m_image_ctx->snap_namespace,
+ m_image_ctx->snap_name);
+ }
+ if (snap_id == CEPH_NOSNAP) {
+ lderr(cct) << "failed to find snapshot " << m_image_ctx->snap_name << dendl;
+ send_close_image(-ENOENT);
+ return nullptr;
+ }
+
+ using klass = OpenRequest<I>;
+ SetSnapRequest<I> *req = SetSnapRequest<I>::create(
+ *m_image_ctx, snap_id,
+ create_context_callback<klass, &klass::handle_set_snap>(this));
+ req->send();
+ return nullptr;
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_set_snap(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to set image snapshot: " << cpp_strerror(*result)
+ << dendl;
+ send_close_image(*result);
+ return nullptr;
+ }
+
+ return finalize(*result);
+}
+
+template <typename I>
+Context *OpenRequest<I>::finalize(int r) {
+ if (r == 0) {
+ auto io_scheduler_cfg =
+ m_image_ctx->config.template get_val<std::string>("rbd_io_scheduler");
+
+ if (io_scheduler_cfg == "simple" && !m_image_ctx->read_only) {
+ auto io_scheduler =
+ io::SimpleSchedulerObjectDispatch<I>::create(m_image_ctx);
+ io_scheduler->init();
+ }
+ }
+
+ return m_on_finish;
+}
+
+template <typename I>
+void OpenRequest<I>::send_close_image(int error_result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_error_result = error_result;
+
+ using klass = OpenRequest<I>;
+ Context *ctx = create_context_callback<klass, &klass::handle_close_image>(
+ this);
+ CloseRequest<I> *req = CloseRequest<I>::create(m_image_ctx, ctx);
+ req->send();
+}
+
+template <typename I>
+Context *OpenRequest<I>::handle_close_image(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to close image: " << cpp_strerror(*result) << dendl;
+ }
+ if (m_error_result < 0) {
+ *result = m_error_result;
+ }
+ return m_on_finish;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::OpenRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/OpenRequest.h b/src/librbd/image/OpenRequest.h
new file mode 100644
index 000000000..0fe218a39
--- /dev/null
+++ b/src/librbd/image/OpenRequest.h
@@ -0,0 +1,149 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_OPEN_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_OPEN_REQUEST_H
+
+#include "include/buffer.h"
+#include <map>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class OpenRequest {
+public:
+ static OpenRequest *create(ImageCtxT *image_ctx, uint64_t flags,
+ Context *on_finish) {
+ return new OpenRequest(image_ctx, flags, on_finish);
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * | (v1)
+ * |-----> V1_DETECT_HEADER
+ * | |
+ * | \-------------------------------\
+ * | (v2) |
+ * \-----> V2_DETECT_HEADER |
+ * | |
+ * v |
+ * V2_GET_ID|NAME |
+ * | |
+ * v (skip if have name) |
+ * V2_GET_NAME_FROM_TRASH |
+ * | |
+ * v |
+ * V2_GET_INITIAL_METADATA |
+ * | |
+ * v |
+ * V2_GET_STRIPE_UNIT_COUNT (skip if |
+ * | disabled) |
+ * v |
+ * V2_GET_CREATE_TIMESTAMP |
+ * | |
+ * v |
+ * V2_GET_ACCESS_MODIFIY_TIMESTAMP |
+ * | |
+ * v |
+ * V2_GET_DATA_POOL --------------> REFRESH
+ * |
+ * v
+ * INIT_PLUGIN_REGISTRY
+ * |
+ * v
+ * INIT_CACHE
+ * |
+ * v
+ * REGISTER_WATCH (skip if
+ * | read-only)
+ * v
+ * SET_SNAP (skip if no snap)
+ * |
+ * v
+ * <finish>
+ * ^
+ * (on error) |
+ * * * * * * * > CLOSE ------------------------/
+ *
+ * @endverbatim
+ */
+
+ OpenRequest(ImageCtxT *image_ctx, uint64_t flags, Context *on_finish);
+
+ ImageCtxT *m_image_ctx;
+ bool m_skip_open_parent_image;
+ Context *m_on_finish;
+
+ bufferlist m_out_bl;
+ int m_error_result;
+
+ void send_v1_detect_header();
+ Context *handle_v1_detect_header(int *result);
+
+ void send_v2_detect_header();
+ Context *handle_v2_detect_header(int *result);
+
+ void send_v2_get_id();
+ Context *handle_v2_get_id(int *result);
+
+ void send_v2_get_name();
+ Context *handle_v2_get_name(int *result);
+
+ void send_v2_get_name_from_trash();
+ Context *handle_v2_get_name_from_trash(int *result);
+
+ void send_v2_get_initial_metadata();
+ Context *handle_v2_get_initial_metadata(int *result);
+
+ void send_v2_get_stripe_unit_count();
+ Context *handle_v2_get_stripe_unit_count(int *result);
+
+ void send_v2_get_create_timestamp();
+ Context *handle_v2_get_create_timestamp(int *result);
+
+ void send_v2_get_access_modify_timestamp();
+ Context *handle_v2_get_access_modify_timestamp(int *result);
+
+ void send_v2_get_data_pool();
+ Context *handle_v2_get_data_pool(int *result);
+
+ void send_refresh();
+ Context *handle_refresh(int *result);
+
+ void send_init_plugin_registry();
+ Context* handle_init_plugin_registry(int *result);
+
+ Context *send_init_cache(int *result);
+
+ Context *send_register_watch(int *result);
+ Context *handle_register_watch(int *result);
+
+ Context *send_set_snap(int *result);
+ Context *handle_set_snap(int *result);
+
+ Context *finalize(int r);
+
+ void send_close_image(int error_result);
+ Context *handle_close_image(int *result);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::OpenRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_OPEN_REQUEST_H
diff --git a/src/librbd/image/PreRemoveRequest.cc b/src/librbd/image/PreRemoveRequest.cc
new file mode 100644
index 000000000..fa4141834
--- /dev/null
+++ b/src/librbd/image/PreRemoveRequest.cc
@@ -0,0 +1,348 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/PreRemoveRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/Utils.h"
+#include "librbd/exclusive_lock/StandardPolicy.h"
+#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/journal/DisabledPolicy.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::PreRemoveRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+namespace {
+
+bool auto_delete_snapshot(const SnapInfo& snap_info) {
+ auto snap_namespace_type = cls::rbd::get_snap_namespace_type(
+ snap_info.snap_namespace);
+ switch (snap_namespace_type) {
+ case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_TRASH:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool ignore_snapshot(const SnapInfo& snap_info) {
+ auto snap_namespace_type = cls::rbd::get_snap_namespace_type(
+ snap_info.snap_namespace);
+ switch (snap_namespace_type) {
+ case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_MIRROR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // anonymous namespace
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+void PreRemoveRequest<I>::send() {
+ auto cct = m_image_ctx->cct;
+ if (m_image_ctx->operations_disabled) {
+ lderr(cct) << "image operations disabled due to unsupported op features"
+ << dendl;
+ finish(-EROFS);
+ return;
+ }
+
+ acquire_exclusive_lock();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::acquire_exclusive_lock() {
+ // lock for write for set_exclusive_lock_policy()
+ std::unique_lock owner_locker{m_image_ctx->owner_lock};
+ if (m_image_ctx->exclusive_lock == nullptr) {
+ owner_locker.unlock();
+ validate_image_removal();
+ return;
+ }
+
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ // refuse to release exclusive lock when (in the midst of) removing
+ // the image
+ m_image_ctx->set_exclusive_lock_policy(
+ new exclusive_lock::StandardPolicy<I>(m_image_ctx));
+
+ // do not attempt to open the journal when removing the image in case
+ // it's corrupt
+ if (m_image_ctx->test_features(RBD_FEATURE_JOURNALING)) {
+ std::unique_lock image_locker{m_image_ctx->image_lock};
+ m_image_ctx->set_journal_policy(new journal::DisabledPolicy());
+ }
+
+ m_exclusive_lock = m_image_ctx->exclusive_lock;
+
+ auto ctx = create_context_callback<
+ PreRemoveRequest<I>,
+ &PreRemoveRequest<I>::handle_exclusive_lock>(this, m_exclusive_lock);
+ m_exclusive_lock->acquire_lock(ctx);
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_exclusive_lock(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0 || !m_image_ctx->exclusive_lock->is_lock_owner()) {
+ if (!m_force) {
+ lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl;
+ finish(-EBUSY);
+ } else {
+ ldout(cct, 5) << "cannot obtain exclusive lock - "
+ << "proceeding due to force flag set" << dendl;
+ shut_down_exclusive_lock();
+ }
+ return;
+ }
+
+ validate_image_removal();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::shut_down_exclusive_lock() {
+ std::shared_lock owner_locker{m_image_ctx->owner_lock};
+ if (m_image_ctx->exclusive_lock == nullptr) {
+ owner_locker.unlock();
+ validate_image_removal();
+ return;
+ }
+
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ auto ctx = create_context_callback<
+ PreRemoveRequest<I>,
+ &PreRemoveRequest<I>::handle_shut_down_exclusive_lock>(this);
+
+ m_exclusive_lock = m_image_ctx->exclusive_lock;
+ m_exclusive_lock->shut_down(ctx);
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_shut_down_exclusive_lock(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ m_exclusive_lock->put();
+ m_exclusive_lock = nullptr;
+
+ if (r < 0) {
+ lderr(cct) << "error shutting down exclusive lock: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ ceph_assert(m_image_ctx->exclusive_lock == nullptr);
+ validate_image_removal();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::validate_image_removal() {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ if (!m_image_ctx->ignore_migrating &&
+ m_image_ctx->test_features(RBD_FEATURE_MIGRATING)) {
+ lderr(cct) << "image in migration state - not removing" << dendl;
+ finish(-EBUSY);
+ return;
+ }
+
+ check_image_snaps();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_image_snaps() {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ m_image_ctx->image_lock.lock_shared();
+ for (auto& snap_info : m_image_ctx->snap_info) {
+ if (auto_delete_snapshot(snap_info.second)) {
+ m_snap_infos.insert(snap_info);
+ } else if (!ignore_snapshot(snap_info.second)) {
+ m_image_ctx->image_lock.unlock_shared();
+
+ ldout(cct, 5) << "image has snapshots - not removing" << dendl;
+ finish(-ENOTEMPTY);
+ return;
+ }
+ }
+ m_image_ctx->image_lock.unlock_shared();
+
+ list_image_watchers();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::list_image_watchers() {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ int flags = LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
+ LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES;
+ auto ctx = create_context_callback<
+ PreRemoveRequest<I>,
+ &PreRemoveRequest<I>::handle_list_image_watchers>(this);
+ auto req = ListWatchersRequest<I>::create(*m_image_ctx, flags, &m_watchers,
+ ctx);
+ req->send();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_list_image_watchers(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "error listing image watchers: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ check_image_watchers();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_image_watchers() {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ if (!m_watchers.empty()) {
+ lderr(cct) << "image has watchers - not removing" << dendl;
+ finish(-EBUSY);
+ return;
+ }
+
+ check_group();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_group() {
+ if (m_image_ctx->old_format) {
+ finish(0);
+ return;
+ }
+
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ librados::ObjectReadOperation op;
+ librbd::cls_client::image_group_get_start(&op);
+
+ auto rados_completion = create_rados_callback<
+ PreRemoveRequest<I>, &PreRemoveRequest<I>::handle_check_group>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid,
+ rados_completion, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_check_group(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ cls::rbd::GroupSpec s;
+ if (r == 0) {
+ auto it = m_out_bl.cbegin();
+ r = librbd::cls_client::image_group_get_finish(&it, &s);
+ }
+ if (r < 0 && r != -EOPNOTSUPP) {
+ lderr(cct) << "error fetching group for image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ if (s.is_valid()) {
+ lderr(cct) << "image is in a group - not removing" << dendl;
+ finish(-EMLINK);
+ return;
+ }
+
+ remove_snapshot();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::remove_snapshot() {
+ if (m_snap_infos.empty()) {
+ finish(0);
+ return;
+ }
+
+ auto cct = m_image_ctx->cct;
+ auto snap_id = m_snap_infos.begin()->first;
+ auto& snap_info = m_snap_infos.begin()->second;
+ ldout(cct, 20) << "snap_id=" << snap_id << ", "
+ << "snap_name=" << snap_info.name << dendl;
+
+ std::shared_lock owner_lock{m_image_ctx->owner_lock};
+ auto ctx = create_context_callback<
+ PreRemoveRequest<I>, &PreRemoveRequest<I>::handle_remove_snapshot>(this);
+ auto req = librbd::operation::SnapshotRemoveRequest<I>::create(
+ *m_image_ctx, snap_info.snap_namespace, snap_info.name,
+ snap_id, ctx);
+ req->send();
+
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_remove_snapshot(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (r == -EBUSY) {
+ ldout(cct, 5) << "skipping attached child" << dendl;
+ if (m_ret_val == 0) {
+ m_ret_val = -ECHILD;
+ }
+ } else if (r < 0 && r != -ENOENT) {
+ auto snap_id = m_snap_infos.begin()->first;
+ lderr(cct) << "failed to auto-prune snapshot " << snap_id << ": "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ ceph_assert(!m_snap_infos.empty());
+ m_snap_infos.erase(m_snap_infos.begin());
+
+ remove_snapshot();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::finish(int r) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << "r=" << r << dendl;
+
+ if (m_ret_val == 0) {
+ m_ret_val = r;
+ }
+
+ m_on_finish->complete(m_ret_val);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::PreRemoveRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/PreRemoveRequest.h b/src/librbd/image/PreRemoveRequest.h
new file mode 100644
index 000000000..06b3bf2f8
--- /dev/null
+++ b/src/librbd/image/PreRemoveRequest.h
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
+
+#include "include/rados/librados.hpp"
+#include "include/buffer.h"
+#include "librbd/ImageCtx.h"
+#include <list>
+#include <map>
+
+class Context;
+
+namespace librbd {
+namespace image {
+
+template <typename ImageCtxT>
+class PreRemoveRequest {
+public:
+
+ static PreRemoveRequest *create(ImageCtxT *image_ctx, bool force,
+ Context *on_finish) {
+ return new PreRemoveRequest(image_ctx, force, on_finish);
+ }
+
+ PreRemoveRequest(ImageCtxT *image_ctx, bool force, Context *on_finish)
+ : m_image_ctx(image_ctx), m_force(force), m_on_finish(on_finish) {
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * | (skip if
+ * v not needed) (error)
+ * ACQUIRE EXCLUSIVE LOCK * * * * * * > SHUT DOWN EXCLUSIVE LOCK
+ * | |
+ * v |
+ * CHECK IMAGE WATCHERS <------------------/
+ * |
+ * v
+ * CHECK GROUP
+ * |
+ * | /------\
+ * | | |
+ * v v |
+ * REMOVE SNAPS ----/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ ImageCtxT* m_image_ctx;
+ bool m_force;
+ Context* m_on_finish;
+
+ decltype(m_image_ctx->exclusive_lock) m_exclusive_lock = nullptr;
+
+ bufferlist m_out_bl;
+ std::list<obj_watch_t> m_watchers;
+
+ std::map<uint64_t, SnapInfo> m_snap_infos;
+ int m_ret_val = 0;
+
+ void acquire_exclusive_lock();
+ void handle_exclusive_lock(int r);
+
+ void shut_down_exclusive_lock();
+ void handle_shut_down_exclusive_lock(int r);
+
+ void validate_image_removal();
+ void check_image_snaps();
+
+ void list_image_watchers();
+ void handle_list_image_watchers(int r);
+
+ void check_image_watchers();
+
+ void check_group();
+ void handle_check_group(int r);
+
+ void remove_snapshot();
+ void handle_remove_snapshot(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::PreRemoveRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
diff --git a/src/librbd/image/RefreshParentRequest.cc b/src/librbd/image/RefreshParentRequest.cc
new file mode 100644
index 000000000..348226c39
--- /dev/null
+++ b/src/librbd/image/RefreshParentRequest.cc
@@ -0,0 +1,244 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/RefreshParentRequest.h"
+#include "include/rados/librados.hpp"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+#include "librbd/migration/OpenSourceImageRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::RefreshParentRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_async_context_callback;
+using util::create_context_callback;
+
+template <typename I>
+RefreshParentRequest<I>::RefreshParentRequest(
+ I &child_image_ctx, const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info, Context *on_finish)
+ : m_child_image_ctx(child_image_ctx), m_parent_md(parent_md),
+ m_migration_info(migration_info), m_on_finish(on_finish),
+ m_parent_image_ctx(nullptr), m_parent_snap_id(CEPH_NOSNAP),
+ m_error_result(0) {
+}
+
+template <typename I>
+bool RefreshParentRequest<I>::is_refresh_required(
+ I &child_image_ctx, const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info) {
+ ceph_assert(ceph_mutex_is_locked(child_image_ctx.image_lock));
+ return (is_open_required(child_image_ctx, parent_md, migration_info) ||
+ is_close_required(child_image_ctx, parent_md, migration_info));
+}
+
+template <typename I>
+bool RefreshParentRequest<I>::is_close_required(
+ I &child_image_ctx, const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info) {
+ return (child_image_ctx.parent != nullptr &&
+ !does_parent_exist(child_image_ctx, parent_md, migration_info));
+}
+
+template <typename I>
+bool RefreshParentRequest<I>::is_open_required(
+ I &child_image_ctx, const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info) {
+ return (does_parent_exist(child_image_ctx, parent_md, migration_info) &&
+ (child_image_ctx.parent == nullptr ||
+ child_image_ctx.parent->md_ctx.get_id() != parent_md.spec.pool_id ||
+ child_image_ctx.parent->md_ctx.get_namespace() !=
+ parent_md.spec.pool_namespace ||
+ child_image_ctx.parent->id != parent_md.spec.image_id ||
+ child_image_ctx.parent->snap_id != parent_md.spec.snap_id));
+}
+
+template <typename I>
+bool RefreshParentRequest<I>::does_parent_exist(
+ I &child_image_ctx, const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info) {
+ if (child_image_ctx.child != nullptr &&
+ child_image_ctx.child->migration_info.empty() && parent_md.overlap == 0) {
+ // intermediate, non-migrating images should only open their parent if they
+ // overlap
+ return false;
+ }
+
+ return (parent_md.spec.pool_id > -1 && parent_md.overlap > 0) ||
+ !migration_info.empty();
+}
+
+template <typename I>
+void RefreshParentRequest<I>::send() {
+ if (is_open_required(m_child_image_ctx, m_parent_md, m_migration_info)) {
+ send_open_parent();
+ } else {
+ // parent will be closed (if necessary) during finalize
+ send_complete(0);
+ }
+}
+
+template <typename I>
+void RefreshParentRequest<I>::apply() {
+ ceph_assert(ceph_mutex_is_wlocked(m_child_image_ctx.image_lock));
+ std::swap(m_child_image_ctx.parent, m_parent_image_ctx);
+}
+
+template <typename I>
+void RefreshParentRequest<I>::finalize(Context *on_finish) {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_on_finish = on_finish;
+ if (m_parent_image_ctx != nullptr) {
+ send_close_parent();
+ } else {
+ send_complete(0);
+ }
+}
+
+template <typename I>
+void RefreshParentRequest<I>::send_open_parent() {
+ ceph_assert(m_parent_md.spec.pool_id >= 0);
+
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ if (!m_migration_info.empty()) {
+ auto ctx = create_async_context_callback(
+ m_child_image_ctx, create_context_callback<
+ RefreshParentRequest<I>,
+ &RefreshParentRequest<I>::handle_open_parent, false>(this));
+ auto req = migration::OpenSourceImageRequest<I>::create(
+ m_child_image_ctx.md_ctx, &m_child_image_ctx, m_parent_md.spec.snap_id,
+ m_migration_info, &m_parent_image_ctx, ctx);
+ req->send();
+ return;
+ }
+
+ librados::IoCtx parent_io_ctx;
+ int r = util::create_ioctx(m_child_image_ctx.md_ctx, "parent image",
+ m_parent_md.spec.pool_id,
+ m_parent_md.spec.pool_namespace, &parent_io_ctx);
+ if (r < 0) {
+ send_complete(r);
+ return;
+ }
+
+ m_parent_image_ctx = new I("", m_parent_md.spec.image_id,
+ m_parent_md.spec.snap_id, parent_io_ctx, true);
+ m_parent_image_ctx->child = &m_child_image_ctx;
+
+ // set rados flags for reading the parent image
+ if (m_child_image_ctx.config.template get_val<bool>("rbd_balance_parent_reads")) {
+ m_parent_image_ctx->set_read_flag(librados::OPERATION_BALANCE_READS);
+ } else if (m_child_image_ctx.config.template get_val<bool>("rbd_localize_parent_reads")) {
+ m_parent_image_ctx->set_read_flag(librados::OPERATION_LOCALIZE_READS);
+ }
+
+ auto ctx = create_async_context_callback(
+ m_child_image_ctx, create_context_callback<
+ RefreshParentRequest<I>,
+ &RefreshParentRequest<I>::handle_open_parent, false>(this));
+ m_parent_image_ctx->state->open(0U, ctx);
+}
+
+template <typename I>
+Context *RefreshParentRequest<I>::handle_open_parent(int *result) {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << " r=" << *result << dendl;
+
+ save_result(result);
+ if (*result < 0) {
+ lderr(cct) << "failed to open parent image: " << cpp_strerror(*result)
+ << dendl;
+
+ // image already closed by open state machine
+ m_parent_image_ctx = nullptr;
+ }
+
+ return m_on_finish;
+}
+
+template <typename I>
+void RefreshParentRequest<I>::send_close_parent() {
+ ceph_assert(m_parent_image_ctx != nullptr);
+
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ auto ctx = create_async_context_callback(
+ m_child_image_ctx, create_context_callback<
+ RefreshParentRequest<I>,
+ &RefreshParentRequest<I>::handle_close_parent, false>(this));
+ m_parent_image_ctx->state->close(ctx);
+}
+
+template <typename I>
+Context *RefreshParentRequest<I>::handle_close_parent(int *result) {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << " r=" << *result << dendl;
+
+ m_parent_image_ctx = nullptr;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to close parent image: " << cpp_strerror(*result)
+ << dendl;
+ }
+
+ send_reset_existence_cache();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshParentRequest<I>::send_reset_existence_cache() {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ Context *ctx = create_async_context_callback(
+ m_child_image_ctx, create_context_callback<
+ RefreshParentRequest<I>,
+ &RefreshParentRequest<I>::handle_reset_existence_cache, false>(this));
+ m_child_image_ctx.io_object_dispatcher->reset_existence_cache(ctx);
+}
+
+template <typename I>
+Context *RefreshParentRequest<I>::handle_reset_existence_cache(int *result) {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << " r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to reset object existence cache: "
+ << cpp_strerror(*result) << dendl;
+ }
+
+ if (m_error_result < 0) {
+ // propagate errors from opening the image
+ *result = m_error_result;
+ } else {
+ *result = 0;
+ }
+ return m_on_finish;
+}
+
+template <typename I>
+void RefreshParentRequest<I>::send_complete(int r) {
+ CephContext *cct = m_child_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ m_on_finish->complete(r);
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::RefreshParentRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/RefreshParentRequest.h b/src/librbd/image/RefreshParentRequest.h
new file mode 100644
index 000000000..086d8ec1b
--- /dev/null
+++ b/src/librbd/image/RefreshParentRequest.h
@@ -0,0 +1,109 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_REFRESH_PARENT_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_REFRESH_PARENT_REQUEST_H
+
+#include "include/int_types.h"
+#include "librbd/Types.h"
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class RefreshParentRequest {
+public:
+ static RefreshParentRequest *create(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info,
+ Context *on_finish) {
+ return new RefreshParentRequest(child_image_ctx, parent_md, migration_info,
+ on_finish);
+ }
+
+ static bool is_refresh_required(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info);
+
+ void send();
+ void apply();
+ void finalize(Context *on_finish);
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * | (open required)
+ * |----------------> OPEN_PARENT * * * * * * * * * * * * * * *
+ * | | *
+ * | v (on error) *
+ * \----------------> <apply> *
+ * | *
+ * | (close required) *
+ * |-----------------> CLOSE_PARENT *
+ * | | *
+ * | v *
+ * | RESET_EXISTENCE *
+ * | | *
+ * | v *
+ * \-----------------> <finish> < * * * *
+ *
+ * @endverbatim
+ */
+
+ RefreshParentRequest(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info, Context *on_finish);
+
+ ImageCtxT &m_child_image_ctx;
+ ParentImageInfo m_parent_md;
+ MigrationInfo m_migration_info;
+ Context *m_on_finish;
+
+ ImageCtxT *m_parent_image_ctx;
+ uint64_t m_parent_snap_id;
+
+ int m_error_result;
+
+ static bool is_close_required(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info);
+ static bool is_open_required(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info);
+ static bool does_parent_exist(ImageCtxT &child_image_ctx,
+ const ParentImageInfo &parent_md,
+ const MigrationInfo &migration_info);
+
+ void send_open_parent();
+ Context *handle_open_parent(int *result);
+
+ void send_close_parent();
+ Context *handle_close_parent(int *result);
+
+ void send_reset_existence_cache();
+ Context *handle_reset_existence_cache(int *result);
+
+ void send_complete(int r);
+
+ void save_result(int *result) {
+ if (m_error_result == 0 && *result < 0) {
+ m_error_result = *result;
+ }
+ }
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::RefreshParentRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_REFRESH_PARENT_REQUEST_H
diff --git a/src/librbd/image/RefreshRequest.cc b/src/librbd/image/RefreshRequest.cc
new file mode 100644
index 000000000..24159c55b
--- /dev/null
+++ b/src/librbd/image/RefreshRequest.cc
@@ -0,0 +1,1575 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/ceph_assert.h"
+
+#include "librbd/image/RefreshRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/lock/cls_lock_client.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/Journal.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/Utils.h"
+#include "librbd/deep_copy/Utils.h"
+#include "librbd/image/GetMetadataRequest.h"
+#include "librbd/image/RefreshParentRequest.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ImageDispatcherInterface.h"
+#include "librbd/journal/Policy.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::RefreshRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_rados_callback;
+using util::create_async_context_callback;
+using util::create_context_callback;
+
+template <typename I>
+RefreshRequest<I>::RefreshRequest(I &image_ctx, bool acquiring_lock,
+ bool skip_open_parent, Context *on_finish)
+ : m_image_ctx(image_ctx), m_acquiring_lock(acquiring_lock),
+ m_skip_open_parent_image(skip_open_parent),
+ m_on_finish(create_async_context_callback(m_image_ctx, on_finish)),
+ m_error_result(0), m_flush_aio(false), m_exclusive_lock(nullptr),
+ m_object_map(nullptr), m_journal(nullptr), m_refresh_parent(nullptr) {
+ m_pool_metadata_io_ctx.dup(image_ctx.md_ctx);
+ m_pool_metadata_io_ctx.set_namespace("");
+}
+
+template <typename I>
+RefreshRequest<I>::~RefreshRequest() {
+ // these require state machine to close
+ ceph_assert(m_exclusive_lock == nullptr);
+ ceph_assert(m_object_map == nullptr);
+ ceph_assert(m_journal == nullptr);
+ ceph_assert(m_refresh_parent == nullptr);
+ ceph_assert(!m_blocked_writes);
+}
+
+template <typename I>
+void RefreshRequest<I>::send() {
+ if (m_image_ctx.old_format) {
+ send_v1_read_header();
+ } else {
+ send_v2_get_mutable_metadata();
+ }
+}
+
+template <typename I>
+void RefreshRequest<I>::send_get_migration_header() {
+ if (m_image_ctx.ignore_migrating) {
+ m_migration_spec = {};
+ if (m_image_ctx.old_format) {
+ send_v1_get_snapshots();
+ } else {
+ send_v2_get_metadata();
+ }
+ return;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::migration_get_start(&op);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_get_migration_header>(this);
+ m_out_bl.clear();
+ m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_get_migration_header(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result >= 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::migration_get_finish(&it, &m_migration_spec);
+ } else if (*result == -ENOENT) {
+ ldout(cct, 5) << this << " " << __func__ << ": no migration header found"
+ << ", retrying" << dendl;
+ send();
+ return nullptr;
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve migration header: "
+ << cpp_strerror(*result) << dendl;
+ return m_on_finish;
+ }
+
+ switch(m_migration_spec.header_type) {
+ case cls::rbd::MIGRATION_HEADER_TYPE_SRC:
+ if (!m_read_only) {
+ lderr(cct) << "image being migrated" << dendl;
+ *result = -EROFS;
+ return m_on_finish;
+ }
+ ldout(cct, 1) << this << " " << __func__ << ": migrating to: "
+ << m_migration_spec << dendl;
+ break;
+ case cls::rbd::MIGRATION_HEADER_TYPE_DST:
+ ldout(cct, 1) << this << " " << __func__ << ": migrating from: "
+ << m_migration_spec << dendl;
+ switch (m_migration_spec.state) {
+ case cls::rbd::MIGRATION_STATE_PREPARING:
+ ldout(cct, 5) << this << " " << __func__ << ": current migration state: "
+ << m_migration_spec.state << ", retrying" << dendl;
+ send();
+ return nullptr;
+ case cls::rbd::MIGRATION_STATE_PREPARED:
+ case cls::rbd::MIGRATION_STATE_EXECUTING:
+ case cls::rbd::MIGRATION_STATE_EXECUTED:
+ break;
+ case cls::rbd::MIGRATION_STATE_ABORTING:
+ if (!m_read_only) {
+ lderr(cct) << this << " " << __func__ << ": migration is being aborted"
+ << dendl;
+ *result = -EROFS;
+ return m_on_finish;
+ }
+ break;
+ default:
+ lderr(cct) << this << " " << __func__ << ": migration is in an "
+ << "unexpected state" << dendl;
+ *result = -EINVAL;
+ return m_on_finish;
+ }
+ break;
+ default:
+ ldout(cct, 1) << this << " " << __func__ << ": migration type "
+ << m_migration_spec.header_type << dendl;
+ *result = -EBADMSG;
+ return m_on_finish;
+ }
+
+ if (m_image_ctx.old_format) {
+ send_v1_get_snapshots();
+ } else {
+ send_v2_get_metadata();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v1_read_header() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v1_read_header>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v1_read_header(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": " << "r=" << *result << dendl;
+
+ rbd_obj_header_ondisk v1_header;
+ bool migrating = false;
+ if (*result < 0) {
+ return m_on_finish;
+ } else if (m_out_bl.length() < sizeof(v1_header)) {
+ lderr(cct) << "v1 header too small" << dendl;
+ *result = -EIO;
+ return m_on_finish;
+ } else if (memcmp(RBD_HEADER_TEXT, m_out_bl.c_str(),
+ sizeof(RBD_HEADER_TEXT)) != 0) {
+ if (memcmp(RBD_MIGRATE_HEADER_TEXT, m_out_bl.c_str(),
+ sizeof(RBD_MIGRATE_HEADER_TEXT)) == 0) {
+ ldout(cct, 1) << this << " " << __func__ << ": migration v1 header detected"
+ << dendl;
+ migrating = true;
+ } else {
+ lderr(cct) << "unrecognized v1 header" << dendl;
+ *result = -ENXIO;
+ return m_on_finish;
+ }
+ }
+
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ m_read_only = m_image_ctx.read_only;
+ m_read_only_flags = m_image_ctx.read_only_flags;
+ }
+
+ memcpy(&v1_header, m_out_bl.c_str(), sizeof(v1_header));
+ m_order = v1_header.options.order;
+ m_size = v1_header.image_size;
+ m_object_prefix = v1_header.block_name;
+ if (migrating) {
+ send_get_migration_header();
+ } else {
+ m_migration_spec = {};
+ send_v1_get_snapshots();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v1_get_snapshots() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::old_snapshot_list_start(&op);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v1_get_snapshots>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v1_get_snapshots(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": " << "r=" << *result << dendl;
+
+ std::vector<std::string> snap_names;
+ std::vector<uint64_t> snap_sizes;
+ if (*result >= 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::old_snapshot_list_finish(&it, &snap_names,
+ &snap_sizes, &m_snapc);
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve v1 snapshots: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ if (!m_snapc.is_valid()) {
+ lderr(cct) << "v1 image snap context is invalid" << dendl;
+ *result = -EIO;
+ return m_on_finish;
+ }
+
+ m_snap_infos.clear();
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ m_snap_infos.push_back({m_snapc.snaps[i],
+ {cls::rbd::UserSnapshotNamespace{}},
+ snap_names[i], snap_sizes[i], {}, 0});
+ }
+
+ send_v1_get_locks();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v1_get_locks() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ rados::cls::lock::get_lock_info_start(&op, RBD_LOCK_NAME);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v1_get_locks>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v1_get_locks(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": "
+ << "r=" << *result << dendl;
+
+ if (*result >= 0) {
+ auto it = m_out_bl.cbegin();
+ ClsLockType lock_type;
+ *result = rados::cls::lock::get_lock_info_finish(&it, &m_lockers,
+ &lock_type, &m_lock_tag);
+ if (*result >= 0) {
+ m_exclusive_locked = (lock_type == ClsLockType::EXCLUSIVE);
+ }
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve locks: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ send_v1_apply();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v1_apply() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // ensure we are not in a rados callback when applying updates
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v1_apply>(this);
+ m_image_ctx.op_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v1_apply(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ apply();
+ return send_flush_aio();
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_mutable_metadata() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ uint64_t snap_id;
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ snap_id = m_image_ctx.snap_id;
+ m_read_only = m_image_ctx.read_only;
+ m_read_only_flags = m_image_ctx.read_only_flags;
+ }
+
+ // mask out the non-primary read-only flag since its state can change
+ bool read_only = (
+ ((m_read_only_flags & ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0) ||
+ (snap_id != CEPH_NOSNAP));
+ librados::ObjectReadOperation op;
+ cls_client::get_size_start(&op, CEPH_NOSNAP);
+ cls_client::get_features_start(&op, read_only);
+ cls_client::get_flags_start(&op, CEPH_NOSNAP);
+ cls_client::get_snapcontext_start(&op);
+ rados::cls::lock::get_lock_info_start(&op, RBD_LOCK_NAME);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_mutable_metadata>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_mutable_metadata(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": "
+ << "r=" << *result << dendl;
+
+ auto it = m_out_bl.cbegin();
+ if (*result >= 0) {
+ uint8_t order;
+ *result = cls_client::get_size_finish(&it, &m_size, &order);
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_features_finish(&it, &m_features,
+ &m_incompatible_features);
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_flags_finish(&it, &m_flags);
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_snapcontext_finish(&it, &m_snapc);
+ }
+
+ if (*result >= 0) {
+ ClsLockType lock_type;
+ *result = rados::cls::lock::get_lock_info_finish(&it, &m_lockers,
+ &lock_type, &m_lock_tag);
+ if (*result >= 0) {
+ m_exclusive_locked = (lock_type == ClsLockType::EXCLUSIVE);
+ }
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve mutable metadata: "
+ << cpp_strerror(*result) << dendl;
+ return m_on_finish;
+ }
+
+ uint64_t unsupported = m_incompatible_features & ~RBD_FEATURES_ALL;
+ if (unsupported != 0ULL) {
+ lderr(cct) << "Image uses unsupported features: " << unsupported << dendl;
+ *result = -ENOSYS;
+ return m_on_finish;
+ }
+
+ if (!m_snapc.is_valid()) {
+ lderr(cct) << "image snap context is invalid!" << dendl;
+ *result = -EIO;
+ return m_on_finish;
+ }
+
+ if (m_acquiring_lock && (m_features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) {
+ ldout(cct, 5) << "ignoring dynamically disabled exclusive lock" << dendl;
+ m_features |= RBD_FEATURE_EXCLUSIVE_LOCK;
+ m_incomplete_update = true;
+ } else {
+ m_incomplete_update = false;
+ }
+
+ if (((m_incompatible_features & RBD_FEATURE_NON_PRIMARY) != 0U) &&
+ ((m_read_only_flags & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) == 0U) &&
+ ((m_image_ctx.read_only_mask & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0U)) {
+ // implies we opened a non-primary image in R/W mode
+ ldout(cct, 5) << "adding non-primary read-only image flag" << dendl;
+ m_read_only_flags |= IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+ } else if ((((m_incompatible_features & RBD_FEATURE_NON_PRIMARY) == 0U) ||
+ ((m_image_ctx.read_only_mask &
+ IMAGE_READ_ONLY_FLAG_NON_PRIMARY) == 0U)) &&
+ ((m_read_only_flags & IMAGE_READ_ONLY_FLAG_NON_PRIMARY) != 0U)) {
+ ldout(cct, 5) << "removing non-primary read-only image flag" << dendl;
+ m_read_only_flags &= ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY;
+ }
+ m_read_only = (m_read_only_flags != 0U);
+
+ m_legacy_parent = false;
+ send_v2_get_parent();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_parent() {
+ // NOTE: remove support when Mimic is EOLed
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": legacy=" << m_legacy_parent
+ << dendl;
+
+ librados::ObjectReadOperation op;
+ if (!m_legacy_parent) {
+ cls_client::parent_get_start(&op);
+ cls_client::parent_overlap_get_start(&op, CEPH_NOSNAP);
+ } else {
+ cls_client::get_parent_start(&op, CEPH_NOSNAP);
+ }
+
+ auto aio_comp = create_rados_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_v2_get_parent>(this);
+ m_out_bl.clear();
+ m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, aio_comp, &op,
+ &m_out_bl);
+ aio_comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_parent(int *result) {
+ // NOTE: remove support when Mimic is EOLed
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ auto it = m_out_bl.cbegin();
+ if (!m_legacy_parent) {
+ if (*result >= 0) {
+ *result = cls_client::parent_get_finish(&it, &m_parent_md.spec);
+ }
+
+ std::optional<uint64_t> parent_overlap;
+ if (*result >= 0) {
+ *result = cls_client::parent_overlap_get_finish(&it, &parent_overlap);
+ }
+
+ if (*result >= 0) {
+ if (parent_overlap) {
+ m_parent_md.overlap = *parent_overlap;
+ m_head_parent_overlap = true;
+ } else {
+ m_parent_md.overlap = 0;
+ m_head_parent_overlap = false;
+ }
+ }
+ } else if (*result >= 0) {
+ *result = cls_client::get_parent_finish(&it, &m_parent_md.spec,
+ &m_parent_md.overlap);
+ m_head_parent_overlap = true;
+ }
+
+ if (*result == -EOPNOTSUPP && !m_legacy_parent) {
+ ldout(cct, 10) << "retrying using legacy parent method" << dendl;
+ m_legacy_parent = true;
+ send_v2_get_parent();
+ return nullptr;
+ } else if (*result < 0) {
+ lderr(cct) << "failed to retrieve parent: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ if ((m_features & RBD_FEATURE_MIGRATING) != 0) {
+ ldout(cct, 1) << "migrating feature set" << dendl;
+ send_get_migration_header();
+ } else {
+ m_migration_spec = {};
+ send_v2_get_metadata();
+ }
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_metadata() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ auto ctx = create_context_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_v2_get_metadata>(this);
+ m_metadata.clear();
+ auto req = GetMetadataRequest<I>::create(
+ m_image_ctx.md_ctx, m_image_ctx.header_oid, true,
+ ImageCtx::METADATA_CONF_PREFIX, ImageCtx::METADATA_CONF_PREFIX, 0U,
+ &m_metadata, ctx);
+ req->send();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_metadata(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve metadata: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ send_v2_get_pool_metadata();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_pool_metadata() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ auto ctx = create_context_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_v2_get_pool_metadata>(this);
+ auto req = GetMetadataRequest<I>::create(
+ m_pool_metadata_io_ctx, RBD_INFO, true, ImageCtx::METADATA_CONF_PREFIX,
+ ImageCtx::METADATA_CONF_PREFIX, 0U, &m_metadata, ctx);
+ req->send();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_pool_metadata(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve pool metadata: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ bool thread_safe = m_image_ctx.image_watcher->is_unregistered();
+ m_image_ctx.apply_metadata(m_metadata, thread_safe);
+
+ send_v2_get_op_features();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_op_features() {
+ if ((m_features & RBD_FEATURE_OPERATIONS) == 0LL) {
+ m_op_features = 0;
+ send_v2_get_group();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::op_features_get_start(&op);
+
+ librados::AioCompletion *comp = create_rados_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_v2_get_op_features>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_op_features(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": "
+ << "r=" << *result << dendl;
+
+ // -EOPNOTSUPP handler not required since feature bit implies OSD
+ // supports the method
+ if (*result >= 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::op_features_get_finish(&it, &m_op_features);
+ }
+
+ if (*result < 0) {
+ lderr(cct) << "failed to retrieve op features: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ send_v2_get_group();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_group() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ cls_client::image_group_get_start(&op);
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_group>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_group(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": "
+ << "r=" << *result << dendl;
+
+ if (*result >= 0) {
+ auto it = m_out_bl.cbegin();
+ *result = cls_client::image_group_get_finish(&it, &m_group_spec);
+ }
+
+ if (*result == -EOPNOTSUPP) {
+ m_group_spec = {};
+ } else if (*result < 0) {
+ lderr(cct) << "failed to retrieve group: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ m_legacy_snapshot = LEGACY_SNAPSHOT_DISABLED;
+ send_v2_get_snapshots();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_get_snapshots() {
+ m_snap_infos.resize(m_snapc.snaps.size());
+ m_snap_flags.resize(m_snapc.snaps.size());
+ m_snap_parents.resize(m_snapc.snaps.size());
+ m_snap_protection.resize(m_snapc.snaps.size());
+
+ if (m_snapc.snaps.empty()) {
+ send_v2_refresh_parent();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ librados::ObjectReadOperation op;
+ for (auto snap_id : m_snapc.snaps) {
+ if (m_legacy_snapshot != LEGACY_SNAPSHOT_DISABLED) {
+ /// NOTE: remove after Luminous is retired
+ cls_client::get_snapshot_name_start(&op, snap_id);
+ cls_client::get_size_start(&op, snap_id);
+ if (m_legacy_snapshot != LEGACY_SNAPSHOT_ENABLED_NO_TIMESTAMP) {
+ cls_client::get_snapshot_timestamp_start(&op, snap_id);
+ }
+ } else {
+ cls_client::snapshot_get_start(&op, snap_id);
+ }
+
+ if (m_legacy_parent) {
+ cls_client::get_parent_start(&op, snap_id);
+ } else {
+ cls_client::parent_overlap_get_start(&op, snap_id);
+ }
+
+ cls_client::get_flags_start(&op, snap_id);
+ cls_client::get_protection_status_start(&op, snap_id);
+ }
+
+ using klass = RefreshRequest<I>;
+ librados::AioCompletion *comp = create_rados_callback<
+ klass, &klass::handle_v2_get_snapshots>(this);
+ m_out_bl.clear();
+ int r = m_image_ctx.md_ctx.aio_operate(m_image_ctx.header_oid, comp, &op,
+ &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_get_snapshots(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": " << "r=" << *result << dendl;
+
+ auto it = m_out_bl.cbegin();
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ if (m_legacy_snapshot != LEGACY_SNAPSHOT_DISABLED) {
+ /// NOTE: remove after Luminous is retired
+ std::string snap_name;
+ if (*result >= 0) {
+ *result = cls_client::get_snapshot_name_finish(&it, &snap_name);
+ }
+
+ uint64_t snap_size;
+ if (*result >= 0) {
+ uint8_t order;
+ *result = cls_client::get_size_finish(&it, &snap_size, &order);
+ }
+
+ utime_t snap_timestamp;
+ if (*result >= 0 &&
+ m_legacy_snapshot != LEGACY_SNAPSHOT_ENABLED_NO_TIMESTAMP) {
+ /// NOTE: remove after Jewel is retired
+ *result = cls_client::get_snapshot_timestamp_finish(&it,
+ &snap_timestamp);
+ }
+
+ if (*result >= 0) {
+ m_snap_infos[i] = {m_snapc.snaps[i],
+ {cls::rbd::UserSnapshotNamespace{}},
+ snap_name, snap_size, snap_timestamp, 0};
+ }
+ } else if (*result >= 0) {
+ *result = cls_client::snapshot_get_finish(&it, &m_snap_infos[i]);
+ }
+
+ if (*result >= 0) {
+ if (m_legacy_parent) {
+ *result = cls_client::get_parent_finish(&it, &m_snap_parents[i].spec,
+ &m_snap_parents[i].overlap);
+ } else {
+ std::optional<uint64_t> parent_overlap;
+ *result = cls_client::parent_overlap_get_finish(&it, &parent_overlap);
+ if (*result >= 0) {
+ if (parent_overlap && m_parent_md.spec.pool_id > -1) {
+ m_snap_parents[i].spec = m_parent_md.spec;
+ m_snap_parents[i].overlap = *parent_overlap;
+ } else {
+ m_snap_parents[i] = {};
+ }
+ }
+ }
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_flags_finish(&it, &m_snap_flags[i]);
+ }
+
+ if (*result >= 0) {
+ *result = cls_client::get_protection_status_finish(
+ &it, &m_snap_protection[i]);
+ }
+
+ if (*result < 0) {
+ break;
+ }
+ }
+
+ if (*result == -ENOENT && m_enoent_retries++ < MAX_ENOENT_RETRIES) {
+ ldout(cct, 10) << "out-of-sync snapshot state detected, retrying" << dendl;
+ send_v2_get_mutable_metadata();
+ return nullptr;
+ } else if (m_legacy_snapshot == LEGACY_SNAPSHOT_DISABLED &&
+ *result == -EOPNOTSUPP) {
+ ldout(cct, 10) << "retrying using legacy snapshot methods" << dendl;
+ m_legacy_snapshot = LEGACY_SNAPSHOT_ENABLED;
+ send_v2_get_snapshots();
+ return nullptr;
+ } else if (m_legacy_snapshot == LEGACY_SNAPSHOT_ENABLED &&
+ *result == -EOPNOTSUPP) {
+ ldout(cct, 10) << "retrying using legacy snapshot methods (jewel)" << dendl;
+ m_legacy_snapshot = LEGACY_SNAPSHOT_ENABLED_NO_TIMESTAMP;
+ send_v2_get_snapshots();
+ return nullptr;
+ } else if (*result < 0) {
+ lderr(cct) << "failed to retrieve snapshots: " << cpp_strerror(*result)
+ << dendl;
+ return m_on_finish;
+ }
+
+ send_v2_refresh_parent();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_refresh_parent() {
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+
+ ParentImageInfo parent_md;
+ MigrationInfo migration_info;
+ int r = get_parent_info(m_image_ctx.snap_id, &parent_md, &migration_info);
+ if (!m_skip_open_parent_image && (r < 0 ||
+ RefreshParentRequest<I>::is_refresh_required(m_image_ctx, parent_md,
+ migration_info))) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_refresh_parent>(this);
+ m_refresh_parent = RefreshParentRequest<I>::create(
+ m_image_ctx, parent_md, migration_info, ctx);
+ }
+ }
+
+ if (m_refresh_parent != nullptr) {
+ m_refresh_parent->send();
+ } else {
+ send_v2_init_exclusive_lock();
+ }
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_refresh_parent(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result == -ENOENT && m_enoent_retries++ < MAX_ENOENT_RETRIES) {
+ ldout(cct, 10) << "out-of-sync parent info detected, retrying" << dendl;
+ ceph_assert(m_refresh_parent != nullptr);
+ delete m_refresh_parent;
+ m_refresh_parent = nullptr;
+ send_v2_get_mutable_metadata();
+ return nullptr;
+ } else if (*result < 0) {
+ lderr(cct) << "failed to refresh parent image: " << cpp_strerror(*result)
+ << dendl;
+ save_result(result);
+ send_v2_apply();
+ return nullptr;
+ }
+
+ send_v2_init_exclusive_lock();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_init_exclusive_lock() {
+ if ((m_features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0 ||
+ m_read_only || !m_image_ctx.snap_name.empty() ||
+ m_image_ctx.exclusive_lock != nullptr) {
+ send_v2_open_object_map();
+ return;
+ }
+
+ // implies exclusive lock dynamically enabled or image open in-progress
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // TODO need safe shut down
+ m_exclusive_lock = m_image_ctx.create_exclusive_lock();
+
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_init_exclusive_lock>(this);
+
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ m_exclusive_lock->init(m_features, ctx);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_init_exclusive_lock(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to initialize exclusive lock: "
+ << cpp_strerror(*result) << dendl;
+ save_result(result);
+ }
+
+ // object map and journal will be opened when exclusive lock is
+ // acquired (if features are enabled)
+ send_v2_apply();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_open_journal() {
+ bool journal_disabled = (
+ (m_features & RBD_FEATURE_JOURNALING) == 0 ||
+ m_read_only ||
+ !m_image_ctx.snap_name.empty() ||
+ m_image_ctx.journal != nullptr ||
+ m_image_ctx.exclusive_lock == nullptr ||
+ !m_image_ctx.exclusive_lock->is_lock_owner());
+ bool journal_disabled_by_policy;
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ journal_disabled_by_policy = (
+ !journal_disabled &&
+ m_image_ctx.get_journal_policy()->journal_disabled());
+ }
+
+ if (journal_disabled || journal_disabled_by_policy) {
+ // journal dynamically enabled -- doesn't own exclusive lock
+ if ((m_features & RBD_FEATURE_JOURNALING) != 0 &&
+ !journal_disabled_by_policy &&
+ m_image_ctx.exclusive_lock != nullptr &&
+ m_image_ctx.journal == nullptr) {
+ auto ctx = new LambdaContext([this](int) {
+ send_v2_block_writes();
+ });
+ m_image_ctx.exclusive_lock->set_require_lock(
+ true, librbd::io::DIRECTION_BOTH, ctx);
+ return;
+ }
+
+ send_v2_block_writes();
+ return;
+ }
+
+ // implies journal dynamically enabled since ExclusiveLock will init
+ // the journal upon acquiring the lock
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_open_journal>(this);
+
+ // TODO need safe close
+ m_journal = m_image_ctx.create_journal();
+ m_journal->open(ctx);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_open_journal(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to initialize journal: " << cpp_strerror(*result)
+ << dendl;
+ save_result(result);
+ }
+
+ send_v2_block_writes();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_block_writes() {
+ bool disabled_journaling = false;
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ disabled_journaling = ((m_features & RBD_FEATURE_EXCLUSIVE_LOCK) != 0 &&
+ (m_features & RBD_FEATURE_JOURNALING) == 0 &&
+ m_image_ctx.journal != nullptr);
+ }
+
+ if (!disabled_journaling) {
+ send_v2_apply();
+ return;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // we need to block writes temporarily to avoid in-flight journal
+ // writes
+ m_blocked_writes = true;
+ Context *ctx = create_context_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_v2_block_writes>(this);
+
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ m_image_ctx.io_image_dispatcher->block_writes(ctx);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_block_writes(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to block writes: " << cpp_strerror(*result)
+ << dendl;
+ save_result(result);
+ }
+ send_v2_apply();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_open_object_map() {
+ if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0 ||
+ m_image_ctx.object_map != nullptr ||
+ (m_image_ctx.snap_name.empty() &&
+ (m_read_only ||
+ m_image_ctx.exclusive_lock == nullptr ||
+ !m_image_ctx.exclusive_lock->is_lock_owner()))) {
+ send_v2_open_journal();
+ return;
+ }
+
+ // implies object map dynamically enabled or image open in-progress
+ // since SetSnapRequest loads the object map for a snapshot and
+ // ExclusiveLock loads the object map for HEAD
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ if (m_image_ctx.snap_name.empty()) {
+ m_object_map = m_image_ctx.create_object_map(CEPH_NOSNAP);
+ } else {
+ for (size_t snap_idx = 0; snap_idx < m_snap_infos.size(); ++snap_idx) {
+ if (m_snap_infos[snap_idx].name == m_image_ctx.snap_name) {
+ m_object_map = m_image_ctx.create_object_map(
+ m_snapc.snaps[snap_idx].val);
+ break;
+ }
+ }
+
+ if (m_object_map == nullptr) {
+ lderr(cct) << "failed to locate snapshot: " << m_image_ctx.snap_name
+ << dendl;
+ send_v2_open_journal();
+ return;
+ }
+ }
+
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_open_object_map>(this);
+ m_object_map->open(ctx);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_open_object_map(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to open object map: " << cpp_strerror(*result)
+ << dendl;
+ m_object_map->put();
+ m_object_map = nullptr;
+
+ if (*result != -EFBIG) {
+ save_result(result);
+ }
+ }
+
+ send_v2_open_journal();
+ return nullptr;
+}
+
+template <typename I>
+void RefreshRequest<I>::send_v2_apply() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // ensure we are not in a rados callback when applying updates
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_apply>(this);
+ m_image_ctx.op_work_queue->queue(ctx, 0);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_apply(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ apply();
+
+ return send_v2_finalize_refresh_parent();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::send_v2_finalize_refresh_parent() {
+ if (m_refresh_parent == nullptr) {
+ return send_v2_shut_down_exclusive_lock();
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_finalize_refresh_parent>(this);
+ m_refresh_parent->finalize(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_finalize_refresh_parent(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ ceph_assert(m_refresh_parent != nullptr);
+ delete m_refresh_parent;
+ m_refresh_parent = nullptr;
+
+ return send_v2_shut_down_exclusive_lock();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::send_v2_shut_down_exclusive_lock() {
+ if (m_exclusive_lock == nullptr) {
+ return send_v2_close_journal();
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // exclusive lock feature was dynamically disabled. in-flight IO will be
+ // flushed and in-flight requests will be canceled before releasing lock
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_shut_down_exclusive_lock>(this);
+ m_exclusive_lock->shut_down(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_shut_down_exclusive_lock(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to shut down exclusive lock: "
+ << cpp_strerror(*result) << dendl;
+ save_result(result);
+ }
+
+ {
+ std::unique_lock owner_locker{m_image_ctx.owner_lock};
+ ceph_assert(m_image_ctx.exclusive_lock == nullptr);
+ }
+
+ ceph_assert(m_exclusive_lock != nullptr);
+ m_exclusive_lock->put();
+ m_exclusive_lock = nullptr;
+
+ return send_v2_close_journal();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::send_v2_close_journal() {
+ if (m_journal == nullptr) {
+ return send_v2_close_object_map();
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // journal feature was dynamically disabled
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_close_journal>(this);
+ m_journal->close(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_close_journal(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ save_result(result);
+ lderr(cct) << "failed to close journal: " << cpp_strerror(*result)
+ << dendl;
+ }
+
+ ceph_assert(m_journal != nullptr);
+ m_journal->put();
+ m_journal = nullptr;
+
+ ceph_assert(m_blocked_writes);
+ m_blocked_writes = false;
+
+ m_image_ctx.io_image_dispatcher->unblock_writes();
+ return send_v2_close_object_map();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::send_v2_close_object_map() {
+ if (m_object_map == nullptr) {
+ return send_flush_aio();
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ // object map was dynamically disabled
+ using klass = RefreshRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_v2_close_object_map>(this);
+ m_object_map->close(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_v2_close_object_map(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to close object map: " << cpp_strerror(*result)
+ << dendl;
+ }
+
+ ceph_assert(m_object_map != nullptr);
+
+ m_object_map->put();
+ m_object_map = nullptr;
+
+ return send_flush_aio();
+}
+
+template <typename I>
+Context *RefreshRequest<I>::send_flush_aio() {
+ if (m_incomplete_update && m_error_result == 0) {
+ // if this was a partial refresh, notify ImageState
+ m_error_result = -ERESTART;
+ }
+
+ if (m_flush_aio) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ auto ctx = create_context_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_flush_aio>(this);
+ auto aio_comp = io::AioCompletion::create_and_start(
+ ctx, util::get_image_ctx(&m_image_ctx), io::AIO_TYPE_FLUSH);
+ auto req = io::ImageDispatchSpec::create_flush(
+ m_image_ctx, io::IMAGE_DISPATCH_LAYER_REFRESH, aio_comp,
+ io::FLUSH_SOURCE_REFRESH, {});
+ req->send();
+ return nullptr;
+ } else if (m_error_result < 0) {
+ // propagate saved error back to caller
+ Context *ctx = create_context_callback<
+ RefreshRequest<I>, &RefreshRequest<I>::handle_error>(this);
+ m_image_ctx.op_work_queue->queue(ctx, 0);
+ return nullptr;
+ }
+
+ return m_on_finish;
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_flush_aio(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to flush pending AIO: " << cpp_strerror(*result)
+ << dendl;
+ }
+
+ return handle_error(result);
+}
+
+template <typename I>
+Context *RefreshRequest<I>::handle_error(int *result) {
+ if (m_error_result < 0) {
+ *result = m_error_result;
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+ }
+ return m_on_finish;
+}
+
+template <typename I>
+void RefreshRequest<I>::apply() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 20) << this << " " << __func__ << dendl;
+
+ std::scoped_lock locker{m_image_ctx.owner_lock, m_image_ctx.image_lock};
+
+ m_image_ctx.read_only_flags = m_read_only_flags;
+ m_image_ctx.read_only = m_read_only;
+ m_image_ctx.size = m_size;
+ m_image_ctx.lockers = m_lockers;
+ m_image_ctx.lock_tag = m_lock_tag;
+ m_image_ctx.exclusive_locked = m_exclusive_locked;
+
+ std::map<uint64_t, uint64_t> migration_reverse_snap_seq;
+
+ if (m_image_ctx.old_format) {
+ m_image_ctx.order = m_order;
+ m_image_ctx.features = 0;
+ m_image_ctx.flags = 0;
+ m_image_ctx.op_features = 0;
+ m_image_ctx.operations_disabled = false;
+ m_image_ctx.object_prefix = std::move(m_object_prefix);
+ m_image_ctx.init_layout(m_image_ctx.md_ctx.get_id());
+ } else {
+ // HEAD revision doesn't have a defined overlap so it's only
+ // applicable to snapshots
+ if (!m_head_parent_overlap) {
+ m_parent_md = {};
+ }
+
+ m_image_ctx.features = m_features;
+ m_image_ctx.flags = m_flags;
+ m_image_ctx.op_features = m_op_features;
+ m_image_ctx.operations_disabled = (
+ (m_op_features & ~RBD_OPERATION_FEATURES_ALL) != 0ULL);
+ m_image_ctx.group_spec = m_group_spec;
+
+ bool migration_info_valid;
+ int r = get_migration_info(&m_image_ctx.parent_md,
+ &m_image_ctx.migration_info,
+ &migration_info_valid);
+ ceph_assert(r == 0); // validated in refresh parent step
+
+ if (migration_info_valid) {
+ for (auto it : m_image_ctx.migration_info.snap_map) {
+ migration_reverse_snap_seq[it.second.front()] = it.first;
+ }
+ } else {
+ m_image_ctx.parent_md = m_parent_md;
+ m_image_ctx.migration_info = {};
+ }
+
+ librados::Rados rados(m_image_ctx.md_ctx);
+ int8_t require_osd_release;
+ r = rados.get_min_compatible_osd(&require_osd_release);
+ if (r == 0 && require_osd_release >= CEPH_RELEASE_OCTOPUS) {
+ m_image_ctx.enable_sparse_copyup = true;
+ }
+ }
+
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ std::vector<librados::snap_t>::const_iterator it = std::find(
+ m_image_ctx.snaps.begin(), m_image_ctx.snaps.end(),
+ m_snapc.snaps[i].val);
+ if (it == m_image_ctx.snaps.end()) {
+ m_flush_aio = true;
+ ldout(cct, 20) << "new snapshot id=" << m_snapc.snaps[i].val
+ << " name=" << m_snap_infos[i].name
+ << " size=" << m_snap_infos[i].image_size
+ << dendl;
+ }
+ }
+
+ m_image_ctx.snaps.clear();
+ m_image_ctx.snap_info.clear();
+ m_image_ctx.snap_ids.clear();
+ auto overlap = m_image_ctx.parent_md.overlap;
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ uint64_t flags = m_image_ctx.old_format ? 0 : m_snap_flags[i];
+ uint8_t protection_status = m_image_ctx.old_format ?
+ static_cast<uint8_t>(RBD_PROTECTION_STATUS_UNPROTECTED) :
+ m_snap_protection[i];
+ ParentImageInfo parent;
+ if (!m_image_ctx.old_format) {
+ if (!m_image_ctx.migration_info.empty()) {
+ parent = m_image_ctx.parent_md;
+ auto it = migration_reverse_snap_seq.find(m_snapc.snaps[i].val);
+ if (it != migration_reverse_snap_seq.end()) {
+ parent.spec.snap_id = it->second;
+ parent.overlap = m_snap_infos[i].image_size;
+ } else {
+ overlap = std::min(overlap, m_snap_infos[i].image_size);
+ parent.overlap = overlap;
+ }
+ } else {
+ parent = m_snap_parents[i];
+ }
+ }
+ m_image_ctx.add_snap(m_snap_infos[i].snapshot_namespace,
+ m_snap_infos[i].name, m_snapc.snaps[i].val,
+ m_snap_infos[i].image_size, parent,
+ protection_status, flags,
+ m_snap_infos[i].timestamp);
+ }
+ m_image_ctx.parent_md.overlap = std::min(overlap, m_image_ctx.size);
+ m_image_ctx.snapc = m_snapc;
+
+ if (m_image_ctx.snap_id != CEPH_NOSNAP &&
+ m_image_ctx.get_snap_id(m_image_ctx.snap_namespace,
+ m_image_ctx.snap_name) != m_image_ctx.snap_id) {
+ lderr(cct) << "tried to read from a snapshot that no longer exists: "
+ << m_image_ctx.snap_name << dendl;
+ m_image_ctx.snap_exists = false;
+ }
+
+ if (m_refresh_parent != nullptr) {
+ m_refresh_parent->apply();
+ }
+ if (m_image_ctx.data_ctx.is_valid()) {
+ m_image_ctx.data_ctx.selfmanaged_snap_set_write_ctx(m_image_ctx.snapc.seq,
+ m_image_ctx.snaps);
+ m_image_ctx.rebuild_data_io_context();
+ }
+
+ // handle dynamically enabled / disabled features
+ if (m_image_ctx.exclusive_lock != nullptr &&
+ !m_image_ctx.test_features(RBD_FEATURE_EXCLUSIVE_LOCK,
+ m_image_ctx.image_lock)) {
+ // disabling exclusive lock will automatically handle closing
+ // object map and journaling
+ ceph_assert(m_exclusive_lock == nullptr);
+ m_exclusive_lock = m_image_ctx.exclusive_lock;
+ } else {
+ if (m_exclusive_lock != nullptr) {
+ ceph_assert(m_image_ctx.exclusive_lock == nullptr);
+ std::swap(m_exclusive_lock, m_image_ctx.exclusive_lock);
+ }
+ if (!m_image_ctx.test_features(RBD_FEATURE_JOURNALING,
+ m_image_ctx.image_lock)) {
+ if (!m_image_ctx.clone_copy_on_read && m_image_ctx.journal != nullptr) {
+ m_image_ctx.exclusive_lock->unset_require_lock(io::DIRECTION_READ);
+ }
+ std::swap(m_journal, m_image_ctx.journal);
+ } else if (m_journal != nullptr) {
+ std::swap(m_journal, m_image_ctx.journal);
+ }
+ if (!m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP,
+ m_image_ctx.image_lock) ||
+ m_object_map != nullptr) {
+ std::swap(m_object_map, m_image_ctx.object_map);
+ }
+ }
+}
+
+template <typename I>
+int RefreshRequest<I>::get_parent_info(uint64_t snap_id,
+ ParentImageInfo *parent_md,
+ MigrationInfo *migration_info) {
+ bool migration_info_valid;
+ int r = get_migration_info(parent_md, migration_info, &migration_info_valid);
+ if (r < 0) {
+ return r;
+ }
+
+ if (migration_info_valid) {
+ return 0;
+ } else if (snap_id == CEPH_NOSNAP) {
+ *parent_md = m_parent_md;
+ *migration_info = {};
+ return 0;
+ } else {
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ if (m_snapc.snaps[i].val == snap_id) {
+ *parent_md = m_snap_parents[i];
+ *migration_info = {};
+ return 0;
+ }
+ }
+ }
+ return -ENOENT;
+}
+
+template <typename I>
+int RefreshRequest<I>::get_migration_info(ParentImageInfo *parent_md,
+ MigrationInfo *migration_info,
+ bool* migration_info_valid) {
+ CephContext *cct = m_image_ctx.cct;
+ if (m_migration_spec.header_type != cls::rbd::MIGRATION_HEADER_TYPE_DST ||
+ (m_migration_spec.state != cls::rbd::MIGRATION_STATE_PREPARED &&
+ m_migration_spec.state != cls::rbd::MIGRATION_STATE_EXECUTING &&
+ m_migration_spec.state != cls::rbd::MIGRATION_STATE_ABORTING)) {
+ if (m_migration_spec.header_type != cls::rbd::MIGRATION_HEADER_TYPE_SRC &&
+ m_migration_spec.pool_id != -1 &&
+ m_migration_spec.state != cls::rbd::MIGRATION_STATE_EXECUTED) {
+ lderr(cct) << this << " " << __func__ << ": invalid migration spec"
+ << dendl;
+ return -EINVAL;
+ }
+
+ *migration_info_valid = false;
+ return 0;
+ }
+
+ if (!m_migration_spec.source_spec.empty()) {
+ // use special pool id just to indicate a parent (migration source image)
+ // exists
+ parent_md->spec.pool_id = std::numeric_limits<int64_t>::max();
+ parent_md->spec.pool_namespace = "";
+ parent_md->spec.image_id = "";
+ } else {
+ parent_md->spec.pool_id = m_migration_spec.pool_id;
+ parent_md->spec.pool_namespace = m_migration_spec.pool_namespace;
+ parent_md->spec.image_id = m_migration_spec.image_id;
+ }
+ parent_md->spec.snap_id = CEPH_NOSNAP;
+ parent_md->overlap = std::min(m_size, m_migration_spec.overlap);
+
+ auto snap_seqs = m_migration_spec.snap_seqs;
+ // If new snapshots have been created on destination image after
+ // migration stared, map the source CEPH_NOSNAP to the earliest of
+ // these snapshots.
+ snapid_t snap_id = snap_seqs.empty() ? 0 : snap_seqs.rbegin()->second;
+ auto it = std::upper_bound(m_snapc.snaps.rbegin(), m_snapc.snaps.rend(),
+ snap_id);
+ if (it != m_snapc.snaps.rend()) {
+ snap_seqs[CEPH_NOSNAP] = *it;
+ } else {
+ snap_seqs[CEPH_NOSNAP] = CEPH_NOSNAP;
+ }
+
+ std::set<uint64_t> snap_ids;
+ for (auto& it : snap_seqs) {
+ snap_ids.insert(it.second);
+ }
+ uint64_t overlap = snap_ids.find(CEPH_NOSNAP) != snap_ids.end() ?
+ parent_md->overlap : 0;
+ for (size_t i = 0; i < m_snapc.snaps.size(); ++i) {
+ if (snap_ids.find(m_snapc.snaps[i].val) != snap_ids.end()) {
+ overlap = std::max(overlap, m_snap_infos[i].image_size);
+ }
+ }
+
+ *migration_info = {m_migration_spec.pool_id, m_migration_spec.pool_namespace,
+ m_migration_spec.image_name, m_migration_spec.image_id,
+ m_migration_spec.source_spec, {}, overlap,
+ m_migration_spec.flatten};
+ *migration_info_valid = true;
+
+ deep_copy::util::compute_snap_map(m_image_ctx.cct, 0, CEPH_NOSNAP, {},
+ snap_seqs, &migration_info->snap_map);
+ return 0;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::RefreshRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/RefreshRequest.h b/src/librbd/image/RefreshRequest.h
new file mode 100644
index 000000000..42f4b4669
--- /dev/null
+++ b/src/librbd/image/RefreshRequest.h
@@ -0,0 +1,275 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_REFRESH_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_REFRESH_REQUEST_H
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+#include "include/utime.h"
+#include "common/snap_types.h"
+#include "cls/lock/cls_lock_types.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Types.h"
+#include <string>
+#include <vector>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template<typename> class RefreshParentRequest;
+
+template<typename ImageCtxT = ImageCtx>
+class RefreshRequest {
+public:
+ static constexpr int MAX_ENOENT_RETRIES = 10;
+
+ static RefreshRequest *create(ImageCtxT &image_ctx, bool acquiring_lock,
+ bool skip_open_parent, Context *on_finish) {
+ return new RefreshRequest(image_ctx, acquiring_lock, skip_open_parent,
+ on_finish);
+ }
+
+ RefreshRequest(ImageCtxT &image_ctx, bool acquiring_lock,
+ bool skip_open_parent, Context *on_finish);
+ ~RefreshRequest();
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start> < * * * * * * * * * * * * * * * * * * * * * * * * * * (ENOENT)
+ * ^ | *
+ * * | (v1) *
+ * * |-----> V1_READ_HEADER -------------> GET_MIGRATION_HEADER (skip if not
+ * * | | migrating)
+ * * | (v2) v
+ * * \-----> V2_GET_MUTABLE_METADATA V1_GET_SNAPSHOTS
+ * * * | |
+ * * * | -EOPNOTSUPP v
+ * * * | * * * V1_GET_LOCKS
+ * * * | * * |
+ * * * v v * v
+ * * * V2_GET_PARENT <apply>
+ * * * | |
+ * * v |
+ * * * * * * GET_MIGRATION_HEADER (skip if not |
+ * (ENOENT) | migrating) |
+ * v |
+ * * V2_GET_METADATA |
+ * * | |
+ * * v |
+ * * V2_GET_POOL_METADATA |
+ * * | |
+ * * v (skip if not enabled) |
+ * * V2_GET_OP_FEATURES |
+ * * | |
+ * * v |
+ * * V2_GET_GROUP |
+ * * | |
+ * * | -EOPNOTSUPP |
+ * * | * * * |
+ * * | * * |
+ * * v v * |
+ * * * V2_GET_SNAPSHOTS (skip if no snaps) |
+ * (ENOENT) | |
+ * * v |
+ * * * V2_REFRESH_PARENT (skip if no parent or |
+ * (ENOENT) | refresh not needed) |
+ * v |
+ * V2_INIT_EXCLUSIVE_LOCK (skip if lock |
+ * | active or disabled) |
+ * v |
+ * V2_OPEN_OBJECT_MAP (skip if map |
+ * | active or disabled) |
+ * v |
+ * V2_OPEN_JOURNAL (skip if journal |
+ * | active or disabled) |
+ * v |
+ * V2_BLOCK_WRITES (skip if journal not |
+ * | disabled) |
+ * v |
+ * <apply> |
+ * | |
+ * v |
+ * V2_FINALIZE_REFRESH_PARENT (skip if refresh |
+ * | not needed) |
+ * (error) v |
+ * * * * * > V2_SHUT_DOWN_EXCLUSIVE_LOCK (skip if lock |
+ * | active or enabled) |
+ * v |
+ * V2_CLOSE_JOURNAL (skip if journal inactive |
+ * | or enabled) |
+ * v |
+ * V2_CLOSE_OBJECT_MAP (skip if map inactive |
+ * | or enabled) |
+ * | |
+ * \-------------------\/--------------------/
+ * |
+ * v
+ * FLUSH (skip if no new
+ * | snapshots)
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ enum LegacySnapshot {
+ LEGACY_SNAPSHOT_DISABLED,
+ LEGACY_SNAPSHOT_ENABLED,
+ LEGACY_SNAPSHOT_ENABLED_NO_TIMESTAMP
+ };
+
+ ImageCtxT &m_image_ctx;
+ bool m_acquiring_lock;
+ bool m_skip_open_parent_image;
+ Context *m_on_finish;
+
+ cls::rbd::MigrationSpec m_migration_spec;
+ int m_error_result;
+ bool m_flush_aio;
+ decltype(m_image_ctx.exclusive_lock) m_exclusive_lock;
+ decltype(m_image_ctx.object_map) m_object_map;
+ decltype(m_image_ctx.journal) m_journal;
+ RefreshParentRequest<ImageCtxT> *m_refresh_parent;
+
+ bufferlist m_out_bl;
+
+ bool m_legacy_parent = false;
+ LegacySnapshot m_legacy_snapshot = LEGACY_SNAPSHOT_DISABLED;
+
+ int m_enoent_retries = 0;
+
+ uint8_t m_order = 0;
+ uint64_t m_size = 0;
+ uint64_t m_features = 0;
+ uint64_t m_incompatible_features = 0;
+ uint64_t m_flags = 0;
+ uint64_t m_op_features = 0;
+ uint32_t m_read_only_flags = 0U;
+ bool m_read_only = false;
+
+ librados::IoCtx m_pool_metadata_io_ctx;
+ std::map<std::string, bufferlist> m_metadata;
+
+ std::string m_object_prefix;
+ ParentImageInfo m_parent_md;
+ bool m_head_parent_overlap = false;
+ cls::rbd::GroupSpec m_group_spec;
+
+ ::SnapContext m_snapc;
+ std::vector<cls::rbd::SnapshotInfo> m_snap_infos;
+ std::vector<ParentImageInfo> m_snap_parents;
+ std::vector<uint8_t> m_snap_protection;
+ std::vector<uint64_t> m_snap_flags;
+
+ std::map<rados::cls::lock::locker_id_t,
+ rados::cls::lock::locker_info_t> m_lockers;
+ std::string m_lock_tag;
+ bool m_exclusive_locked = false;
+
+ bool m_blocked_writes = false;
+ bool m_incomplete_update = false;
+
+ void send_get_migration_header();
+ Context *handle_get_migration_header(int *result);
+
+ void send_v1_read_header();
+ Context *handle_v1_read_header(int *result);
+
+ void send_v1_get_snapshots();
+ Context *handle_v1_get_snapshots(int *result);
+
+ void send_v1_get_locks();
+ Context *handle_v1_get_locks(int *result);
+
+ void send_v1_apply();
+ Context *handle_v1_apply(int *result);
+
+ void send_v2_get_mutable_metadata();
+ Context *handle_v2_get_mutable_metadata(int *result);
+
+ void send_v2_get_parent();
+ Context *handle_v2_get_parent(int *result);
+
+ void send_v2_get_metadata();
+ Context *handle_v2_get_metadata(int *result);
+
+ void send_v2_get_pool_metadata();
+ Context *handle_v2_get_pool_metadata(int *result);
+
+ void send_v2_get_op_features();
+ Context *handle_v2_get_op_features(int *result);
+
+ void send_v2_get_group();
+ Context *handle_v2_get_group(int *result);
+
+ void send_v2_get_snapshots();
+ Context *handle_v2_get_snapshots(int *result);
+
+ void send_v2_get_snapshots_legacy();
+ Context *handle_v2_get_snapshots_legacy(int *result);
+
+ void send_v2_refresh_parent();
+ Context *handle_v2_refresh_parent(int *result);
+
+ void send_v2_init_exclusive_lock();
+ Context *handle_v2_init_exclusive_lock(int *result);
+
+ void send_v2_open_journal();
+ Context *handle_v2_open_journal(int *result);
+
+ void send_v2_block_writes();
+ Context *handle_v2_block_writes(int *result);
+
+ void send_v2_open_object_map();
+ Context *handle_v2_open_object_map(int *result);
+
+ void send_v2_apply();
+ Context *handle_v2_apply(int *result);
+
+ Context *send_v2_finalize_refresh_parent();
+ Context *handle_v2_finalize_refresh_parent(int *result);
+
+ Context *send_v2_shut_down_exclusive_lock();
+ Context *handle_v2_shut_down_exclusive_lock(int *result);
+
+ Context *send_v2_close_journal();
+ Context *handle_v2_close_journal(int *result);
+
+ Context *send_v2_close_object_map();
+ Context *handle_v2_close_object_map(int *result);
+
+ Context *send_flush_aio();
+ Context *handle_flush_aio(int *result);
+
+ Context *handle_error(int *result);
+
+ void save_result(int *result) {
+ if (m_error_result == 0 && *result < 0) {
+ m_error_result = *result;
+ }
+ }
+
+ void apply();
+ int get_parent_info(uint64_t snap_id, ParentImageInfo *parent_md,
+ MigrationInfo *migration_info);
+ int get_migration_info(ParentImageInfo *parent_md,
+ MigrationInfo *migration_info,
+ bool* migration_info_valid);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::RefreshRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_REFRESH_REQUEST_H
diff --git a/src/librbd/image/RemoveRequest.cc b/src/librbd/image/RemoveRequest.cc
new file mode 100644
index 000000000..42af593b1
--- /dev/null
+++ b/src/librbd/image/RemoveRequest.cc
@@ -0,0 +1,617 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/RemoveRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/internal.h"
+#include "librbd/ImageState.h"
+#include "librbd/Journal.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/image/DetachChildRequest.h"
+#include "librbd/image/PreRemoveRequest.h"
+#include "librbd/journal/RemoveRequest.h"
+#include "librbd/journal/TypeTraits.h"
+#include "librbd/mirror/DisableRequest.h"
+#include "librbd/operation/TrimRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::RemoveRequest: " << this << " " \
+ << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+using librados::IoCtx;
+using util::create_context_callback;
+using util::create_async_context_callback;
+using util::create_rados_callback;
+
+template<typename I>
+RemoveRequest<I>::RemoveRequest(IoCtx &ioctx, const std::string &image_name,
+ const std::string &image_id, bool force,
+ bool from_trash_remove,
+ ProgressContext &prog_ctx,
+ ContextWQ *op_work_queue, Context *on_finish)
+ : m_ioctx(ioctx), m_image_name(image_name), m_image_id(image_id),
+ m_force(force), m_from_trash_remove(from_trash_remove),
+ m_prog_ctx(prog_ctx), m_op_work_queue(op_work_queue),
+ m_on_finish(on_finish) {
+ m_cct = reinterpret_cast<CephContext *>(m_ioctx.cct());
+}
+
+template<typename I>
+RemoveRequest<I>::RemoveRequest(IoCtx &ioctx, I *image_ctx, bool force,
+ bool from_trash_remove,
+ ProgressContext &prog_ctx,
+ ContextWQ *op_work_queue, Context *on_finish)
+ : m_ioctx(ioctx), m_image_name(image_ctx->name), m_image_id(image_ctx->id),
+ m_image_ctx(image_ctx), m_force(force),
+ m_from_trash_remove(from_trash_remove), m_prog_ctx(prog_ctx),
+ m_op_work_queue(op_work_queue), m_on_finish(on_finish),
+ m_cct(image_ctx->cct), m_header_oid(image_ctx->header_oid),
+ m_old_format(image_ctx->old_format), m_unknown_format(false) {
+}
+
+template<typename I>
+void RemoveRequest<I>::send() {
+ ldout(m_cct, 20) << dendl;
+
+ open_image();
+}
+
+template<typename I>
+void RemoveRequest<I>::open_image() {
+ if (m_image_ctx != nullptr) {
+ pre_remove_image();
+ return;
+ }
+
+ m_image_ctx = I::create(m_image_id.empty() ? m_image_name : "", m_image_id,
+ nullptr, m_ioctx, false);
+
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ Context *ctx = create_context_callback<klass, &klass::handle_open_image>(
+ this);
+
+ m_image_ctx->state->open(OPEN_FLAG_SKIP_OPEN_PARENT, ctx);
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_open_image(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ m_image_ctx = nullptr;
+
+ if (r != -ENOENT) {
+ lderr(m_cct) << "error opening image: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ remove_image();
+ return;
+ }
+
+ m_image_id = m_image_ctx->id;
+ m_image_name = m_image_ctx->name;
+ m_header_oid = m_image_ctx->header_oid;
+ m_old_format = m_image_ctx->old_format;
+ m_unknown_format = false;
+
+ pre_remove_image();
+}
+
+template<typename I>
+void RemoveRequest<I>::pre_remove_image() {
+ ldout(m_cct, 5) << dendl;
+
+ auto ctx = create_context_callback<
+ RemoveRequest<I>, &RemoveRequest<I>::handle_pre_remove_image>(this);
+ auto req = PreRemoveRequest<I>::create(m_image_ctx, m_force, ctx);
+ req->send();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_pre_remove_image(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ if (r == -ECHILD) {
+ r = -ENOTEMPTY;
+ }
+ send_close_image(r);
+ return;
+ }
+
+ if (!m_image_ctx->data_ctx.is_valid()) {
+ detach_child();
+ return;
+ }
+
+ trim_image();
+}
+
+template<typename I>
+void RemoveRequest<I>::trim_image() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ Context *ctx = create_async_context_callback(
+ *m_image_ctx, create_context_callback<
+ klass, &klass::handle_trim_image>(this));
+
+ std::shared_lock owner_lock{m_image_ctx->owner_lock};
+ auto req = librbd::operation::TrimRequest<I>::create(
+ *m_image_ctx, ctx, m_image_ctx->size, 0, m_prog_ctx);
+ req->send();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_trim_image(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to remove some object(s): "
+ << cpp_strerror(r) << dendl;
+ send_close_image(r);
+ return;
+ }
+
+ if (m_old_format) {
+ send_close_image(r);
+ return;
+ }
+
+ detach_child();
+}
+
+template<typename I>
+void RemoveRequest<I>::detach_child() {
+ ldout(m_cct, 20) << dendl;
+
+ auto ctx = create_context_callback<
+ RemoveRequest<I>, &RemoveRequest<I>::handle_detach_child>(this);
+ auto req = DetachChildRequest<I>::create(*m_image_ctx, ctx);
+ req->send();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_detach_child(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "failed to detach child from parent: "
+ << cpp_strerror(r) << dendl;
+ send_close_image(r);
+ return;
+ }
+
+ send_disable_mirror();
+}
+
+template<typename I>
+void RemoveRequest<I>::send_disable_mirror() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_disable_mirror>(this);
+
+ mirror::DisableRequest<I> *req =
+ mirror::DisableRequest<I>::create(m_image_ctx, m_force, !m_force, ctx);
+ req->send();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_disable_mirror(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r == -EOPNOTSUPP) {
+ r = 0;
+ } else if (r < 0) {
+ lderr(m_cct) << "error disabling image mirroring: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ // one last chance to ensure all snapshots have been deleted
+ m_image_ctx->image_lock.lock_shared();
+ if (!m_image_ctx->snap_info.empty()) {
+ ldout(m_cct, 5) << "image has snapshots - not removing" << dendl;
+ m_ret_val = -ENOTEMPTY;
+ }
+ m_image_ctx->image_lock.unlock_shared();
+
+ send_close_image(r);
+}
+
+template<typename I>
+void RemoveRequest<I>::send_close_image(int r) {
+ ldout(m_cct, 20) << dendl;
+
+ m_ret_val = r;
+ using klass = RemoveRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_send_close_image>(this);
+
+ m_image_ctx->state->close(ctx);
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_send_close_image(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_cct) << "error encountered while closing image: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ m_image_ctx = nullptr;
+ if (m_ret_val < 0) {
+ r = m_ret_val;
+ finish(r);
+ return;
+ }
+
+ remove_header();
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_header() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_remove_header>(this);
+ int r = m_ioctx.aio_remove(m_header_oid, rados_completion);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_remove_header(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing header: " << cpp_strerror(r) << dendl;
+ m_ret_val = r;
+ }
+
+ remove_image();
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_header_v2() {
+ ldout(m_cct, 20) << dendl;
+
+ if (m_header_oid.empty()) {
+ m_header_oid = util::header_name(m_image_id);
+ }
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_remove_header_v2>(this);
+ int r = m_ioctx.aio_remove(m_header_oid, rados_completion);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_remove_header_v2(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing header: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ send_journal_remove();
+}
+
+template<typename I>
+void RemoveRequest<I>::send_journal_remove() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_journal_remove>(this);
+
+ typename journal::TypeTraits<I>::ContextWQ* context_wq;
+ Journal<I>::get_work_queue(m_cct, &context_wq);
+
+ journal::RemoveRequest<I> *req = journal::RemoveRequest<I>::create(
+ m_ioctx, m_image_id, Journal<>::IMAGE_CLIENT_ID, context_wq, ctx);
+ req->send();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_journal_remove(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to remove image journal: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ } else {
+ r = 0;
+ }
+
+ send_object_map_remove();
+}
+
+template<typename I>
+void RemoveRequest<I>::send_object_map_remove() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_object_map_remove>(this);
+
+ int r = ObjectMap<>::aio_remove(m_ioctx,
+ m_image_id,
+ rados_completion);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_object_map_remove(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to remove image journal: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ } else {
+ r = 0;
+ }
+
+ mirror_image_remove();
+}
+
+template<typename I>
+void RemoveRequest<I>::mirror_image_remove() {
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectWriteOperation op;
+ cls_client::mirror_image_remove(&op, m_image_id);
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_mirror_image_remove>(this);
+ int r = m_ioctx.aio_operate(RBD_MIRRORING, rados_completion, &op);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_mirror_image_remove(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT && r != -EOPNOTSUPP) {
+ lderr(m_cct) << "failed to remove mirror image state: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ if (m_from_trash_remove) {
+ // both the id object and the directory entry have been removed in
+ // a previous call to trash_move.
+ finish(0);
+ return;
+ }
+
+ remove_id_object();
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_image() {
+ ldout(m_cct, 20) << dendl;
+
+ if (m_old_format || m_unknown_format) {
+ remove_v1_image();
+ } else {
+ remove_v2_image();
+ }
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_v1_image() {
+ ldout(m_cct, 20) << dendl;
+
+ Context *ctx = new LambdaContext([this] (int r) {
+ r = tmap_rm(m_ioctx, m_image_name);
+ handle_remove_v1_image(r);
+ });
+
+ m_op_work_queue->queue(ctx, 0);
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_remove_v1_image(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ m_old_format = (r == 0);
+ if (r == 0 || (r < 0 && !m_unknown_format)) {
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing image from v1 directory: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ m_on_finish->complete(r);
+ delete this;
+ return;
+ }
+
+ if (!m_old_format) {
+ remove_v2_image();
+ }
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_v2_image() {
+ ldout(m_cct, 20) << dendl;
+
+ if (m_image_id.empty()) {
+ dir_get_image_id();
+ return;
+ } else if (m_image_name.empty()) {
+ dir_get_image_name();
+ return;
+ }
+
+ remove_header_v2();
+ return;
+}
+
+template<typename I>
+void RemoveRequest<I>::dir_get_image_id() {
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectReadOperation op;
+ librbd::cls_client::dir_get_id_start(&op, m_image_name);
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_dir_get_image_id>(this);
+ m_out_bl.clear();
+ int r = m_ioctx.aio_operate(RBD_DIRECTORY, rados_completion, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_dir_get_image_id(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error fetching image id: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ if (r == 0) {
+ auto iter = m_out_bl.cbegin();
+ r = librbd::cls_client::dir_get_id_finish(&iter, &m_image_id);
+ if (r < 0) {
+ finish(r);
+ return;
+ }
+ }
+
+ remove_header_v2();
+}
+
+template<typename I>
+void RemoveRequest<I>::dir_get_image_name() {
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectReadOperation op;
+ librbd::cls_client::dir_get_name_start(&op, m_image_id);
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_dir_get_image_name>(this);
+ m_out_bl.clear();
+ int r = m_ioctx.aio_operate(RBD_DIRECTORY, rados_completion, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_dir_get_image_name(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error fetching image name: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ if (r == 0) {
+ auto iter = m_out_bl.cbegin();
+ r = librbd::cls_client::dir_get_name_finish(&iter, &m_image_name);
+ if (r < 0) {
+ finish(r);
+ return;
+ }
+ }
+
+ remove_header_v2();
+}
+
+template<typename I>
+void RemoveRequest<I>::remove_id_object() {
+ ldout(m_cct, 20) << dendl;
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_remove_id_object>(this);
+ int r = m_ioctx.aio_remove(util::id_obj_name(m_image_name), rados_completion);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_remove_id_object(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing id object: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ dir_remove_image();
+}
+
+template<typename I>
+void RemoveRequest<I>::dir_remove_image() {
+ ldout(m_cct, 20) << dendl;
+
+ librados::ObjectWriteOperation op;
+ librbd::cls_client::dir_remove_image(&op, m_image_name, m_image_id);
+
+ using klass = RemoveRequest<I>;
+ librados::AioCompletion *rados_completion =
+ create_rados_callback<klass, &klass::handle_dir_remove_image>(this);
+ int r = m_ioctx.aio_operate(RBD_DIRECTORY, rados_completion, &op);
+ ceph_assert(r == 0);
+ rados_completion->release();
+}
+
+template<typename I>
+void RemoveRequest<I>::handle_dir_remove_image(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "error removing image from v2 directory: "
+ << cpp_strerror(r) << dendl;
+ }
+
+ finish(r);
+}
+
+template<typename I>
+void RemoveRequest<I>::finish(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::RemoveRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/RemoveRequest.h b/src/librbd/image/RemoveRequest.h
new file mode 100644
index 000000000..b03f8fc7c
--- /dev/null
+++ b/src/librbd/image/RemoveRequest.h
@@ -0,0 +1,197 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_REMOVE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_REMOVE_REQUEST_H
+
+#include "include/rados/librados.hpp"
+#include "librbd/ImageCtx.h"
+#include "librbd/image/TypeTraits.h"
+#include "common/Timer.h"
+
+#include <list>
+
+class Context;
+
+namespace librbd {
+
+class ProgressContext;
+
+namespace image {
+
+template<typename ImageCtxT = ImageCtx>
+class RemoveRequest {
+private:
+ // mock unit testing support
+ typedef ::librbd::image::TypeTraits<ImageCtxT> TypeTraits;
+ typedef typename TypeTraits::ContextWQ ContextWQ;
+public:
+ static RemoveRequest *create(librados::IoCtx &ioctx,
+ const std::string &image_name,
+ const std::string &image_id,
+ bool force, bool from_trash_remove,
+ ProgressContext &prog_ctx,
+ ContextWQ *op_work_queue,
+ Context *on_finish) {
+ return new RemoveRequest(ioctx, image_name, image_id, force,
+ from_trash_remove, prog_ctx, op_work_queue,
+ on_finish);
+ }
+
+ static RemoveRequest *create(librados::IoCtx &ioctx, ImageCtxT *image_ctx,
+ bool force, bool from_trash_remove,
+ ProgressContext &prog_ctx,
+ ContextWQ *op_work_queue,
+ Context *on_finish) {
+ return new RemoveRequest(ioctx, image_ctx, force, from_trash_remove,
+ prog_ctx, op_work_queue, on_finish);
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * (skip if already opened) OPEN IMAGE------------------\
+ * | |
+ * v |
+ * PRE REMOVE IMAGE * * * |
+ * | * |
+ * v * |
+ * (skip if invalid data pool) TRIM IMAGE * * * * * |
+ * | * |
+ * v * |
+ * DETACH CHILD * |
+ * | * |
+ * v * v
+ * CLOSE IMAGE < * * * * |
+ * | |
+ * error v |
+ * /------<--------\ REMOVE HEADER<--------------/
+ * | | / |
+ * | |-------<-------/ |
+ * | | v
+ * | | REMOVE JOURNAL
+ * | | / |
+ * | |-------<-------/ |
+ * | | v
+ * v ^ REMOVE OBJECTMAP
+ * | | / |
+ * | |-------<-------/ |
+ * | | v
+ * | | REMOVE MIRROR IMAGE
+ * | | / |
+ * | |-------<-------/ |
+ * | | v
+ * | | REMOVE ID OBJECT
+ * | | / |
+ * | |-------<-------/ |
+ * | | v
+ * | | REMOVE IMAGE
+ * | | / |
+ * | \-------<-------/ |
+ * | v
+ * \------------------>------------<finish>
+ *
+ * @endverbatim
+ */
+
+ RemoveRequest(librados::IoCtx &ioctx, const std::string &image_name,
+ const std::string &image_id, bool force, bool from_trash_remove,
+ ProgressContext &prog_ctx, ContextWQ *op_work_queue,
+ Context *on_finish);
+
+ RemoveRequest(librados::IoCtx &ioctx, ImageCtxT *image_ctx, bool force,
+ bool from_trash_remove, ProgressContext &prog_ctx,
+ ContextWQ *op_work_queue, Context *on_finish);
+
+ librados::IoCtx &m_ioctx;
+ std::string m_image_name;
+ std::string m_image_id;
+ ImageCtxT *m_image_ctx = nullptr;
+ bool m_force;
+ bool m_from_trash_remove;
+ ProgressContext &m_prog_ctx;
+ ContextWQ *m_op_work_queue;
+ Context *m_on_finish;
+
+ CephContext *m_cct;
+ std::string m_header_oid;
+ bool m_old_format = false;
+ bool m_unknown_format = true;
+
+ librados::IoCtx m_parent_io_ctx;
+
+ decltype(m_image_ctx->exclusive_lock) m_exclusive_lock = nullptr;
+
+ int m_ret_val = 0;
+ bufferlist m_out_bl;
+ std::list<obj_watch_t> m_watchers;
+
+ std::map<uint64_t, SnapInfo> m_snap_infos;
+
+ void open_image();
+ void handle_open_image(int r);
+
+ void send_journal_remove();
+ void handle_journal_remove(int r);
+
+ void send_object_map_remove();
+ void handle_object_map_remove(int r);
+
+ void mirror_image_remove();
+ void handle_mirror_image_remove(int r);
+
+ void pre_remove_image();
+ void handle_pre_remove_image(int r);
+
+ void trim_image();
+ void handle_trim_image(int r);
+
+ void detach_child();
+ void handle_detach_child(int r);
+
+ void send_disable_mirror();
+ void handle_disable_mirror(int r);
+
+ void send_close_image(int r);
+ void handle_send_close_image(int r);
+
+ void remove_header();
+ void handle_remove_header(int r);
+
+ void remove_header_v2();
+ void handle_remove_header_v2(int r);
+
+ void remove_image();
+
+ void remove_v1_image();
+ void handle_remove_v1_image(int r);
+
+ void remove_v2_image();
+
+ void dir_get_image_id();
+ void handle_dir_get_image_id(int r);
+
+ void dir_get_image_name();
+ void handle_dir_get_image_name(int r);
+
+ void remove_id_object();
+ void handle_remove_id_object(int r);
+
+ void dir_remove_image();
+ void handle_dir_remove_image(int r);
+
+ void finish(int r);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::RemoveRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_REMOVE_REQUEST_H
diff --git a/src/librbd/image/SetFlagsRequest.cc b/src/librbd/image/SetFlagsRequest.cc
new file mode 100644
index 000000000..fa00ed981
--- /dev/null
+++ b/src/librbd/image/SetFlagsRequest.cc
@@ -0,0 +1,78 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/SetFlagsRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "include/ceph_assert.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::SetFlagsRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+SetFlagsRequest<I>::SetFlagsRequest(I *image_ctx, uint64_t flags,
+ uint64_t mask, Context *on_finish)
+ : m_image_ctx(image_ctx), m_flags(flags), m_mask(mask),
+ m_on_finish(on_finish) {
+}
+
+template <typename I>
+void SetFlagsRequest<I>::send() {
+ send_set_flags();
+}
+
+template <typename I>
+void SetFlagsRequest<I>::send_set_flags() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 20) << __func__ << dendl;
+
+ std::unique_lock image_locker{m_image_ctx->image_lock};
+ std::vector<uint64_t> snap_ids;
+ snap_ids.push_back(CEPH_NOSNAP);
+ for (auto it : m_image_ctx->snap_info) {
+ snap_ids.push_back(it.first);
+ }
+
+ Context *ctx = create_context_callback<
+ SetFlagsRequest<I>, &SetFlagsRequest<I>::handle_set_flags>(this);
+ C_Gather *gather_ctx = new C_Gather(cct, ctx);
+
+ for (auto snap_id : snap_ids) {
+ librados::ObjectWriteOperation op;
+ cls_client::set_flags(&op, snap_id, m_flags, m_mask);
+
+ librados::AioCompletion *comp =
+ create_rados_callback(gather_ctx->new_sub());
+ int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+ }
+ gather_ctx->activate();
+}
+
+template <typename I>
+Context *SetFlagsRequest<I>::handle_set_flags(int *result) {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 20) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "set_flags failed: " << cpp_strerror(*result)
+ << dendl;
+ }
+ return m_on_finish;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::SetFlagsRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/SetFlagsRequest.h b/src/librbd/image/SetFlagsRequest.h
new file mode 100644
index 000000000..be67e176a
--- /dev/null
+++ b/src/librbd/image/SetFlagsRequest.h
@@ -0,0 +1,61 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_SET_FLAGS_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_SET_FLAGS_REQUEST_H
+
+#include "include/buffer.h"
+#include <map>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace image {
+
+template <typename ImageCtxT = ImageCtx>
+class SetFlagsRequest {
+public:
+ static SetFlagsRequest *create(ImageCtxT *image_ctx, uint64_t flags,
+ uint64_t mask, Context *on_finish) {
+ return new SetFlagsRequest(image_ctx, flags, mask, on_finish);
+ }
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * | . . .
+ * v v .
+ * SET_FLAGS . (for every snapshot)
+ * | . .
+ * v . . .
+ * <finis>
+ *
+ * @endverbatim
+ */
+
+ SetFlagsRequest(ImageCtxT *image_ctx, uint64_t flags, uint64_t mask,
+ Context *on_finish);
+
+ ImageCtxT *m_image_ctx;
+ uint64_t m_flags;
+ uint64_t m_mask;
+ Context *m_on_finish;
+
+ void send_set_flags();
+ Context *handle_set_flags(int *result);
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::SetFlagsRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_SET_FLAGS_REQUEST_H
diff --git a/src/librbd/image/SetSnapRequest.cc b/src/librbd/image/SetSnapRequest.cc
new file mode 100644
index 000000000..fbc234aef
--- /dev/null
+++ b/src/librbd/image/SetSnapRequest.cc
@@ -0,0 +1,368 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/SetSnapRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ObjectMap.h"
+#include "librbd/Utils.h"
+#include "librbd/image/RefreshParentRequest.h"
+#include "librbd/io/ImageDispatcherInterface.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::SetSnapRequest: "
+
+namespace librbd {
+namespace image {
+
+using util::create_context_callback;
+
+template <typename I>
+SetSnapRequest<I>::SetSnapRequest(I &image_ctx, uint64_t snap_id,
+ Context *on_finish)
+ : m_image_ctx(image_ctx), m_snap_id(snap_id), m_on_finish(on_finish),
+ m_exclusive_lock(nullptr), m_object_map(nullptr), m_refresh_parent(nullptr),
+ m_writes_blocked(false) {
+}
+
+template <typename I>
+SetSnapRequest<I>::~SetSnapRequest() {
+ ceph_assert(!m_writes_blocked);
+ delete m_refresh_parent;
+ if (m_object_map) {
+ m_object_map->put();
+ }
+ if (m_exclusive_lock) {
+ m_exclusive_lock->put();
+ }
+}
+
+template <typename I>
+void SetSnapRequest<I>::send() {
+ if (m_snap_id == CEPH_NOSNAP) {
+ send_init_exclusive_lock();
+ } else {
+ send_block_writes();
+ }
+}
+
+template <typename I>
+void SetSnapRequest<I>::send_init_exclusive_lock() {
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ if (m_image_ctx.exclusive_lock != nullptr) {
+ ceph_assert(m_image_ctx.snap_id == CEPH_NOSNAP);
+ send_complete();
+ return;
+ }
+ }
+
+ if (m_image_ctx.read_only ||
+ !m_image_ctx.test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ int r = 0;
+ if (send_refresh_parent(&r) != nullptr) {
+ send_complete();
+ }
+ return;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ m_exclusive_lock = ExclusiveLock<I>::create(m_image_ctx);
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_init_exclusive_lock>(this);
+
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ m_exclusive_lock->init(m_image_ctx.features, ctx);
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_init_exclusive_lock(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to initialize exclusive lock: "
+ << cpp_strerror(*result) << dendl;
+ finalize();
+ return m_on_finish;
+ }
+ return send_refresh_parent(result);
+}
+
+template <typename I>
+void SetSnapRequest<I>::send_block_writes() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ m_writes_blocked = true;
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_block_writes>(this);
+
+ std::shared_lock owner_locker{m_image_ctx.owner_lock};
+ m_image_ctx.io_image_dispatcher->block_writes(ctx);
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_block_writes(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to block writes: " << cpp_strerror(*result)
+ << dendl;
+ finalize();
+ return m_on_finish;
+ }
+
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ auto it = m_image_ctx.snap_info.find(m_snap_id);
+ if (it == m_image_ctx.snap_info.end()) {
+ ldout(cct, 5) << "failed to locate snapshot '" << m_snap_id << "'"
+ << dendl;
+
+ *result = -ENOENT;
+ finalize();
+ return m_on_finish;
+ }
+ }
+
+ return send_shut_down_exclusive_lock(result);
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::send_shut_down_exclusive_lock(int *result) {
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+ m_exclusive_lock = m_image_ctx.exclusive_lock;
+ }
+
+ if (m_exclusive_lock == nullptr) {
+ return send_refresh_parent(result);
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_shut_down_exclusive_lock>(this);
+ m_exclusive_lock->shut_down(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_shut_down_exclusive_lock(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to shut down exclusive lock: "
+ << cpp_strerror(*result) << dendl;
+ finalize();
+ return m_on_finish;
+ }
+
+ return send_refresh_parent(result);
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::send_refresh_parent(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+
+ ParentImageInfo parent_md;
+ bool refresh_parent;
+ {
+ std::shared_lock image_locker{m_image_ctx.image_lock};
+
+ const auto parent_info = m_image_ctx.get_parent_info(m_snap_id);
+ if (parent_info == nullptr) {
+ *result = -ENOENT;
+ lderr(cct) << "failed to retrieve snapshot parent info" << dendl;
+ finalize();
+ return m_on_finish;
+ }
+
+ parent_md = *parent_info;
+ refresh_parent = RefreshParentRequest<I>::is_refresh_required(
+ m_image_ctx, parent_md, m_image_ctx.migration_info);
+ }
+
+ if (!refresh_parent) {
+ if (m_snap_id == CEPH_NOSNAP) {
+ // object map is loaded when exclusive lock is acquired
+ *result = apply();
+ finalize();
+ return m_on_finish;
+ } else {
+ // load snapshot object map
+ return send_open_object_map(result);
+ }
+ }
+
+ ldout(cct, 10) << __func__ << dendl;
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_refresh_parent>(this);
+ m_refresh_parent = RefreshParentRequest<I>::create(m_image_ctx, parent_md,
+ m_image_ctx.migration_info,
+ ctx);
+ m_refresh_parent->send();
+ return nullptr;
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_refresh_parent(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to refresh snapshot parent: " << cpp_strerror(*result)
+ << dendl;
+ finalize();
+ return m_on_finish;
+ }
+
+ if (m_snap_id == CEPH_NOSNAP) {
+ // object map is loaded when exclusive lock is acquired
+ *result = apply();
+ if (*result < 0) {
+ finalize();
+ return m_on_finish;
+ }
+
+ return send_finalize_refresh_parent(result);
+ } else {
+ // load snapshot object map
+ return send_open_object_map(result);
+ }
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::send_open_object_map(int *result) {
+ if (!m_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP)) {
+ *result = apply();
+ if (*result < 0) {
+ finalize();
+ return m_on_finish;
+ }
+
+ return send_finalize_refresh_parent(result);
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_open_object_map>(this);
+ m_object_map = ObjectMap<I>::create(m_image_ctx, m_snap_id);
+ m_object_map->open(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_open_object_map(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to open object map: " << cpp_strerror(*result)
+ << dendl;
+ m_object_map->put();
+ m_object_map = nullptr;
+ }
+
+ *result = apply();
+ if (*result < 0) {
+ finalize();
+ return m_on_finish;
+ }
+
+ return send_finalize_refresh_parent(result);
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::send_finalize_refresh_parent(int *result) {
+ if (m_refresh_parent == nullptr) {
+ finalize();
+ return m_on_finish;
+ }
+
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ using klass = SetSnapRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_finalize_refresh_parent>(this);
+ m_refresh_parent->finalize(ctx);
+ return nullptr;
+}
+
+template <typename I>
+Context *SetSnapRequest<I>::handle_finalize_refresh_parent(int *result) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << ": r=" << *result << dendl;
+
+ if (*result < 0) {
+ lderr(cct) << "failed to close parent image: " << cpp_strerror(*result)
+ << dendl;
+ }
+ finalize();
+ return m_on_finish;
+}
+
+template <typename I>
+int SetSnapRequest<I>::apply() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ std::scoped_lock locker{m_image_ctx.owner_lock, m_image_ctx.image_lock};
+ if (m_snap_id != CEPH_NOSNAP) {
+ ceph_assert(m_image_ctx.exclusive_lock == nullptr);
+ int r = m_image_ctx.snap_set(m_snap_id);
+ if (r < 0) {
+ return r;
+ }
+ } else {
+ std::swap(m_image_ctx.exclusive_lock, m_exclusive_lock);
+ m_image_ctx.snap_unset();
+ }
+
+ if (m_refresh_parent != nullptr) {
+ m_refresh_parent->apply();
+ }
+
+ std::swap(m_object_map, m_image_ctx.object_map);
+ return 0;
+}
+
+template <typename I>
+void SetSnapRequest<I>::finalize() {
+ if (m_writes_blocked) {
+ m_image_ctx.io_image_dispatcher->unblock_writes();
+ m_writes_blocked = false;
+ }
+}
+
+template <typename I>
+void SetSnapRequest<I>::send_complete() {
+ finalize();
+ m_on_finish->complete(0);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::SetSnapRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/SetSnapRequest.h b/src/librbd/image/SetSnapRequest.h
new file mode 100644
index 000000000..c12ea9f27
--- /dev/null
+++ b/src/librbd/image/SetSnapRequest.h
@@ -0,0 +1,118 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_SNAP_SET_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_SNAP_SET_REQUEST_H
+
+#include "cls/rbd/cls_rbd_client.h"
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+template <typename> class ExclusiveLock;
+class ImageCtx;
+template <typename> class ObjectMap;
+
+namespace image {
+
+template <typename> class RefreshParentRequest;
+
+template <typename ImageCtxT = ImageCtx>
+class SetSnapRequest {
+public:
+ static SetSnapRequest *create(ImageCtxT &image_ctx, uint64_t snap_id,
+ Context *on_finish) {
+ return new SetSnapRequest(image_ctx, snap_id, on_finish);
+ }
+
+ ~SetSnapRequest();
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * | (set snap)
+ * |-----------> BLOCK_WRITES
+ * | |
+ * | v
+ * | SHUTDOWN_EXCLUSIVE_LOCK (skip if lock inactive
+ * | | or disabled)
+ * | v
+ * | REFRESH_PARENT (skip if no parent
+ * | | or refresh not needed)
+ * | v
+ * | OPEN_OBJECT_MAP (skip if map disabled)
+ * | |
+ * | v
+ * | <apply>
+ * | |
+ * | v
+ * | FINALIZE_REFRESH_PARENT (skip if no parent
+ * | | or refresh not needed)
+ * | v
+ * | <finish>
+ * |
+ * \-----------> INIT_EXCLUSIVE_LOCK (skip if active or
+ * | disabled)
+ * v
+ * REFRESH_PARENT (skip if no parent
+ * | or refresh not needed)
+ * v
+ * <apply>
+ * |
+ * v
+ * FINALIZE_REFRESH_PARENT (skip if no parent
+ * | or refresh not needed)
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ SetSnapRequest(ImageCtxT &image_ctx, uint64_t snap_id, Context *on_finish);
+
+ ImageCtxT &m_image_ctx;
+ uint64_t m_snap_id;
+ Context *m_on_finish;
+
+ ExclusiveLock<ImageCtxT> *m_exclusive_lock;
+ ObjectMap<ImageCtxT> *m_object_map;
+ RefreshParentRequest<ImageCtxT> *m_refresh_parent;
+
+ bool m_writes_blocked;
+
+ void send_block_writes();
+ Context *handle_block_writes(int *result);
+
+ void send_init_exclusive_lock();
+ Context *handle_init_exclusive_lock(int *result);
+
+ Context *send_shut_down_exclusive_lock(int *result);
+ Context *handle_shut_down_exclusive_lock(int *result);
+
+ Context *send_refresh_parent(int *result);
+ Context *handle_refresh_parent(int *result);
+
+ Context *send_open_object_map(int *result);
+ Context *handle_open_object_map(int *result);
+
+ Context *send_finalize_refresh_parent(int *result);
+ Context *handle_finalize_refresh_parent(int *result);
+
+ int apply();
+ void finalize();
+ void send_complete();
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::SetSnapRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_SNAP_SET_REQUEST_H
diff --git a/src/librbd/image/TypeTraits.h b/src/librbd/image/TypeTraits.h
new file mode 100644
index 000000000..2989e30b5
--- /dev/null
+++ b/src/librbd/image/TypeTraits.h
@@ -0,0 +1,21 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_TYPE_TRAITS_H
+#define CEPH_LIBRBD_IMAGE_TYPE_TRAITS_H
+
+namespace librbd {
+
+namespace asio { struct ContextWQ; }
+
+namespace image {
+
+template <typename ImageCtxT>
+struct TypeTraits {
+ typedef asio::ContextWQ ContextWQ;
+};
+
+} // namespace image
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_IMAGE_TYPE_TRAITS_H
diff --git a/src/librbd/image/Types.h b/src/librbd/image/Types.h
new file mode 100644
index 000000000..44c66e227
--- /dev/null
+++ b/src/librbd/image/Types.h
@@ -0,0 +1,20 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef LIBRBD_IMAGE_TYPES_H
+#define LIBRBD_IMAGE_TYPES_H
+
+namespace librbd {
+namespace image {
+
+enum {
+ CREATE_FLAG_SKIP_MIRROR_ENABLE = 1 << 0,
+ CREATE_FLAG_FORCE_MIRROR_ENABLE = 1 << 1,
+ CREATE_FLAG_MIRROR_ENABLE_MASK = (CREATE_FLAG_SKIP_MIRROR_ENABLE |
+ CREATE_FLAG_FORCE_MIRROR_ENABLE),
+};
+
+} // namespace image
+} // librbd
+
+#endif // LIBRBD_IMAGE_TYPES_H
diff --git a/src/librbd/image/ValidatePoolRequest.cc b/src/librbd/image/ValidatePoolRequest.cc
new file mode 100644
index 000000000..6f2872e25
--- /dev/null
+++ b/src/librbd/image/ValidatePoolRequest.cc
@@ -0,0 +1,234 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/ValidatePoolRequest.h"
+#include "include/rados/librados.hpp"
+#include "include/ceph_assert.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/asio/ContextWQ.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::ValidatePoolRequest: " \
+ << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+namespace {
+
+const std::string OVERWRITE_VALIDATED("overwrite validated");
+const std::string VALIDATE("validate");
+
+} // anonymous namespace
+
+using util::create_rados_callback;
+using util::create_context_callback;
+using util::create_async_context_callback;
+
+template <typename I>
+ValidatePoolRequest<I>::ValidatePoolRequest(librados::IoCtx& io_ctx,
+ Context *on_finish)
+ : m_cct(reinterpret_cast<CephContext*>(io_ctx.cct())),
+ m_on_finish(on_finish) {
+ // validatation should occur in default namespace
+ m_io_ctx.dup(io_ctx);
+ m_io_ctx.set_namespace("");
+ }
+
+template <typename I>
+void ValidatePoolRequest<I>::send() {
+ read_rbd_info();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::read_rbd_info() {
+ ldout(m_cct, 5) << dendl;
+
+ auto comp = create_rados_callback<
+ ValidatePoolRequest<I>,
+ &ValidatePoolRequest<I>::handle_read_rbd_info>(this);
+
+ librados::ObjectReadOperation op;
+ op.read(0, 0, nullptr, nullptr);
+
+ m_out_bl.clear();
+ int r = m_io_ctx.aio_operate(RBD_INFO, comp, &op, &m_out_bl);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::handle_read_rbd_info(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r >= 0) {
+ bufferlist validated_bl;
+ validated_bl.append(OVERWRITE_VALIDATED);
+
+ bufferlist validate_bl;
+ validate_bl.append(VALIDATE);
+
+ if (m_out_bl.contents_equal(validated_bl)) {
+ // already validated pool
+ finish(0);
+ return;
+ } else if (m_out_bl.contents_equal(validate_bl)) {
+ // implies snapshot was already successfully created
+ overwrite_rbd_info();
+ return;
+ }
+ } else if (r < 0 && r != -ENOENT) {
+ lderr(m_cct) << "failed to read RBD info: " << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ create_snapshot();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::create_snapshot() {
+ ldout(m_cct, 5) << dendl;
+
+ // allocate a self-managed snapshot id if this a new pool to force
+ // self-managed snapshot mode
+ auto comp = create_rados_callback<
+ ValidatePoolRequest<I>,
+ &ValidatePoolRequest<I>::handle_create_snapshot>(this);
+ m_io_ctx.aio_selfmanaged_snap_create(&m_snap_id, comp);
+ comp->release();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::handle_create_snapshot(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r == -EINVAL) {
+ lderr(m_cct) << "pool not configured for self-managed RBD snapshot support"
+ << dendl;
+ finish(r);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "failed to allocate self-managed snapshot: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ write_rbd_info();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::write_rbd_info() {
+ ldout(m_cct, 5) << dendl;
+
+ bufferlist bl;
+ bl.append(VALIDATE);
+
+ librados::ObjectWriteOperation op;
+ op.create(true);
+ op.write(0, bl);
+
+ auto comp = create_rados_callback<
+ ValidatePoolRequest<I>,
+ &ValidatePoolRequest<I>::handle_write_rbd_info>(this);
+ int r = m_io_ctx.aio_operate(RBD_INFO, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::handle_write_rbd_info(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r == -EOPNOTSUPP) {
+ lderr(m_cct) << "pool missing required overwrite support" << dendl;
+ m_ret_val = -EINVAL;
+ } else if (r < 0 && r != -EEXIST) {
+ lderr(m_cct) << "failed to write RBD info: " << cpp_strerror(r) << dendl;
+ m_ret_val = r;
+ }
+
+ remove_snapshot();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::remove_snapshot() {
+ ldout(m_cct, 5) << dendl;
+
+ auto comp = create_rados_callback<
+ ValidatePoolRequest<I>,
+ &ValidatePoolRequest<I>::handle_remove_snapshot>(this);
+ m_io_ctx.aio_selfmanaged_snap_remove(m_snap_id, comp);
+ comp->release();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::handle_remove_snapshot(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r < 0) {
+ // not a fatal error
+ lderr(m_cct) << "failed to remove validation snapshot: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ if (m_ret_val < 0) {
+ finish(m_ret_val);
+ return;
+ }
+
+ overwrite_rbd_info();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::overwrite_rbd_info() {
+ ldout(m_cct, 5) << dendl;
+
+ bufferlist bl;
+ bl.append(OVERWRITE_VALIDATED);
+
+ librados::ObjectWriteOperation op;
+ op.write(0, bl);
+
+ auto comp = create_rados_callback<
+ ValidatePoolRequest<I>,
+ &ValidatePoolRequest<I>::handle_overwrite_rbd_info>(this);
+ int r = m_io_ctx.aio_operate(RBD_INFO, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::handle_overwrite_rbd_info(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+
+ if (r == -EOPNOTSUPP) {
+ lderr(m_cct) << "pool missing required overwrite support" << dendl;
+ finish(-EINVAL);
+ return;
+ } else if (r < 0) {
+ lderr(m_cct) << "failed to validate overwrite support: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void ValidatePoolRequest<I>::finish(int r) {
+ ldout(m_cct, 5) << "r=" << r << dendl;
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::ValidatePoolRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/ValidatePoolRequest.h b/src/librbd/image/ValidatePoolRequest.h
new file mode 100644
index 000000000..74f384417
--- /dev/null
+++ b/src/librbd/image/ValidatePoolRequest.h
@@ -0,0 +1,93 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_VALIDATE_POOL_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_VALIDATE_POOL_REQUEST_H
+
+#include "include/common_fwd.h"
+#include "include/rados/librados.hpp"
+#include "include/buffer.h"
+
+class Context;
+
+namespace librbd {
+
+struct ImageCtx;
+namespace asio { struct ContextWQ; }
+
+namespace image {
+
+template <typename ImageCtxT>
+class ValidatePoolRequest {
+public:
+ static ValidatePoolRequest* create(librados::IoCtx& io_ctx,
+ Context *on_finish) {
+ return new ValidatePoolRequest(io_ctx, on_finish);
+ }
+
+ ValidatePoolRequest(librados::IoCtx& io_ctx, Context *on_finish);
+
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v (overwrites validated)
+ * READ RBD INFO . . . . . . . . .
+ * | . .
+ * | . (snapshots validated) .
+ * | . . . . . . . . . . .
+ * v . .
+ * CREATE SNAPSHOT . .
+ * | . .
+ * v . .
+ * WRITE RBD INFO . .
+ * | . .
+ * v . .
+ * REMOVE SNAPSHOT . .
+ * | . .
+ * v . .
+ * OVERWRITE RBD INFO < . . . .
+ * | .
+ * v .
+ * <finish> < . . . . . . . . . .`
+ *
+ * @endverbatim
+ */
+
+ librados::IoCtx m_io_ctx;
+ CephContext* m_cct;
+ Context* m_on_finish;
+
+ int m_ret_val = 0;
+ bufferlist m_out_bl;
+ uint64_t m_snap_id = 0;
+
+ void read_rbd_info();
+ void handle_read_rbd_info(int r);
+
+ void create_snapshot();
+ void handle_create_snapshot(int r);
+
+ void write_rbd_info();
+ void handle_write_rbd_info(int r);
+
+ void remove_snapshot();
+ void handle_remove_snapshot(int r);
+
+ void overwrite_rbd_info();
+ void handle_overwrite_rbd_info(int r);
+
+ void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::ValidatePoolRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_VALIDATE_POOL_REQUEST_H
diff --git a/src/librbd/image_watcher/NotifyLockOwner.cc b/src/librbd/image_watcher/NotifyLockOwner.cc
new file mode 100644
index 000000000..fe441d7f2
--- /dev/null
+++ b/src/librbd/image_watcher/NotifyLockOwner.cc
@@ -0,0 +1,96 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image_watcher/NotifyLockOwner.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/WatchNotifyTypes.h"
+#include "librbd/watcher/Notifier.h"
+#include <map>
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image_watcher::NotifyLockOwner: " \
+ << this << " " << __func__
+
+namespace librbd {
+
+namespace image_watcher {
+
+using namespace watch_notify;
+using util::create_context_callback;
+
+NotifyLockOwner::NotifyLockOwner(ImageCtx &image_ctx,
+ watcher::Notifier &notifier,
+ bufferlist &&bl, Context *on_finish)
+ : m_image_ctx(image_ctx), m_notifier(notifier), m_bl(std::move(bl)),
+ m_on_finish(on_finish) {
+}
+
+void NotifyLockOwner::send() {
+ send_notify();
+}
+
+void NotifyLockOwner::send_notify() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 20) << dendl;
+
+ ceph_assert(ceph_mutex_is_locked(m_image_ctx.owner_lock));
+ m_notifier.notify(m_bl, &m_notify_response, create_context_callback<
+ NotifyLockOwner, &NotifyLockOwner::handle_notify>(this));
+}
+
+void NotifyLockOwner::handle_notify(int r) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 20) << ": r=" << r << dendl;
+
+ if (r < 0 && r != -ETIMEDOUT) {
+ lderr(cct) << ": lock owner notification failed: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ bufferlist response;
+ bool lock_owner_responded = false;
+ for (auto &it : m_notify_response.acks) {
+ if (it.second.length() > 0) {
+ if (lock_owner_responded) {
+ lderr(cct) << ": duplicate lock owners detected" << dendl;
+ finish(-EINVAL);
+ return;
+ }
+ lock_owner_responded = true;
+ response = std::move(it.second);
+ }
+ }
+
+ if (!lock_owner_responded) {
+ ldout(cct, 1) << ": no lock owners detected" << dendl;
+ finish(-ETIMEDOUT);
+ return;
+ }
+
+ try {
+ auto iter = response.cbegin();
+
+ ResponseMessage response_message;
+ using ceph::decode;
+ decode(response_message, iter);
+
+ r = response_message.result;
+ ldout(cct, 20) << " client responded with r=" << r << dendl;
+ } catch (const buffer::error &err) {
+ r = -EINVAL;
+ }
+ finish(r);
+}
+
+void NotifyLockOwner::finish(int r) {
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace image_watcher
+} // namespace librbd
diff --git a/src/librbd/image_watcher/NotifyLockOwner.h b/src/librbd/image_watcher/NotifyLockOwner.h
new file mode 100644
index 000000000..6249bc128
--- /dev/null
+++ b/src/librbd/image_watcher/NotifyLockOwner.h
@@ -0,0 +1,50 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_WATCHER_NOTIFY_LOCK_OWNER_H
+#define CEPH_LIBRBD_IMAGE_WATCHER_NOTIFY_LOCK_OWNER_H
+
+#include "include/buffer.h"
+#include "librbd/watcher/Types.h"
+
+class Context;
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace watcher { class Notifier; }
+
+namespace image_watcher {
+
+class NotifyLockOwner {
+public:
+ static NotifyLockOwner *create(ImageCtx &image_ctx,
+ watcher::Notifier &notifier,
+ bufferlist &&bl, Context *on_finish) {
+ return new NotifyLockOwner(image_ctx, notifier, std::move(bl), on_finish);
+ }
+
+ NotifyLockOwner(ImageCtx &image_ctx, watcher::Notifier &notifier,
+ bufferlist &&bl, Context *on_finish);
+
+ void send();
+
+private:
+ ImageCtx &m_image_ctx;
+ watcher::Notifier &m_notifier;
+
+ bufferlist m_bl;
+ watcher::NotifyResponse m_notify_response;
+ Context *m_on_finish;
+
+ void send_notify();
+ void handle_notify(int r);
+
+ void finish(int r);
+};
+
+} // namespace image_watcher
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_IMAGE_WATCHER_NOTIFY_LOCK_OWNER_H