}
template <typename I>
-int ImageState<I>::unregister_update_watcher(uint64_t handle) {
+void ImageState<I>::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 <typename I>
+int ImageState<I>::unregister_update_watcher(uint64_t handle) {
C_SaferCond ctx;
- m_update_watchers->unregister_watcher(handle, &ctx);
+ unregister_update_watcher(handle, &ctx);
return ctx.wait();
}
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);
if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_JOURNAL) {
Journal<I>::promote(&m_image_ctx, ctx);
} else if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT) {
- auto req = mirror::snapshot::PromoteRequest<I>::create(&m_image_ctx, ctx);
+ auto req = mirror::snapshot::PromoteRequest<I>::create(&m_image_ctx,
+ m_force, ctx);
req->send();
} else {
lderr(cct) << "unknown image mirror mode: " << m_mirror_image.mode << dendl;
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;
}
template <typename I>
void CreateNonPrimaryRequest<I>::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};
*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<I>,
&CreateNonPrimaryRequest<I>::handle_write_image_state>(this);
bufferlist m_out_bl;
+ bool is_orphan() const {
+ return m_primary_mirror_uuid.empty();
+ }
+
void refresh_image();
void handle_refresh_image(int r);
// 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
namespace mirror {
namespace snapshot {
+using librbd::util::create_async_context_callback;
using librbd::util::create_context_callback;
template <typename I>
template <typename I>
void PromoteRequest<I>::refresh_image() {
if (!m_image_ctx->state->is_refresh_required()) {
- create_snapshot();
+ create_promote_snapshot();
return;
}
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 <typename I>
+void PromoteRequest<I>::create_orphan_snapshot() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 20) << dendl;
+
+ auto ctx = create_context_callback<
+ PromoteRequest<I>,
+ &PromoteRequest<I>::handle_create_orphan_snapshot>(this);
+
+ auto req = CreateNonPrimaryRequest<I>::create(m_image_ctx, "", CEPH_NOSNAP,
+ {}, nullptr, ctx);
+ req->send();
+}
+
+template <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::list_watchers() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 20) << dendl;
+
+ auto ctx = create_context_callback<
+ PromoteRequest<I>,
+ &PromoteRequest<I>::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<I>::create(
+ *m_image_ctx, flags, &m_watchers, ctx);
+ req->send();
+}
+
+template <typename I>
+void PromoteRequest<I>::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 <typename I>
-void PromoteRequest<I>::create_snapshot() {
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::unregister_update_watcher() {
CephContext *cct = m_image_ctx->cct;
ldout(cct, 20) << dendl;
auto ctx = create_context_callback<
- PromoteRequest<I>, &PromoteRequest<I>::handle_create_snapshot>(this);
+ PromoteRequest<I>,
+ &PromoteRequest<I>::handle_unregister_update_watcher>(this);
+
+ m_image_ctx->state->unregister_update_watcher(m_update_watcher_handle, ctx);
+}
+
+template <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::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<I>,
+ &PromoteRequest<I>::handle_acquire_exclusive_lock>(this);
+
+ m_image_ctx->exclusive_lock->acquire_lock(ctx);
+ return;
+ }
+ }
+
+ rollback();
+}
+
+template <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::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<I>, &PromoteRequest<I>::handle_rollback>(this));
+
+ m_image_ctx->operations->execute_snap_rollback(snap_namespace, snap_name,
+ m_progress_ctx, ctx);
+}
+
+template <typename I>
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::create_promote_snapshot() {
+ CephContext *cct = m_image_ctx->cct;
+ ldout(cct, 20) << dendl;
+
+ auto ctx = create_context_callback<
+ PromoteRequest<I>,
+ &PromoteRequest<I>::handle_create_promote_snapshot>(this);
auto req = CreatePrimaryRequest<I>::create(m_image_ctx, false, true, nullptr,
ctx);
}
template <typename I>
-void PromoteRequest<I>::handle_create_snapshot(int r) {
+void PromoteRequest<I>::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 <typename I>
+void PromoteRequest<I>::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<I>,
+ &PromoteRequest<I>::handle_release_exclusive_lock>(this);
+
+ m_image_ctx->exclusive_lock->release_lock(ctx);
+ return;
+ }
+ }
+
+ finish(0);
+}
+
+template <typename I>
+void PromoteRequest<I>::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;
#define CEPH_LIBRBD_MIRROR_SNAPSHOT_PROMOTE_REQUEST_H
#include "include/buffer.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/internal.h"
#include <string>
#include <set>
+class SafeTimer;
struct Context;
namespace librbd {
template <typename ImageCtxT = librbd::ImageCtx>
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();
*
* <start>
* |
- * 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
* <finish>
*/
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<obj_watch_t> 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);
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
namespace snapshot {
namespace util {
+namespace {
+
struct Mock {
static Mock* s_instance;
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) {
--- /dev/null
+// -*- 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<MockTestImageCtx> {
+ std::list<obj_watch_t> *watchers;
+ Context* on_finish = nullptr;
+ static ListWatchersRequest* s_instance;
+ static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
+ std::list<obj_watch_t> *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<MockTestImageCtx>* ListWatchersRequest<MockTestImageCtx>::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<MockTestImageCtx> {
+ 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<MockTestImageCtx>* CreateNonPrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct CreatePrimaryRequest<MockTestImageCtx> {
+ 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<MockTestImageCtx>* CreatePrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/PromoteRequest.cc"
+template class librbd::mirror::snapshot::PromoteRequest<librbd::MockTestImageCtx>;
+
+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<MockTestImageCtx> MockListWatchersRequest;
+ typedef PromoteRequest<MockTestImageCtx> MockPromoteRequest;
+ typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef CreatePrimaryRequest<MockTestImageCtx> 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<obj_watch_t> &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
+
namespace librbd {
+class UpdateWatchCtx;
+
struct MockImageState {
MOCK_CONST_METHOD0(is_refresh_required, bool());
MOCK_METHOD1(refresh, void(Context*));
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
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));
+}