From: Jason Dillaman Date: Wed, 19 Aug 2015 17:32:42 +0000 (-0400) Subject: tests: initial set of test cases for op state machines X-Git-Tag: v10.0.2~121^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F6703%2Fhead;p=ceph.git tests: initial set of test cases for op state machines Signed-off-by: Jason Dillaman --- diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am index 76cc13adf4a4..00764b4ac41f 100644 --- a/src/test/Makefile-client.am +++ b/src/test/Makefile-client.am @@ -358,7 +358,11 @@ unittest_librbd_SOURCES = \ test/librbd/object_map/test_mock_SnapshotCreateRequest.cc \ test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc \ test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc \ - test/librbd/object_map/test_mock_UpdateRequest.cc + test/librbd/object_map/test_mock_UpdateRequest.cc \ + 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_SnapshotUnprotectRequest.cc unittest_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS) -DTEST_LIBRBD_INTERNALS unittest_librbd_LDADD = \ librbd_test.la librbd_api.la librbd_internal.la $(LIBRBD_TYPES) \ @@ -394,9 +398,11 @@ noinst_HEADERS += \ test/librbd/test_fixture.h \ test/librbd/test_mock_fixture.h \ test/librbd/test_support.h \ + test/librbd/mock/MockAioImageRequestWQ.h \ test/librbd/mock/MockContextWQ.h \ test/librbd/mock/MockImageCtx.h \ test/librbd/mock/MockImageWatcher.h \ + test/librbd/mock/MockJournal.h \ test/librbd/mock/MockObjectMap.h if LINUX diff --git a/src/test/librbd/mock/MockAioImageRequestWQ.h b/src/test/librbd/mock/MockAioImageRequestWQ.h new file mode 100644 index 000000000000..cd34807e9d78 --- /dev/null +++ b/src/test/librbd/mock/MockAioImageRequestWQ.h @@ -0,0 +1,18 @@ +// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H +#define CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H + +#include "gmock/gmock.h" + +namespace librbd { + +struct MockAioImageRequestWQ { + MOCK_METHOD1(block_writes, void(Context *)); + MOCK_METHOD0(unblock_writes, void()); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_AIO_IMAGE_REQUEST_WQ_H diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h index 53d6fd0d01e1..9ded05dd16c6 100644 --- a/src/test/librbd/mock/MockImageCtx.h +++ b/src/test/librbd/mock/MockImageCtx.h @@ -4,8 +4,10 @@ #ifndef CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H #define CEPH_TEST_LIBRBD_MOCK_IMAGE_CTX_H +#include "test/librbd/mock/MockAioImageRequestWQ.h" #include "test/librbd/mock/MockContextWQ.h" #include "test/librbd/mock/MockImageWatcher.h" +#include "test/librbd/mock/MockJournal.h" #include "test/librbd/mock/MockObjectMap.h" #include "common/RWLock.h" #include "librbd/ImageCtx.h" @@ -33,9 +35,9 @@ struct MockImageCtx { header_oid(image_ctx.header_oid), id(image_ctx.id), parent_md(image_ctx.parent_md), - aio_work_queue(new MockContextWQ()), + aio_work_queue(new MockAioImageRequestWQ()), op_work_queue(new MockContextWQ()), - image_watcher(NULL), + image_watcher(NULL), journal(NULL), concurrent_management_ops(image_ctx.concurrent_management_ops) { md_ctx.dup(image_ctx.md_ctx); @@ -98,12 +100,14 @@ struct MockImageCtx { xlist*> async_requests; Cond async_requests_cond; - MockContextWQ *aio_work_queue; + MockAioImageRequestWQ *aio_work_queue; MockContextWQ *op_work_queue; MockImageWatcher *image_watcher; MockObjectMap object_map; + MockJournal *journal; + int concurrent_management_ops; }; diff --git a/src/test/librbd/mock/MockJournal.h b/src/test/librbd/mock/MockJournal.h new file mode 100644 index 000000000000..0535d0af0a81 --- /dev/null +++ b/src/test/librbd/mock/MockJournal.h @@ -0,0 +1,24 @@ +// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_JOURNAL_H +#define CEPH_TEST_LIBRBD_MOCK_JOURNAL_H + +#include "gmock/gmock.h" +#include "librbd/Journal.h" + +namespace librbd { + +struct MockJournal { + MOCK_CONST_METHOD0(is_journal_ready, bool()); + MOCK_CONST_METHOD0(is_journal_replaying, bool()); + + MOCK_METHOD1(wait_for_journal_ready, void(Context *)); + + MOCK_METHOD1(append_op_event, uint64_t(journal::EventEntry&)); + MOCK_METHOD2(commit_op_event, void(uint64_t, int)); +}; + +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_JOURNAL_H diff --git a/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 000000000000..8ae12d0b60f0 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,236 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotCreateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotCreateRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockOperationSnapshotCreateRequest : public TestMockFixture { +public: + typedef SnapshotCreateRequest MockSnapshotCreateRequest; + + void expect_block_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.aio_work_queue, block_writes(_)) + .WillRepeatedly(CompleteContext(0, NULL)); + } + + void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_supported(_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + + void expect_allocate_snap_id(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + selfmanaged_snap_create(_)); + if (r < 0 && r != -ESTALE) { + expect.WillOnce(Return(r)); + } else { + expect.Times(r < 0 ? 2 : 1).WillRepeatedly(DoDefault()); + } + } + + void expect_release_snap_id(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + selfmanaged_snap_remove(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_snap_create(MockImageCtx &mock_image_ctx, int r) { + if (!mock_image_ctx.old_format) { + EXPECT_CALL(*mock_image_ctx.image_watcher, assert_header_locked(_)) + .Times(r == -ESTALE ? 2 : 1); + } + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, "rbd", + mock_image_ctx.old_format ? "snap_add" : + "snapshot_add", + _, _, _)); + if (r == -ESTALE) { + expect.WillOnce(Return(r)).WillOnce(DoDefault()); + } else if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_object_map_snap_create(MockImageCtx &mock_image_ctx) { + bool enabled = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_OBJECT_MAP); + EXPECT_CALL(mock_image_ctx.object_map, enabled(_)) + .WillOnce(Return(enabled)); + if (enabled) { + EXPECT_CALL(mock_image_ctx.object_map, snapshot_add(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + 0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_update_snap_context(MockImageCtx &mock_image_ctx) { + // state machine checks to ensure a refresh hasn't already added the snap + EXPECT_CALL(mock_image_ctx, get_snap_info(_)) + .WillOnce(Return(reinterpret_cast(NULL))); + EXPECT_CALL(mock_image_ctx, add_snap("snap1", _, _, _, _, _)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.aio_work_queue, unblock_writes()) + .Times(1); + } + +}; + +TEST_F(TestMockOperationSnapshotCreateRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, 0); + expect_update_snap_context(mock_image_ctx); + expect_object_map_snap_create(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, AllocateSnapIdError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapStale) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -ESTALE); + expect_snap_create(mock_image_ctx, -ESTALE); + expect_update_snap_context(mock_image_ctx); + expect_object_map_snap_create(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, CreateSnapError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, -EINVAL); + expect_release_snap_id(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, ReleaseSnapIdError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, -EINVAL); + expect_release_snap_id(mock_image_ctx, -ESTALE); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc new file mode 100644 index 000000000000..e77ceb22f87d --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc @@ -0,0 +1,190 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotProtectRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotProtectRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockOperationSnapshotProtectRequest : public TestMockFixture { +public: + typedef SnapshotProtectRequest MockSnapshotProtectRequest; + + void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_id(_)) + .WillOnce(Return(snap_id)); + } + + void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected, + int r) { + auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(is_protected), Return(0))); + } + } + + void expect_set_protection_status(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, "rbd", + "set_protection_status", _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } +}; + +TEST_F(TestMockOperationSnapshotProtectRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, GetSnapIdMissing) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, IsSnapProtectedError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, SnapAlreadyProtected) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, true, 0); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotProtectRequest, SetProtectionStateError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_protected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotProtectRequest *req = new MockSnapshotProtectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc new file mode 100644 index 000000000000..323f95b6bc56 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,314 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotRemoveRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockOperationSnapshotRemoveRequest : public TestMockFixture { +public: + typedef SnapshotRemoveRequest MockSnapshotRemoveRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = librbd::snap_create(ictx, snap_name); + if (r < 0) { + return r; + } + + r = librbd::snap_protect(ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_object_map_snap_remove(MockImageCtx &mock_image_ctx, int r) { + bool enabled = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_OBJECT_MAP); + EXPECT_CALL(mock_image_ctx.object_map, enabled(_)) + .WillOnce(Return(enabled)); + if (enabled) { + EXPECT_CALL(mock_image_ctx.object_map, snapshot_remove(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + r, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_get_parent_spec(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(mock_image_ctx, get_parent_spec(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + parent_spec &parent_spec = mock_image_ctx.snap_info.rbegin()->second.parent.spec; + expect.WillOnce(DoAll(SetArgPointee<1>(parent_spec), + Return(0))); + } + } + + void expect_remove_child(MockImageCtx &mock_image_ctx, int r) { + bool deep_flatten = mock_image_ctx.image_ctx->test_features(RBD_FEATURE_DEEP_FLATTEN); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, "rbd", "remove_child",_, + _, _)); + if (deep_flatten) { + expect.Times(0); + } else { + expect.WillOnce(Return(r)); + } + } + + void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.old_format) { + return; + } + + EXPECT_CALL(*mock_image_ctx.image_watcher, is_lock_owner()) + .WillRepeatedly(Return(false)); + } + + void expect_snap_remove(MockImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, "rbd", + mock_image_ctx.old_format ? "snap_remove" : + "snapshot_remove", + _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_rm_snap(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, rm_snap(_, _)).Times(1); + } + + void expect_release_snap_id(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + selfmanaged_snap_remove(_)) + .WillOnce(DoDefault()); + } + +}; + +TEST_F(TestMockOperationSnapshotRemoveRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_get_parent_spec(mock_image_ctx, 0); + expect_verify_lock_ownership(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + expect_release_snap_id(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, FlattenedCloneRemovesChild) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, librbd::flatten(ictx, prog_ctx)); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_get_parent_spec(mock_image_ctx, 0); + expect_remove_child(mock_image_ctx, -ENOENT); + expect_verify_lock_ownership(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + expect_release_snap_id(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, ObjectMapSnapRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildParentError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_get_parent_spec(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + if (ictx->test_features(RBD_FEATURE_DEEP_FLATTEN)) { + std::cout << "SKIPPING" << std::endl; + return SUCCEED(); + } + + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, librbd::flatten(ictx, prog_ctx)); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_get_parent_spec(mock_image_ctx, 0); + expect_remove_child(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveSnapError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_get_parent_spec(mock_image_ctx, 0); + expect_verify_lock_ownership(mock_image_ctx); + expect_snap_remove(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, "snap1", snap_id); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc new file mode 100644 index 000000000000..7ec65b48ae0e --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc @@ -0,0 +1,275 @@ +// -*- 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 "include/rados/librados.hpp" +#include "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/operation/SnapshotUnprotectRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// template definitions +#include "librbd/operation/SnapshotUnprotectRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::SetArgReferee; +using ::testing::SetArgPointee; +using ::testing::WithArg; + +class TestMockOperationSnapshotUnprotectRequest : public TestMockFixture { +public: + typedef SnapshotUnprotectRequest MockSnapshotUnprotectRequest; + + void expect_get_snap_id(MockImageCtx &mock_image_ctx, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, get_snap_id(_)) + .WillOnce(Return(snap_id)); + } + + void expect_is_snap_unprotected(MockImageCtx &mock_image_ctx, + bool is_unprotected, int r) { + auto &expect = EXPECT_CALL(mock_image_ctx, is_snap_unprotected(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(is_unprotected), Return(0))); + } + } + + void expect_set_protection_status(MockImageCtx &mock_image_ctx, + uint64_t snap_id, uint8_t status, int r) { + bufferlist bl; + ::encode(snap_id, bl); + ::encode(status, bl); + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, "rbd", + "set_protection_status", ContentsEqual(bl), + _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + size_t expect_create_pool_io_contexts(MockImageCtx &mock_image_ctx) { + librados::MockTestMemIoCtxImpl &io_ctx_impl = + get_mock_io_ctx(mock_image_ctx.md_ctx); + librados::MockTestMemRadosClient *rados_client = + io_ctx_impl.get_mock_rados_client(); + + std::list > pools; + int r = rados_client->pool_list(pools); + if (r < 0) { + ADD_FAILURE() << "failed to list pools"; + return 0; + } + + EXPECT_CALL(*rados_client, create_ioctx(_, _)) + .Times(pools.size()).WillRepeatedly(DoAll( + GetReference(&io_ctx_impl), Return(&io_ctx_impl))); + return pools.size(); + } + + void expect_get_children(MockImageCtx &mock_image_ctx, size_t pools, int r) { + bufferlist bl; + std::set children; + ::encode(children, bl); + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, "rbd", "get_children", _, + _, _)); + if (r < 0) { + expect.WillRepeatedly(Return(r)); + } else { + expect.Times(pools).WillRepeatedly(DoAll( + SetArgPointee<5>(bl), Return(0))); + } + } +}; + +TEST_F(TestMockOperationSnapshotUnprotectRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, 0); + size_t pools = expect_create_pool_io_contexts(mock_image_ctx); + expect_get_children(mock_image_ctx, pools, -ENOENT); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTED, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, GetSnapIdMissing) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, CEPH_NOSNAP); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, IsSnapUnprotectedError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_unprotected(mock_image_ctx, false, -EBADMSG); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBADMSG, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, SnapAlreadyUnprotected) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_get_snap_id(mock_image_ctx, ictx->snap_info.rbegin()->first); + expect_is_snap_unprotected(mock_image_ctx, true, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, SetProtectionStatusError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotUnprotectRequest, ChildrenExist) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, librbd::snap_create(ictx, "snap1")); + ASSERT_EQ(0, librbd::ictx_check(ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_snap_id(mock_image_ctx, snap_id); + expect_is_snap_unprotected(mock_image_ctx, false, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_UNPROTECTING, 0); + size_t pools = expect_create_pool_io_contexts(mock_image_ctx); + expect_get_children(mock_image_ctx, pools, 0); + expect_set_protection_status(mock_image_ctx, snap_id, + RBD_PROTECTION_STATUS_PROTECTED, 0); + + C_SaferCond cond_ctx; + MockSnapshotUnprotectRequest *req = new MockSnapshotUnprotectRequest( + mock_image_ctx, &cond_ctx, "snap1"); + { + RWLock::RLocker owner_locker(mock_image_ctx.owner_lock); + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/test_mock_fixture.cc b/src/test/librbd/test_mock_fixture.cc index 1839b9150eca..d181914f3717 100644 --- a/src/test/librbd/test_mock_fixture.cc +++ b/src/test/librbd/test_mock_fixture.cc @@ -9,9 +9,11 @@ // template definitions #include "librbd/AsyncRequest.cc" #include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" template class librbd::AsyncRequest; template class librbd::AsyncObjectThrottle; +template class librbd::operation::Request; using ::testing::_; using ::testing::DoDefault;