diff options
Diffstat (limited to 'src/librbd/image')
36 files changed, 9278 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 |