diff options
Diffstat (limited to 'src/test/librbd/test_mock_Watcher.cc')
-rw-r--r-- | src/test/librbd/test_mock_Watcher.cc | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/src/test/librbd/test_mock_Watcher.cc b/src/test/librbd/test_mock_Watcher.cc new file mode 100644 index 000000000..54b395b1f --- /dev/null +++ b/src/test/librbd/test_mock_Watcher.cc @@ -0,0 +1,405 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "common/Cond.h" +#include "common/ceph_mutex.h" +#include "librados/AioCompletionImpl.h" +#include "librbd/Watcher.h" +#include "librbd/watcher/RewatchRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <list> + +namespace librbd { + +namespace { + +struct MockWatcher : public Watcher { + std::string oid; + + MockWatcher(librados::IoCtx& ioctx, asio::ContextWQ *work_queue, + const std::string& oid) + : Watcher(ioctx, work_queue, oid) { + } + + virtual void handle_notify(uint64_t notify_id, uint64_t handle, + uint64_t notifier_id, bufferlist &bl) { + } +}; + +} // anonymous namespace +} // namespace librbd + +namespace librbd { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::WithArg; +using ::testing::WithArgs; + +class TestMockWatcher : public TestMockFixture { +public: + TestMockWatcher() = default; + + virtual void SetUp() { + TestMockFixture::SetUp(); + + m_oid = get_temp_image_name(); + + bufferlist bl; + ASSERT_EQ(0, m_ioctx.write_full(m_oid, bl)); + } + + void expect_aio_watch(MockImageCtx &mock_image_ctx, int r, + const std::function<void()> &action = std::function<void()>()) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + librados::MockTestMemRadosClient *mock_rados_client( + mock_io_ctx.get_mock_rados_client()); + + EXPECT_CALL(mock_io_ctx, aio_watch(m_oid, _, _, _)) + .WillOnce(DoAll(WithArgs<1, 2, 3>(Invoke([this, &mock_image_ctx, mock_rados_client, r, action]( + librados::AioCompletionImpl *c, uint64_t *cookie, + librados::WatchCtx2 *watch_ctx) { + if (r == 0) { + *cookie = 234U; + m_watch_ctx = watch_ctx; + } + + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new LambdaContext([mock_rados_client, action, c](int r) { + if (action) { + action(); + } + + mock_rados_client->finish_aio_completion(c, r); + }), r); + notify_watch(); + })), Return(0))); + } + + void expect_aio_unwatch(MockImageCtx &mock_image_ctx, int r, + const std::function<void()> &action = std::function<void()>()) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + librados::MockTestMemRadosClient *mock_rados_client( + mock_io_ctx.get_mock_rados_client()); + + EXPECT_CALL(mock_io_ctx, aio_unwatch(_, _)) + .WillOnce(DoAll(Invoke([this, &mock_image_ctx, mock_rados_client, r, action]( + uint64_t handle, librados::AioCompletionImpl *c) { + c->get(); + mock_image_ctx.image_ctx->op_work_queue->queue(new LambdaContext([mock_rados_client, action, c](int r) { + if (action) { + action(); + } + + mock_rados_client->finish_aio_completion(c, r); + }), r); + notify_watch(); + }), Return(0))); + } + + std::string m_oid; + librados::WatchCtx2 *m_watch_ctx = nullptr; + + void notify_watch() { + std::lock_guard locker{m_lock}; + ++m_watch_count; + m_cond.notify_all(); + } + + bool wait_for_watch(MockImageCtx &mock_image_ctx, size_t count) { + using namespace std::chrono_literals; + std::unique_lock locker{m_lock}; + while (m_watch_count < count) { + if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) { + return false; + } + } + m_watch_count -= count; + return true; + } + + ceph::mutex m_lock = ceph::make_mutex("TestMockWatcher::m_lock"); + ceph::condition_variable m_cond; + size_t m_watch_count = 0; +}; + +TEST_F(TestMockWatcher, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, RegisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(-EINVAL, register_ctx.wait()); +} + +TEST_F(TestMockWatcher, UnregisterError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(-EINVAL, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, Reregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterUnwatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, -EINVAL); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 3)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EPERM); + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 4)); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchBlocklist) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + expect_aio_watch(mock_image_ctx, -EBLOCKLISTED); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 1)); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -EBLOCKLISTED); + + // wait for recovery unwatch/watch + ASSERT_TRUE(wait_for_watch(mock_image_ctx, 2)); + ASSERT_TRUE(mock_image_watcher.is_blocklisted()); + + C_SaferCond unregister_ctx; + mock_image_watcher.unregister_watch(&unregister_ctx); + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterUnwatchPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_unwatch(mock_image_ctx, -EBLOCKLISTED, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -EBLOCKLISTED); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterWatchPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_watch(mock_image_ctx, -ESHUTDOWN, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +TEST_F(TestMockWatcher, ReregisterPendingUnregister) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockWatcher mock_image_watcher(m_ioctx, ictx->op_work_queue, m_oid); + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_aio_watch(mock_image_ctx, 0); + expect_aio_unwatch(mock_image_ctx, 0); + + // inject an unregister + C_SaferCond unregister_ctx; + expect_aio_watch(mock_image_ctx, 0, + [&mock_image_watcher, &unregister_ctx]() { + mock_image_watcher.unregister_watch(&unregister_ctx); + }); + + expect_aio_unwatch(mock_image_ctx, 0); + + C_SaferCond register_ctx; + mock_image_watcher.register_watch(®ister_ctx); + ASSERT_EQ(0, register_ctx.wait()); + + ceph_assert(m_watch_ctx != nullptr); + m_watch_ctx->handle_error(0, -ESHUTDOWN); + + ASSERT_EQ(0, unregister_ctx.wait()); +} + +} // namespace librbd |