set(librbd_internal_srcs
AsyncObjectThrottle.cc
AsyncRequest.cc
+ DeepCopyRequest.cc
ExclusiveLock.cc
ImageCtx.cc
ImageState.cc
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
--- /dev/null
+// -*- 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 <typename I>
+DeepCopyRequest<I>::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 <typename I>
+DeepCopyRequest<I>::~DeepCopyRequest() {
+ assert(m_snapshot_copy_request == nullptr);
+ assert(m_image_copy_request == nullptr);
+}
+
+template <typename I>
+void DeepCopyRequest<I>::send() {
+ int r = validate_copy_points();
+ if (r < 0) {
+ finish(r);
+ return;
+ }
+
+ send_copy_snapshots();
+}
+
+template <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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<I>, &DeepCopyRequest<I>::handle_copy_snapshots>(this);
+ m_snapshot_copy_request = SnapshotCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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<I>, &DeepCopyRequest<I>::handle_set_head>(this);
+ auto req = SetHeadRequest<I>::create(m_dst_image_ctx, size, parent_spec,
+ parent_overlap, ctx);
+ req->send();
+}
+
+template <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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<I>, &DeepCopyRequest<I>::handle_copy_image>(this);
+ m_image_copy_request = ImageCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::handle_copy_object_map(int r) {
+ ldout(m_cct, 20) << dendl;
+
+ assert(r == 0);
+ send_refresh_object_map();
+}
+
+template <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::send_copy_metadata() {
+ ldout(m_cct, 20) << dendl;
+
+ Context *ctx = create_context_callback<
+ DeepCopyRequest<I>, &DeepCopyRequest<I>::handle_copy_metadata>(this);
+ auto request = MetadataCopyRequest<I>::create(m_src_image_ctx,
+ m_dst_image_ctx, ctx);
+ request->send();
+}
+
+template <typename I>
+void DeepCopyRequest<I>::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 <typename I>
+int DeepCopyRequest<I>::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 <typename I>
+void DeepCopyRequest<I>::finish(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ put();
+}
+
+} // namespace librbd
+
+template class librbd::DeepCopyRequest<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <map>
+#include <vector>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace deep_copy {
+
+template <typename> class ImageCopyRequest;
+template <typename> class SnapshotCopyRequest;
+
+}
+
+template <typename ImageCtxT = ImageCtx>
+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
+ *
+ * <start>
+ * |
+ * 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 .
+ * <finish> < . . . . . . . . . . . . . .
+ *
+ * @endverbatim
+ */
+
+ typedef std::vector<librados::snap_t> SnapIds;
+ typedef std::map<librados::snap_t, SnapIds> 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<ImageCtxT> *m_snapshot_copy_request = nullptr;
+ deep_copy::ImageCopyRequest<ImageCtxT> *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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_REQUEST_H
#include "include/types.h"
#include "cls/rbd/cls_rbd_types.h"
+#include <map>
#include <string>
namespace librbd {
l_librbd_last,
};
+typedef std::map<uint64_t, uint64_t> 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. */
#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
return 0;
}
+template <typename I>
+int Image<I>::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<uint64_t>(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 <typename I>
+int Image<I>::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
namespace librbd {
+class ImageOptions;
+class ProgressContext;
+
struct ImageCtx;
namespace api {
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
--- /dev/null
+// -*- 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 <typename I>
+ImageCopyRequest<I>::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 <typename I>
+void ImageCopyRequest<I>::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 <typename I>
+void ImageCopyRequest<I>::cancel() {
+ Mutex::Locker locker(m_lock);
+
+ ldout(m_cct, 20) << dendl;
+ m_canceled = true;
+}
+
+template <typename I>
+void ImageCopyRequest<I>::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<int64_t>("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 <typename I>
+void ImageCopyRequest<I>::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<I> *req = ObjectCopyRequest<I>::create(
+ m_src_image_ctx, m_dst_image_ctx, m_snap_map, ono, ctx);
+ req->send();
+}
+
+template <typename I>
+void ImageCopyRequest<I>::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 <typename I>
+void ImageCopyRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <functional>
+#include <map>
+#include <queue>
+#include <vector>
+#include <boost/optional.hpp>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+class ProgressContext;
+
+namespace deep_copy {
+
+template <typename ImageCtxT = ImageCtx>
+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
+ *
+ * <start> . . . . .
+ * | . . (parallel execution of
+ * v v . multiple objects at once)
+ * COPY_OBJECT . . . .
+ * |
+ * v
+ * <finish>
+ *
+ * @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<uint64_t>, std::greater<uint64_t>> 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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_IMAGE_DEEP_COPY_REQUEST_H
--- /dev/null
+// -*- 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 <typename I>
+MetadataCopyRequest<I>::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 <typename I>
+void MetadataCopyRequest<I>::send() {
+ list_src_metadata();
+}
+
+template <typename I>
+void MetadataCopyRequest<I>::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<I>,
+ &MetadataCopyRequest<I>::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 <typename I>
+void MetadataCopyRequest<I>::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 <typename I>
+void MetadataCopyRequest<I>::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<I>,
+ &MetadataCopyRequest<I>::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 <typename I>
+void MetadataCopyRequest<I>::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 <typename I>
+void MetadataCopyRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <map>
+#include <string>
+
+class Context;
+
+namespace librbd {
+namespace deep_copy {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+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
+ *
+ * <start>
+ * |
+ * v
+ * LIST_SRC_METADATA <------\
+ * | | (repeat if additional
+ * v | metadata)
+ * SET_DST_METADATA --------/
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+ typedef std::map<std::string, bufferlist> 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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_METADATA_COPY_REQUEST_H
--- /dev/null
+// -*- 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 <typename I>
+ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::send() {
+ send_list_snaps();
+}
+
+template <typename I>
+void ObjectCopyRequest<I>::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<I>, &ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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<I>, &ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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<uint32_t>(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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+Context *ObjectCopyRequest<I>::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 <typename I>
+uint64_t ObjectCopyRequest<I>::src_to_dst_object_offset(uint64_t objectno,
+ uint64_t offset) {
+ std::vector<std::pair<uint64_t, uint64_t>> 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<object_t, std::vector<ObjectExtent>> 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 <typename I>
+void ObjectCopyRequest<I>::compute_src_object_extents() {
+ std::vector<std::pair<uint64_t, uint64_t>> 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<object_t, std::vector<ObjectExtent>> 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 <typename I>
+void ObjectCopyRequest<I>::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<uint64_t> 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<uint64_t> 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<uint64_t> read_interval;
+ read_interval.insert(e.offset, e.length);
+
+ if (end_size < prev_end_size) {
+ interval_set<uint64_t> 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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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 <typename I>
+void ObjectCopyRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <list>
+#include <map>
+#include <string>
+
+class Context;
+class RWLock;
+
+namespace librbd {
+namespace deep_copy {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+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
+ *
+ * <start>
+ * | /----------------------\
+ * | | |
+ * 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
+ * <finish>
+ *
+ * @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<uint64_t, SrcObjectExtent> SrcObjectExtents;
+
+ enum CopyOpType {
+ COPY_OP_TYPE_WRITE,
+ COPY_OP_TYPE_ZERO,
+ COPY_OP_TYPE_TRUNC,
+ COPY_OP_TYPE_REMOVE,
+ };
+
+ typedef std::map<uint64_t, uint64_t> 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<CopyOp> CopyOps;
+ typedef std::pair<librados::snap_t, librados::snap_t> WriteReadSnapIds;
+ typedef std::map<librados::snap_t, uint8_t> SnapObjectStates;
+ typedef std::map<librados::snap_t, std::map<uint64_t, uint64_t>> 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<uint64_t> 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<WriteReadSnapIds, CopyOps> m_read_ops;
+ std::list<WriteReadSnapIds> m_read_snaps;
+ std::map<librados::snap_t, CopyOps> m_write_ops;
+ std::map<librados::snap_t, interval_set<uint64_t>> m_zero_interval;
+ std::map<librados::snap_t, interval_set<uint64_t>> m_dst_zero_interval;
+ std::map<librados::snap_t, uint8_t> 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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_OBJECT_COPY_REQUEST_H
--- /dev/null
+// -*- 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 <typename I>
+SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::send() {
+ send_set_size();
+}
+
+template <typename I>
+void SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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 <typename I>
+Context *SetHeadRequest<I>::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 <typename I>
+void SetHeadRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <map>
+#include <set>
+#include <string>
+#include <tuple>
+
+class Context;
+
+namespace librbd {
+namespace deep_copy {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+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
+ *
+ * <start>
+ * |
+ * v (skip if not needed)
+ * SET_SIZE
+ * |
+ * v (skip if not needed)
+ * REMOVE_PARENT
+ * |
+ * v (skip if not needed)
+ * SET_PARENT
+ * |
+ * v
+ * <finish>
+ *
+ * @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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_SET_HEAD_REQUEST_H
--- /dev/null
+// -*- 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 <typename I>
+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<cls::rbd::SnapshotNamespace,
+ std::string>,
+ 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 <typename I>
+SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::cancel() {
+ Mutex::Locker locker(m_lock);
+
+ ldout(m_cct, 20) << dendl;
+ m_canceled = true;
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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<cls::rbd::UserSnapshotNamespace>(&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 <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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<cls::rbd::UserSnapshotNamespace>(&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<I> *req = SnapshotCreateRequest<I>::create(
+ m_dst_image_ctx, m_snap_name, m_snap_namespace, size, parent_spec,
+ parent_overlap, ctx);
+ req->send();
+}
+
+template <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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 <typename I>
+bool SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::error(int r) {
+ ldout(m_cct, 20) << "r=" << r << dendl;
+
+ m_work_queue->queue(new FunctionContext([this, r](int r1) { finish(r); }));
+}
+
+template <typename I>
+int SnapshotCopyRequest<I>::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 <typename I>
+Context *SnapshotCopyRequest<I>::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 <typename I>
+void SnapshotCopyRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <map>
+#include <set>
+#include <string>
+#include <tuple>
+
+class Context;
+
+namespace librbd {
+namespace deep_copy {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+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
+ *
+ * <start>
+ * |
+ * | /-----------\
+ * | | |
+ * 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
+ * <finish>
+ *
+ * @endverbatim
+ */
+
+ typedef std::set<librados::snap_t> 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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_COPY_REQUEST_H
--- /dev/null
+// -*- 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 <typename I>
+SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::send() {
+ send_set_head();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::send_set_head() {
+ ldout(m_cct, 20) << dendl;
+
+ auto ctx = create_context_callback<
+ SnapshotCreateRequest<I>, &SnapshotCreateRequest<I>::handle_set_head>(this);
+ auto req = SetHeadRequest<I>::create(m_dst_image_ctx, m_size, m_parent_spec,
+ m_parent_overlap, ctx);
+ req->send();
+}
+
+template <typename I>
+void SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::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 <typename I>
+Context *SnapshotCreateRequest<I>::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 <typename I>
+void SnapshotCreateRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <map>
+#include <set>
+#include <string>
+#include <tuple>
+
+class Context;
+
+namespace librbd {
+namespace deep_copy {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+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
+ *
+ * <start>
+ * |
+ * v
+ * SET_HEAD
+ * |
+ * v
+ * CREATE_SNAP
+ * |
+ * v (skip if not needed)
+ * CREATE_OBJECT_MAP
+ * |
+ * v
+ * <finish>
+ *
+ * @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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_DEEP_COPY_SNAPSHOT_CREATE_REQUEST_H
--- /dev/null
+// -*- 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 <boost/optional.hpp>
+
+namespace librbd {
+namespace deep_copy {
+
+typedef std::vector<librados::snap_t> SnapIds;
+typedef std::map<librados::snap_t, SnapIds> SnapMap;
+
+typedef boost::optional<uint64_t> ObjectNumber;
+
+} // namespace deep_copy
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_DEEP_COPY_TYPES_H
--- /dev/null
+// -*- 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
--- /dev/null
+// -*- 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 <boost/optional.hpp>
+
+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
#include "include/encoding.h"
#include "include/types.h"
#include "include/utime.h"
+#include "librbd/Types.h"
#include <iosfwd>
#include <list>
#include <boost/none.hpp>
struct MirrorPeerClientMeta {
typedef std::list<MirrorPeerSyncPoint> SyncPoints;
- typedef std::map<uint64_t, uint64_t> SnapSeqs;
static const ClientMetaType TYPE = MIRROR_PEER_CLIENT_META_TYPE;
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,
test_internal.cc
test_mirroring.cc
test_BlockGuard.cc
+ test_DeepCopy.cc
test_Groups.cc
test_MirroringWatcher.cc
test_ObjectMap.cc
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
--- /dev/null
+// -*- 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 <boost/scope_exit.hpp>
+
+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<librbd::MockTestImageCtx> {
+ 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<uint64_t, Context *> object_contexts;
+
+ ObjectCopyRequest() : lock("lock") {
+ s_instance = this;
+ }
+};
+
+ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+} // namespace librbd
+
+// template definitions
+#include "librbd/deep_copy/ImageCopyRequest.cc"
+template class librbd::deep_copy::ImageCopyRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace deep_copy {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Return;
+
+class TestMockDeepCopyImageCopyRequest : public TestMockFixture {
+public:
+ typedef ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest;
+ typedef ObjectCopyRequest<librbd::MockTestImageCtx> 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<void()> 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<void()> 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<void()> 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
--- /dev/null
+// -*- 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 <map>
+
+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<librbd::MockTestImageCtx> MockMetadataCopyRequest;
+ typedef std::map<std::string, bufferlist> 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
--- /dev/null
+// -*- 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<librbd::MockTestImageCtx>;
+
+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<uint64_t> *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<int>(len), r);
+
+ interval_set<uint64_t> w;
+ w.insert(off, len);
+ what->union_of(w);
+ }
+ std::cout << " wrote " << *what << std::endl;
+}
+
+} // anonymous namespace
+
+class TestMockDeepCopyObjectCopyRequest : public TestMockFixture {
+public:
+ typedef ObjectCopyRequest<librbd::MockTestImageCtx> 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<librados::snap_t> m_src_snap_ids;
+ std::vector<librados::snap_t> 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<FunctionContext>([](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<uint64_t> &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<uint64_t> &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<Context>(
+ 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<std::pair<cls::rbd::SnapshotNamespace,
+ std::string>,
+ 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<uint64_t> 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<uint64_t> 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<uint64_t> 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<uint64_t> 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<uint64_t> 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<uint64_t> one;
+ scribble(m_src_image_ctx, 10, 102400, &one);
+ ASSERT_EQ(0, create_snap("one"));
+
+ interval_set<uint64_t> two;
+ scribble(m_src_image_ctx, 10, 102400, &two);
+ ASSERT_EQ(0, create_snap("two"));
+
+ if (one.range_end() < two.range_end()) {
+ interval_set<uint64_t> 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<uint64_t> 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<uint64_t> 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
--- /dev/null
+// -*- 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<librbd::MockTestImageCtx>;
+
+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<librbd::MockTestImageCtx> 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<FunctionContext>([](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
--- /dev/null
+// -*- 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<librbd::MockTestImageCtx> {
+ 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<librbd::MockTestImageCtx>* SnapshotCreateRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+} // namespace librbd
+
+// template definitions
+#include "librbd/deep_copy/SnapshotCopyRequest.cc"
+template class librbd::deep_copy::SnapshotCopyRequest<librbd::MockTestImageCtx>;
+
+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<librbd::MockTestImageCtx> MockSnapshotCopyRequest;
+ typedef SnapshotCreateRequest<librbd::MockTestImageCtx> 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<FunctionContext>([](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
--- /dev/null
+// -*- 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<librbd::MockTestImageCtx> {
+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<librbd::MockTestImageCtx>* SetHeadRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+} // namespace librbd
+
+// template definitions
+#include "librbd/deep_copy/SnapshotCreateRequest.cc"
+template class librbd::deep_copy::SnapshotCreateRequest<librbd::MockTestImageCtx>;
+
+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<librbd::MockTestImageCtx> MockSetHeadRequest;
+ typedef SnapshotCreateRequest<librbd::MockTestImageCtx> 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<FunctionContext>([](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
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,
--- /dev/null
+// -*- 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<librbd::snap_info_t> 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<ssize_t>(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<ssize_t>(bl.length()),
+ m_src_ictx->io_work_queue->write(0 * bl.length(), bl.length(),
+ bufferlist{bl}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(bl1.length()),
+ m_src_ictx->io_work_queue->write(0 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(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<ssize_t>(bl1.length()),
+ m_src_ictx->io_work_queue->write(1 * bl.length(), bl1.length(),
+ bufferlist{bl1}, 0));
+ ASSERT_EQ(static_cast<ssize_t>(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<ssize_t>(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<ssize_t>(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<char>('A' + i)));
+ uint64_t off = std::min(static_cast<uint64_t>(rand() % size),
+ static_cast<uint64_t>(size - len));
+ std::cout << "write: " << static_cast<char>('A' + i) << " " << off
+ << "~" << len << std::endl;
+ ASSERT_EQ(static_cast<ssize_t>(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<uint64_t>(rand() % size),
+ static_cast<uint64_t>(size - len));
+ std::cout << "discard: " << off << "~" << len << std::endl;
+ ASSERT_EQ(static_cast<ssize_t>(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();
+}
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);
--- /dev/null
+// -*- 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<librbd::MockTestImageCtx> {
+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<librbd::MockTestImageCtx> {
+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<librbd::MockTestImageCtx> {
+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<librbd::MockTestImageCtx> {
+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<librbd::MockTestImageCtx>* ImageCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+MetadataCopyRequest<librbd::MockTestImageCtx>* MetadataCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+SetHeadRequest<librbd::MockTestImageCtx>* SetHeadRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+SnapshotCopyRequest<librbd::MockTestImageCtx>* SnapshotCopyRequest<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx> MockDeepCopyRequest;
+ typedef librbd::deep_copy::ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest;
+ typedef librbd::deep_copy::MetadataCopyRequest<librbd::MockTestImageCtx> MockMetadataCopyRequest;
+ typedef librbd::deep_copy::SetHeadRequest<librbd::MockTestImageCtx> MockSetHeadRequest;
+ typedef librbd::deep_copy::SnapshotCopyRequest<librbd::MockTestImageCtx> 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<FunctionContext>([](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<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx>::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<librbd::MockTestImageCtx>::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());
+}
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);
}
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 =
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 =