// -*- 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 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 &action = std::function()) { 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 &action = std::function()) { 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