From 17e98dae739e4cba184c7168b6fe5b3206b6f1ea Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Sat, 30 Nov 2019 17:07:38 +0000 Subject: [PATCH] librbd: force promote for snapshot mirroring Signed-off-by: Mykola Golub --- src/librbd/ImageState.cc | 10 +- src/librbd/ImageState.h | 1 + src/librbd/mirror/PromoteRequest.cc | 3 +- .../snapshot/CreateNonPrimaryRequest.cc | 12 +- .../mirror/snapshot/CreateNonPrimaryRequest.h | 4 + src/librbd/mirror/snapshot/PromoteRequest.cc | 301 +++++++++++++- src/librbd/mirror/snapshot/PromoteRequest.h | 88 ++++- src/test/librbd/CMakeLists.txt | 1 + .../test_mock_CreatePrimaryRequest.cc | 4 + .../snapshot/test_mock_PromoteRequest.cc | 374 ++++++++++++++++++ src/test/librbd/mock/MockImageState.h | 5 + src/test/librbd/test_mirroring.cc | 36 ++ 12 files changed, 817 insertions(+), 22 deletions(-) create mode 100644 src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc diff --git a/src/librbd/ImageState.cc b/src/librbd/ImageState.cc index 9cfa2141670..24cb10d3eff 100644 --- a/src/librbd/ImageState.cc +++ b/src/librbd/ImageState.cc @@ -435,12 +435,18 @@ int ImageState::register_update_watcher(UpdateWatchCtx *watcher, } template -int ImageState::unregister_update_watcher(uint64_t handle) { +void ImageState::unregister_update_watcher(uint64_t handle, + Context *on_finish) { CephContext *cct = m_image_ctx->cct; ldout(cct, 20) << __func__ << ": handle=" << handle << dendl; + m_update_watchers->unregister_watcher(handle, on_finish); +} + +template +int ImageState::unregister_update_watcher(uint64_t handle) { C_SaferCond ctx; - m_update_watchers->unregister_watcher(handle, &ctx); + unregister_update_watcher(handle, &ctx); return ctx.wait(); } diff --git a/src/librbd/ImageState.h b/src/librbd/ImageState.h index 9daa5137ed9..e51f18e94bd 100644 --- a/src/librbd/ImageState.h +++ b/src/librbd/ImageState.h @@ -46,6 +46,7 @@ public: void handle_prepare_lock_complete(); int register_update_watcher(UpdateWatchCtx *watcher, uint64_t *handle); + void unregister_update_watcher(uint64_t handle, Context *on_finish); int unregister_update_watcher(uint64_t handle); void flush_update_watchers(Context *on_finish); void shut_down_update_watchers(Context *on_finish); diff --git a/src/librbd/mirror/PromoteRequest.cc b/src/librbd/mirror/PromoteRequest.cc index 2905d13bff6..e2bc6bd0582 100644 --- a/src/librbd/mirror/PromoteRequest.cc +++ b/src/librbd/mirror/PromoteRequest.cc @@ -76,7 +76,8 @@ void PromoteRequest::promote() { if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_JOURNAL) { Journal::promote(&m_image_ctx, ctx); } else if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT) { - auto req = mirror::snapshot::PromoteRequest::create(&m_image_ctx, ctx); + auto req = mirror::snapshot::PromoteRequest::create(&m_image_ctx, + m_force, ctx); req->send(); } else { lderr(cct) << "unknown image mirror mode: " << m_mirror_image.mode << dendl; diff --git a/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.cc b/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.cc index 426117b69b7..21f821f3d5f 100644 --- a/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.cc +++ b/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.cc @@ -100,7 +100,7 @@ void CreateNonPrimaryRequest::handle_get_mirror_image(int r) { return; } - if (!util::can_create_non_primary_snapshot(m_image_ctx)) { + if (!is_orphan() && !util::can_create_non_primary_snapshot(m_image_ctx)) { finish(-EINVAL); return; } @@ -143,9 +143,6 @@ void CreateNonPrimaryRequest::handle_create_snapshot(int r) { template void CreateNonPrimaryRequest::write_image_state() { - CephContext *cct = m_image_ctx->cct; - ldout(cct, 20) << dendl; - uint64_t snap_id; { std::shared_lock image_locker{m_image_ctx->image_lock}; @@ -158,6 +155,13 @@ void CreateNonPrimaryRequest::write_image_state() { *m_snap_id = snap_id; } + if (is_orphan()) { + finish(0); + } + + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + auto ctx = create_context_callback< CreateNonPrimaryRequest, &CreateNonPrimaryRequest::handle_write_image_state>(this); diff --git a/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.h b/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.h index a2ab6adc5c4..2b0fb68cd27 100644 --- a/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.h +++ b/src/librbd/mirror/snapshot/CreateNonPrimaryRequest.h @@ -81,6 +81,10 @@ private: bufferlist m_out_bl; + bool is_orphan() const { + return m_primary_mirror_uuid.empty(); + } + void refresh_image(); void handle_refresh_image(int r); diff --git a/src/librbd/mirror/snapshot/PromoteRequest.cc b/src/librbd/mirror/snapshot/PromoteRequest.cc index c9e59e03065..9fe4bfe2060 100644 --- a/src/librbd/mirror/snapshot/PromoteRequest.cc +++ b/src/librbd/mirror/snapshot/PromoteRequest.cc @@ -2,14 +2,19 @@ // vim: ts=8 sw=2 smarttab #include "librbd/mirror/snapshot/PromoteRequest.h" +#include "common/Timer.h" #include "common/dout.h" #include "common/errno.h" #include "cls/rbd/cls_rbd_client.h" +#include "librbd/ExclusiveLock.h" #include "librbd/ImageCtx.h" #include "librbd/ImageState.h" #include "librbd/Operations.h" #include "librbd/Utils.h" +#include "librbd/image/ListWatchersRequest.h" +#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h" #include "librbd/mirror/snapshot/CreatePrimaryRequest.h" +#include "librbd/mirror/snapshot/Utils.h" #define dout_subsys ceph_subsys_rbd #undef dout_prefix @@ -20,6 +25,7 @@ namespace librbd { namespace mirror { namespace snapshot { +using librbd::util::create_async_context_callback; using librbd::util::create_context_callback; template @@ -30,7 +36,7 @@ void PromoteRequest::send() { template void PromoteRequest::refresh_image() { if (!m_image_ctx->state->is_refresh_required()) { - create_snapshot(); + create_promote_snapshot(); return; } @@ -53,16 +59,262 @@ void PromoteRequest::handle_refresh_image(int r) { return; } - create_snapshot(); + if (!util::can_create_primary_snapshot(m_image_ctx, false, m_force, + &m_rollback_snap_id)) { + lderr(cct) << "cannot promote" << dendl; + finish(-EINVAL); + return; + } else if (m_rollback_snap_id == CEPH_NOSNAP) { + create_promote_snapshot(); + return; + } + + create_orphan_snapshot(); +} + +template +void PromoteRequest::create_orphan_snapshot() { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + auto ctx = create_context_callback< + PromoteRequest, + &PromoteRequest::handle_create_orphan_snapshot>(this); + + auto req = CreateNonPrimaryRequest::create(m_image_ctx, "", CEPH_NOSNAP, + {}, nullptr, ctx); + req->send(); +} + +template +void PromoteRequest::handle_create_orphan_snapshot(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to create orphan snapshot: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + list_watchers(); +} + +template +void PromoteRequest::list_watchers() { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + auto ctx = create_context_callback< + PromoteRequest, + &PromoteRequest::handle_list_watchers>(this); + + m_watchers.clear(); + auto flags = librbd::image::LIST_WATCHERS_FILTER_OUT_MY_INSTANCE | + librbd::image::LIST_WATCHERS_MIRROR_INSTANCES_ONLY; + auto req = librbd::image::ListWatchersRequest::create( + *m_image_ctx, flags, &m_watchers, ctx); + req->send(); +} + +template +void PromoteRequest::handle_list_watchers(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to list watchers: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + if (m_watchers.empty()) { + acquire_exclusive_lock(); + return; + } + + wait_update_notify(); } template -void PromoteRequest::create_snapshot() { +void PromoteRequest::wait_update_notify() { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + ImageCtx::get_timer_instance(cct, &m_timer, &m_timer_lock); + + std::lock_guard timer_lock{*m_timer_lock}; + + m_scheduler_ticks = 5; + + int r = m_image_ctx->state->register_update_watcher(&m_update_watch_ctx, + &m_update_watcher_handle); + if (r < 0) { + lderr(cct) << "failed to register update watcher: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + scheduler_unregister_update_watcher(); +} + +template +void PromoteRequest::handle_update_notify() { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + std::lock_guard timer_lock{*m_timer_lock}; + m_scheduler_ticks = 0; +} + +template +void PromoteRequest::scheduler_unregister_update_watcher() { + ceph_assert(ceph_mutex_is_locked(*m_timer_lock)); + + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "scheduler_ticks=" << m_scheduler_ticks << dendl; + + if (m_scheduler_ticks > 0) { + m_scheduler_ticks--; + m_timer->add_event_after(1, new LambdaContext([this](int) { + scheduler_unregister_update_watcher(); + })); + return; + } + + m_image_ctx->op_work_queue->queue(new LambdaContext([this](int) { + unregister_update_watcher(); + }), 0); +} + +template +void PromoteRequest::unregister_update_watcher() { CephContext *cct = m_image_ctx->cct; ldout(cct, 20) << dendl; auto ctx = create_context_callback< - PromoteRequest, &PromoteRequest::handle_create_snapshot>(this); + PromoteRequest, + &PromoteRequest::handle_unregister_update_watcher>(this); + + m_image_ctx->state->unregister_update_watcher(m_update_watcher_handle, ctx); +} + +template +void PromoteRequest::handle_unregister_update_watcher(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to unregister update watcher: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + list_watchers(); +} + +template +void PromoteRequest::acquire_exclusive_lock() { + { + std::unique_lock locker{m_image_ctx->owner_lock}; + if (m_image_ctx->exclusive_lock != nullptr && + !m_image_ctx->exclusive_lock->is_lock_owner()) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + m_lock_acquired = true; + m_image_ctx->exclusive_lock->block_requests(0); + + auto ctx = create_context_callback< + PromoteRequest, + &PromoteRequest::handle_acquire_exclusive_lock>(this); + + m_image_ctx->exclusive_lock->acquire_lock(ctx); + return; + } + } + + rollback(); +} + +template +void PromoteRequest::handle_acquire_exclusive_lock(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to acquire exclusive lock: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } else { + std::unique_lock locker{m_image_ctx->owner_lock}; + if (m_image_ctx->exclusive_lock != nullptr && + !m_image_ctx->exclusive_lock->is_lock_owner()) { + lderr(cct) << "failed to acquire exclusive lock" << dendl; + r = m_image_ctx->exclusive_lock->get_unlocked_op_error(); + locker.unlock(); + finish(r); + } + } + + rollback(); +} + +template +void PromoteRequest::rollback() { + if (m_rollback_snap_id == CEPH_NOSNAP) { + create_promote_snapshot(); + return; + } + + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + std::shared_lock owner_locker{m_image_ctx->owner_lock}; + std::shared_lock image_locker{m_image_ctx->image_lock}; + + auto info = m_image_ctx->get_snap_info(m_rollback_snap_id); + ceph_assert(info != nullptr); + auto snap_namespace = info->snap_namespace; + auto snap_name = info->name; + + image_locker.unlock(); + + auto ctx = create_async_context_callback( + *m_image_ctx, create_context_callback< + PromoteRequest, &PromoteRequest::handle_rollback>(this)); + + m_image_ctx->operations->execute_snap_rollback(snap_namespace, snap_name, + m_progress_ctx, ctx); +} + +template +void PromoteRequest::handle_rollback(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to rollback: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + create_promote_snapshot(); +} + +template +void PromoteRequest::create_promote_snapshot() { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + auto ctx = create_context_callback< + PromoteRequest, + &PromoteRequest::handle_create_promote_snapshot>(this); auto req = CreatePrimaryRequest::create(m_image_ctx, false, true, nullptr, ctx); @@ -70,12 +322,49 @@ void PromoteRequest::create_snapshot() { } template -void PromoteRequest::handle_create_snapshot(int r) { +void PromoteRequest::handle_create_promote_snapshot(int r) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "failed to create promote snapshot: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + release_exclusive_lock(); +} + +template +void PromoteRequest::release_exclusive_lock() { + if (m_lock_acquired) { + std::unique_lock locker{m_image_ctx->owner_lock}; + if (m_image_ctx->exclusive_lock != nullptr) { + CephContext *cct = m_image_ctx->cct; + ldout(cct, 20) << dendl; + + m_image_ctx->exclusive_lock->unblock_requests(); + + auto ctx = create_context_callback< + PromoteRequest, + &PromoteRequest::handle_release_exclusive_lock>(this); + + m_image_ctx->exclusive_lock->release_lock(ctx); + return; + } + } + + finish(0); +} + +template +void PromoteRequest::handle_release_exclusive_lock(int r) { CephContext *cct = m_image_ctx->cct; ldout(cct, 20) << "r=" << r << dendl; if (r < 0) { - lderr(cct) << "failed to create mirror snapshot: " << cpp_strerror(r) + lderr(cct) << "failed to release exclusive lock: " << cpp_strerror(r) << dendl; finish(r); return; diff --git a/src/librbd/mirror/snapshot/PromoteRequest.h b/src/librbd/mirror/snapshot/PromoteRequest.h index 7efd921d8d1..f6eb92b61da 100644 --- a/src/librbd/mirror/snapshot/PromoteRequest.h +++ b/src/librbd/mirror/snapshot/PromoteRequest.h @@ -5,10 +5,13 @@ #define CEPH_LIBRBD_MIRROR_SNAPSHOT_PROMOTE_REQUEST_H #include "include/buffer.h" +#include "include/rbd/librbd.hpp" +#include "librbd/internal.h" #include #include +class SafeTimer; struct Context; namespace librbd { @@ -21,12 +24,13 @@ namespace snapshot { template class PromoteRequest { public: - static PromoteRequest *create(ImageCtxT *image_ctx, Context *on_finish) { - return new PromoteRequest(image_ctx, on_finish); + static PromoteRequest *create(ImageCtxT *image_ctx, bool force, + Context *on_finish) { + return new PromoteRequest(image_ctx, force, on_finish); } - PromoteRequest(ImageCtxT *image_ctx, Context *on_finish) - : m_image_ctx(image_ctx), m_on_finish(on_finish) { + PromoteRequest(ImageCtxT *image_ctx, bool force, Context *on_finish) + : m_image_ctx(image_ctx), m_force(force), m_on_finish(on_finish) { } void send(); @@ -37,11 +41,29 @@ private: * * * | - * v - * REFRESH_IMAGE + * v (can promote) + * REFRESH_IMAGE -------------------------------\ + * | | + * | (needs rollback) | + * v | + * CREATE_ORPHAN_SNAPSHOT | + * | | + * | /-- UNREGISTER_UPDATE_WATCHER <-\ | + * v v | | + * LIST_WATCHERS ----> WAIT_UPDATE_NOTIFY --/ | + * | | + * | (no watchers) | + * v | + * ACQUIRE_EXCLUSIVE_LOCK | + * | (skip if not needed) | + * v | + * ROLLBACK | + * | | + * v | + * CREATE_PROMOTE_SNAPSHOT <--------------------/ * | * v - * CREATE_SNAPSHOT + * RELEASE_EXCLUSIVE_LOCK (skip if not needed) * | * v * @@ -50,13 +72,61 @@ private: */ ImageCtxT *m_image_ctx; + bool m_force; Context *m_on_finish; + uint64_t m_rollback_snap_id = CEPH_NOSNAP; + bool m_lock_acquired = false; + NoOpProgressContext m_progress_ctx; + + class UpdateWatchCtx : public librbd::UpdateWatchCtx { + public: + UpdateWatchCtx(PromoteRequest *promote_request) + : promote_request(promote_request) { + } + + void handle_notify() { + promote_request->handle_update_notify(); + } + + private: + PromoteRequest *promote_request; + + } m_update_watch_ctx = {this}; + + std::list m_watchers; + uint64_t m_update_watcher_handle = 0; + uint64_t m_scheduler_ticks = 0; + SafeTimer *m_timer = nullptr; + ceph::mutex *m_timer_lock = nullptr; + void refresh_image(); void handle_refresh_image(int r); - void create_snapshot(); - void handle_create_snapshot(int r); + void create_orphan_snapshot(); + void handle_create_orphan_snapshot(int r); + + void list_watchers(); + void handle_list_watchers(int r); + + void wait_update_notify(); + void handle_update_notify(); + void scheduler_unregister_update_watcher(); + + void unregister_update_watcher(); + void handle_unregister_update_watcher(int r); + + void acquire_exclusive_lock(); + void handle_acquire_exclusive_lock(int r); + + void rollback(); + void handle_rollback(int r); + + void create_promote_snapshot(); + void handle_create_promote_snapshot(int r); + + void release_exclusive_lock(); + void handle_release_exclusive_lock(int r); void finish(int r); diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 19cc88bfb2e..b0c68237e87 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -87,6 +87,7 @@ set(unittest_librbd_srcs managed_lock/test_mock_ReleaseRequest.cc mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc mirror/snapshot/test_mock_CreatePrimaryRequest.cc + mirror/snapshot/test_mock_PromoteRequest.cc mirror/snapshot/test_mock_UnlinkPeerRequest.cc mirror/snapshot/test_mock_Utils.cc mirror/test_mock_DisableRequest.cc diff --git a/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc index 97b6a22fea3..08e55662ecd 100644 --- a/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc +++ b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc @@ -27,6 +27,8 @@ namespace mirror { namespace snapshot { namespace util { +namespace { + struct Mock { static Mock* s_instance; @@ -40,6 +42,8 @@ struct Mock { Mock *Mock::s_instance = nullptr; +} // anonymous namespace + template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx, bool demoted, bool force, uint64_t *rollback_snap_id) { diff --git a/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc new file mode 100644 index 00000000000..304ad629531 --- /dev/null +++ b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc @@ -0,0 +1,374 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/stringify.h" +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockOperations.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/image/ListWatchersRequest.h" +#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h" +#include "librbd/mirror/snapshot/CreatePrimaryRequest.h" +#include "librbd/mirror/snapshot/PromoteRequest.h" +#include "librbd/mirror/snapshot/Utils.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct ListWatchersRequest { + std::list *watchers; + Context* on_finish = nullptr; + static ListWatchersRequest* s_instance; + static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags, + std::list *watchers, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->watchers = watchers; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + ListWatchersRequest() { + s_instance = this; + } +}; + +ListWatchersRequest* ListWatchersRequest::s_instance = nullptr; + +} // namespace image + +namespace mirror { +namespace snapshot { +namespace util { + +namespace { + +struct Mock { + static Mock* s_instance; + + Mock() { + s_instance = this; + } + + MOCK_METHOD4(can_create_primary_snapshot, + bool(librbd::MockTestImageCtx *, bool, bool, uint64_t *)); +}; + +Mock *Mock::s_instance = nullptr; + +} // anonymous namespace + +template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx, + bool demoted, bool force, + uint64_t *rollback_snap_id) { + return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted, + force, rollback_snap_id); +} + +} // namespace util + +template <> +struct CreateNonPrimaryRequest { + std::string primary_mirror_uuid; + uint64_t primary_snap_id = CEPH_NOSNAP; + Context* on_finish = nullptr; + static CreateNonPrimaryRequest* s_instance; + static CreateNonPrimaryRequest *create(MockTestImageCtx *image_ctx, + const std::string &primary_mirror_uuid, + uint64_t primary_snap_id, + const ImageState &image_state, + uint64_t *snap_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->primary_mirror_uuid = primary_mirror_uuid; + s_instance->primary_snap_id = primary_snap_id; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreateNonPrimaryRequest() { + s_instance = this; + } +}; + +CreateNonPrimaryRequest* CreateNonPrimaryRequest::s_instance = nullptr; + +template <> +struct CreatePrimaryRequest { + bool demoted = false; + bool force = false; + Context* on_finish = nullptr; + static CreatePrimaryRequest* s_instance; + static CreatePrimaryRequest *create(MockTestImageCtx *image_ctx, bool demoted, + bool force, uint64_t *snap_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->demoted = demoted; + s_instance->force = force; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreatePrimaryRequest() { + s_instance = this; + } +}; + +CreatePrimaryRequest* CreatePrimaryRequest::s_instance = nullptr; + +} // namespace snapshot +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/mirror/snapshot/PromoteRequest.cc" +template class librbd::mirror::snapshot::PromoteRequest; + +namespace librbd { +namespace mirror { +namespace snapshot { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockMirrorSnapshotPromoteRequest : public TestMockFixture { +public: + typedef librbd::image::ListWatchersRequest MockListWatchersRequest; + typedef PromoteRequest MockPromoteRequest; + typedef CreateNonPrimaryRequest MockCreateNonPrimaryRequest; + typedef CreatePrimaryRequest MockCreatePrimaryRequest; + typedef util::Mock MockUtils; + + void expect_refresh_image(MockTestImageCtx &mock_image_ctx, + bool refresh_required, int r) { + EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()) + .WillOnce(Return(refresh_required)); + if (refresh_required) { + EXPECT_CALL(*mock_image_ctx.state, refresh(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool force, + uint64_t rollback_snap_id, + bool result) { + EXPECT_CALL(mock_utils, + can_create_primary_snapshot(_, false, force, _)) + .WillOnce(DoAll( + WithArg<3>(Invoke([rollback_snap_id](uint64_t *snap_id) { + *snap_id = rollback_snap_id; + })), + Return(result))); + } + + void expect_create_orphan_snapshot( + MockTestImageCtx &mock_image_ctx, + MockCreateNonPrimaryRequest &mock_create_non_primary_request, int r) { + EXPECT_CALL(mock_create_non_primary_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_create_non_primary_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_create_non_primary_request.on_finish, r); + })); + } + + void expect_list_watchers( + MockTestImageCtx &mock_image_ctx, + MockListWatchersRequest &mock_list_watchers_request, + const std::list &watchers, int r) { + EXPECT_CALL(mock_list_watchers_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_list_watchers_request, watchers, r]() { + *mock_list_watchers_request.watchers = watchers; + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_list_watchers_request.on_finish, r); + })); + } + + void expect_acquire_lock(MockTestImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.exclusive_lock == nullptr) { + return; + } + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillOnce(Return(false)); + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(_)); + EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + if (r == 0) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillOnce(Return(true)); + } + } + + void expect_get_snap_info(MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const SnapInfo* snap_info) { + EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id)) + .WillOnce(Return(snap_info)); + } + + void expect_rollback(MockTestImageCtx &mock_image_ctx, uint64_t snap_id, + const SnapInfo* snap_info, int r) { + expect_get_snap_info(mock_image_ctx, snap_id, snap_info); + EXPECT_CALL(*mock_image_ctx.operations, + execute_snap_rollback(snap_info->snap_namespace, + snap_info->name, _, _)) + .WillOnce(WithArg<3>(CompleteContext( + r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_create_promote_snapshot( + MockTestImageCtx &mock_image_ctx, + MockCreatePrimaryRequest &mock_create_primary_request, int r) { + EXPECT_CALL(mock_create_primary_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_create_primary_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_create_primary_request.on_finish, r); + })); + } + + void expect_release_lock(MockTestImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.exclusive_lock == nullptr) { + return; + } + EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests()); + EXPECT_CALL(*mock_image_ctx.exclusive_lock, release_lock(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } +}; + +TEST_F(TestMockMirrorSnapshotPromoteRequest, Success) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + const bool force = false; + + InSequence seq; + + expect_refresh_image(mock_image_ctx, true, 0); + MockUtils mock_utils; + expect_can_create_primary_snapshot(mock_utils, force, CEPH_NOSNAP, true); + MockCreatePrimaryRequest mock_create_primary_request; + expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request, + 0); + C_SaferCond ctx; + auto req = new MockPromoteRequest(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessRollback) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + const bool force = true; + + InSequence seq; + + expect_refresh_image(mock_image_ctx, true, 0); + MockUtils mock_utils; + expect_can_create_primary_snapshot(mock_utils, force, 123, true); + MockCreateNonPrimaryRequest mock_create_non_primary_request; + expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request, + 0); + MockListWatchersRequest mock_list_watchers_request; + expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0); + expect_acquire_lock(mock_image_ctx, 0); + + SnapInfo snap_info = {"snap", cls::rbd::MirrorPrimarySnapshotNamespace{}, 0, + {}, 0, 0, {}}; + expect_rollback(mock_image_ctx, 123, &snap_info, 0); + MockCreatePrimaryRequest mock_create_primary_request; + expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request, + 0); + expect_release_lock(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = new MockPromoteRequest(&mock_image_ctx, force, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorSnapshotPromoteRequest, RefreshError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + InSequence seq; + + expect_refresh_image(mock_image_ctx, true, -EINVAL); + + C_SaferCond ctx; + auto req = new MockPromoteRequest(&mock_image_ctx, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorSnapshotPromoteRequest, ErrorCannotRollback) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + const bool force = false; + + InSequence seq; + + expect_refresh_image(mock_image_ctx, true, 0); + MockUtils mock_utils; + expect_can_create_primary_snapshot(mock_utils, force, CEPH_NOSNAP, false); + + C_SaferCond ctx; + auto req = new MockPromoteRequest(&mock_image_ctx, force, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace snapshot +} // namespace mirror +} // namespace librbd + diff --git a/src/test/librbd/mock/MockImageState.h b/src/test/librbd/mock/MockImageState.h index a817d333e1d..b9c8c8d0759 100644 --- a/src/test/librbd/mock/MockImageState.h +++ b/src/test/librbd/mock/MockImageState.h @@ -12,6 +12,8 @@ class Context; namespace librbd { +class UpdateWatchCtx; + struct MockImageState { MOCK_CONST_METHOD0(is_refresh_required, bool()); MOCK_METHOD1(refresh, void(Context*)); @@ -25,6 +27,9 @@ struct MockImageState { MOCK_METHOD1(prepare_lock, void(Context*)); MOCK_METHOD0(handle_prepare_lock_complete, void()); + + MOCK_METHOD2(register_update_watcher, int(UpdateWatchCtx *, uint64_t *)); + MOCK_METHOD2(unregister_update_watcher, void(uint64_t, Context *)); }; } // namespace librbd diff --git a/src/test/librbd/test_mirroring.cc b/src/test/librbd/test_mirroring.cc index b085a11f138..17f9cef43fb 100644 --- a/src/test/librbd/test_mirroring.cc +++ b/src/test/librbd/test_mirroring.cc @@ -1396,3 +1396,39 @@ TEST_F(TestMirroring, SnapshotImageState) ASSERT_EQ(0, image.close()); ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); } + +TEST_F(TestMirroring, SnapshotPromoteDemote) +{ + REQUIRE_FORMAT_V2(); + + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE)); + std::string peer_uuid; + ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid, + RBD_MIRROR_PEER_DIRECTION_RX_TX, + "cluster", "client")); + + uint64_t features; + ASSERT_TRUE(get_features(&features)); + features &= ~RBD_FEATURE_JOURNALING; + int order = 20; + ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features, + &order)); + + librbd::Image image; + ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str())); + ASSERT_EQ(0, image.mirror_image_enable()); + librbd::mirror_image_mode_t mode; + ASSERT_EQ(0, image.mirror_image_get_mode(&mode)); + ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mode); + + ASSERT_EQ(-EINVAL, image.mirror_image_promote(false)); + ASSERT_EQ(0, image.mirror_image_demote()); + ASSERT_EQ(0, image.mirror_image_promote(false)); + ASSERT_EQ(0, image.mirror_image_demote()); + ASSERT_EQ(0, image.mirror_image_promote(false)); + + ASSERT_EQ(0, image.close()); + ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str())); + ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid)); + ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED)); +} -- 2.39.5