From 5a1a21711282eb3ed76f257d43d71a153cea8ec8 Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Sat, 14 Oct 2017 18:58:11 +0300 Subject: [PATCH] librbd: generalized deep copy function (based on rbd-mirror image sync) Signed-off-by: Mykola Golub --- src/librbd/CMakeLists.txt | 8 + src/librbd/DeepCopyRequest.cc | 374 ++++++++ src/librbd/DeepCopyRequest.h | 135 +++ src/librbd/Types.h | 3 + src/librbd/api/Image.cc | 119 +++ src/librbd/api/Image.h | 8 + src/librbd/deep_copy/ImageCopyRequest.cc | 170 ++++ src/librbd/deep_copy/ImageCopyRequest.h | 100 +++ src/librbd/deep_copy/MetadataCopyRequest.cc | 123 +++ src/librbd/deep_copy/MetadataCopyRequest.h | 77 ++ src/librbd/deep_copy/ObjectCopyRequest.cc | 692 +++++++++++++++ src/librbd/deep_copy/ObjectCopyRequest.h | 182 ++++ src/librbd/deep_copy/SetHeadRequest.cc | 219 +++++ src/librbd/deep_copy/SetHeadRequest.h | 87 ++ src/librbd/deep_copy/SnapshotCopyRequest.cc | 559 ++++++++++++ src/librbd/deep_copy/SnapshotCopyRequest.h | 124 +++ src/librbd/deep_copy/SnapshotCreateRequest.cc | 185 ++++ src/librbd/deep_copy/SnapshotCreateRequest.h | 95 ++ src/librbd/deep_copy/Types.h | 21 + src/librbd/deep_copy/Utils.cc | 34 + src/librbd/deep_copy/Utils.h | 26 + src/librbd/journal/Types.h | 2 +- src/librbd/operation/SnapshotRemoveRequest.h | 7 + src/test/librbd/CMakeLists.txt | 8 + .../deep_copy/test_mock_ImageCopyRequest.cc | 508 +++++++++++ .../test_mock_MetadataCopyRequest.cc | 180 ++++ .../deep_copy/test_mock_ObjectCopyRequest.cc | 817 ++++++++++++++++++ .../deep_copy/test_mock_SetHeadRequest.cc | 233 +++++ .../test_mock_SnapshotCopyRequest.cc | 722 ++++++++++++++++ .../test_mock_SnapshotCreateRequest.cc | 256 ++++++ src/test/librbd/mock/MockImageCtx.h | 1 + src/test/librbd/test_DeepCopy.cc | 405 +++++++++ src/test/librbd/test_main.cc | 10 +- src/test/librbd/test_mock_DeepCopyRequest.cc | 436 ++++++++++ .../test_mock_EventPreprocessor.cc | 6 +- 35 files changed, 6924 insertions(+), 8 deletions(-) create mode 100644 src/librbd/DeepCopyRequest.cc create mode 100644 src/librbd/DeepCopyRequest.h create mode 100644 src/librbd/deep_copy/ImageCopyRequest.cc create mode 100644 src/librbd/deep_copy/ImageCopyRequest.h create mode 100644 src/librbd/deep_copy/MetadataCopyRequest.cc create mode 100644 src/librbd/deep_copy/MetadataCopyRequest.h create mode 100644 src/librbd/deep_copy/ObjectCopyRequest.cc create mode 100644 src/librbd/deep_copy/ObjectCopyRequest.h create mode 100644 src/librbd/deep_copy/SetHeadRequest.cc create mode 100644 src/librbd/deep_copy/SetHeadRequest.h create mode 100644 src/librbd/deep_copy/SnapshotCopyRequest.cc create mode 100644 src/librbd/deep_copy/SnapshotCopyRequest.h create mode 100644 src/librbd/deep_copy/SnapshotCreateRequest.cc create mode 100644 src/librbd/deep_copy/SnapshotCreateRequest.h create mode 100644 src/librbd/deep_copy/Types.h create mode 100644 src/librbd/deep_copy/Utils.cc create mode 100644 src/librbd/deep_copy/Utils.h create mode 100644 src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc create mode 100644 src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc create mode 100644 src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc create mode 100644 src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc create mode 100644 src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc create mode 100644 src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc create mode 100644 src/test/librbd/test_DeepCopy.cc create mode 100644 src/test/librbd/test_mock_DeepCopyRequest.cc diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 12d585021f7..fc32f7faaa7 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(rbd_types STATIC set(librbd_internal_srcs AsyncObjectThrottle.cc AsyncRequest.cc + DeepCopyRequest.cc ExclusiveLock.cc ImageCtx.cc ImageState.cc @@ -27,6 +28,13 @@ set(librbd_internal_srcs api/Mirror.cc cache/ImageWriteback.cc cache/PassthroughImageCache.cc + deep_copy/ImageCopyRequest.cc + deep_copy/MetadataCopyRequest.cc + deep_copy/ObjectCopyRequest.cc + deep_copy/SetHeadRequest.cc + deep_copy/SnapshotCopyRequest.cc + deep_copy/SnapshotCreateRequest.cc + deep_copy/Utils.cc exclusive_lock/AutomaticPolicy.cc exclusive_lock/PreAcquireRequest.cc exclusive_lock/PostAcquireRequest.cc diff --git a/src/librbd/DeepCopyRequest.cc b/src/librbd/DeepCopyRequest.cc new file mode 100644 index 00000000000..268a4595492 --- /dev/null +++ b/src/librbd/DeepCopyRequest.cc @@ -0,0 +1,374 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "DeepCopyRequest.h" +#include "common/errno.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" +#include "librbd/deep_copy/ImageCopyRequest.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/deep_copy/SnapshotCopyRequest.h" +#include "librbd/internal.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::DeepCopyRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { + +using namespace librbd::deep_copy; + +using librbd::util::create_context_callback; +using librbd::util::create_rados_callback; +using librbd::util::unique_lock_name; + +template +DeepCopyRequest::DeepCopyRequest(I *src_image_ctx, I *dst_image_ctx, + librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const ObjectNumber &object_number, + ContextWQ *work_queue, SnapSeqs *snap_seqs, + ProgressContext *prog_ctx, + Context *on_finish) + : RefCountedObject(dst_image_ctx->cct, 1), m_src_image_ctx(src_image_ctx), + m_dst_image_ctx(dst_image_ctx), m_snap_id_start(snap_id_start), + m_snap_id_end(snap_id_end), m_object_number(object_number), + m_work_queue(work_queue), m_snap_seqs(snap_seqs), m_prog_ctx(prog_ctx), + m_on_finish(on_finish), m_cct(dst_image_ctx->cct), + m_lock(unique_lock_name("DeepCopyRequest::m_lock", this)) { +} + +template +DeepCopyRequest::~DeepCopyRequest() { + assert(m_snapshot_copy_request == nullptr); + assert(m_image_copy_request == nullptr); +} + +template +void DeepCopyRequest::send() { + int r = validate_copy_points(); + if (r < 0) { + finish(r); + return; + } + + send_copy_snapshots(); +} + +template +void DeepCopyRequest::cancel() { + Mutex::Locker locker(m_lock); + + ldout(m_cct, 20) << dendl; + + m_canceled = true; + + if (m_snapshot_copy_request != nullptr) { + m_snapshot_copy_request->cancel(); + } + + if (m_image_copy_request != nullptr) { + m_image_copy_request->cancel(); + } +} + +template +void DeepCopyRequest::send_copy_snapshots() { + m_lock.Lock(); + if (m_canceled) { + m_lock.Unlock(); + finish(-ECANCELED); + return; + } + + ldout(m_cct, 20) << dendl; + + Context *ctx = create_context_callback< + DeepCopyRequest, &DeepCopyRequest::handle_copy_snapshots>(this); + m_snapshot_copy_request = SnapshotCopyRequest::create( + m_src_image_ctx, m_dst_image_ctx, m_work_queue, m_snap_seqs, ctx); + m_snapshot_copy_request->get(); + m_lock.Unlock(); + + m_snapshot_copy_request->send(); +} + +template +void DeepCopyRequest::handle_copy_snapshots(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + { + Mutex::Locker locker(m_lock); + m_snapshot_copy_request->put(); + m_snapshot_copy_request = nullptr; + if (r == 0 && m_canceled) { + r = -ECANCELED; + } + } + + if (r == -ECANCELED) { + ldout(m_cct, 10) << "snapshot copy canceled" << dendl; + finish(r); + return; + } else if (r < 0) { + lderr(m_cct) << "failed to copy snapshot metadata: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + send_set_head(); +} + +template +void DeepCopyRequest::send_set_head() { + if (m_snap_id_end != CEPH_NOSNAP) { + send_copy_image(); + return; + } + + ldout(m_cct, 20) << dendl; + + uint64_t size; + ParentSpec parent_spec; + uint64_t parent_overlap; + { + RWLock::RLocker src_locker(m_src_image_ctx->snap_lock); + size = m_src_image_ctx->size; + parent_spec = m_src_image_ctx->parent_md.spec; + parent_overlap = m_src_image_ctx->parent_md.overlap; + } + + auto ctx = create_context_callback< + DeepCopyRequest, &DeepCopyRequest::handle_set_head>(this); + auto req = SetHeadRequest::create(m_dst_image_ctx, size, parent_spec, + parent_overlap, ctx); + req->send(); +} + +template +void DeepCopyRequest::handle_set_head(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + { + Mutex::Locker locker(m_lock); + if (r == 0 && m_canceled) { + r = -ECANCELED; + } + } + + if (r < 0) { + if (r != -ECANCELED) { + lderr(m_cct) << "failed to set head: " << cpp_strerror(r) << dendl; + } + finish(r); + return; + } + + send_copy_image(); +} + +template +void DeepCopyRequest::send_copy_image() { + m_lock.Lock(); + if (m_canceled) { + m_lock.Unlock(); + finish(-ECANCELED); + return; + } + + ldout(m_cct, 20) << dendl; + + Context *ctx = create_context_callback< + DeepCopyRequest, &DeepCopyRequest::handle_copy_image>(this); + m_image_copy_request = ImageCopyRequest::create( + m_src_image_ctx, m_dst_image_ctx, m_snap_id_start, m_snap_id_end, + m_object_number, *m_snap_seqs, m_prog_ctx, ctx); + m_image_copy_request->get(); + m_lock.Unlock(); + + m_image_copy_request->send(); +} + +template +void DeepCopyRequest::handle_copy_image(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + { + Mutex::Locker locker(m_lock); + m_image_copy_request->put(); + m_image_copy_request = nullptr; + if (r == 0 && m_canceled) { + r = -ECANCELED; + } + } + + if (r == -ECANCELED) { + ldout(m_cct, 10) << "image copy canceled" << dendl; + finish(r); + return; + } else if (r < 0) { + lderr(m_cct) << "failed to copy image: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + send_copy_object_map(); +} + +template +void DeepCopyRequest::send_copy_object_map() { + m_dst_image_ctx->owner_lock.get_read(); + m_dst_image_ctx->snap_lock.get_read(); + + if (!m_dst_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP, + m_dst_image_ctx->snap_lock)) { + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + send_copy_metadata(); + return; + } + if (m_snap_id_end == CEPH_NOSNAP) { + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + send_refresh_object_map(); + return; + } + + assert(m_dst_image_ctx->object_map != nullptr); + + ldout(m_cct, 20) << dendl; + + Context *finish_op_ctx = nullptr; + if (m_dst_image_ctx->exclusive_lock != nullptr) { + finish_op_ctx = m_dst_image_ctx->exclusive_lock->start_op(); + } + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + finish(-EROFS); + return; + } + + // rollback the object map (copy snapshot object map to HEAD) + RWLock::WLocker object_map_locker(m_dst_image_ctx->object_map_lock); + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_copy_object_map(r); + finish_op_ctx->complete(0); + }); + assert(m_snap_seqs->count(m_snap_id_end) > 0); + librados::snap_t copy_snap_id = (*m_snap_seqs)[m_snap_id_end]; + m_dst_image_ctx->object_map->rollback(copy_snap_id, ctx); + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); +} + +template +void DeepCopyRequest::handle_copy_object_map(int r) { + ldout(m_cct, 20) << dendl; + + assert(r == 0); + send_refresh_object_map(); +} + +template +void DeepCopyRequest::send_refresh_object_map() { + Context *finish_op_ctx = nullptr; + { + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + if (m_dst_image_ctx->exclusive_lock != nullptr) { + finish_op_ctx = m_dst_image_ctx->exclusive_lock->start_op(); + } + } + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + ldout(m_cct, 20) << dendl; + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_refresh_object_map(r); + finish_op_ctx->complete(0); + }); + m_object_map = m_dst_image_ctx->create_object_map(CEPH_NOSNAP); + m_object_map->open(ctx); +} + +template +void DeepCopyRequest::handle_refresh_object_map(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + assert(r == 0); + { + RWLock::WLocker snap_locker(m_dst_image_ctx->snap_lock); + std::swap(m_dst_image_ctx->object_map, m_object_map); + } + delete m_object_map; + + send_copy_metadata(); +} + +template +void DeepCopyRequest::send_copy_metadata() { + ldout(m_cct, 20) << dendl; + + Context *ctx = create_context_callback< + DeepCopyRequest, &DeepCopyRequest::handle_copy_metadata>(this); + auto request = MetadataCopyRequest::create(m_src_image_ctx, + m_dst_image_ctx, ctx); + request->send(); +} + +template +void DeepCopyRequest::handle_copy_metadata(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to copy metadata: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + finish(0); +} + +template +int DeepCopyRequest::validate_copy_points() { + RWLock::RLocker snap_locker(m_src_image_ctx->snap_lock); + + if (m_snap_id_start != 0 && + m_src_image_ctx->snap_info.find(m_snap_id_start) == + m_src_image_ctx->snap_info.end()) { + lderr(m_cct) << "invalid start snap_id " << m_snap_id_start << dendl; + return -EINVAL; + } + + if (m_snap_id_end != CEPH_NOSNAP && + m_src_image_ctx->snap_info.find(m_snap_id_end) == + m_src_image_ctx->snap_info.end()) { + lderr(m_cct) << "invalid end snap_id " << m_snap_id_end << dendl; + return -EINVAL; + } + + return 0; +} + +template +void DeepCopyRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + m_on_finish->complete(r); + put(); +} + +} // namespace librbd + +template class librbd::DeepCopyRequest; diff --git a/src/librbd/DeepCopyRequest.h b/src/librbd/DeepCopyRequest.h new file mode 100644 index 00000000000..e48527c94df --- /dev/null +++ b/src/librbd/DeepCopyRequest.h @@ -0,0 +1,135 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_REQUEST_H + +#include "common/Mutex.h" +#include "common/RefCountedObj.h" +#include "include/int_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/Types.h" +#include "librbd/deep_copy/Types.h" + +#include +#include + +class Context; + +namespace librbd { + +class ImageCtx; + +namespace deep_copy { + +template class ImageCopyRequest; +template class SnapshotCopyRequest; + +} + +template +class DeepCopyRequest : public RefCountedObject { +public: + static DeepCopyRequest* create(ImageCtxT *src_image_ctx, + ImageCtxT *dst_image_ctx, + librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const deep_copy::ObjectNumber &object_number, + ContextWQ *work_queue, + SnapSeqs *snap_seqs, + ProgressContext *prog_ctx, + Context *on_finish) { + return new DeepCopyRequest(src_image_ctx, dst_image_ctx, snap_id_start, + snap_id_end, object_number, work_queue, + snap_seqs, prog_ctx, on_finish); + } + + DeepCopyRequest(ImageCtxT *src_image_ctx, ImageCtxT *dst_image_ctx, + librados::snap_t snap_id_start, librados::snap_t snap_id_end, + const deep_copy::ObjectNumber &object_number, + ContextWQ *work_queue, SnapSeqs *snap_seqs, + ProgressContext *prog_ctx, Context *on_finish); + ~DeepCopyRequest(); + + void send(); + void cancel(); + +private: + /** + * @verbatim + * + * + * | + * v + * COPY_SNAPSHOTS + * | + * v + * SET_HEAD (skip if not needed) + * | + * v + * COPY_IMAGE . . . . . . . . . . . . . . + * | . + * v . + * COPY_OBJECT_MAP (skip if object . + * | map disabled) . + * v . + * REFRESH_OBJECT_MAP (skip if object . (image copy canceled) + * | map disabled) . + * v . + * COPY_METADATA . + * | . + * v . + * < . . . . . . . . . . . . . . + * + * @endverbatim + */ + + typedef std::vector SnapIds; + typedef std::map SnapMap; + + ImageCtxT *m_src_image_ctx; + ImageCtxT *m_dst_image_ctx; + librados::snap_t m_snap_id_start; + librados::snap_t m_snap_id_end; + deep_copy::ObjectNumber m_object_number; + ContextWQ *m_work_queue; + SnapSeqs *m_snap_seqs; + ProgressContext *m_prog_ctx; + Context *m_on_finish; + + CephContext *m_cct; + Mutex m_lock; + bool m_canceled = false; + + deep_copy::SnapshotCopyRequest *m_snapshot_copy_request = nullptr; + deep_copy::ImageCopyRequest *m_image_copy_request = nullptr; + decltype(ImageCtxT::object_map) m_object_map = nullptr; + + void send_copy_snapshots(); + void handle_copy_snapshots(int r); + + void send_set_head(); + void handle_set_head(int r); + + void send_copy_image(); + void handle_copy_image(int r); + + void send_copy_object_map(); + void handle_copy_object_map(int r); + + void send_refresh_object_map(); + void handle_refresh_object_map(int r); + + void send_copy_metadata(); + void handle_copy_metadata(int r); + + int validate_copy_points(); + + void finish(int r); +}; + +} // namespace librbd + +extern template class librbd::DeepCopyRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_REQUEST_H diff --git a/src/librbd/Types.h b/src/librbd/Types.h index 83f1e5689ba..809311adec2 100644 --- a/src/librbd/Types.h +++ b/src/librbd/Types.h @@ -6,6 +6,7 @@ #include "include/types.h" #include "cls/rbd/cls_rbd_types.h" +#include #include namespace librbd { @@ -53,6 +54,8 @@ enum { l_librbd_last, }; +typedef std::map SnapSeqs; + /** @brief Unique identification of a parent in clone relationship. * Cloning an image creates a child image that keeps a reference * to its parent. This allows copy-on-write images. */ diff --git a/src/librbd/api/Image.cc b/src/librbd/api/Image.cc index 43eb592a7b6..d4983d5f299 100644 --- a/src/librbd/api/Image.cc +++ b/src/librbd/api/Image.cc @@ -6,8 +6,11 @@ #include "common/dout.h" #include "common/errno.h" #include "cls/rbd/cls_rbd_client.h" +#include "librbd/DeepCopyRequest.h" +#include "librbd/ExclusiveLock.h" #include "librbd/ImageCtx.h" #include "librbd/ImageState.h" +#include "librbd/internal.h" #define dout_subsys ceph_subsys_rbd #undef dout_prefix @@ -111,6 +114,122 @@ int Image::list_children(I *ictx, const ParentSpec &parent_spec, return 0; } +template +int Image::deep_copy(I *src, librados::IoCtx& dest_md_ctx, + const char *destname, ImageOptions& opts, + ProgressContext &prog_ctx) { + CephContext *cct = (CephContext *)dest_md_ctx.cct(); + ldout(cct, 20) << src->name + << (src->snap_name.length() ? "@" + src->snap_name : "") + << " -> " << destname << " opts = " << opts << dendl; + + uint64_t features; + uint64_t src_size; + { + RWLock::RLocker snap_locker(src->snap_lock); + features = src->features; + src_size = src->get_image_size(src->snap_id); + } + uint64_t format = src->old_format ? 1 : 2; + if (opts.get(RBD_IMAGE_OPTION_FORMAT, &format) != 0) { + opts.set(RBD_IMAGE_OPTION_FORMAT, format); + } + if (format == 1) { + lderr(cct) << "old format not supported for destination image" << dendl; + return -EINVAL; + } + uint64_t stripe_unit = src->stripe_unit; + if (opts.get(RBD_IMAGE_OPTION_STRIPE_UNIT, &stripe_unit) != 0) { + opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit); + } + uint64_t stripe_count = src->stripe_count; + if (opts.get(RBD_IMAGE_OPTION_STRIPE_COUNT, &stripe_count) != 0) { + opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count); + } + uint64_t order = src->order; + if (opts.get(RBD_IMAGE_OPTION_ORDER, &order) != 0) { + opts.set(RBD_IMAGE_OPTION_ORDER, order); + } + if (opts.get(RBD_IMAGE_OPTION_FEATURES, &features) != 0) { + opts.set(RBD_IMAGE_OPTION_FEATURES, features); + } + if (features & ~RBD_FEATURES_ALL) { + lderr(cct) << "librbd does not support requested features" << dendl; + return -ENOSYS; + } + + int r = create(dest_md_ctx, destname, "", src_size, opts, "", "", false); + if (r < 0) { + lderr(cct) << "header creation failed" << dendl; + return r; + } + opts.set(RBD_IMAGE_OPTION_ORDER, static_cast(order)); + + ImageCtx *dest = new librbd::ImageCtx(destname, "", NULL, + dest_md_ctx, false); + r = dest->state->open(false); + if (r < 0) { + lderr(cct) << "failed to read newly created header" << dendl; + return r; + } + + C_SaferCond lock_ctx; + { + RWLock::WLocker locker(dest->owner_lock); + + if (dest->exclusive_lock == nullptr || + dest->exclusive_lock->is_lock_owner()) { + lock_ctx.complete(0); + } else { + dest->exclusive_lock->acquire_lock(&lock_ctx); + } + } + + r = lock_ctx.wait(); + if (r < 0) { + lderr(cct) << "failed to request exclusive lock: " << cpp_strerror(r) + << dendl; + dest->state->close(); + return r; + } + + r = deep_copy(src, dest, prog_ctx); + + int close_r = dest->state->close(); + if (r == 0 && close_r < 0) { + r = close_r; + } + return r; +} + +template +int Image::deep_copy(I *src, I *dest, ProgressContext &prog_ctx) { + CephContext *cct = src->cct; + librados::snap_t snap_id_start = 0; + librados::snap_t snap_id_end; + { + RWLock::RLocker snap_locker(src->snap_lock); + snap_id_end = src->snap_id; + } + + ThreadPool *thread_pool; + ContextWQ *op_work_queue; + ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue); + + C_SaferCond cond; + SnapSeqs snap_seqs; + auto req = DeepCopyRequest<>::create(src, dest, snap_id_start, snap_id_end, + boost::none, op_work_queue, &snap_seqs, + &prog_ctx, &cond); + req->send(); + int r = cond.wait(); + if (r < 0) { + return r; + } + + return 0; +} + } // namespace api } // namespace librbd diff --git a/src/librbd/api/Image.h b/src/librbd/api/Image.h index 5f3dfff1443..d690d44cc27 100644 --- a/src/librbd/api/Image.h +++ b/src/librbd/api/Image.h @@ -13,6 +13,9 @@ namespace librados { struct IoCtx; } namespace librbd { +class ImageOptions; +class ProgressContext; + struct ImageCtx; namespace api { @@ -30,6 +33,11 @@ struct Image { static int list_children(ImageCtxT *ictx, const ParentSpec &parent_spec, PoolImageIds *pool_image_ids); + static int deep_copy(ImageCtxT *ictx, librados::IoCtx& dest_md_ctx, + const char *destname, ImageOptions& opts, + ProgressContext &prog_ctx); + static int deep_copy(ImageCtxT *src, ImageCtxT *dest, + ProgressContext &prog_ctx); }; } // namespace api diff --git a/src/librbd/deep_copy/ImageCopyRequest.cc b/src/librbd/deep_copy/ImageCopyRequest.cc new file mode 100644 index 00000000000..2006ea3bff0 --- /dev/null +++ b/src/librbd/deep_copy/ImageCopyRequest.cc @@ -0,0 +1,170 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "ImageCopyRequest.h" +#include "ObjectCopyRequest.h" +#include "common/errno.h" +#include "librbd/Utils.h" +#include "librbd/deep_copy/Utils.h" +#include "osdc/Striper.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::ImageCopyRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { +namespace deep_copy { + +using librbd::util::unique_lock_name; + +template +ImageCopyRequest::ImageCopyRequest(I *src_image_ctx, I *dst_image_ctx, + librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const ObjectNumber &object_number, + const SnapSeqs &snap_seqs, + ProgressContext *prog_ctx, + Context *on_finish) + : RefCountedObject(dst_image_ctx->cct, 1), m_src_image_ctx(src_image_ctx), + m_dst_image_ctx(dst_image_ctx), m_snap_id_start(snap_id_start), + m_snap_id_end(snap_id_end), m_object_number(object_number), + m_snap_seqs(snap_seqs), m_prog_ctx(prog_ctx), m_on_finish(on_finish), + m_cct(dst_image_ctx->cct), + m_lock(unique_lock_name("ImageCopyRequest::m_lock", this)) { +} + +template +void ImageCopyRequest::send() { + util::compute_snap_map(m_snap_id_start, m_snap_id_end, m_snap_seqs, + &m_snap_map); + if (m_snap_map.empty()) { + lderr(m_cct) << "failed to map snapshots within boundary" << dendl; + finish(-EINVAL); + return; + } + + send_object_copies(); +} + +template +void ImageCopyRequest::cancel() { + Mutex::Locker locker(m_lock); + + ldout(m_cct, 20) << dendl; + m_canceled = true; +} + +template +void ImageCopyRequest::send_object_copies() { + m_object_no = 0; + if (m_object_number) { + m_object_no = *m_object_number + 1; + } + + uint64_t size; + { + RWLock::RLocker snap_locker(m_src_image_ctx->snap_lock); + size = m_src_image_ctx->get_image_size(CEPH_NOSNAP); + for (auto snap_id : m_src_image_ctx->snaps) { + size = std::max(size, m_src_image_ctx->get_image_size(snap_id)); + } + } + m_end_object_no = Striper::get_num_objects(m_dst_image_ctx->layout, size); + + ldout(m_cct, 20) << "start_object=" << m_object_no << ", " + << "end_object=" << m_end_object_no << dendl; + + bool complete; + { + Mutex::Locker locker(m_lock); + for (int i = 0; + i < m_cct->_conf->get_val("rbd_concurrent_management_ops"); + ++i) { + send_next_object_copy(); + if (m_ret_val < 0 && m_current_ops == 0) { + break; + } + } + complete = (m_current_ops == 0); + } + + if (complete) { + finish(m_ret_val); + } +} + +template +void ImageCopyRequest::send_next_object_copy() { + assert(m_lock.is_locked()); + + if (m_canceled && m_ret_val == 0) { + ldout(m_cct, 10) << "image copy canceled" << dendl; + m_ret_val = -ECANCELED; + } + + if (m_ret_val < 0 || m_object_no >= m_end_object_no) { + return; + } + + uint64_t ono = m_object_no++; + + ldout(m_cct, 20) << "object_num=" << ono << dendl; + + ++m_current_ops; + + Context *ctx = new FunctionContext( + [this, ono](int r) { + handle_object_copy(ono, r); + }); + ObjectCopyRequest *req = ObjectCopyRequest::create( + m_src_image_ctx, m_dst_image_ctx, m_snap_map, ono, ctx); + req->send(); +} + +template +void ImageCopyRequest::handle_object_copy(uint64_t object_no, int r) { + ldout(m_cct, 20) << "object_no=" << object_no << ", r=" << r << dendl; + + bool complete; + { + Mutex::Locker locker(m_lock); + assert(m_current_ops > 0); + --m_current_ops; + + if (r < 0) { + lderr(m_cct) << "object copy failed: " << cpp_strerror(r) << dendl; + if (m_ret_val == 0) { + m_ret_val = r; + } + } else { + m_copied_objects.push(object_no); + while (m_copied_objects.top() == + (m_object_number ? *m_object_number + 1 : 0)) { + m_object_number = m_copied_objects.top(); + m_copied_objects.pop(); + m_prog_ctx->update_progress(*m_object_number + 1, m_end_object_no); + } + } + + send_next_object_copy(); + complete = (m_current_ops == 0); + } + + if (complete) { + finish(m_ret_val); + } +} + +template +void ImageCopyRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + m_on_finish->complete(r); + put(); +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::ImageCopyRequest; diff --git a/src/librbd/deep_copy/ImageCopyRequest.h b/src/librbd/deep_copy/ImageCopyRequest.h new file mode 100644 index 00000000000..27636db4de6 --- /dev/null +++ b/src/librbd/deep_copy/ImageCopyRequest.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_DEEP_COPY_IMAGE_DEEP_COPY_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_IMAGE_DEEP_COPY_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "common/Mutex.h" +#include "common/RefCountedObj.h" +#include "librbd/Types.h" +#include "librbd/deep_copy/Types.h" +#include +#include +#include +#include +#include + +class Context; + +namespace librbd { + +class ImageCtx; +class ProgressContext; + +namespace deep_copy { + +template +class ImageCopyRequest : public RefCountedObject { +public: + static ImageCopyRequest* create(ImageCtxT *src_image_ctx, + ImageCtxT *dst_image_ctx, + librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const ObjectNumber &object_number, + const SnapSeqs &snap_seqs, + ProgressContext *prog_ctx, + Context *on_finish) { + return new ImageCopyRequest(src_image_ctx, dst_image_ctx, snap_id_start, + snap_id_end, object_number, snap_seqs, prog_ctx, + on_finish); + } + + ImageCopyRequest(ImageCtxT *src_image_ctx, ImageCtxT *dst_image_ctx, + librados::snap_t snap_id_start, librados::snap_t snap_id_end, + const ObjectNumber &object_number, const SnapSeqs &snap_seqs, + ProgressContext *prog_ctx, Context *on_finish); + + void send(); + void cancel(); + +private: + /** + * @verbatim + * + * . . . . . + * | . . (parallel execution of + * v v . multiple objects at once) + * COPY_OBJECT . . . . + * | + * v + * + * + * @endverbatim + */ + + ImageCtxT *m_src_image_ctx; + ImageCtxT *m_dst_image_ctx; + librados::snap_t m_snap_id_start; + librados::snap_t m_snap_id_end; + ObjectNumber m_object_number; + SnapSeqs m_snap_seqs; + ProgressContext *m_prog_ctx; + Context *m_on_finish; + + CephContext *m_cct; + Mutex m_lock; + bool m_canceled = false; + + uint64_t m_object_no = 0; + uint64_t m_end_object_no = 0; + uint64_t m_current_ops = 0; + std::priority_queue< + uint64_t, std::vector, std::greater> m_copied_objects; + SnapMap m_snap_map; + int m_ret_val = 0; + + void send_object_copies(); + void send_next_object_copy(); + void handle_object_copy(uint64_t object_no, int r); + + void finish(int r); +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::ImageCopyRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_IMAGE_DEEP_COPY_REQUEST_H diff --git a/src/librbd/deep_copy/MetadataCopyRequest.cc b/src/librbd/deep_copy/MetadataCopyRequest.cc new file mode 100644 index 00000000000..9f786087b35 --- /dev/null +++ b/src/librbd/deep_copy/MetadataCopyRequest.cc @@ -0,0 +1,123 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "MetadataCopyRequest.h" +#include "common/dout.h" +#include "common/errno.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/Utils.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::MetadataCopyRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { +namespace deep_copy { + +namespace { + +const uint64_t MAX_METADATA_ITEMS = 128; + +} // anonymous namespace + +using librbd::util::create_rados_callback; + +template +MetadataCopyRequest::MetadataCopyRequest(I *src_image_ctx, I *dst_image_ctx, + Context *on_finish) + : m_src_image_ctx(src_image_ctx), m_dst_image_ctx(dst_image_ctx), + m_on_finish(on_finish), m_cct(dst_image_ctx->cct) { +} + +template +void MetadataCopyRequest::send() { + list_src_metadata(); +} + +template +void MetadataCopyRequest::list_src_metadata() { + ldout(m_cct, 20) << "start_key=" << m_last_metadata_key << dendl; + + librados::ObjectReadOperation op; + librbd::cls_client::metadata_list_start(&op, m_last_metadata_key, MAX_METADATA_ITEMS); + + librados::AioCompletion *aio_comp = create_rados_callback< + MetadataCopyRequest, + &MetadataCopyRequest::handle_list_src_metadata>(this); + m_out_bl.clear(); + m_src_image_ctx->md_ctx.aio_operate(m_src_image_ctx->header_oid, + aio_comp, &op, &m_out_bl); + aio_comp->release(); +} + +template +void MetadataCopyRequest::handle_list_src_metadata(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + Metadata metadata; + if (r == 0) { + bufferlist::iterator it = m_out_bl.begin(); + r = librbd::cls_client::metadata_list_finish(&it, &metadata); + } + + if (r < 0) { + lderr(m_cct) << "failed to retrieve metadata: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + if (metadata.empty()) { + finish(0); + return; + } + + m_last_metadata_key = metadata.rbegin()->first; + m_more_metadata = (metadata.size() >= MAX_METADATA_ITEMS); + set_dst_metadata(metadata); +} + +template +void MetadataCopyRequest::set_dst_metadata(const Metadata& metadata) { + ldout(m_cct, 20) << "count=" << metadata.size() << dendl; + + librados::ObjectWriteOperation op; + librbd::cls_client::metadata_set(&op, metadata); + + librados::AioCompletion *aio_comp = create_rados_callback< + MetadataCopyRequest, + &MetadataCopyRequest::handle_set_dst_metadata>(this); + m_dst_image_ctx->md_ctx.aio_operate(m_dst_image_ctx->header_oid, aio_comp, + &op); + aio_comp->release(); +} + +template +void MetadataCopyRequest::handle_set_dst_metadata(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to set metadata: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + if (m_more_metadata) { + list_src_metadata(); + return; + } + + finish(0); +} + +template +void MetadataCopyRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + m_on_finish->complete(r); + delete this; +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::MetadataCopyRequest; diff --git a/src/librbd/deep_copy/MetadataCopyRequest.h b/src/librbd/deep_copy/MetadataCopyRequest.h new file mode 100644 index 00000000000..5bd69d8bb97 --- /dev/null +++ b/src/librbd/deep_copy/MetadataCopyRequest.h @@ -0,0 +1,77 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_METADATA_COPY_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_METADATA_COPY_REQUEST_H + +#include "include/int_types.h" +#include "include/buffer.h" +#include "include/rados/librados.hpp" +#include "librbd/ImageCtx.h" +#include +#include + +class Context; + +namespace librbd { +namespace deep_copy { + +template +class MetadataCopyRequest { +public: + static MetadataCopyRequest* create(ImageCtxT *src_image_ctx, + ImageCtxT *dst_image_ctx, + Context *on_finish) { + return new MetadataCopyRequest(src_image_ctx, dst_image_ctx, on_finish); + } + + MetadataCopyRequest(ImageCtxT *src_image_ctx, ImageCtxT *dst_image_ctx, + Context *on_finish); + + void send(); + +private: + /** + * @verbatim + * + * + * | + * v + * LIST_SRC_METADATA <------\ + * | | (repeat if additional + * v | metadata) + * SET_DST_METADATA --------/ + * | + * v + * + * + * @endverbatim + */ + typedef std::map Metadata; + + ImageCtxT *m_src_image_ctx; + ImageCtxT *m_dst_image_ctx; + Context *m_on_finish; + + CephContext *m_cct; + bufferlist m_out_bl; + + std::string m_last_metadata_key; + bool m_more_metadata = false; + + void list_src_metadata(); + void handle_list_src_metadata(int r); + + void set_dst_metadata(const Metadata& metadata); + void handle_set_dst_metadata(int r); + + void finish(int r); + +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::MetadataCopyRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_METADATA_COPY_REQUEST_H diff --git a/src/librbd/deep_copy/ObjectCopyRequest.cc b/src/librbd/deep_copy/ObjectCopyRequest.cc new file mode 100644 index 00000000000..8bd9c576838 --- /dev/null +++ b/src/librbd/deep_copy/ObjectCopyRequest.cc @@ -0,0 +1,692 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "ObjectCopyRequest.h" +#include "common/errno.h" +#include "librados/snap_set_diff.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ObjectMap.h" +#include "librbd/Utils.h" +#include "osdc/Striper.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::ObjectCopyRequest: " \ + << this << " " << __func__ << ": " + +namespace librados { + +inline bool operator==(const clone_info_t& rhs, const clone_info_t& lhs) { + return (rhs.cloneid == lhs.cloneid && + rhs.snaps == lhs.snaps && + rhs.overlap == lhs.overlap && + rhs.size == lhs.size); +} + +inline bool operator==(const snap_set_t& rhs, const snap_set_t& lhs) { + return (rhs.clones == lhs.clones && + rhs.seq == lhs.seq); +} + +} // namespace librados + +namespace librbd { +namespace deep_copy { + +using librbd::util::create_context_callback; +using librbd::util::create_rados_callback; + +template +ObjectCopyRequest::ObjectCopyRequest(I *src_image_ctx, I *dst_image_ctx, + const SnapMap &snap_map, + uint64_t dst_object_number, + Context *on_finish) + : m_src_image_ctx(src_image_ctx), m_dst_image_ctx(dst_image_ctx), + m_cct(dst_image_ctx->cct), m_snap_map(snap_map), + m_dst_object_number(dst_object_number), m_on_finish(on_finish) { + assert(!m_snap_map.empty()); + + m_src_io_ctx.dup(m_src_image_ctx->data_ctx); + m_dst_io_ctx.dup(m_dst_image_ctx->data_ctx); + + m_dst_oid = m_dst_image_ctx->get_object_name(dst_object_number); + + ldout(m_cct, 20) << "dst_oid=" << m_dst_oid << dendl; + + compute_src_object_extents(); +} + +template +void ObjectCopyRequest::send() { + send_list_snaps(); +} + +template +void ObjectCopyRequest::send_list_snaps() { + assert(!m_src_objects.empty()); + m_src_ono = *m_src_objects.begin(); + m_src_oid = m_src_image_ctx->get_object_name(m_src_ono); + + ldout(m_cct, 20) << "src_oid=" << m_src_oid << dendl; + + librados::AioCompletion *rados_completion = create_rados_callback< + ObjectCopyRequest, &ObjectCopyRequest::handle_list_snaps>(this); + + librados::ObjectReadOperation op; + m_snap_set = {}; + m_snap_ret = 0; + op.list_snaps(&m_snap_set, &m_snap_ret); + + m_src_io_ctx.snap_set_read(CEPH_SNAPDIR); + int r = m_src_io_ctx.aio_operate(m_src_oid, rados_completion, &op, + nullptr); + assert(r == 0); + rados_completion->release(); +} + +template +void ObjectCopyRequest::handle_list_snaps(int r) { + if (r == 0 && m_snap_ret < 0) { + r = m_snap_ret; + } + + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r == -ENOENT) { + r = 0; + } + + if (r < 0) { + lderr(m_cct) << "failed to list snaps: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + if (m_retry_missing_read) { + if (m_snap_set == m_retry_snap_set) { + lderr(m_cct) << "read encountered missing object using up-to-date snap set" + << dendl; + finish(-ENOENT); + return; + } + + ldout(m_cct, 20) << "retrying using updated snap set" << dendl; + m_retry_missing_read = false; + m_retry_snap_set = {}; + } + + compute_read_ops(); + send_read_object(); +} + +template +void ObjectCopyRequest::send_read_object() { + + if (m_read_snaps.empty()) { + // all snapshots have been read + merge_write_ops(); + + assert(!m_src_objects.empty()); + m_src_objects.erase(m_src_objects.begin()); + + if (!m_src_objects.empty()) { + send_list_snaps(); + return; + } + + // all objects have been read + + compute_zero_ops(); + + if (m_write_ops.empty()) { + // nothing to copy + finish(0); + return; + } + + send_write_object(); + return; + } + + auto index = *m_read_snaps.begin(); + auto src_snap_seq = index.second; + + bool read_required = false; + librados::ObjectReadOperation op; + + for (auto ©_op : m_read_ops[index]) { + if (!read_required) { + // map the copy op start snap id back to the necessary read snap id + m_src_io_ctx.snap_set_read(src_snap_seq); + + ldout(m_cct, 20) << "src_snap_seq=" << src_snap_seq << dendl; + read_required = true; + } + ldout(m_cct, 20) << "read op: " << copy_op.src_offset << "~" + << copy_op.length << dendl; + op.sparse_read(copy_op.src_offset, copy_op.length, ©_op.src_extent_map, + ©_op.out_bl, nullptr); + op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL | + LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } + + if (!read_required) { + // nothing written to this object for this snapshot (must be trunc/remove) + handle_read_object(0); + } + + auto ctx = create_context_callback< + ObjectCopyRequest, &ObjectCopyRequest::handle_read_object>(this); + auto comp = create_rados_callback(ctx); + + ldout(m_cct, 20) << "read " << m_src_oid << dendl; + + int r = m_src_io_ctx.aio_operate(m_src_oid, comp, &op, nullptr); + assert(r == 0); + comp->release(); +} + +template +void ObjectCopyRequest::handle_read_object(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r == -ENOENT) { + m_retry_snap_set = m_snap_set; + m_retry_missing_read = true; + + ldout(m_cct, 5) << "object missing potentially due to removed snapshot" + << dendl; + send_list_snaps(); + return; + } + + if (r < 0) { + lderr(m_cct) << "failed to read from source object: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + assert(!m_read_snaps.empty()); + m_read_snaps.erase(m_read_snaps.begin()); + + send_read_object(); +} + +template +void ObjectCopyRequest::send_write_object() { + assert(!m_write_ops.empty()); + + // retrieve the destination snap context for the op + SnapIds dst_snap_ids; + librados::snap_t dst_snap_seq = 0; + librados::snap_t src_snap_seq = m_write_ops.begin()->first; + if (src_snap_seq != 0) { + auto snap_map_it = m_snap_map.find(src_snap_seq); + assert(snap_map_it != m_snap_map.end()); + + // write snapshot context should be before actual snapshot + if (snap_map_it != m_snap_map.begin()) { + --snap_map_it; + assert(!snap_map_it->second.empty()); + dst_snap_seq = snap_map_it->second.front(); + dst_snap_ids = snap_map_it->second; + } + } + + ldout(m_cct, 20) << "dst_snap_seq=" << dst_snap_seq << ", " + << "dst_snaps=" << dst_snap_ids << dendl; + + librados::ObjectWriteOperation op; + uint64_t buffer_offset; + + for (auto ©_op : m_write_ops.begin()->second) { + switch (copy_op.type) { + case COPY_OP_TYPE_WRITE: + buffer_offset = 0; + for (auto &e : copy_op.dst_extent_map) { + ldout(m_cct, 20) << ": write op: " << e.first << "~" << e.second + << dendl; + bufferlist tmpbl; + tmpbl.substr_of(copy_op.out_bl, buffer_offset, e.second); + op.write(e.first, tmpbl); + op.set_op_flags2(LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL | + LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + buffer_offset += e.second; + } + break; + case COPY_OP_TYPE_ZERO: + ldout(m_cct, 20) << "zero op: " << copy_op.dst_offset << "~" + << copy_op.length << dendl; + op.zero(copy_op.dst_offset, copy_op.length); + break; + case COPY_OP_TYPE_TRUNC: + ldout(m_cct, 20) << "trunc op: " << copy_op.dst_offset << dendl; + op.truncate(copy_op.dst_offset); + break; + case COPY_OP_TYPE_REMOVE: + ldout(m_cct, 20) << "remove op" << dendl; + op.remove(); + break; + default: + ceph_abort(); + } + } + + if (op.size() == 0) { + handle_write_object(0); + return; + } + + Context *finish_op_ctx; + { + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + finish_op_ctx = start_lock_op(m_dst_image_ctx->owner_lock); + } + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_write_object(r); + finish_op_ctx->complete(0); + }); + librados::AioCompletion *comp = create_rados_callback(ctx); + int r = m_dst_io_ctx.aio_operate(m_dst_oid, comp, &op, dst_snap_seq, + dst_snap_ids); + assert(r == 0); + comp->release(); +} + +template +void ObjectCopyRequest::handle_write_object(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r == -ENOENT) { + r = 0; + } + if (r < 0) { + lderr(m_cct) << "failed to write to destination object: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + m_write_ops.erase(m_write_ops.begin()); + if (!m_write_ops.empty()) { + send_write_object(); + return; + } + + send_update_object_map(); +} + +template +void ObjectCopyRequest::send_update_object_map() { + if (!m_dst_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP) || + m_dst_object_state.empty()) { + finish(0); + return; + } + + m_dst_image_ctx->owner_lock.get_read(); + m_dst_image_ctx->snap_lock.get_read(); + if (m_dst_image_ctx->object_map == nullptr) { + // possible that exclusive lock was lost in background + lderr(m_cct) << "object map is not initialized" << dendl; + + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + finish(-EINVAL); + return; + } + + auto &dst_object_state = *m_dst_object_state.begin(); + auto it = m_snap_map.find(dst_object_state.first); + assert(it != m_snap_map.end()); + auto dst_snap_id = it->second.front(); + auto object_state = dst_object_state.second; + m_dst_object_state.erase(m_dst_object_state.begin()); + + ldout(m_cct, 20) << "dst_snap_id=" << dst_snap_id << ", object_state=" + << static_cast(object_state) << dendl; + + auto finish_op_ctx = start_lock_op(m_dst_image_ctx->owner_lock); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_update_object_map(r); + finish_op_ctx->complete(0); + }); + + RWLock::WLocker object_map_locker(m_dst_image_ctx->object_map_lock); + bool sent = m_dst_image_ctx->object_map->template aio_update< + Context, &Context::complete>(dst_snap_id, m_dst_object_number, object_state, + {}, {}, ctx); + m_dst_image_ctx->snap_lock.put_read(); + m_dst_image_ctx->owner_lock.put_read(); + if (!sent) { + assert(dst_snap_id == CEPH_NOSNAP); + handle_update_object_map(0); + } +} + +template +void ObjectCopyRequest::handle_update_object_map(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + assert(r == 0); + if (!m_dst_object_state.empty()) { + send_update_object_map(); + return; + } + finish(0); +} + +template +Context *ObjectCopyRequest::start_lock_op(RWLock &owner_lock) { + assert(m_dst_image_ctx->owner_lock.is_locked()); + if (m_dst_image_ctx->exclusive_lock == nullptr) { + return new FunctionContext([](int r) {}); + } + return m_dst_image_ctx->exclusive_lock->start_op(); +} + +template +uint64_t ObjectCopyRequest::src_to_dst_object_offset(uint64_t objectno, + uint64_t offset) { + std::vector> image_extents; + Striper::extent_to_file(m_cct, &m_src_image_ctx->layout, objectno, offset, 1, + image_extents); + assert(image_extents.size() == 1); + auto dst_object_offset = image_extents.begin()->first; + + std::map> dst_object_extents; + Striper::file_to_extents(m_cct, m_dst_image_ctx->format_string, + &m_dst_image_ctx->layout, dst_object_offset, 1, 0, + dst_object_extents); + assert(dst_object_extents.size() == 1); + assert(dst_object_extents.begin()->second.size() == 1); + auto &e = *dst_object_extents.begin()->second.begin(); + assert(e.objectno == m_dst_object_number); + + return e.offset; +} + +template +void ObjectCopyRequest::compute_src_object_extents() { + std::vector> image_extents; + Striper::extent_to_file(m_cct, &m_dst_image_ctx->layout, m_dst_object_number, + 0, m_dst_image_ctx->layout.object_size, image_extents); + + size_t total = 0; + for (auto &e : image_extents) { + std::map> src_object_extents; + Striper::file_to_extents(m_cct, m_src_image_ctx->format_string, + &m_src_image_ctx->layout, e.first, e.second, 0, + src_object_extents); + auto stripe_unit = std::min(m_src_image_ctx->layout.stripe_unit, + m_dst_image_ctx->layout.stripe_unit); + for (auto &p : src_object_extents) { + for (auto &s : p.second) { + m_src_objects.insert(s.objectno); + total += s.length; + while (s.length > 0) { + assert(s.length >= stripe_unit); + auto dst_object_offset = src_to_dst_object_offset(s.objectno, s.offset); + m_src_object_extents[dst_object_offset] = {s.objectno, s.offset, + stripe_unit}; + s.offset += stripe_unit; + s.length -= stripe_unit; + } + } + } + } + + assert(total == m_dst_image_ctx->layout.object_size); + + ldout(m_cct, 20) << m_src_object_extents.size() << " src extents" << dendl; +} + +template +void ObjectCopyRequest::compute_read_ops() { + m_read_ops = {}; + m_read_snaps = {}; + m_zero_interval = {}; + + librados::snap_t src_copy_point_snap_id = m_snap_map.rbegin()->first; + uint64_t prev_end_size = 0; + bool prev_exists = false; + librados::snap_t start_src_snap_id = 0; + + for (auto &pair : m_snap_map) { + assert(!pair.second.empty()); + librados::snap_t end_src_snap_id = pair.first; + librados::snap_t end_dst_snap_id = pair.second.front(); + + interval_set diff; + uint64_t end_size; + bool exists; + librados::snap_t clone_end_snap_id; + calc_snap_set_diff(m_cct, m_snap_set, start_src_snap_id, + end_src_snap_id, &diff, &end_size, &exists, + &clone_end_snap_id); + if (!exists) { + end_size = 0; + } + + ldout(m_cct, 20) << "start_src_snap_id=" << start_src_snap_id << ", " + << "end_src_snap_id=" << end_src_snap_id << ", " + << "clone_end_snap_id=" << clone_end_snap_id << ", " + << "end_dst_snap_id=" << end_dst_snap_id << ", " + << "diff=" << diff << ", " + << "end_size=" << end_size << ", " + << "exists=" << exists << dendl; + + m_zero_interval[end_src_snap_id] = {}; + + if (exists || prev_exists) { + // clip diff to size of object (in case it was truncated) + if (end_size < prev_end_size) { + interval_set trunc; + trunc.insert(end_size, prev_end_size); + trunc.intersection_of(diff); + diff.subtract(trunc); + ldout(m_cct, 20) << "clearing truncate diff: " << trunc << dendl; + } + + if (exists) { + // reads should be issued against the newest (existing) snapshot within + // the associated snapshot object clone. writes should be issued + // against the oldest snapshot in the snap_map. + assert(clone_end_snap_id >= end_src_snap_id); + if (clone_end_snap_id > src_copy_point_snap_id) { + // do not read past the copy point snapshot + clone_end_snap_id = src_copy_point_snap_id; + } + } + + for (auto &it : m_src_object_extents) { + auto dst_object_offset = it.first; + auto &e = it.second; + + if (e.object_no != m_src_ono) { + continue; + } + + interval_set read_interval; + read_interval.insert(e.offset, e.length); + + if (end_size < prev_end_size) { + interval_set zero_interval; + zero_interval.insert(end_size, prev_end_size - end_size); + zero_interval.intersection_of(read_interval); + if (!zero_interval.empty()) { + auto it = zero_interval.begin(); + auto offset = it.get_start() - e.offset; + m_zero_interval[end_src_snap_id].insert(dst_object_offset + offset, + it.get_len()); + ldout(m_cct, 20) << "extent " << e.offset << "~" << e.length + << " intersects truncation " << end_size << "~" + << prev_end_size - end_size << ", inserting zero " + << dst_object_offset + offset << "~" + << it.get_len() << dendl; + } + } + + // limit read interval to diff + read_interval.intersection_of(diff); + + ldout(m_cct, 20) << "src_object_extent: " << e.offset << "~" << e.length + << ", dst_object_offset=" << dst_object_offset + << ", read: " << read_interval << dendl; + + assert(exists || read_interval.empty()); + + for (auto it = read_interval.begin(); it != read_interval.end(); + it++) { + assert(it.get_start() >= e.offset); + auto offset = it.get_start() - e.offset; + ldout(m_cct, 20) << "read/write op: " << it.get_start() << "~" + << it.get_len() << " dst: " + << dst_object_offset + offset << dendl; + m_read_ops[{end_src_snap_id, clone_end_snap_id}] + .emplace_back(COPY_OP_TYPE_WRITE, it.get_start(), + dst_object_offset + offset, it.get_len()); + } + } + } + + prev_end_size = end_size; + prev_exists = exists; + start_src_snap_id = end_src_snap_id; + } + + for (auto &it : m_read_ops) { + m_read_snaps.push_back(it.first); + } +} + +template +void ObjectCopyRequest::merge_write_ops() { + ldout(m_cct, 20) << dendl; + + for (auto &it : m_zero_interval) { + m_dst_zero_interval[it.first].insert(it.second); + } + + for (auto &it : m_read_ops) { + auto src_snap_seq = it.first.first; + auto ©_ops = it.second; + for (auto ©_op : copy_ops) { + uint64_t src_offset = copy_op.src_offset; + uint64_t dst_offset = copy_op.dst_offset; + for (auto &e : copy_op.src_extent_map) { + uint64_t zero_len = e.first - src_offset; + if (zero_len > 0) { + ldout(m_cct, 20) << "src_snap_seq=" << src_snap_seq + << ", inserting zero " << dst_offset << "~" + << zero_len << dendl; + m_dst_zero_interval[src_snap_seq].insert(dst_offset, zero_len); + src_offset += zero_len; + dst_offset += zero_len; + } + copy_op.dst_extent_map[dst_offset] = e.second; + src_offset += e.second; + dst_offset += e.second; + } + if (dst_offset < copy_op.dst_offset + copy_op.length) { + uint64_t zero_len = copy_op.dst_offset + copy_op.length - dst_offset; + ldout(m_cct, 20) << "src_snap_seq=" << src_snap_seq + << ", inserting zero " << dst_offset << "~" + << zero_len << dendl; + m_dst_zero_interval[src_snap_seq].insert(dst_offset, zero_len); + } else { + assert(dst_offset == copy_op.dst_offset + copy_op.length); + } + m_write_ops[src_snap_seq].emplace_back(std::move(copy_op)); + } + } +} + +template +void ObjectCopyRequest::compute_zero_ops() { + ldout(m_cct, 20) << dendl; + + bool fast_diff = m_dst_image_ctx->test_features(RBD_FEATURE_FAST_DIFF); + uint64_t prev_end_size = 0; + for (auto &it : m_dst_zero_interval) { + auto src_snap_seq = it.first; + auto &zero_interval = it.second; + + uint64_t end_size = prev_end_size; + + // update end_size if there are writes into higher offsets + auto iter = m_write_ops.find(src_snap_seq); + if (iter != m_write_ops.end()) { + for (auto ©_op : iter->second) { + for (auto &e : copy_op.dst_extent_map) { + end_size = std::max(end_size, e.first + e.second); + } + } + } + + for (auto z = zero_interval.begin(); z != zero_interval.end(); z++) { + if (z.get_start() + z.get_len() >= end_size) { + // zero interval at the object end + if (z.get_start() < prev_end_size) { + if (z.get_start() == 0) { + m_write_ops[src_snap_seq] + .emplace_back(COPY_OP_TYPE_REMOVE, 0, 0, 0); + ldout(m_cct, 20) << "COPY_OP_TYPE_REMOVE" << dendl; + } else { + m_write_ops[src_snap_seq] + .emplace_back(COPY_OP_TYPE_TRUNC, 0, z.get_start(), 0); + ldout(m_cct, 20) << "COPY_OP_TYPE_TRUNC " << z.get_start() << dendl; + } + } + end_size = z.get_start(); + } else { + // zero interval inside the object + m_write_ops[src_snap_seq] + .emplace_back(COPY_OP_TYPE_ZERO, 0, z.get_start(), z.get_len()); + ldout(m_cct, 20) << "COPY_OP_TYPE_ZERO " << z.get_start() << "~" + << z.get_len() << dendl; + } + } + ldout(m_cct, 20) << "src_snap_seq=" << src_snap_seq << ", end_size=" + << end_size << dendl; + if (end_size > 0) { + m_dst_object_state[src_snap_seq] = OBJECT_EXISTS; + if (fast_diff && end_size == prev_end_size && + m_write_ops[src_snap_seq].empty()) { + m_dst_object_state[src_snap_seq] = OBJECT_EXISTS_CLEAN; + } + } + prev_end_size = end_size; + } +} + +template +void ObjectCopyRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + // ensure IoCtxs are closed prior to proceeding + auto on_finish = m_on_finish; + delete this; + + on_finish->complete(r); +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::ObjectCopyRequest; diff --git a/src/librbd/deep_copy/ObjectCopyRequest.h b/src/librbd/deep_copy/ObjectCopyRequest.h new file mode 100644 index 00000000000..e974b56d8f4 --- /dev/null +++ b/src/librbd/deep_copy/ObjectCopyRequest.h @@ -0,0 +1,182 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_OBJECT_COPY_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_OBJECT_COPY_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "common/snap_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/deep_copy/Types.h" +#include +#include +#include + +class Context; +class RWLock; + +namespace librbd { +namespace deep_copy { + +template +class ObjectCopyRequest { +public: + static ObjectCopyRequest* create(ImageCtxT *src_image_ctx, + ImageCtxT *dst_image_ctx, + const SnapMap &snap_map, + uint64_t object_number, Context *on_finish) { + return new ObjectCopyRequest(src_image_ctx, dst_image_ctx, snap_map, + object_number, on_finish); + } + + ObjectCopyRequest(ImageCtxT *src_image_ctx, ImageCtxT *dst_image_ctx, + const SnapMap &snap_map, uint64_t object_number, + Context *on_finish); + + void send(); + + // testing support + inline librados::IoCtx &get_src_io_ctx() { + return m_src_io_ctx; + } + inline librados::IoCtx &get_dst_io_ctx() { + return m_dst_io_ctx; + } + +private: + /** + * @verbatim + * + * + * | /----------------------\ + * | | | + * v v | (repeat for each src object) + * LIST_SNAPS < * * * | + * | * (-ENOENT and snap set stale) + * | * * * * * * | + * | * /-----------\ | + * | * | | (repeat for each snapshot) + * v * v | | + * READ_OBJECT ---------/ | + * | | | + * | \----------------------/ + * | + * | /-----------\ + * | | | (repeat for each snapshot) + * v v | + * WRITE_OBJECT --------/ + * | + * | /-----------\ + * | | | (repeat for each snapshot) + * v v | + * UPDATE_OBJECT_MAP ---/ (skip if object + * | map disabled) + * v + * + * + * @endverbatim + */ + + struct SrcObjectExtent { + uint64_t object_no = 0; + uint64_t offset = 0; + uint64_t length = 0; + + SrcObjectExtent() { + } + SrcObjectExtent(uint64_t object_no, uint64_t offset, uint64_t length) + : object_no(object_no), offset(offset), length(length) { + } + }; + + typedef std::map SrcObjectExtents; + + enum CopyOpType { + COPY_OP_TYPE_WRITE, + COPY_OP_TYPE_ZERO, + COPY_OP_TYPE_TRUNC, + COPY_OP_TYPE_REMOVE, + }; + + typedef std::map ExtentMap; + + struct CopyOp { + CopyOp(CopyOpType type, uint64_t src_offset, uint64_t dst_offset, + uint64_t length) + : type(type), src_offset(src_offset), dst_offset(dst_offset), + length(length) { + } + + CopyOpType type; + uint64_t src_offset; + uint64_t dst_offset; + uint64_t length; + + ExtentMap src_extent_map; + ExtentMap dst_extent_map; + bufferlist out_bl; + }; + + typedef std::list CopyOps; + typedef std::pair WriteReadSnapIds; + typedef std::map SnapObjectStates; + typedef std::map> SnapObjectSizes; + + ImageCtxT *m_src_image_ctx; + ImageCtxT *m_dst_image_ctx; + CephContext *m_cct; + const SnapMap &m_snap_map; + uint64_t m_dst_object_number; + Context *m_on_finish; + + decltype(m_src_image_ctx->data_ctx) m_src_io_ctx; + decltype(m_dst_image_ctx->data_ctx) m_dst_io_ctx; + std::string m_dst_oid; + + std::set m_src_objects; + uint64_t m_src_ono; + std::string m_src_oid; + SrcObjectExtents m_src_object_extents; + librados::snap_set_t m_snap_set; + int m_snap_ret = 0; + bool m_retry_missing_read = false; + librados::snap_set_t m_retry_snap_set; + + std::map m_read_ops; + std::list m_read_snaps; + std::map m_write_ops; + std::map> m_zero_interval; + std::map> m_dst_zero_interval; + std::map m_dst_object_state; + + void send_list_snaps(); + void handle_list_snaps(int r); + + void send_read_object(); + void handle_read_object(int r); + + void send_write_object(); + void handle_write_object(int r); + + void send_update_object_map(); + void handle_update_object_map(int r); + + Context *start_lock_op(RWLock &owner_lock); + + uint64_t src_to_dst_object_offset(uint64_t objectno, uint64_t offset); + + void compute_src_object_extents(); + void compute_read_ops(); + void merge_write_ops(); + void compute_zero_ops(); + + void finish(int r); +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::ObjectCopyRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_OBJECT_COPY_REQUEST_H diff --git a/src/librbd/deep_copy/SetHeadRequest.cc b/src/librbd/deep_copy/SetHeadRequest.cc new file mode 100644 index 00000000000..067c2979a57 --- /dev/null +++ b/src/librbd/deep_copy/SetHeadRequest.cc @@ -0,0 +1,219 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "SetHeadRequest.h" +#include "common/errno.h" +#include "cls/rbd/cls_rbd_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/Utils.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::SetHeadRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { +namespace deep_copy { + +using librbd::util::create_context_callback; +using librbd::util::create_rados_callback; + +template +SetHeadRequest::SetHeadRequest(I *image_ctx, uint64_t size, + const librbd::ParentSpec &spec, + uint64_t parent_overlap, + Context *on_finish) + : m_image_ctx(image_ctx), m_size(size), m_parent_spec(spec), + m_parent_overlap(parent_overlap), m_on_finish(on_finish), + m_cct(image_ctx->cct) { +} + +template +void SetHeadRequest::send() { + send_set_size(); +} + +template +void SetHeadRequest::send_set_size() { + m_image_ctx->snap_lock.get_read(); + if (m_image_ctx->size == m_size) { + m_image_ctx->snap_lock.put_read(); + send_remove_parent(); + return; + } + m_image_ctx->snap_lock.put_read(); + + ldout(m_cct, 20) << dendl; + + // Change the image size on disk so that the snapshot picks up + // the expected size. We can do this because the last snapshot + // we process is the sync snapshot which was created to match the + // image size. We also don't need to worry about trimming because + // we track the highest possible object number within the sync record + librados::ObjectWriteOperation op; + librbd::cls_client::set_size(&op, m_size); + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_set_size(r); + finish_op_ctx->complete(0); + }); + librados::AioCompletion *comp = create_rados_callback(ctx); + int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op); + assert(r == 0); + comp->release(); +} + +template +void SetHeadRequest::handle_set_size(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to update image size: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + { + // adjust in-memory image size now that it's updated on disk + RWLock::WLocker snap_locker(m_image_ctx->snap_lock); + m_image_ctx->size = m_size; + } + + send_remove_parent(); +} + +template +void SetHeadRequest::send_remove_parent() { + m_image_ctx->parent_lock.get_read(); + if (m_image_ctx->parent_md.spec.pool_id == -1 || + m_image_ctx->parent_md.spec == m_parent_spec) { + m_image_ctx->parent_lock.put_read(); + send_set_parent(); + return; + } + m_image_ctx->parent_lock.put_read(); + + ldout(m_cct, 20) << dendl; + + librados::ObjectWriteOperation op; + librbd::cls_client::remove_parent(&op); + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_remove_parent(r); + finish_op_ctx->complete(0); + }); + librados::AioCompletion *comp = create_rados_callback(ctx); + int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op); + assert(r == 0); + comp->release(); +} + +template +void SetHeadRequest::handle_remove_parent(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to remove parent: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + { + // adjust in-memory parent now that it's updated on disk + RWLock::WLocker parent_locker(m_image_ctx->parent_lock); + m_image_ctx->parent_md.spec = {}; + m_image_ctx->parent_md.overlap = 0; + } + + send_set_parent(); +} + +template +void SetHeadRequest::send_set_parent() { + m_image_ctx->parent_lock.get_read(); + if (m_image_ctx->parent_md.spec == m_parent_spec && + m_image_ctx->parent_md.overlap == m_parent_overlap) { + m_image_ctx->parent_lock.put_read(); + finish(0); + return; + } + m_image_ctx->parent_lock.put_read(); + + ldout(m_cct, 20) << dendl; + + librados::ObjectWriteOperation op; + librbd::cls_client::set_parent(&op, m_parent_spec, m_parent_overlap); + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_set_parent(r); + finish_op_ctx->complete(0); + }); + librados::AioCompletion *comp = create_rados_callback(ctx); + int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, comp, &op); + assert(r == 0); + comp->release(); +} + +template +void SetHeadRequest::handle_set_parent(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to set parent: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + { + // adjust in-memory parent now that it's updated on disk + RWLock::WLocker parent_locker(m_image_ctx->parent_lock); + m_image_ctx->parent_md.spec = m_parent_spec; + m_image_ctx->parent_md.overlap = m_parent_overlap; + } + + finish(0); +} + +template +Context *SetHeadRequest::start_lock_op() { + RWLock::RLocker owner_locker(m_image_ctx->owner_lock); + if (m_image_ctx->exclusive_lock == nullptr) { + return new FunctionContext([](int r) {}); + } + return m_image_ctx->exclusive_lock->start_op(); +} + +template +void SetHeadRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + m_on_finish->complete(r); + delete this; +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::SetHeadRequest; diff --git a/src/librbd/deep_copy/SetHeadRequest.h b/src/librbd/deep_copy/SetHeadRequest.h new file mode 100644 index 00000000000..7754ff73553 --- /dev/null +++ b/src/librbd/deep_copy/SetHeadRequest.h @@ -0,0 +1,87 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_SET_HEAD_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_SET_HEAD_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "common/snap_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/Types.h" +#include +#include +#include +#include + +class Context; + +namespace librbd { +namespace deep_copy { + +template +class SetHeadRequest { +public: + static SetHeadRequest* create(ImageCtxT *image_ctx, uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, + Context *on_finish) { + return new SetHeadRequest(image_ctx, size, parent_spec, parent_overlap, + on_finish); + } + + SetHeadRequest(ImageCtxT *image_ctx, uint64_t size, + const librbd::ParentSpec &parent_spec, uint64_t parent_overlap, + Context *on_finish); + + void send(); + +private: + /** + * @verbatim + * + * + * | + * v (skip if not needed) + * SET_SIZE + * | + * v (skip if not needed) + * REMOVE_PARENT + * | + * v (skip if not needed) + * SET_PARENT + * | + * v + * + * + * @endverbatim + */ + + ImageCtxT *m_image_ctx; + uint64_t m_size; + librbd::ParentSpec m_parent_spec; + uint64_t m_parent_overlap; + Context *m_on_finish; + + CephContext *m_cct; + + void send_set_size(); + void handle_set_size(int r); + + void send_remove_parent(); + void handle_remove_parent(int r); + + void send_set_parent(); + void handle_set_parent(int r); + + Context *start_lock_op(); + + void finish(int r); +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::SetHeadRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_SET_HEAD_REQUEST_H diff --git a/src/librbd/deep_copy/SnapshotCopyRequest.cc b/src/librbd/deep_copy/SnapshotCopyRequest.cc new file mode 100644 index 00000000000..00327194d03 --- /dev/null +++ b/src/librbd/deep_copy/SnapshotCopyRequest.cc @@ -0,0 +1,559 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "SnapshotCopyRequest.h" +#include "SnapshotCreateRequest.h" +#include "common/errno.h" +#include "common/WorkQueue.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::SnapshotCopyRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { +namespace deep_copy { + +namespace { + +template +const std::string &get_snapshot_name(I *image_ctx, librados::snap_t snap_id) { + auto snap_it = std::find_if(image_ctx->snap_ids.begin(), + image_ctx->snap_ids.end(), + [snap_id]( + const std::pair< + std::pair, + librados::snap_t> &pair) { + return pair.second == snap_id; + }); + assert(snap_it != image_ctx->snap_ids.end()); + return snap_it->first.second; +} + +} // anonymous namespace + +using librbd::util::unique_lock_name; + +template +SnapshotCopyRequest::SnapshotCopyRequest(I *src_image_ctx, + I *dst_image_ctx, + ContextWQ *work_queue, + SnapSeqs *snap_seqs, + Context *on_finish) + : RefCountedObject(dst_image_ctx->cct, 1), m_src_image_ctx(src_image_ctx), + m_dst_image_ctx(dst_image_ctx), m_work_queue(work_queue), + m_snap_seqs_result(snap_seqs), m_snap_seqs(*snap_seqs), + m_on_finish(on_finish), m_cct(dst_image_ctx->cct), + m_lock(unique_lock_name("SnapshotCopyRequest::m_lock", this)) { + // snap ids ordered from oldest to newest + m_src_snap_ids.insert(src_image_ctx->snaps.begin(), + src_image_ctx->snaps.end()); + m_dst_snap_ids.insert(dst_image_ctx->snaps.begin(), + dst_image_ctx->snaps.end()); +} + +template +void SnapshotCopyRequest::send() { + librbd::ParentSpec src_parent_spec; + int r = validate_parent(m_src_image_ctx, &src_parent_spec); + if (r < 0) { + lderr(m_cct) << "source image parent spec mismatch" << dendl; + error(r); + return; + } + + r = validate_parent(m_dst_image_ctx, &m_dst_parent_spec); + if (r < 0) { + lderr(m_cct) << "destination image parent spec mismatch" << dendl; + error(r); + return; + } + + send_snap_unprotect(); +} + +template +void SnapshotCopyRequest::cancel() { + Mutex::Locker locker(m_lock); + + ldout(m_cct, 20) << dendl; + m_canceled = true; +} + +template +void SnapshotCopyRequest::send_snap_unprotect() { + + SnapIdSet::iterator snap_id_it = m_dst_snap_ids.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_id_it = m_dst_snap_ids.upper_bound(m_prev_snap_id); + } + + for (; snap_id_it != m_dst_snap_ids.end(); ++snap_id_it) { + librados::snap_t dst_snap_id = *snap_id_it; + + m_dst_image_ctx->snap_lock.get_read(); + + bool dst_unprotected; + int r = m_dst_image_ctx->is_snap_unprotected(dst_snap_id, &dst_unprotected); + if (r < 0) { + lderr(m_cct) << "failed to retrieve destination snap unprotect status: " + << cpp_strerror(r) << dendl; + m_dst_image_ctx->snap_lock.put_read(); + finish(r); + return; + } + m_dst_image_ctx->snap_lock.put_read(); + + if (dst_unprotected) { + // snap is already unprotected -- check next snap + continue; + } + + // if destination snapshot is protected and (1) it isn't in our mapping + // table, or (2) the source snapshot isn't protected, unprotect it + auto snap_seq_it = std::find_if( + m_snap_seqs.begin(), m_snap_seqs.end(), + [dst_snap_id](const SnapSeqs::value_type& pair) { + return pair.second == dst_snap_id; + }); + + if (snap_seq_it != m_snap_seqs.end()) { + m_src_image_ctx->snap_lock.get_read(); + bool src_unprotected; + r = m_src_image_ctx->is_snap_unprotected(snap_seq_it->first, + &src_unprotected); + ldout(m_cct, 20) << "m_src_image_ctx->is_snap_unprotected("<< snap_seq_it->first << "): r=" << r << ", src_unprotected=" << src_unprotected << dendl; + if (r == -ENOENT) { + src_unprotected = true; + r = 0; + } + if (r < 0) { + lderr(m_cct) << "failed to retrieve source snap unprotect status: " + << cpp_strerror(r) << dendl; + m_src_image_ctx->snap_lock.put_read(); + finish(r); + return; + } + m_src_image_ctx->snap_lock.put_read(); + + if (src_unprotected) { + // source is unprotected -- unprotect destination snap + break; + } + } else { + // source snapshot doesn't exist -- unprotect destination snap + break; + } + } + + if (snap_id_it == m_dst_snap_ids.end()) { + // no destination snapshots to unprotect + m_prev_snap_id = CEPH_NOSNAP; + send_snap_remove(); + return; + } + + m_prev_snap_id = *snap_id_it; + m_snap_name = get_snapshot_name(m_dst_image_ctx, m_prev_snap_id); + + ldout(m_cct, 20) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_snap_unprotect(r); + finish_op_ctx->complete(0); + }); + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + m_dst_image_ctx->operations->execute_snap_unprotect( + cls::rbd::UserSnapshotNamespace(), m_snap_name.c_str(), ctx); +} + +template +void SnapshotCopyRequest::handle_snap_unprotect(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to unprotect snapshot '" << m_snap_name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + if (handle_cancellation()) { + return; + } + + send_snap_unprotect(); +} + +template +void SnapshotCopyRequest::send_snap_remove() { + SnapIdSet::iterator snap_id_it = m_dst_snap_ids.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_id_it = m_dst_snap_ids.upper_bound(m_prev_snap_id); + } + + for (; snap_id_it != m_dst_snap_ids.end(); ++snap_id_it) { + librados::snap_t dst_snap_id = *snap_id_it; + + cls::rbd::SnapshotNamespace snap_namespace; + m_dst_image_ctx->snap_lock.get_read(); + int r = m_dst_image_ctx->get_snap_namespace(dst_snap_id, &snap_namespace); + m_dst_image_ctx->snap_lock.put_read(); + if (r < 0) { + lderr(m_cct) << "failed to retrieve destination snap namespace: " + << m_snap_name << dendl; + finish(r); + return; + } + + if (boost::get(&snap_namespace) == + nullptr) { + continue; + } + + // if the destination snapshot isn't in our mapping table, remove it + auto snap_seq_it = std::find_if( + m_snap_seqs.begin(), m_snap_seqs.end(), + [dst_snap_id](const SnapSeqs::value_type& pair) { + return pair.second == dst_snap_id; + }); + + if (snap_seq_it == m_snap_seqs.end()) { + break; + } + } + + if (snap_id_it == m_dst_snap_ids.end()) { + // no destination snapshots to delete + m_prev_snap_id = CEPH_NOSNAP; + send_snap_create(); + return; + } + + m_prev_snap_id = *snap_id_it; + m_snap_name = get_snapshot_name(m_dst_image_ctx, m_prev_snap_id); + + ldout(m_cct, 20) << "" + << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_snap_remove(r); + finish_op_ctx->complete(0); + }); + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + m_dst_image_ctx->operations->execute_snap_remove( + cls::rbd::UserSnapshotNamespace(), m_snap_name.c_str(), ctx); +} + +template +void SnapshotCopyRequest::handle_snap_remove(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to remove snapshot '" << m_snap_name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + if (handle_cancellation()) { + return; + } + + send_snap_remove(); +} + +template +void SnapshotCopyRequest::send_snap_create() { + SnapIdSet::iterator snap_id_it = m_src_snap_ids.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_id_it = m_src_snap_ids.upper_bound(m_prev_snap_id); + } + + for (; snap_id_it != m_src_snap_ids.end(); ++snap_id_it) { + librados::snap_t src_snap_id = *snap_id_it; + + cls::rbd::SnapshotNamespace snap_namespace; + m_src_image_ctx->snap_lock.get_read(); + int r = m_src_image_ctx->get_snap_namespace(src_snap_id, &snap_namespace); + m_src_image_ctx->snap_lock.put_read(); + if (r < 0) { + lderr(m_cct) << "failed to retrieve source snap namespace: " + << m_snap_name << dendl; + finish(r); + return; + } + + // if the source snapshot isn't in our mapping table, create it + if (m_snap_seqs.find(src_snap_id) == m_snap_seqs.end() && + boost::get(&snap_namespace) != nullptr) { + break; + } + } + + if (snap_id_it == m_src_snap_ids.end()) { + // no source snapshots to create + m_prev_snap_id = CEPH_NOSNAP; + send_snap_protect(); + return; + } + + m_prev_snap_id = *snap_id_it; + m_snap_name = get_snapshot_name(m_src_image_ctx, m_prev_snap_id); + + m_src_image_ctx->snap_lock.get_read(); + auto snap_info_it = m_src_image_ctx->snap_info.find(m_prev_snap_id); + if (snap_info_it == m_src_image_ctx->snap_info.end()) { + m_src_image_ctx->snap_lock.put_read(); + lderr(m_cct) << "failed to retrieve source snap info: " << m_snap_name + << dendl; + finish(-ENOENT); + return; + } + + uint64_t size = snap_info_it->second.size; + m_snap_namespace = snap_info_it->second.snap_namespace; + librbd::ParentSpec parent_spec; + uint64_t parent_overlap = 0; + if (snap_info_it->second.parent.spec.pool_id != -1) { + parent_spec = m_dst_parent_spec; + parent_overlap = snap_info_it->second.parent.overlap; + } + m_src_image_ctx->snap_lock.put_read(); + + ldout(m_cct, 20) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << ", " + << "size=" << size << ", " + << "parent_info=[" + << "pool_id=" << parent_spec.pool_id << ", " + << "image_id=" << parent_spec.image_id << ", " + << "snap_id=" << parent_spec.snap_id << ", " + << "overlap=" << parent_overlap << "]" << dendl; + + Context *finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_snap_create(r); + finish_op_ctx->complete(0); + }); + SnapshotCreateRequest *req = SnapshotCreateRequest::create( + m_dst_image_ctx, m_snap_name, m_snap_namespace, size, parent_spec, + parent_overlap, ctx); + req->send(); +} + +template +void SnapshotCopyRequest::handle_snap_create(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to create snapshot '" << m_snap_name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + if (handle_cancellation()) { + return; + } + + assert(m_prev_snap_id != CEPH_NOSNAP); + + auto snap_it = m_dst_image_ctx->snap_ids.find( + {cls::rbd::UserSnapshotNamespace(), m_snap_name}); + assert(snap_it != m_dst_image_ctx->snap_ids.end()); + librados::snap_t dst_snap_id = snap_it->second; + + ldout(m_cct, 20) << "mapping source snap id " << m_prev_snap_id << " to " + << dst_snap_id << dendl; + m_snap_seqs[m_prev_snap_id] = dst_snap_id; + + send_snap_create(); +} + +template +void SnapshotCopyRequest::send_snap_protect() { + SnapIdSet::iterator snap_id_it = m_src_snap_ids.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_id_it = m_src_snap_ids.upper_bound(m_prev_snap_id); + } + + for (; snap_id_it != m_src_snap_ids.end(); ++snap_id_it) { + librados::snap_t src_snap_id = *snap_id_it; + + m_src_image_ctx->snap_lock.get_read(); + + bool src_protected; + int r = m_src_image_ctx->is_snap_protected(src_snap_id, &src_protected); + if (r < 0) { + lderr(m_cct) << "failed to retrieve source snap protect status: " + << cpp_strerror(r) << dendl; + m_src_image_ctx->snap_lock.put_read(); + finish(r); + return; + } + m_src_image_ctx->snap_lock.put_read(); + + if (!src_protected) { + // snap is not protected -- check next snap + continue; + } + + // if destination snapshot is not protected, protect it + auto snap_seq_it = m_snap_seqs.find(src_snap_id); + assert(snap_seq_it != m_snap_seqs.end()); + + m_dst_image_ctx->snap_lock.get_read(); + bool dst_protected; + r = m_dst_image_ctx->is_snap_protected(snap_seq_it->second, &dst_protected); + if (r < 0) { + lderr(m_cct) << "failed to retrieve destination snap protect status: " + << cpp_strerror(r) << dendl; + m_dst_image_ctx->snap_lock.put_read(); + finish(r); + return; + } + m_dst_image_ctx->snap_lock.put_read(); + + if (!dst_protected) { + break; + } + } + + if (snap_id_it == m_src_snap_ids.end()) { + // no destination snapshots to protect + m_prev_snap_id = CEPH_NOSNAP; + finish(0); + return; + } + + m_prev_snap_id = *snap_id_it; + m_snap_name = get_snapshot_name(m_src_image_ctx, m_prev_snap_id); + + ldout(m_cct, 20) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_snap_protect(r); + finish_op_ctx->complete(0); + }); + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + m_dst_image_ctx->operations->execute_snap_protect( + cls::rbd::UserSnapshotNamespace(), m_snap_name.c_str(), ctx); +} + +template +void SnapshotCopyRequest::handle_snap_protect(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to protect snapshot '" << m_snap_name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + if (handle_cancellation()) { + return; + } + + send_snap_protect(); +} + +template +bool SnapshotCopyRequest::handle_cancellation() { + { + Mutex::Locker locker(m_lock); + if (!m_canceled) { + return false; + } + } + ldout(m_cct, 10) << "snapshot copy canceled" << dendl; + finish(-ECANCELED); + return true; +} + +template +void SnapshotCopyRequest::error(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + m_work_queue->queue(new FunctionContext([this, r](int r1) { finish(r); })); +} + +template +int SnapshotCopyRequest::validate_parent(I *image_ctx, + librbd::ParentSpec *spec) { + RWLock::RLocker owner_locker(image_ctx->owner_lock); + RWLock::RLocker snap_locker(image_ctx->snap_lock); + + // ensure source image's parent specs are still consistent + *spec = image_ctx->parent_md.spec; + for (auto &snap_info_pair : image_ctx->snap_info) { + auto &parent_spec = snap_info_pair.second.parent.spec; + if (parent_spec.pool_id == -1) { + continue; + } else if (spec->pool_id == -1) { + *spec = parent_spec; + continue; + } + + if (*spec != parent_spec) { + return -EINVAL; + } + } + return 0; +} + +template +Context *SnapshotCopyRequest::start_lock_op() { + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + if (m_dst_image_ctx->exclusive_lock == nullptr) { + return new FunctionContext([](int r) {}); + } + return m_dst_image_ctx->exclusive_lock->start_op(); +} + +template +void SnapshotCopyRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r == 0) { + *m_snap_seqs_result = m_snap_seqs; + } + + m_on_finish->complete(r); + put(); +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::SnapshotCopyRequest; diff --git a/src/librbd/deep_copy/SnapshotCopyRequest.h b/src/librbd/deep_copy/SnapshotCopyRequest.h new file mode 100644 index 00000000000..825e2d338e6 --- /dev/null +++ b/src/librbd/deep_copy/SnapshotCopyRequest.h @@ -0,0 +1,124 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_COPY_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_COPY_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "common/RefCountedObj.h" +#include "common/snap_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/Types.h" +#include +#include +#include +#include + +class Context; + +namespace librbd { +namespace deep_copy { + +template +class SnapshotCopyRequest : public RefCountedObject { +public: + static SnapshotCopyRequest* create(ImageCtxT *src_image_ctx, + ImageCtxT *dst_image_ctx, + ContextWQ *work_queue, + SnapSeqs *snap_seqs, + Context *on_finish) { + return new SnapshotCopyRequest(src_image_ctx, dst_image_ctx, work_queue, + snap_seqs, on_finish); + } + + SnapshotCopyRequest(ImageCtxT *src_image_ctx, ImageCtxT *dst_image_ctx, + ContextWQ *work_queue, SnapSeqs *snap_seqs, + Context *on_finish); + + void send(); + void cancel(); + +private: + /** + * @verbatim + * + * + * | + * | /-----------\ + * | | | + * v v | (repeat as needed) + * UNPROTECT_SNAP ----/ + * | + * | /-----------\ + * | | | + * v v | (repeat as needed) + * REMOVE_SNAP -------/ + * | + * | /-----------\ + * | | | + * v v | (repeat as needed) + * CREATE_SNAP -------/ + * | + * | /-----------\ + * | | | + * v v | (repeat as needed) + * PROTECT_SNAP ------/ + * | + * v + * + * + * @endverbatim + */ + + typedef std::set SnapIdSet; + + ImageCtxT *m_src_image_ctx; + ImageCtxT *m_dst_image_ctx; + ContextWQ *m_work_queue; + SnapSeqs *m_snap_seqs_result; + SnapSeqs m_snap_seqs; + Context *m_on_finish; + + CephContext *m_cct; + SnapIdSet m_src_snap_ids; + SnapIdSet m_dst_snap_ids; + librados::snap_t m_prev_snap_id = CEPH_NOSNAP; + + std::string m_snap_name; + cls::rbd::SnapshotNamespace m_snap_namespace; + + librbd::ParentSpec m_dst_parent_spec; + + Mutex m_lock; + bool m_canceled = false; + + void send_snap_unprotect(); + void handle_snap_unprotect(int r); + + void send_snap_remove(); + void handle_snap_remove(int r); + + void send_snap_create(); + void handle_snap_create(int r); + + void send_snap_protect(); + void handle_snap_protect(int r); + + bool handle_cancellation(); + + void error(int r); + + int validate_parent(ImageCtxT *image_ctx, librbd::ParentSpec *spec); + + Context *start_lock_op(); + + void finish(int r); +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::SnapshotCopyRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_COPY_REQUEST_H diff --git a/src/librbd/deep_copy/SnapshotCreateRequest.cc b/src/librbd/deep_copy/SnapshotCreateRequest.cc new file mode 100644 index 00000000000..a9503765ac2 --- /dev/null +++ b/src/librbd/deep_copy/SnapshotCreateRequest.cc @@ -0,0 +1,185 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "SetHeadRequest.h" +#include "SnapshotCreateRequest.h" +#include "common/errno.h" +#include "cls/rbd/cls_rbd_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" +#include "osdc/Striper.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::deep_copy::SnapshotCreateRequest: " \ + << this << " " << __func__ << ": " + +namespace librbd { +namespace deep_copy { + +using librbd::util::create_context_callback; +using librbd::util::create_rados_callback; + +template +SnapshotCreateRequest::SnapshotCreateRequest( + I *dst_image_ctx, const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, const librbd::ParentSpec &spec, uint64_t parent_overlap, + Context *on_finish) + : m_dst_image_ctx(dst_image_ctx), m_snap_name(snap_name), + m_snap_namespace(snap_namespace), m_size(size), + m_parent_spec(spec), m_parent_overlap(parent_overlap), + m_on_finish(on_finish), m_cct(dst_image_ctx->cct) { +} + +template +void SnapshotCreateRequest::send() { + send_set_head(); +} + +template +void SnapshotCreateRequest::send_set_head() { + ldout(m_cct, 20) << dendl; + + auto ctx = create_context_callback< + SnapshotCreateRequest, &SnapshotCreateRequest::handle_set_head>(this); + auto req = SetHeadRequest::create(m_dst_image_ctx, m_size, m_parent_spec, + m_parent_overlap, ctx); + req->send(); +} + +template +void SnapshotCreateRequest::handle_set_head(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to set head: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + send_create_snap(); +} + +template +void SnapshotCreateRequest::send_create_snap() { + ldout(m_cct, 20) << "snap_name=" << m_snap_name << dendl; + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_create_snap(r); + finish_op_ctx->complete(0); + }); + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + m_dst_image_ctx->operations->execute_snap_create(m_snap_namespace, + m_snap_name.c_str(), + ctx, + 0U, true); +} + +template +void SnapshotCreateRequest::handle_create_snap(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to create snapshot '" << m_snap_name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + send_create_object_map(); +} +template +void SnapshotCreateRequest::send_create_object_map() { + + if (!m_dst_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP)) { + finish(0); + return; + } + + m_dst_image_ctx->snap_lock.get_read(); + auto snap_it = m_dst_image_ctx->snap_ids.find( + {cls::rbd::UserSnapshotNamespace(), m_snap_name}); + if (snap_it == m_dst_image_ctx->snap_ids.end()) { + lderr(m_cct) << "failed to locate snap: " << m_snap_name << dendl; + m_dst_image_ctx->snap_lock.put_read(); + finish(-ENOENT); + return; + } + librados::snap_t local_snap_id = snap_it->second; + m_dst_image_ctx->snap_lock.put_read(); + + std::string object_map_oid(librbd::ObjectMap<>::object_map_name( + m_dst_image_ctx->id, local_snap_id)); + uint64_t object_count = Striper::get_num_objects(m_dst_image_ctx->layout, + m_size); + ldout(m_cct, 20) << "object_map_oid=" << object_map_oid << ", " + << "object_count=" << object_count << dendl; + + // initialize an empty object map of the correct size (object sync + // will populate the object map) + librados::ObjectWriteOperation op; + librbd::cls_client::object_map_resize(&op, object_count, OBJECT_NONEXISTENT); + + auto finish_op_ctx = start_lock_op(); + if (finish_op_ctx == nullptr) { + lderr(m_cct) << "lost exclusive lock" << dendl; + finish(-EROFS); + return; + } + + auto ctx = new FunctionContext([this, finish_op_ctx](int r) { + handle_create_object_map(r); + finish_op_ctx->complete(0); + }); + librados::AioCompletion *comp = create_rados_callback(ctx); + int r = m_dst_image_ctx->md_ctx.aio_operate(object_map_oid, comp, &op); + assert(r == 0); + comp->release(); +} + +template +void SnapshotCreateRequest::handle_create_object_map(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to create object map: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + finish(0); +} + +template +Context *SnapshotCreateRequest::start_lock_op() { + RWLock::RLocker owner_locker(m_dst_image_ctx->owner_lock); + if (m_dst_image_ctx->exclusive_lock == nullptr) { + return new FunctionContext([](int r) {}); + } + return m_dst_image_ctx->exclusive_lock->start_op(); +} + +template +void SnapshotCreateRequest::finish(int r) { + ldout(m_cct, 20) << "r=" << r << dendl; + + m_on_finish->complete(r); + delete this; +} + +} // namespace deep_copy +} // namespace librbd + +template class librbd::deep_copy::SnapshotCreateRequest; diff --git a/src/librbd/deep_copy/SnapshotCreateRequest.h b/src/librbd/deep_copy/SnapshotCreateRequest.h new file mode 100644 index 00000000000..4ddcefd19d9 --- /dev/null +++ b/src/librbd/deep_copy/SnapshotCreateRequest.h @@ -0,0 +1,95 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_CREATE_REQUEST_H +#define CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_CREATE_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include "common/snap_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/Types.h" +#include +#include +#include +#include + +class Context; + +namespace librbd { +namespace deep_copy { + +template +class SnapshotCreateRequest { +public: + static SnapshotCreateRequest* create(ImageCtxT *dst_image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, + Context *on_finish) { + return new SnapshotCreateRequest(dst_image_ctx, snap_name, snap_namespace, size, + parent_spec, parent_overlap, on_finish); + } + + SnapshotCreateRequest(ImageCtxT *dst_image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, Context *on_finish); + + void send(); + +private: + /** + * @verbatim + * + * + * | + * v + * SET_HEAD + * | + * v + * CREATE_SNAP + * | + * v (skip if not needed) + * CREATE_OBJECT_MAP + * | + * v + * + * + * @endverbatim + */ + + ImageCtxT *m_dst_image_ctx; + std::string m_snap_name; + cls::rbd::SnapshotNamespace m_snap_namespace; + uint64_t m_size; + librbd::ParentSpec m_parent_spec; + uint64_t m_parent_overlap; + Context *m_on_finish; + + CephContext *m_cct; + + void send_set_head(); + void handle_set_head(int r); + + void send_create_snap(); + void handle_create_snap(int r); + + void send_create_object_map(); + void handle_create_object_map(int r); + + Context *start_lock_op(); + + void finish(int r); +}; + +} // namespace deep_copy +} // namespace librbd + +extern template class librbd::deep_copy::SnapshotCreateRequest; + +#endif // CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_CREATE_REQUEST_H diff --git a/src/librbd/deep_copy/Types.h b/src/librbd/deep_copy/Types.h new file mode 100644 index 00000000000..1b513c35d45 --- /dev/null +++ b/src/librbd/deep_copy/Types.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_DEEP_COPY_TYPES_H +#define CEPH_LIBRBD_DEEP_COPY_TYPES_H + +#include "include/int_types.h" +#include + +namespace librbd { +namespace deep_copy { + +typedef std::vector SnapIds; +typedef std::map SnapMap; + +typedef boost::optional ObjectNumber; + +} // namespace deep_copy +} // namespace librbd + +#endif // CEPH_LIBRBD_DEEP_COPY_TYPES_H diff --git a/src/librbd/deep_copy/Utils.cc b/src/librbd/deep_copy/Utils.cc new file mode 100644 index 00000000000..85a4b5b9853 --- /dev/null +++ b/src/librbd/deep_copy/Utils.cc @@ -0,0 +1,34 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "Utils.h" + +namespace librbd { +namespace deep_copy { +namespace util { + +void compute_snap_map(librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const SnapSeqs &snap_seqs, + SnapMap *snap_map) { + SnapIds snap_ids; + for (auto &it : snap_seqs) { + snap_ids.insert(snap_ids.begin(), it.second); + if (it.first < snap_id_start) { + continue; + } else if (snap_id_end != CEPH_NOSNAP && it.first > snap_id_end) { + break; + } + + (*snap_map)[it.first] = snap_ids; + } + + if (snap_id_end == CEPH_NOSNAP) { + snap_ids.insert(snap_ids.begin(), CEPH_NOSNAP); + (*snap_map)[CEPH_NOSNAP] = snap_ids; + } +} + +} // util +} // namespace deep_copy +} // namespace librbd diff --git a/src/librbd/deep_copy/Utils.h b/src/librbd/deep_copy/Utils.h new file mode 100644 index 00000000000..28e1d6d7ea4 --- /dev/null +++ b/src/librbd/deep_copy/Utils.h @@ -0,0 +1,26 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_DEEP_COPY_UTILS_H +#define CEPH_LIBRBD_DEEP_COPY_UTILS_H + +#include "include/rados/librados.hpp" +#include "librbd/Types.h" +#include "librbd/deep_copy/Types.h" + +#include + +namespace librbd { +namespace deep_copy { +namespace util { + +void compute_snap_map(librados::snap_t snap_id_start, + librados::snap_t snap_id_end, + const SnapSeqs &snap_seqs, + SnapMap *snap_map); + +} // namespace util +} // namespace deep_copy +} // namespace librbd + +#endif // CEPH_LIBRBD_DEEP_COPY_UTILS_H diff --git a/src/librbd/journal/Types.h b/src/librbd/journal/Types.h index 085ec023f7e..00fc2333ef5 100644 --- a/src/librbd/journal/Types.h +++ b/src/librbd/journal/Types.h @@ -10,6 +10,7 @@ #include "include/encoding.h" #include "include/types.h" #include "include/utime.h" +#include "librbd/Types.h" #include #include #include @@ -526,7 +527,6 @@ enum MirrorPeerState { struct MirrorPeerClientMeta { typedef std::list SyncPoints; - typedef std::map SnapSeqs; static const ClientMetaType TYPE = MIRROR_PEER_CLIENT_META_TYPE; diff --git a/src/librbd/operation/SnapshotRemoveRequest.h b/src/librbd/operation/SnapshotRemoveRequest.h index 0f4331ee255..71fb8441228 100644 --- a/src/librbd/operation/SnapshotRemoveRequest.h +++ b/src/librbd/operation/SnapshotRemoveRequest.h @@ -56,6 +56,13 @@ public: STATE_ERROR }; + static SnapshotRemoveRequest *create( + ImageCtxT &image_ctx, const cls::rbd::SnapshotNamespace &snap_namespace, + const std::string &snap_name, uint64_t snap_id, Context *on_finish) { + return new SnapshotRemoveRequest(image_ctx, on_finish, snap_namespace, + snap_name, snap_id); + } + SnapshotRemoveRequest(ImageCtxT &image_ctx, Context *on_finish, const cls::rbd::SnapshotNamespace &snap_namespace, const std::string &snap_name, diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 8aa61a9456f..b85a3592e71 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -6,6 +6,7 @@ set(librbd_test test_internal.cc test_mirroring.cc test_BlockGuard.cc + test_DeepCopy.cc test_Groups.cc test_MirroringWatcher.cc test_ObjectMap.cc @@ -27,10 +28,17 @@ set_target_properties(rbd_test_mock PROPERTIES COMPILE_FLAGS set(unittest_librbd_srcs test_main.cc test_mock_fixture.cc + test_mock_DeepCopyRequest.cc test_mock_ExclusiveLock.cc test_mock_Journal.cc test_mock_ManagedLock.cc test_mock_ObjectMap.cc + deep_copy/test_mock_ImageCopyRequest.cc + deep_copy/test_mock_MetadataCopyRequest.cc + deep_copy/test_mock_ObjectCopyRequest.cc + deep_copy/test_mock_SetHeadRequest.cc + deep_copy/test_mock_SnapshotCopyRequest.cc + deep_copy/test_mock_SnapshotCreateRequest.cc exclusive_lock/test_mock_PreAcquireRequest.cc exclusive_lock/test_mock_PostAcquireRequest.cc exclusive_lock/test_mock_PreReleaseRequest.cc diff --git a/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc new file mode 100644 index 00000000000..a839f9f4f5f --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc @@ -0,0 +1,508 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/ImageCopyRequest.h" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/internal.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +struct ObjectCopyRequest { + static ObjectCopyRequest* s_instance; + static ObjectCopyRequest* create( + librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, const SnapMap &snap_map, + uint64_t object_number, Context *on_finish) { + assert(s_instance != nullptr); + Mutex::Locker locker(s_instance->lock); + s_instance->snap_map = &snap_map; + s_instance->object_contexts[object_number] = on_finish; + s_instance->cond.Signal(); + return s_instance; + } + + MOCK_METHOD0(send, void()); + + Mutex lock; + Cond cond; + + const SnapMap *snap_map = nullptr; + std::map object_contexts; + + ObjectCopyRequest() : lock("lock") { + s_instance = this; + } +}; + +ObjectCopyRequest* ObjectCopyRequest::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/ImageCopyRequest.cc" +template class librbd::deep_copy::ImageCopyRequest; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +class TestMockDeepCopyImageCopyRequest : public TestMockFixture { +public: + typedef ImageCopyRequest MockImageCopyRequest; + typedef ObjectCopyRequest MockObjectCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + librbd::SnapSeqs m_snap_seqs; + SnapMap m_snap_map; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void expect_get_image_size(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(_)) + .WillOnce(Return(size)).RetiresOnSaturation(); + } + + void expect_object_copy_send(MockObjectCopyRequest &mock_object_copy_request) { + EXPECT_CALL(mock_object_copy_request, send()); + } + + bool complete_object_copy(MockObjectCopyRequest &mock_object_copy_request, + uint64_t object_num, int r, + std::function fn = []() {}) { + Mutex::Locker locker(mock_object_copy_request.lock); + while (mock_object_copy_request.object_contexts.count(object_num) == 0) { + if (mock_object_copy_request.cond.WaitInterval(mock_object_copy_request.lock, + utime_t(10, 0)) != 0) { + return false; + } + } + + FunctionContext *wrapper_ctx = new FunctionContext( + [&mock_object_copy_request, object_num, fn] (int r) { + fn(); + mock_object_copy_request.object_contexts[object_num]->complete(r); + }); + m_work_queue->queue(wrapper_ctx, r); + return true; + } + + SnapMap wait_for_snap_map(MockObjectCopyRequest &mock_object_copy_request) { + Mutex::Locker locker(mock_object_copy_request.lock); + while (mock_object_copy_request.snap_map == nullptr) { + if (mock_object_copy_request.cond.WaitInterval(mock_object_copy_request.lock, + utime_t(10, 0)) != 0) { + return SnapMap(); + } + } + return *mock_object_copy_request.snap_map; + } + + int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id) { + int r = image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), snap_name); + if (r < 0) { + return r; + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + + if (image_ctx->snap_ids.count({cls::rbd::UserSnapshotNamespace(), + snap_name}) == 0) { + return -ENOENT; + } + + if (snap_id != nullptr) { + *snap_id = image_ctx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}]; + } + return 0; + } + + int create_snap(const char* snap_name, + librados::snap_t *src_snap_id_ = nullptr) { + librados::snap_t src_snap_id; + int r = create_snap(m_src_image_ctx, snap_name, &src_snap_id); + if (r < 0) { + return r; + } + + if (src_snap_id_ != nullptr) { + *src_snap_id_ = src_snap_id; + } + + librados::snap_t dst_snap_id; + r = create_snap(m_dst_image_ctx, snap_name, &dst_snap_id); + if (r < 0) { + return r; + } + + // collection of all existing snaps in dst image + SnapIds dst_snap_ids({dst_snap_id}); + if (!m_snap_map.empty()) { + dst_snap_ids.insert(dst_snap_ids.end(), + m_snap_map.rbegin()->second.begin(), + m_snap_map.rbegin()->second.end()); + } + m_snap_map[src_snap_id] = dst_snap_ids; + m_snap_seqs[src_snap_id] = dst_snap_id; + return 0; + } +}; + +TEST_F(TestMockDeepCopyImageCopyRequest, SimpleImage) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, Throttled) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + uint64_t object_count = 55; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + expect_get_image_size(mock_src_image_ctx, + object_count * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, 0); + + EXPECT_CALL(mock_object_copy_request, send()).Times(object_count); + + class ProgressContext : public librbd::ProgressContext { + public: + uint64_t object_count; + librbd::deep_copy::ObjectNumber expected_object_number; + + ProgressContext(uint64_t object_count) + : object_count(object_count) { + } + + int update_progress(uint64_t object_no, uint64_t end_object_no) override { + EXPECT_LE(object_no, object_count); + EXPECT_EQ(end_object_no, object_count); + if (!expected_object_number) { + expected_object_number = 0; + } else { + expected_object_number = *expected_object_number + 1; + } + EXPECT_EQ(*expected_object_number, object_no - 1); + + return 0; + } + } prog_ctx(object_count); + + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, boost::none, + m_snap_seqs, &prog_ctx, &ctx); + request->send(); + + std::function sleep_fn = [request]() { + sleep(1); + }; + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + for (uint64_t i = 0; i < object_count; ++i) { + if (i % 10 == 0) { + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, i, 0, sleep_fn)); + } else { + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, i, 0)); + } + } + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, SnapshotSubset) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1")); + ASSERT_EQ(0, create_snap("snap2", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_get_image_size(mock_src_image_ctx, 0); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, + boost::none, m_snap_seqs, &no_op, + &ctx); + request->send(); + + SnapMap snap_map(m_snap_map); + snap_map.erase(snap_map.begin()); + ASSERT_EQ(snap_map, wait_for_snap_map(mock_object_copy_request)); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, RestartPartialSync) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 2 * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, + librbd::deep_copy::ObjectNumber{0U}, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 1, 0)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, Cancel) { + std::string max_ops_str; + ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str)); + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "1")); + BOOST_SCOPE_EXIT( (max_ops_str) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", + max_ops_str.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 1 << m_src_image_ctx->order); + expect_get_image_size(mock_src_image_ctx, 0); + expect_object_copy_send(mock_object_copy_request); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + request->cancel(); + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, 0)); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, Cancel_Inflight_Sync) { + std::string max_ops_str; + ASSERT_EQ(0, _rados.conf_get("rbd_concurrent_management_ops", max_ops_str)); + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", "3")); + BOOST_SCOPE_EXIT( (max_ops_str) ) { + ASSERT_EQ(0, _rados.conf_set("rbd_concurrent_management_ops", + max_ops_str.c_str())); + } BOOST_SCOPE_EXIT_END; + + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockObjectCopyRequest mock_object_copy_request; + + InSequence seq; + expect_get_image_size(mock_src_image_ctx, 6 * (1 << m_src_image_ctx->order)); + expect_get_image_size(mock_src_image_ctx, m_image_size); + + EXPECT_CALL(mock_object_copy_request, send()).Times(6); + + struct ProgressContext : public librbd::ProgressContext { + librbd::deep_copy::ObjectNumber object_number; + + int update_progress(uint64_t object_no, uint64_t end_object_no) override { + object_number = object_number ? *object_number + 1 : 0; + return 0; + } + } prog_ctx; + + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, snap_id_end, boost::none, + m_snap_seqs, &prog_ctx, &ctx); + request->send(); + + ASSERT_EQ(m_snap_map, wait_for_snap_map(mock_object_copy_request)); + + std::function cancel_fn = [request]() { + sleep(2); + request->cancel(); + }; + + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 0, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 1, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 2, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 3, 0, cancel_fn)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 4, 0)); + ASSERT_TRUE(complete_object_copy(mock_object_copy_request, 5, 0)); + + ASSERT_EQ(-ECANCELED, ctx.wait()); + ASSERT_EQ(5u, prog_ctx.object_number.get()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, MissingSnap) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 0, 123, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, MissingFromSnap) { + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + 123, snap_id_end, boost::none, + m_snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, EmptySnapMap) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, + boost::none, {{0, 0}}, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyImageCopyRequest, EmptySnapSeqs) { + librados::snap_t snap_id_start; + librados::snap_t snap_id_end; + ASSERT_EQ(0, create_snap("snap1", &snap_id_start)); + ASSERT_EQ(0, create_snap("copy", &snap_id_end)); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::NoOpProgressContext no_op; + C_SaferCond ctx; + auto request = new MockImageCopyRequest(&mock_src_image_ctx, + &mock_dst_image_ctx, + snap_id_start, snap_id_end, + boost::none, {}, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc new file mode 100644 index 00000000000..09c266c0e5c --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_MetadataCopyRequest.cc @@ -0,0 +1,180 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "include/rbd/librbd.hpp" +#include "include/stringify.h" +#include "librbd/ImageCtx.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/MetadataCopyRequest.cc" + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopyMetadataCopyRequest : public TestMockFixture { +public: + typedef MetadataCopyRequest MockMetadataCopyRequest; + typedef std::map Metadata; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void expect_metadata_list(librbd::MockTestImageCtx &mock_image_ctx, + const Metadata& metadata, int r) { + bufferlist out_bl; + ::encode(metadata, out_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("metadata_list"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)), + Return(r))); + } + + void expect_metadata_set(librbd::MockTestImageCtx &mock_image_ctx, + const Metadata& metadata, int r) { + bufferlist in_bl; + ::encode(metadata, in_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("metadata_set"), ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockDeepCopyMetadataCopyRequest, Success) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + size_t idx = 1; + Metadata key_values_1; + for (; idx <= 128; ++idx) { + bufferlist bl; + bl.append("value" + stringify(idx)); + key_values_1.emplace("key" + stringify(idx), bl); + } + + Metadata key_values_2; + for (; idx <= 255; ++idx) { + bufferlist bl; + bl.append("value" + stringify(idx)); + key_values_2.emplace("key" + stringify(idx), bl); + } + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values_1, 0); + expect_metadata_set(mock_dst_image_ctx, key_values_1, 0); + expect_metadata_list(mock_src_image_ctx, key_values_2, 0); + expect_metadata_set(mock_dst_image_ctx, key_values_2, 0); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, Empty) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, 0); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, MetadataListError) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, -EINVAL); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyMetadataCopyRequest, MetadataSetError) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + Metadata key_values; + bufferlist bl; + bl.append("value"); + key_values.emplace("key", bl); + + InSequence seq; + expect_metadata_list(mock_src_image_ctx, key_values, 0); + expect_metadata_set(mock_dst_image_ctx, key_values, -EINVAL); + + C_SaferCond ctx; + auto request = MockMetadataCopyRequest::create(&mock_src_image_ctx, + &mock_dst_image_ctx, + &ctx); + request->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_sync +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc new file mode 100644 index 00000000000..c46cc9e373b --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc @@ -0,0 +1,817 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "include/interval_set.h" +#include "include/rbd/librbd.hpp" +#include "include/rbd/object_map_types.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/ObjectCopyRequest.cc" +template class librbd::deep_copy::ObjectCopyRequest; + +bool operator==(const SnapContext& rhs, const SnapContext& lhs) { + return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps); +} + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::WithArg; + +namespace { + +void scribble(librbd::ImageCtx *image_ctx, int num_ops, size_t max_size, + interval_set *what) +{ + uint64_t object_size = 1 << image_ctx->order; + for (int i = 0; i < num_ops; i++) { + uint64_t off = rand() % (object_size - max_size + 1); + uint64_t len = 1 + rand() % max_size; + std::cout << __func__ << ": off=" << off << ", len=" << len << std::endl; + + bufferlist bl; + bl.append(std::string(len, '1')); + + int r = image_ctx->io_work_queue->write(off, len, std::move(bl), 0); + ASSERT_EQ(static_cast(len), r); + + interval_set w; + w.insert(off, len); + what->union_of(w); + } + std::cout << " wrote " << *what << std::endl; +} + +} // anonymous namespace + +class TestMockDeepCopyObjectCopyRequest : public TestMockFixture { +public: + typedef ObjectCopyRequest MockObjectCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + SnapMap m_snap_map; + std::vector m_src_snap_ids; + std::vector m_dst_snap_ids; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::NoOpProgressContext no_op; + m_image_size = 1 << m_src_image_ctx->order; + ASSERT_EQ(0, m_src_image_ctx->operations->resize(m_image_size, true, no_op)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + bool is_fast_diff(librbd::MockImageCtx &mock_image_ctx) { + return (mock_image_ctx.features & RBD_FEATURE_FAST_DIFF) != 0; + } + + void prepare_exclusive_lock(librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((mock_image_ctx.features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((m_src_image_ctx->features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + EXPECT_CALL(mock_exclusive_lock, start_op()).WillOnce( + ReturnNew([](int) {})); + } + + void expect_list_snaps(librbd::MockTestImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx, + const librados::snap_set_t &snap_set) { + expect_get_object_name(mock_image_ctx); + expect_set_snap_read(mock_io_ctx, CEPH_SNAPDIR); + EXPECT_CALL(mock_io_ctx, + list_snaps(mock_image_ctx.image_ctx->get_object_name(0), _)) + .WillOnce(DoAll(WithArg<1>(Invoke([&snap_set](librados::snap_set_t *out_snap_set) { + *out_snap_set = snap_set; + })), + Return(0))); + } + + void expect_list_snaps(librbd::MockTestImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx, int r) { + expect_get_object_name(mock_image_ctx); + expect_set_snap_read(mock_io_ctx, CEPH_SNAPDIR); + auto &expect = EXPECT_CALL(mock_io_ctx, + list_snaps(mock_image_ctx.image_ctx->get_object_name(0), + _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_object_name(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_object_name(0)) + .WillOnce(Return(mock_image_ctx.image_ctx->get_object_name(0))); + } + + MockObjectCopyRequest *create_request( + librbd::MockTestImageCtx &mock_src_image_ctx, + librbd::MockTestImageCtx &mock_dst_image_ctx, Context *on_finish) { + expect_get_object_name(mock_dst_image_ctx); + return new MockObjectCopyRequest(&mock_src_image_ctx, &mock_dst_image_ctx, + m_snap_map, 0, on_finish); + } + + void expect_set_snap_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t snap_id) { + EXPECT_CALL(mock_io_ctx, set_snap_read(snap_id)); + } + + void expect_sparse_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, uint64_t offset, + uint64_t length, int r) { + + auto &expect = EXPECT_CALL(mock_io_ctx, sparse_read(_, offset, length, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_sparse_read(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const interval_set &extents, int r) { + for (auto extent : extents) { + expect_sparse_read(mock_io_ctx, extent.first, extent.second, r); + if (r < 0) { + break; + } + } + } + + void expect_write(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t offset, uint64_t length, + const SnapContext &snapc, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, write(_, _, length, offset, snapc)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_write(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const interval_set &extents, + const SnapContext &snapc, int r) { + for (auto extent : extents) { + expect_write(mock_io_ctx, extent.first, extent.second, snapc, r); + if (r < 0) { + break; + } + } + } + + void expect_truncate(librados::MockTestMemIoCtxImpl &mock_io_ctx, + uint64_t offset, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, truncate(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove(librados::MockTestMemIoCtxImpl &mock_io_ctx, int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, remove(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_update_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap &mock_object_map, + librados::snap_t snap_id, uint8_t state, + int r) { + if (mock_image_ctx.image_ctx->object_map != nullptr) { + auto &expect = EXPECT_CALL(mock_object_map, aio_update(snap_id, 0, 1, state, _, _, _)); + if (r < 0) { + expect.WillOnce(DoAll(WithArg<6>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + })), + Return(true))); + } else { + expect.WillOnce(DoAll(WithArg<6>(Invoke([&mock_image_ctx, snap_id, state, r](Context *ctx) { + assert(mock_image_ctx.image_ctx->snap_lock.is_locked()); + assert(mock_image_ctx.image_ctx->object_map_lock.is_wlocked()); + mock_image_ctx.image_ctx->object_map->aio_update( + snap_id, 0, 1, state, boost::none, {}, ctx); + })), + Return(true))); + } + } + } + + int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id) { + int r = image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), snap_name); + if (r < 0) { + return r; + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + + if (image_ctx->snap_ids.count({cls::rbd::UserSnapshotNamespace(), + snap_name}) == 0) { + return -ENOENT; + } + + if (snap_id != nullptr) { + *snap_id = image_ctx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}]; + } + return 0; + } + + int create_snap(const char* snap_name) { + librados::snap_t src_snap_id; + int r = create_snap(m_src_image_ctx, snap_name, &src_snap_id); + if (r < 0) { + return r; + } + + librados::snap_t dst_snap_id; + r = create_snap(m_dst_image_ctx, snap_name, &dst_snap_id); + if (r < 0) { + return r; + } + + // collection of all existing snaps in dst image + SnapIds dst_snap_ids({dst_snap_id}); + if (!m_snap_map.empty()) { + dst_snap_ids.insert(dst_snap_ids.end(), + m_snap_map.rbegin()->second.begin(), + m_snap_map.rbegin()->second.end()); + } + m_snap_map[src_snap_id] = dst_snap_ids; + m_src_snap_ids.push_back(src_snap_id); + m_dst_snap_ids.push_back(dst_snap_id); + + return 0; + } + + std::string get_snap_name(librbd::ImageCtx *image_ctx, + librados::snap_t snap_id) { + auto it = std::find_if(image_ctx->snap_ids.begin(), + image_ctx->snap_ids.end(), + [snap_id](const std::pair, + librados::snap_t> &pair) { + return (pair.second == snap_id); + }); + if (it == image_ctx->snap_ids.end()) { + return ""; + } + return it->first.second; + } + + int compare_objects() { + SnapMap snap_map(m_snap_map); + if (snap_map.empty()) { + return -ENOENT; + } + + int r; + uint64_t object_size = 1 << m_src_image_ctx->order; + while (!snap_map.empty()) { + librados::snap_t src_snap_id = snap_map.begin()->first; + librados::snap_t dst_snap_id = *snap_map.begin()->second.begin(); + snap_map.erase(snap_map.begin()); + + std::string snap_name = get_snap_name(m_src_image_ctx, src_snap_id); + if (snap_name.empty()) { + return -ENOENT; + } + + std::cout << "comparing '" << snap_name << " (" << src_snap_id + << " to " << dst_snap_id << ")" << std::endl; + + r = librbd::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + + r = librbd::snap_set(m_dst_image_ctx, + cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + + bufferlist src_bl; + src_bl.append(std::string(object_size, '1')); + r = m_src_image_ctx->io_work_queue->read( + 0, object_size, librbd::io::ReadResult{&src_bl}, 0); + if (r < 0) { + return r; + } + + bufferlist dst_bl; + dst_bl.append(std::string(object_size, '1')); + r = m_dst_image_ctx->io_work_queue->read( + 0, object_size, librbd::io::ReadResult{&dst_bl}, 0); + if (r < 0) { + return r; + } + + if (!src_bl.contents_equal(dst_bl)) { + return -EBADMSG; + } + } + + r = librbd::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), + nullptr); + if (r < 0) { + return r; + } + r = librbd::snap_set(m_dst_image_ctx, + cls::rbd::UserSnapshotNamespace(), + nullptr); + if (r < 0) { + return r; + } + + return 0; + } +}; + +TEST_F(TestMockDeepCopyObjectCopyRequest, DNE) { + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, -ENOENT); + + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Write) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadMissingStaleSnapSet) { + ASSERT_EQ(0, create_snap("one")); + ASSERT_EQ(0, create_snap("two")); + + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("three")); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + librados::clone_info_t dummy_clone_info; + dummy_clone_info.cloneid = librados::SNAP_HEAD; + dummy_clone_info.size = 123; + + librados::snap_set_t dummy_snap_set1; + dummy_snap_set1.clones.push_back(dummy_clone_info); + + dummy_clone_info.size = 234; + librados::snap_set_t dummy_snap_set2; + dummy_snap_set2.clones.push_back(dummy_clone_info); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, dummy_snap_set1); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, 123, -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, dummy_snap_set2); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, 234, -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[3]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), + {m_dst_snap_ids[1], {m_dst_snap_ids[1], + m_dst_snap_ids[0]}}, + 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[2], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[3], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadMissingUpToDateSnapMap) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), -ENOENT); + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + + request->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, ReadError) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), -EINVAL); + + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, WriteError) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, -EINVAL); + + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, WriteSnaps) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + + interval_set two; + scribble(m_src_image_ctx, 10, 102400, &two); + ASSERT_EQ(0, create_snap("two")); + + if (one.range_end() < two.range_end()) { + interval_set resize_diff; + resize_diff.insert(one.range_end(), two.range_end() - one.range_end()); + two.union_of(resize_diff); + } + + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[2]); + expect_sparse_read(mock_src_io_ctx, two, 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, two, + {m_dst_snap_ids[0], {m_dst_snap_ids[0]}}, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[2], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Trim) { + ASSERT_EQ(0, m_src_image_ctx->operations->metadata_set( + "conf_rbd_skip_partial_discard", "false")); + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + + // trim the object + uint64_t trim_offset = rand() % one.range_end(); + ASSERT_LE(0, m_src_image_ctx->io_work_queue->discard( + trim_offset, one.range_end() - trim_offset, m_src_image_ctx->skip_partial_discard)); + ASSERT_EQ(0, create_snap("copy")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[0]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_truncate(mock_dst_io_ctx, trim_offset, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], OBJECT_EXISTS, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +TEST_F(TestMockDeepCopyObjectCopyRequest, Remove) { + // scribble some data + interval_set one; + scribble(m_src_image_ctx, 10, 102400, &one); + ASSERT_EQ(0, create_snap("one")); + ASSERT_EQ(0, create_snap("two")); + + // remove the object + uint64_t object_size = 1 << m_src_image_ctx->order; + ASSERT_LE(0, m_src_image_ctx->io_work_queue->discard(0, object_size, m_src_image_ctx->skip_partial_discard)); + ASSERT_EQ(0, create_snap("copy")); + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + librbd::MockObjectMap mock_object_map; + mock_dst_image_ctx.object_map = &mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + C_SaferCond ctx; + MockObjectCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + + librados::MockTestMemIoCtxImpl &mock_src_io_ctx(get_mock_io_ctx( + request->get_src_io_ctx())); + librados::MockTestMemIoCtxImpl &mock_dst_io_ctx(get_mock_io_ctx( + request->get_dst_io_ctx())); + + InSequence seq; + expect_list_snaps(mock_src_image_ctx, mock_src_io_ctx, 0); + expect_set_snap_read(mock_src_io_ctx, m_src_snap_ids[1]); + expect_sparse_read(mock_src_io_ctx, 0, one.range_end(), 0); + expect_start_op(mock_exclusive_lock); + expect_write(mock_dst_io_ctx, 0, one.range_end(), {0, {}}, 0); + expect_start_op(mock_exclusive_lock); + expect_remove(mock_dst_io_ctx, 0); + expect_start_op(mock_exclusive_lock); + uint8_t state = OBJECT_EXISTS; + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[0], state, 0); + expect_start_op(mock_exclusive_lock); + expect_update_object_map(mock_dst_image_ctx, mock_object_map, + m_dst_snap_ids[1], is_fast_diff(mock_dst_image_ctx) ? + OBJECT_EXISTS_CLEAN : OBJECT_EXISTS, 0); + + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(0, compare_objects()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc b/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc new file mode 100644 index 00000000000..b29b875e27d --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SetHeadRequest.cc @@ -0,0 +1,233 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librados_test_stub/LibradosTestStub.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "osdc/Striper.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librbd/deep_copy/SetHeadRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SetHeadRequest.cc" +template class librbd::deep_copy::SetHeadRequest; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySetHeadRequest : public TestMockFixture { +public: + typedef SetHeadRequest MockSetHeadRequest; + + librbd::ImageCtx *m_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_image_ctx->cct, &m_thread_pool, + &m_work_queue); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op()).WillOnce( + ReturnNew([](int) {})); + } + + void expect_test_features(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_set_size(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("set_size"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_parent(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("remove_parent"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_set_parent(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), StrEq("set_parent"), _, _, _)) + .WillOnce(Return(r)); + } + + MockSetHeadRequest *create_request( + librbd::MockTestImageCtx &mock_local_image_ctx, uint64_t size, + const librbd::ParentSpec &parent_spec, uint64_t parent_overlap, + Context *on_finish) { + return new MockSetHeadRequest(&mock_local_image_ctx, size, parent_spec, + parent_overlap, on_finish); + } +}; + +TEST_F(TestMockDeepCopySetHeadRequest, Resize) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_size(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, 123, {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, ResizeError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_size(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, 123, {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveParent) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_remove_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveParentError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_remove_parent(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, RemoveSetParent) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec.pool_id = 213; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_remove_parent(mock_image_ctx, 0); + expect_start_op(mock_exclusive_lock); + expect_set_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentSpec) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentOverlap) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + mock_image_ctx.parent_md.spec = {123, "test", 0}; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + mock_image_ctx.parent_md.spec, 123, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySetHeadRequest, SetParentError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_start_op(mock_exclusive_lock); + expect_set_parent(mock_image_ctx, -ESTALE); + + C_SaferCond ctx; + auto request = create_request(mock_image_ctx, m_image_ctx->size, + {123, "test", 0}, 0, &ctx); + request->send(); + ASSERT_EQ(-ESTALE, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc new file mode 100644 index 00000000000..a8e90efdcb8 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SnapshotCopyRequest.cc @@ -0,0 +1,722 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/SnapshotCopyRequest.h" +#include "librbd/deep_copy/SnapshotCreateRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +struct SnapshotCreateRequest { + static SnapshotCreateRequest* s_instance; + static SnapshotCreateRequest* create(librbd::MockTestImageCtx* image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, + Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + SnapshotCreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SnapshotCreateRequest* SnapshotCreateRequest::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SnapshotCopyRequest.cc" +template class librbd::deep_copy::SnapshotCopyRequest; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySnapshotCopyRequest : public TestMockFixture { +public: + typedef SnapshotCopyRequest MockSnapshotCopyRequest; + typedef SnapshotCreateRequest MockSnapshotCreateRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + librbd::SnapSeqs m_snap_seqs; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + librbd::RBD rbd; + std::string dst_image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(rbd, m_ioctx, dst_image_name, m_image_size)); + ASSERT_EQ(0, open_image(dst_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void prepare_exclusive_lock(librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((mock_image_ctx.features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + if ((m_src_image_ctx->features & RBD_FEATURE_EXCLUSIVE_LOCK) == 0) { + return; + } + EXPECT_CALL(mock_exclusive_lock, start_op()).WillOnce( + ReturnNew([](int) {})); + } + + void expect_get_snap_namespace(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_namespace(snap_id, _)) + .WillOnce(DoAll(SetArgPointee<1>(cls::rbd::UserSnapshotNamespace()), + Return(0))); + } + + void expect_snap_create(librbd::MockTestImageCtx &mock_image_ctx, + MockSnapshotCreateRequest &mock_snapshot_create_request, + const std::string &snap_name, uint64_t snap_id, int r) { + EXPECT_CALL(mock_snapshot_create_request, send()) + .WillOnce(DoAll(Invoke([&mock_image_ctx, snap_id, snap_name]() { + inject_snap(mock_image_ctx, snap_id, snap_name); + }), + Invoke([this, &mock_snapshot_create_request, r]() { + m_work_queue->queue(mock_snapshot_create_request.on_finish, r); + }))); + } + + void expect_snap_remove(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_protect(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_protect(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_unprotect(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_snap_is_protected(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, bool is_protected, int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(snap_id, _)) + .WillOnce(DoAll(SetArgPointee<1>(is_protected), + Return(r))); + } + + void expect_snap_is_unprotected(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, bool is_unprotected, int r) { + EXPECT_CALL(mock_image_ctx, is_snap_unprotected(snap_id, _)) + .WillOnce(DoAll(SetArgPointee<1>(is_unprotected), + Return(r))); + } + + static void inject_snap(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const std::string &snap_name) { + mock_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}] = snap_id; + } + + MockSnapshotCopyRequest *create_request( + librbd::MockTestImageCtx &mock_src_image_ctx, + librbd::MockTestImageCtx &mock_dst_image_ctx, Context *on_finish) { + return new MockSnapshotCopyRequest(&mock_src_image_ctx, &mock_dst_image_ctx, + m_work_queue, &m_snap_seqs, on_finish); + } + + int create_snap(librbd::ImageCtx *image_ctx, const std::string &snap_name, + bool protect = false) { + int r = image_ctx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + + if (protect) { + r = image_ctx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str()); + if (r < 0) { + return r; + } + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + return 0; + } + + void validate_snap_seqs(const librbd::SnapSeqs &snap_seqs) { + ASSERT_EQ(snap_seqs, m_snap_seqs); + } +}; + +TEST_F(TestMockDeepCopySnapshotCopyRequest, Empty) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreate) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap2")); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t src_snap_id2 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap2"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", 12, 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id2); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap2", 14, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id2, false, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}, {src_snap_id2, 14}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateError) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t src_snap_id1 = mock_src_image_ctx.snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateCancel) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(mock_snapshot_create_request, send()) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + Invoke([this, &mock_snapshot_create_request]() { + m_work_queue->queue(mock_snapshot_create_request.on_finish, 0); + }))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapRemoveAndCreate) { + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1")); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1")); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapRemoveError) { + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, dst_snap_id1}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", -EBUSY); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectCancel) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_snap_is_unprotected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(*mock_dst_image_ctx.operations, + execute_snap_unprotect(_, StrEq("snap1"), _)) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + WithArg<2>(Invoke([this](Context *ctx) { + m_work_queue->queue(ctx, 0); + })))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapUnprotectRemove) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, + m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}], + false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_unprotect(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_remove(mock_dst_image_ctx, "snap1", 0); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, false, 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapCreateProtect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCreateRequest mock_snapshot_create_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_dst_image_ctx, mock_snapshot_create_request, "snap1", + 12, 0); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, 12, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, 12}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtect) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", 0); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + validate_snap_seqs({{src_snap_id1, dst_snap_id1}}); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtectError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_protect(mock_dst_image_ctx, "snap1", -EINVAL); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCopyRequest, SnapProtectCancel) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snap(m_src_image_ctx, "snap1", true)); + ASSERT_EQ(0, create_snap(m_dst_image_ctx, "snap1", true)); + + uint64_t src_snap_id1 = m_src_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + uint64_t dst_snap_id1 = m_dst_image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace(), "snap1"}]; + m_snap_seqs[src_snap_id1] = dst_snap_id1; + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + + librbd::MockExclusiveLock mock_exclusive_lock; + prepare_exclusive_lock(mock_dst_image_ctx, mock_exclusive_lock); + + C_SaferCond ctx; + MockSnapshotCopyRequest *request = create_request(mock_src_image_ctx, + mock_dst_image_ctx, &ctx); + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_snap_is_unprotected(mock_dst_image_ctx, dst_snap_id1, true, 0); + expect_get_snap_namespace(mock_dst_image_ctx, dst_snap_id1); + expect_get_snap_namespace(mock_src_image_ctx, src_snap_id1); + expect_snap_is_protected(mock_src_image_ctx, src_snap_id1, true, 0); + expect_snap_is_protected(mock_dst_image_ctx, dst_snap_id1, false, 0); + expect_start_op(mock_exclusive_lock); + EXPECT_CALL(*mock_dst_image_ctx.operations, + execute_snap_protect(_, StrEq("snap1"), _)) + .WillOnce(DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + WithArg<2>(Invoke([this](Context *ctx) { + m_work_queue->queue(ctx, 0); + })))); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 00000000000..f12a48c31c7 --- /dev/null +++ b/src/test/librbd/deep_copy/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,256 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librados_test_stub/LibradosTestStub.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "osdc/Striper.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/deep_copy/SnapshotCreateRequest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +class SetHeadRequest { +public: + static SetHeadRequest* s_instance; + Context *on_finish; + + static SetHeadRequest* create(librbd::MockTestImageCtx *image_ctx, + uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetHeadRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetHeadRequest* SetHeadRequest::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/deep_copy/SnapshotCreateRequest.cc" +template class librbd::deep_copy::SnapshotCreateRequest; + +namespace librbd { +namespace deep_copy { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockDeepCopySnapshotCreateRequest : public TestMockFixture { +public: + typedef SetHeadRequest MockSetHeadRequest; + typedef SnapshotCreateRequest MockSnapshotCreateRequest; + + librbd::ImageCtx *m_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_image_ctx->cct, &m_thread_pool, + &m_work_queue); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op()).WillOnce( + ReturnNew([](int) {})); + } + + void expect_test_features(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_set_head(MockSetHeadRequest &mock_set_head_request, int r) { + EXPECT_CALL(mock_set_head_request, send()) + .WillOnce(Invoke([this, &mock_set_head_request, r]() { + mock_set_head_request.on_finish->complete(r); + })); + } + + void expect_snap_create(librbd::MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, uint64_t snap_id, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_create(_, StrEq(snap_name), _, 0, true)) + .WillOnce(DoAll(InvokeWithoutArgs([&mock_image_ctx, snap_id, snap_name]() { + inject_snap(mock_image_ctx, snap_id, snap_name); + }), + WithArg<2>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + })))); + } + + void expect_object_map_resize(librbd::MockTestImageCtx &mock_image_ctx, + librados::snap_t snap_id, int r) { + std::string oid(librbd::ObjectMap<>::object_map_name(mock_image_ctx.id, + snap_id)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, _)) + .WillOnce(Return(r)); + } + + static void inject_snap(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const std::string &snap_name) { + mock_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}] = snap_id; + } + + MockSnapshotCreateRequest *create_request(librbd::MockTestImageCtx &mock_local_image_ctx, + const std::string &snap_name, + const cls::rbd::SnapshotNamespace &snap_namespace, + uint64_t size, + const librbd::ParentSpec &spec, + uint64_t parent_overlap, + Context *on_finish) { + return new MockSnapshotCreateRequest(&mock_local_image_ctx, snap_name, snap_namespace, size, + spec, parent_overlap, on_finish); + } +}; + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SnapCreate) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, false); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SetHeadError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + 123, {}, 0, + &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, SnapCreateError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, ResizeObjectMap) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_start_op(mock_exclusive_lock); + expect_object_map_resize(mock_image_ctx, 10, 0); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopySnapshotCreateRequest, ResizeObjectMapError) { + librbd::MockTestImageCtx mock_image_ctx(*m_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + MockSetHeadRequest mock_set_head_request; + + InSequence seq; + expect_set_head(mock_set_head_request, 0); + expect_start_op(mock_exclusive_lock); + expect_snap_create(mock_image_ctx, "snap1", 10, 0); + expect_test_features(mock_image_ctx, RBD_FEATURE_OBJECT_MAP, true); + expect_start_op(mock_exclusive_lock); + expect_object_map_resize(mock_image_ctx, 10, -EINVAL); + + C_SaferCond ctx; + MockSnapshotCreateRequest *request = create_request(mock_image_ctx, + "snap1", + cls::rbd::UserSnapshotNamespace(), + m_image_ctx->size, + {}, 0, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace deep_copy +} // namespace librbd diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h index 3b922819459..ee020c13509 100644 --- a/src/test/librbd/mock/MockImageCtx.h +++ b/src/test/librbd/mock/MockImageCtx.h @@ -155,6 +155,7 @@ struct MockImageCtx { librados::snap_t(cls::rbd::SnapshotNamespace snap_namespace, std::string in_snap_name)); MOCK_CONST_METHOD1(get_snap_info, const SnapInfo*(librados::snap_t)); + MOCK_CONST_METHOD2(get_snap_name, int(librados::snap_t, std::string *)); MOCK_CONST_METHOD2(get_snap_namespace, int(librados::snap_t, cls::rbd::SnapshotNamespace *out_snap_namespace)); MOCK_CONST_METHOD2(get_parent_spec, int(librados::snap_t in_snap_id, diff --git a/src/test/librbd/test_DeepCopy.cc b/src/test/librbd/test_DeepCopy.cc new file mode 100644 index 00000000000..ec78686a0d9 --- /dev/null +++ b/src/test/librbd/test_DeepCopy.cc @@ -0,0 +1,405 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/internal.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" + +void register_test_deep_copy() { +} + +struct TestDeepCopy : public TestFixture { + void SetUp() override { + TestFixture::SetUp(); + + std::string image_name = get_temp_image_name(); + int order = 22; + uint64_t size = (1 << order) * 20; + uint64_t features = 0; + bool old_format = get_features(&features); + EXPECT_EQ(0, create_image_full_pp(m_rbd, m_ioctx, image_name, size, + features, old_format, &order)); + ASSERT_EQ(0, open_image(image_name, &m_src_ictx)); + + if (old_format) { + // The destination should always be in the new format. + uint64_t format = 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_FORMAT, format)); + } + } + + void TearDown() override { + if (m_src_ictx != nullptr) { + deep_copy(); + if (m_dst_ictx != nullptr) { + compare(); + close_image(m_dst_ictx); + } + close_image(m_src_ictx); + } + + TestFixture::TearDown(); + } + + void deep_copy() { + std::string dst_name = get_temp_image_name(); + librbd::NoOpProgressContext no_op; + EXPECT_EQ(0, m_src_ictx->io_work_queue->flush()); + EXPECT_EQ(0, librbd::api::Image<>::deep_copy(m_src_ictx, m_src_ictx->md_ctx, + dst_name.c_str(), m_opts, + no_op)); + EXPECT_EQ(0, open_image(dst_name, &m_dst_ictx)); + } + + void compare() { + vector src_snaps, dst_snaps; + + EXPECT_EQ(m_src_ictx->size, m_dst_ictx->size); + EXPECT_EQ(0, librbd::snap_list(m_src_ictx, src_snaps)); + EXPECT_EQ(0, librbd::snap_list(m_dst_ictx, dst_snaps)); + EXPECT_EQ(src_snaps.size(), dst_snaps.size()); + for (size_t i = 0; i <= src_snaps.size(); i++) { + const char *src_snap_name = nullptr; + const char *dst_snap_name = nullptr; + if (i < src_snaps.size()) { + EXPECT_EQ(src_snaps[i].name, dst_snaps[i].name); + src_snap_name = src_snaps[i].name.c_str(); + dst_snap_name = dst_snaps[i].name.c_str(); + } + EXPECT_EQ(0, librbd::snap_set(m_src_ictx, cls::rbd::UserSnapshotNamespace(), + src_snap_name)); + EXPECT_EQ(0, librbd::snap_set(m_dst_ictx, cls::rbd::UserSnapshotNamespace(), + dst_snap_name)); + uint64_t src_size, dst_size; + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + RWLock::RLocker dst_locker(m_dst_ictx->snap_lock); + src_size = m_src_ictx->get_image_size(m_src_ictx->snap_id); + dst_size = m_dst_ictx->get_image_size(m_dst_ictx->snap_id); + } + EXPECT_EQ(src_size, dst_size); + + ssize_t read_size = 1 << m_src_ictx->order; + uint64_t offset = 0; + while (offset < src_size) { + read_size = std::min(read_size, static_cast(src_size - offset)); + + bufferptr src_ptr(read_size); + bufferlist src_bl; + src_bl.push_back(src_ptr); + librbd::io::ReadResult src_result{&src_bl}; + EXPECT_EQ(read_size, m_src_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{src_result}, 0)); + + bufferptr dst_ptr(read_size); + bufferlist dst_bl; + dst_bl.push_back(dst_ptr); + librbd::io::ReadResult dst_result{&dst_bl}; + EXPECT_EQ(read_size, m_dst_ictx->io_work_queue->read( + offset, read_size, librbd::io::ReadResult{dst_result}, 0)); + + if (!src_bl.contents_equal(dst_bl)) { + std::cout << "snap: " << (src_snap_name ? src_snap_name : "null") + << ", block " << offset << "~" << read_size << " differs" + << std::endl; + // std::cout << "src block: " << std::endl; src_bl.hexdump(std::cout); + // std::cout << "dst block: " << std::endl; dst_bl.hexdump(std::cout); + } + EXPECT_TRUE(src_bl.contents_equal(dst_bl)); + offset += read_size; + } + } + } + + void test_no_snaps() { + bufferlist bl; + bl.append(std::string(((1 << m_src_ictx->order) * 2) + 1, '1')); + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(2 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + } + + void test_snaps() { + bufferlist bl; + bl.append(std::string(((1 << m_src_ictx->order) * 2) + 1, '1')); + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap1")); + + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(1 * bl.length(), bl.length(), + bufferlist{bl}, 0)); + bufferlist bl1; + bl1.append(std::string(1000, 'X')); + ASSERT_EQ(static_cast(bl1.length()), + m_src_ictx->io_work_queue->write(0 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast(bl1.length()), + m_src_ictx->io_work_queue->discard(bl1.length() + 10, + bl1.length(), false)); + + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap2")); + ASSERT_EQ(static_cast(bl1.length()), + m_src_ictx->io_work_queue->write(1 * bl.length(), bl1.length(), + bufferlist{bl1}, 0)); + ASSERT_EQ(static_cast(bl1.length()), + m_src_ictx->io_work_queue->discard(2 * bl1.length() + 10, + bl1.length(), false)); + } + + void test_snap_discard() { + bufferlist bl; + bl.append(std::string(100, '1')); + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(0, bl.length(), bufferlist{bl}, + 0)); + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + ASSERT_EQ(0, snap_create(*m_src_ictx, "snap")); + + size_t len = (1 << m_src_ictx->order) * 2; + ASSERT_EQ(static_cast(len), + m_src_ictx->io_work_queue->discard(0, len, false)); + } + + void test_stress() { + uint64_t initial_size, size; + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + size = initial_size = m_src_ictx->get_image_size(CEPH_NOSNAP); + } + + int nsnaps = 4; + const char *c = getenv("TEST_RBD_DEEPCOPY_STRESS_NSNAPS"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nsnaps); + } + + int nwrites = 4; + c = getenv("TEST_RBD_DEEPCOPY_STRESS_NWRITES"); + if (c != NULL) { + std::stringstream ss(c); + ASSERT_TRUE(ss >> nwrites); + } + + for (int i = 0; i < nsnaps; i++) { + for (int j = 0; j < nwrites; j++) { + size_t len = rand() % ((1 << m_src_ictx->order) * 2); + ASSERT_GT(size, len); + bufferlist bl; + bl.append(std::string(len, static_cast('A' + i))); + uint64_t off = std::min(static_cast(rand() % size), + static_cast(size - len)); + std::cout << "write: " << static_cast('A' + i) << " " << off + << "~" << len << std::endl; + ASSERT_EQ(static_cast(bl.length()), + m_src_ictx->io_work_queue->write(off, bl.length(), + bufferlist{bl}, 0)); + len = rand() % ((1 << m_src_ictx->order) * 2); + ASSERT_GT(size, len); + off = std::min(static_cast(rand() % size), + static_cast(size - len)); + std::cout << "discard: " << off << "~" << len << std::endl; + ASSERT_EQ(static_cast(len), + m_src_ictx->io_work_queue->discard(off, len, false)); + } + + ASSERT_EQ(0, m_src_ictx->io_work_queue->flush()); + + std::string snap_name = "snap" + stringify(i); + std::cout << "snap: " << snap_name << std::endl; + ASSERT_EQ(0, snap_create(*m_src_ictx, snap_name.c_str())); + + if (rand() % 2) { + librbd::NoOpProgressContext no_op; + uint64_t new_size = initial_size + rand() % size; + std::cout << "resize: " << new_size << std::endl; + ASSERT_EQ(0, m_src_ictx->operations->resize(new_size, true, no_op)); + { + RWLock::RLocker src_locker(m_src_ictx->snap_lock); + size = m_src_ictx->get_image_size(CEPH_NOSNAP); + } + ASSERT_EQ(new_size, size); + } + } + } + + librbd::ImageCtx *m_src_ictx = nullptr; + librbd::ImageCtx *m_dst_ictx = nullptr; + librbd::ImageOptions m_opts; +}; + +TEST_F(TestDeepCopy, Empty) +{ +} + +TEST_F(TestDeepCopy, NoSnaps) +{ + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps) +{ + test_snaps(); +} + +TEST_F(TestDeepCopy, SnapDiscard) +{ + test_snap_discard(); +} + +TEST_F(TestDeepCopy, Stress) +{ + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Stress_LargerDstObjSize) +{ + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 1; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Stress_SmallerDstObjSize) +{ + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + uint64_t stripe_unit = m_src_ictx->stripe_unit >> 2; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Stress_StrippingLargerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order + 1 + rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_stress(); +} + +TEST_F(TestDeepCopy, NoSnaps_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_no_snaps(); +} + +TEST_F(TestDeepCopy, Snaps_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1; + uint64_t stripe_unit = 1 << (order - 2); + uint64_t stripe_count = 4; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_snaps(); +} + +TEST_F(TestDeepCopy, Stress_StrippingSmallerDstObjSize) +{ + REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); + + uint64_t order = m_src_ictx->order - 1 - rand() % 2; + uint64_t stripe_unit = 1 << (order - rand() % 4); + uint64_t stripe_count = 2 + rand() % 14; + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_ORDER, order)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit)); + ASSERT_EQ(0, m_opts.set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count)); + + test_stress(); +} diff --git a/src/test/librbd/test_main.cc b/src/test/librbd/test_main.cc index 281ccabfb78..654c8912d6f 100644 --- a/src/test/librbd/test_main.cc +++ b/src/test/librbd/test_main.cc @@ -10,30 +10,32 @@ extern void register_test_librbd(); #ifdef TEST_LIBRBD_INTERNALS +extern void register_test_deep_copy(); extern void register_test_groups(); extern void register_test_image_watcher(); extern void register_test_internal(); extern void register_test_journal_entries(); extern void register_test_journal_replay(); -extern void register_test_object_map(); -extern void register_test_operations(); extern void register_test_mirroring(); extern void register_test_mirroring_watcher(); +extern void register_test_object_map(); +extern void register_test_operations(); #endif // TEST_LIBRBD_INTERNALS int main(int argc, char **argv) { register_test_librbd(); #ifdef TEST_LIBRBD_INTERNALS + register_test_deep_copy(); register_test_groups(); register_test_image_watcher(); register_test_internal(); register_test_journal_entries(); register_test_journal_replay(); - register_test_object_map(); - register_test_operations(); register_test_mirroring(); register_test_mirroring_watcher(); + register_test_object_map(); + register_test_operations(); #endif // TEST_LIBRBD_INTERNALS ::testing::InitGoogleTest(&argc, argv); diff --git a/src/test/librbd/test_mock_DeepCopyRequest.cc b/src/test/librbd/test_mock_DeepCopyRequest.cc new file mode 100644 index 00000000000..dbc63b5ea76 --- /dev/null +++ b/src/test/librbd/test_mock_DeepCopyRequest.cc @@ -0,0 +1,436 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "include/rbd/librbd.hpp" +#include "librbd/DeepCopyRequest.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/internal.h" +#include "librbd/deep_copy/ImageCopyRequest.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "librbd/deep_copy/SetHeadRequest.h" +#include "librbd/deep_copy/SnapshotCopyRequest.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librbd/test_support.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace deep_copy { + +template <> +class ImageCopyRequest { +public: + static ImageCopyRequest* s_instance; + Context *on_finish; + + static ImageCopyRequest* create( + librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, librados::snap_t snap_id_start, + librados::snap_t snap_id_end, const ObjectNumber &object_number, + const SnapSeqs &snap_seqs, ProgressContext *prog_ctx, + Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + ImageCopyRequest() { + s_instance = this; + } + + void put() { + } + + void get() { + } + + MOCK_METHOD0(cancel, void()); + MOCK_METHOD0(send, void()); +}; + +template <> +class MetadataCopyRequest { +public: + static MetadataCopyRequest* s_instance; + Context *on_finish; + + static MetadataCopyRequest* create(librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MetadataCopyRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +class SetHeadRequest { +public: + static SetHeadRequest* s_instance; + Context *on_finish; + + static SetHeadRequest* create(librbd::MockTestImageCtx *image_ctx, + uint64_t size, + const librbd::ParentSpec &parent_spec, + uint64_t parent_overlap, Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetHeadRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +class SnapshotCopyRequest { +public: + static SnapshotCopyRequest* s_instance; + Context *on_finish; + + static SnapshotCopyRequest* create(librbd::MockTestImageCtx *src_image_ctx, + librbd::MockTestImageCtx *dst_image_ctx, + ContextWQ *work_queue, SnapSeqs *snap_seqs, + Context *on_finish) { + assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SnapshotCopyRequest() { + s_instance = this; + } + + void put() { + } + + void get() { + } + + MOCK_METHOD0(cancel, void()); + MOCK_METHOD0(send, void()); +}; + +ImageCopyRequest* ImageCopyRequest::s_instance = nullptr; +MetadataCopyRequest* MetadataCopyRequest::s_instance = nullptr; +SetHeadRequest* SetHeadRequest::s_instance = nullptr; +SnapshotCopyRequest* SnapshotCopyRequest::s_instance = nullptr; + +} // namespace deep_copy +} // namespace librbd + +// template definitions +#include "librbd/DeepCopyRequest.cc" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNew; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockDeepCopyRequest : public TestMockFixture { +public: + typedef librbd::DeepCopyRequest MockDeepCopyRequest; + typedef librbd::deep_copy::ImageCopyRequest MockImageCopyRequest; + typedef librbd::deep_copy::MetadataCopyRequest MockMetadataCopyRequest; + typedef librbd::deep_copy::SetHeadRequest MockSetHeadRequest; + typedef librbd::deep_copy::SnapshotCopyRequest MockSnapshotCopyRequest; + + librbd::ImageCtx *m_src_image_ctx; + librbd::ImageCtx *m_dst_image_ctx; + ThreadPool *m_thread_pool; + ContextWQ *m_work_queue; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::RBD rbd; + ASSERT_EQ(0, open_image(m_image_name, &m_src_image_ctx)); + + ASSERT_EQ(0, open_image(m_image_name, &m_dst_image_ctx)); + + librbd::ImageCtx::get_thread_pool_instance(m_src_image_ctx->cct, + &m_thread_pool, &m_work_queue); + } + + void TearDown() override { + TestMockFixture::TearDown(); + } + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_start_op(librbd::MockExclusiveLock &mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, start_op()).WillOnce( + ReturnNew([](int) {})); + } + + void expect_rollback_object_map(librbd::MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, rollback(_, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context *ctx) { + m_work_queue->queue(ctx, r); + }))); + } + + void expect_create_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap *mock_object_map) { + EXPECT_CALL(mock_image_ctx, create_object_map(CEPH_NOSNAP)) + .WillOnce(Return(mock_object_map)); + } + + void expect_open_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, open(_)) + .WillOnce(Invoke([this](Context *ctx) { + m_work_queue->queue(ctx, 0); + })); + } + + void expect_copy_snapshots( + MockSnapshotCopyRequest &mock_snapshot_copy_request, int r) { + EXPECT_CALL(mock_snapshot_copy_request, send()) + .WillOnce(Invoke([this, &mock_snapshot_copy_request, r]() { + m_work_queue->queue(mock_snapshot_copy_request.on_finish, r); + })); + } + + void expect_set_head(MockSetHeadRequest &mock_set_head_request, int r) { + EXPECT_CALL(mock_set_head_request, send()) + .WillOnce(Invoke([this, &mock_set_head_request, r]() { + mock_set_head_request.on_finish->complete(r); + })); + } + + void expect_copy_image(MockImageCopyRequest &mock_image_copy_request, int r) { + EXPECT_CALL(mock_image_copy_request, send()) + .WillOnce(Invoke([this, &mock_image_copy_request, r]() { + m_work_queue->queue(mock_image_copy_request.on_finish, r); + })); + } + + void expect_copy_object_map(librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockObjectMap *mock_object_map) { + expect_start_op(mock_exclusive_lock); + expect_rollback_object_map(*mock_object_map, 0); + } + + void expect_refresh_object_map(librbd::MockTestImageCtx &mock_image_ctx, + librbd::MockObjectMap *mock_object_map) { + expect_start_op(*mock_image_ctx.exclusive_lock); + expect_create_object_map(mock_image_ctx, mock_object_map); + expect_open_object_map(mock_image_ctx, *mock_object_map); + } + + void expect_copy_metadata(MockMetadataCopyRequest &mock_metadata_copy_request, + int r) { + EXPECT_CALL(mock_metadata_copy_request, send()) + .WillOnce(Invoke([this, &mock_metadata_copy_request, r]() { + m_work_queue->queue(mock_metadata_copy_request.on_finish, r); + })); + } +}; + +TEST_F(TestMockDeepCopyRequest, SimpleCopy) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSetHeadRequest mock_set_head_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_set_head(mock_set_head_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map); + } + expect_copy_metadata(mock_metadata_copy_request, 0); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, boost::none, + m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopySnapshots) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSnapshotCopyRequest mock_snapshot_copy_request; + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, boost::none, + m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnSetHead) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockSetHeadRequest mock_set_head_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_set_head(mock_set_head_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, boost::none, + m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopyImage) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockSetHeadRequest mock_set_head_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_set_head(mock_set_head_request, 0); + expect_copy_image(mock_image_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, boost::none, + m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, ErrorOnCopyMetadata) { + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSetHeadRequest mock_set_head_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_set_head(mock_set_head_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map); + } + expect_copy_metadata(mock_metadata_copy_request, -EINVAL); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, CEPH_NOSNAP, boost::none, + m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockDeepCopyRequest, Snap) { + EXPECT_EQ(0, snap_create(*m_src_image_ctx, "copy")); + EXPECT_EQ(0, librbd::snap_set(m_src_image_ctx, + cls::rbd::UserSnapshotNamespace(), "copy")); + + librbd::MockTestImageCtx mock_src_image_ctx(*m_src_image_ctx); + librbd::MockTestImageCtx mock_dst_image_ctx(*m_dst_image_ctx); + MockImageCopyRequest mock_image_copy_request; + MockMetadataCopyRequest mock_metadata_copy_request; + MockSnapshotCopyRequest mock_snapshot_copy_request; + + librbd::MockExclusiveLock mock_exclusive_lock; + mock_dst_image_ctx.exclusive_lock = &mock_exclusive_lock; + + librbd::MockObjectMap *mock_object_map = + is_feature_enabled(RBD_FEATURE_OBJECT_MAP) ? + new librbd::MockObjectMap() : nullptr; + mock_dst_image_ctx.object_map = mock_object_map; + + expect_test_features(mock_dst_image_ctx); + + InSequence seq; + expect_copy_snapshots(mock_snapshot_copy_request, 0); + expect_copy_image(mock_image_copy_request, 0); + if (mock_object_map != nullptr) { + expect_copy_object_map(mock_exclusive_lock, mock_object_map); + expect_refresh_object_map(mock_dst_image_ctx, mock_object_map); + } + expect_copy_metadata(mock_metadata_copy_request, 0); + + C_SaferCond ctx; + librbd::SnapSeqs snap_seqs = {{m_src_image_ctx->snap_id, 123}}; + librbd::NoOpProgressContext no_op; + auto request = librbd::DeepCopyRequest::create( + &mock_src_image_ctx, &mock_dst_image_ctx, 0, m_src_image_ctx->snap_id, + boost::none, m_work_queue, &snap_seqs, &no_op, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} diff --git a/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc b/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc index d7bbce2d5a5..84511411f74 100644 --- a/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc +++ b/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc @@ -133,7 +133,7 @@ TEST_F(TestMockImageReplayerEventPreprocessor, PreprocessSnapMapPrune) { event_preprocessor.preprocess(&event_entry, &ctx); ASSERT_EQ(0, ctx.wait()); - librbd::journal::MirrorPeerClientMeta::SnapSeqs expected_snap_seqs = {{5, 6}}; + librbd::SnapSeqs expected_snap_seqs = {{5, 6}}; ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs); } @@ -159,7 +159,7 @@ TEST_F(TestMockImageReplayerEventPreprocessor, PreprocessSnapRename) { event_preprocessor.preprocess(&event_entry, &ctx); ASSERT_EQ(0, ctx.wait()); - librbd::journal::MirrorPeerClientMeta::SnapSeqs expected_snap_seqs = {{5, 6}}; + librbd::SnapSeqs expected_snap_seqs = {{5, 6}}; ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs); librbd::journal::SnapRenameEvent *event = @@ -211,7 +211,7 @@ TEST_F(TestMockImageReplayerEventPreprocessor, PreprocessSnapRenameKnown) { event_preprocessor.preprocess(&event_entry, &ctx); ASSERT_EQ(0, ctx.wait()); - librbd::journal::MirrorPeerClientMeta::SnapSeqs expected_snap_seqs = {{5, 6}}; + librbd::SnapSeqs expected_snap_seqs = {{5, 6}}; ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs); librbd::journal::SnapRenameEvent *event = -- 2.39.5