From: Jason Dillaman Date: Tue, 22 Dec 2015 18:40:51 +0000 (-0500) Subject: tests: new unit test for snap rollback state machine X-Git-Tag: v10.0.3~24^2~6 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=532c9d2325139bcb7982d533c79fb61651a88b17;p=ceph.git tests: new unit test for snap rollback state machine Signed-off-by: Jason Dillaman --- diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am index 72877d20a462..967dde46baac 100644 --- a/src/test/Makefile-client.am +++ b/src/test/Makefile-client.am @@ -379,6 +379,7 @@ unittest_librbd_SOURCES = \ test/librbd/operation/test_mock_SnapshotCreateRequest.cc \ test/librbd/operation/test_mock_SnapshotProtectRequest.cc \ test/librbd/operation/test_mock_SnapshotRemoveRequest.cc \ + test/librbd/operation/test_mock_SnapshotRollbackRequest.cc \ test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc unittest_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS) -DTEST_LIBRBD_INTERNALS unittest_librbd_LDADD = \ diff --git a/src/test/librados_test_stub/MockTestMemIoCtxImpl.h b/src/test/librados_test_stub/MockTestMemIoCtxImpl.h index 3bc957e373ae..c9da11b1c04b 100644 --- a/src/test/librados_test_stub/MockTestMemIoCtxImpl.h +++ b/src/test/librados_test_stub/MockTestMemIoCtxImpl.h @@ -79,6 +79,12 @@ public: return TestMemIoCtxImpl::selfmanaged_snap_remove(snap_id); } + MOCK_METHOD2(selfmanaged_snap_rollback, int(const std::string& oid, + uint64_t snap_id)); + int do_selfmanaged_snap_rollback(const std::string& oid, uint64_t snap_id) { + return TestMemIoCtxImpl::selfmanaged_snap_rollback(oid, snap_id); + } + MOCK_METHOD3(truncate, int(const std::string& oid, uint64_t size, const SnapContext &snapc)); @@ -111,6 +117,7 @@ public: ON_CALL(*this, remove(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_remove)); ON_CALL(*this, selfmanaged_snap_create(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_create)); ON_CALL(*this, selfmanaged_snap_remove(_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_remove)); + ON_CALL(*this, selfmanaged_snap_rollback(_, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_selfmanaged_snap_rollback)); ON_CALL(*this, truncate(_,_,_)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_truncate)); ON_CALL(*this, write(_, _, _, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_write)); ON_CALL(*this, write_full(_, _, _)).WillByDefault(Invoke(this, &MockTestMemIoCtxImpl::do_write_full)); diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h index 1883819a6d85..7d198f5caba3 100644 --- a/src/test/librbd/mock/MockImageCtx.h +++ b/src/test/librbd/mock/MockImageCtx.h @@ -31,6 +31,7 @@ struct MockImageCtx { snapc(image_ctx.snapc), snaps(image_ctx.snaps), snap_info(image_ctx.snap_info), + object_cacher(image_ctx.object_cacher), old_format(image_ctx.old_format), read_only(image_ctx.read_only), owner_lock("owner_lock"), @@ -94,6 +95,8 @@ struct MockImageCtx { ctx.wait(); } + MOCK_CONST_METHOD1(get_object_name, std::string(uint64_t)); + MOCK_CONST_METHOD0(get_current_size, uint64_t()); MOCK_CONST_METHOD1(get_image_size, uint64_t(librados::snap_t)); MOCK_CONST_METHOD1(get_snap_id, librados::snap_t(std::string in_snap_name)); MOCK_CONST_METHOD1(get_snap_info, const SnapInfo*(librados::snap_t)); @@ -131,6 +134,7 @@ struct MockImageCtx { std::vector snaps; std::map snap_info; + ObjectCacher *object_cacher; bool old_format; bool read_only; diff --git a/src/test/librbd/mock/MockObjectMap.h b/src/test/librbd/mock/MockObjectMap.h index 6be9beaab32a..5a0aeeca6f74 100644 --- a/src/test/librbd/mock/MockObjectMap.h +++ b/src/test/librbd/mock/MockObjectMap.h @@ -21,6 +21,7 @@ struct MockObjectMap { MOCK_METHOD2(snapshot_add, void(uint64_t snap_id, Context *on_finish)); MOCK_METHOD2(snapshot_remove, void(uint64_t snap_id, Context *on_finish)); + MOCK_METHOD2(rollback, void(uint64_t snap_id, Context *on_finish)); }; } // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc new file mode 100644 index 000000000000..365d086c5868 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,310 @@ +// -*- 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 "include/stringify.h" +#include "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotRollbackRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace operation { + +template <> +struct ResizeRequest { + static ResizeRequest *s_instance; + Context *on_finish = nullptr; + + static ResizeRequest* create(MockImageCtx &image_ctx, Context *on_finish, + uint64_t new_size, ProgressContext &prog_ctx, + uint64_t journal_op_tid, bool disable_journal) { + assert(s_instance != nullptr); + assert(journal_op_tid == 0); + assert(disable_journal); + s_instance->on_finish = on_finish; + return s_instance; + } + + ResizeRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ResizeRequest *ResizeRequest::s_instance = nullptr; + +} // namespace operation +} // namespace librbd + +// template definitions +#include "librbd/operation/SnapshotRollbackRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; + +class TestMockOperationSnapshotRollbackRequest : public TestMockFixture { +public: + typedef SnapshotRollbackRequest MockSnapshotRollbackRequest; + typedef ResizeRequest MockResizeRequest; + + void expect_block_writes(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.aio_work_queue, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.aio_work_queue, unblock_writes()) + .Times(1); + } + + void expect_get_image_size(MockImageCtx &mock_image_ctx, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(CEPH_NOSNAP)) + .WillOnce(Return(size)); + } + + void expect_resize(MockImageCtx &mock_image_ctx, + MockResizeRequest &mock_resize_request, int r) { + expect_get_image_size(mock_image_ctx, 123); + EXPECT_CALL(mock_resize_request, send()) + .WillOnce(FinishRequest(&mock_resize_request, r, + &mock_image_ctx)); + } + + void expect_rollback_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_object_map, rollback(_, _)) + .WillOnce(WithArg<1>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_get_object_name(MockImageCtx &mock_image_ctx, + uint64_t object_num) { + EXPECT_CALL(mock_image_ctx, get_object_name(object_num)) + .WillOnce(Return("object-name-" + stringify(object_num))); + } + + void expect_get_current_size(MockImageCtx &mock_image_ctx, uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_current_size()) + .WillOnce(Return(size)); + } + + void expect_rollback_snap_id(MockImageCtx &mock_image_ctx, + const std::string &oid, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + selfmanaged_snap_rollback(oid, _)) + .WillOnce(Return(r)); + } + + void expect_rollback(MockImageCtx &mock_image_ctx, int r) { + expect_get_current_size(mock_image_ctx, 1); + expect_get_object_name(mock_image_ctx, 0); + expect_rollback_snap_id(mock_image_ctx, "object-name-0", r); + } + + void expect_create_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map) { + EXPECT_CALL(mock_image_ctx, create_object_map(_)) + .WillOnce(Return(mock_object_map)); + } + + void expect_open_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, open(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_lock_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + EXPECT_CALL(mock_object_map, lock(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_refresh_object_map(MockImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map) { + if (mock_image_ctx.object_map != nullptr) { + expect_create_object_map(mock_image_ctx, &mock_object_map); + expect_open_object_map(mock_image_ctx, mock_object_map); + } + } + + void expect_invalidate_cache(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.object_cacher != nullptr) { + EXPECT_CALL(mock_image_ctx, invalidate_cache(_)) + .WillOnce(CompleteContext(r, NULL)); + } + } + + int when_snap_rollback(MockImageCtx &mock_image_ctx, + const std::string &snap_name, + uint64_t snap_id, uint64_t snap_size) { + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotRollbackRequest *req = new MockSnapshotRollbackRequest( + mock_image_ctx, &cond_ctx, snap_name, snap_id, snap_size, prog_ctx); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + return cond_ctx.wait(); + } +}; + +TEST_F(TestMockOperationSnapshotRollbackRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, 0); + expect_commit_op_event(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, BlockWritesError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, SkipResize) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_get_image_size(mock_image_ctx, 345); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, 0); + expect_commit_op_event(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_snap_rollback(mock_image_ctx, "snap", 123, 345)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, ResizeError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, RollbackObjectsError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_rollback_object_map(mock_image_ctx, mock_object_map); + expect_rollback(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +TEST_F(TestMockOperationSnapshotRollbackRequest, InvalidateCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap *mock_object_map = new MockObjectMap(); + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + *mock_object_map); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + MockResizeRequest mock_resize_request; + expect_append_op_event(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_rollback_object_map(mock_image_ctx, *mock_object_map); + expect_rollback(mock_image_ctx, 0); + expect_refresh_object_map(mock_image_ctx, *mock_object_map); + expect_invalidate_cache(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_snap_rollback(mock_image_ctx, "snap", 123, 0)); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/test_mock_fixture.cc b/src/test/librbd/test_mock_fixture.cc index d181914f3717..f5a2394fc32c 100644 --- a/src/test/librbd/test_mock_fixture.cc +++ b/src/test/librbd/test_mock_fixture.cc @@ -17,6 +17,8 @@ template class librbd::operation::Request; using ::testing::_; using ::testing::DoDefault; +using ::testing::Return; +using ::testing::WithArg; TestMockFixture::TestRadosClientPtr TestMockFixture::s_test_rados_client; ::testing::NiceMock * @@ -68,3 +70,52 @@ librados::MockTestMemIoCtxImpl &TestMockFixture::get_mock_io_ctx( reinterpret_cast(&ioctx); return **mock; } + +void TestMockFixture::initialize_features(librbd::ImageCtx *ictx, + librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockJournal &mock_journal, + librbd::MockObjectMap &mock_object_map) { + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } +} + +void TestMockFixture::expect_is_journal_replaying(librbd::MockJournal &mock_journal) { + EXPECT_CALL(mock_journal, is_journal_replaying()).WillOnce(Return(false)); +} + +void TestMockFixture::expect_is_journal_ready(librbd::MockJournal &mock_journal) { + EXPECT_CALL(mock_journal, is_journal_ready()).WillOnce(Return(true)); +} + +void TestMockFixture::expect_allocate_op_tid(librbd::MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.journal != nullptr) { + EXPECT_CALL(*mock_image_ctx.journal, allocate_op_tid()) + .WillOnce(Return(1U)); + } +} + +void TestMockFixture::expect_append_op_event(librbd::MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_allocate_op_tid(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.journal, append_op_event_mock(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } +} + +void TestMockFixture::expect_commit_op_event(librbd::MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_is_journal_ready(*mock_image_ctx.journal); + EXPECT_CALL(*mock_image_ctx.journal, commit_op_event(1U, r)); + } +} + diff --git a/src/test/librbd/test_mock_fixture.h b/src/test/librbd/test_mock_fixture.h index bf8a0af7b9d7..ce3e6d5abb17 100644 --- a/src/test/librbd/test_mock_fixture.h +++ b/src/test/librbd/test_mock_fixture.h @@ -74,6 +74,18 @@ public: void expect_op_work_queue(librbd::MockImageCtx &mock_image_ctx); void expect_unlock_exclusive_lock(librbd::ImageCtx &ictx); + void initialize_features(librbd::ImageCtx *ictx, + librbd::MockImageCtx &mock_image_ctx, + librbd::MockExclusiveLock &mock_exclusive_lock, + librbd::MockJournal &mock_journal, + librbd::MockObjectMap &mock_object_map); + + void expect_is_journal_replaying(librbd::MockJournal &mock_journal); + void expect_is_journal_ready(librbd::MockJournal &mock_journal); + void expect_allocate_op_tid(librbd::MockImageCtx &mock_image_ctx); + void expect_append_op_event(librbd::MockImageCtx &mock_image_ctx, int r); + void expect_commit_op_event(librbd::MockImageCtx &mock_image_ctx, int r); + private: static TestRadosClientPtr s_test_rados_client; static ::testing::NiceMock *s_mock_rados_client;