diff options
Diffstat (limited to 'src/test/librbd/operation')
10 files changed, 4588 insertions, 0 deletions
diff --git a/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc b/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc new file mode 100644 index 000000000..171ac41a7 --- /dev/null +++ b/src/test/librbd/operation/test_mock_DisableFeaturesRequest.cc @@ -0,0 +1,538 @@ +// -*- 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/librbd/mock/MockJournalPolicy.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/image/SetFlagsRequest.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/mirror/DisableRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/StandardPolicy.h" +#include "librbd/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "librbd/object_map/RemoveRequest.h" +#include "librbd/operation/DisableFeaturesRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template<> +struct Journal<MockOperationImageCtx> { + static void get_work_queue(CephContext*, MockContextWQ**) { + } +}; + +namespace image { + +template<> +class SetFlagsRequest<MockOperationImageCtx> { +public: + static SetFlagsRequest *s_instance; + Context *on_finish = nullptr; + + static SetFlagsRequest *create(MockOperationImageCtx *image_ctx, uint64_t flags, + uint64_t mask, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetFlagsRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetFlagsRequest<MockOperationImageCtx> *SetFlagsRequest<MockOperationImageCtx>::s_instance; + +} // namespace image + +namespace journal { + +template<> +class RemoveRequest<MockOperationImageCtx> { +public: + static RemoveRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveRequest *create(IoCtx &ioctx, const std::string &imageid, + const std::string &client_id, + MockContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockOperationImageCtx> *RemoveRequest<MockOperationImageCtx>::s_instance; + +template<> +class StandardPolicy<MockOperationImageCtx> : public MockJournalPolicy { +public: + StandardPolicy(MockOperationImageCtx* image_ctx) { + } +}; + +template <> +struct TypeTraits<MockOperationImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +} // namespace journal + +namespace mirror { + +template<> +class DisableRequest<MockOperationImageCtx> { +public: + static DisableRequest *s_instance; + Context *on_finish = nullptr; + + static DisableRequest *create(MockOperationImageCtx *image_ctx, bool force, + bool remove, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + DisableRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DisableRequest<MockOperationImageCtx> *DisableRequest<MockOperationImageCtx>::s_instance; + +} // namespace mirror + +namespace object_map { + +template<> +class RemoveRequest<MockOperationImageCtx> { +public: + static RemoveRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveRequest *create(MockOperationImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockOperationImageCtx> *RemoveRequest<MockOperationImageCtx>::s_instance; + +} // namespace object_map + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#include "librbd/operation/DisableFeaturesRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::_; + +class TestMockOperationDisableFeaturesRequest : public TestMockFixture { +public: + typedef librbd::image::SetFlagsRequest<MockOperationImageCtx> MockSetFlagsRequest; + typedef librbd::journal::RemoveRequest<MockOperationImageCtx> MockRemoveJournalRequest; + typedef librbd::mirror::DisableRequest<MockOperationImageCtx> MockDisableMirrorRequest; + typedef librbd::object_map::RemoveRequest<MockOperationImageCtx> MockRemoveObjectMapRequest; + typedef DisableFeaturesRequest<MockOperationImageCtx> MockDisableFeaturesRequest; + + class MirrorModeEnabler { + public: + MirrorModeEnabler(librados::IoCtx &ioctx, cls::rbd::MirrorMode mirror_mode) + : m_ioctx(ioctx), m_mirror_mode(mirror_mode) { + EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid")); + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(&m_ioctx, m_mirror_mode)); + } + + ~MirrorModeEnabler() { + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED)); + } + private: + librados::IoCtx &m_ioctx; + cls::rbd::MirrorMode m_mirror_mode; + }; + + void expect_prepare_lock(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + expect_op_work_queue(mock_image_ctx); + } + + void expect_handle_prepare_lock_complete(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, unblock_writes()).Times(1); + } + + void expect_verify_lock_ownership(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_block_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(0)).Times(1); + } + } + + void expect_unblock_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests()).Times(1); + } + } + + void expect_set_flags_request_send( + MockOperationImageCtx &mock_image_ctx, + MockSetFlagsRequest &mock_set_flags_request, int r) { + EXPECT_CALL(mock_set_flags_request, send()) + .WillOnce(FinishRequest(&mock_set_flags_request, r, + &mock_image_ctx)); + } + + void expect_disable_mirror_request_send( + MockOperationImageCtx &mock_image_ctx, + MockDisableMirrorRequest &mock_disable_mirror_request, int r) { + EXPECT_CALL(mock_disable_mirror_request, send()) + .WillOnce(FinishRequest(&mock_disable_mirror_request, r, + &mock_image_ctx)); + } + + void expect_close_journal(MockOperationImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.journal, close(_)) + .WillOnce(Invoke([&mock_image_ctx, r](Context *on_finish) { + mock_image_ctx.journal = nullptr; + mock_image_ctx.image_ctx->op_work_queue->queue(on_finish, r); + })); + } + + void expect_remove_journal_request_send( + MockOperationImageCtx &mock_image_ctx, + MockRemoveJournalRequest &mock_remove_journal_request, int r) { + EXPECT_CALL(mock_remove_journal_request, send()) + .WillOnce(FinishRequest(&mock_remove_journal_request, r, + &mock_image_ctx)); + } + + void expect_remove_object_map_request_send( + MockOperationImageCtx &mock_image_ctx, + MockRemoveObjectMapRequest &mock_remove_object_map_request, int r) { + EXPECT_CALL(mock_remove_object_map_request, send()) + .WillOnce(FinishRequest(&mock_remove_object_map_request, r, + &mock_image_ctx)); + } + + void expect_notify_update(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, notify_update(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + +}; + +TEST_F(TestMockOperationDisableFeaturesRequest, All) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + uint64_t features_to_disable = RBD_FEATURES_MUTABLE & features; + + REQUIRE(features_to_disable); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + if (features_to_disable & RBD_FEATURE_JOURNALING) { + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, 0); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + } + if (features_to_disable & RBD_FEATURE_OBJECT_MAP) { + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, 0); + } + if (features_to_disable & (RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF)) { + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + } + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, features_to_disable, false); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, ObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF, false); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, ObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockRemoveObjectMapRequest mock_remove_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_remove_object_map_request_send(mock_image_ctx, + mock_remove_object_map_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF, false); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, Mirroring) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_block_requests(mock_image_ctx); + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, 0); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING, false); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationDisableFeaturesRequest, MirroringError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockRemoveJournalRequest mock_remove_journal_request; + MockDisableMirrorRequest mock_disable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_is_journal_replaying(*mock_image_ctx.journal); + expect_block_requests(mock_image_ctx); + expect_disable_mirror_request_send(mock_image_ctx, + mock_disable_mirror_request, -EINVAL); + expect_close_journal(mock_image_ctx, 0); + expect_remove_journal_request_send(mock_image_ctx, + mock_remove_journal_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockDisableFeaturesRequest *req = new MockDisableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING, false); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc b/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc new file mode 100644 index 000000000..b7bf7d178 --- /dev/null +++ b/src/test/librbd/operation/test_mock_EnableFeaturesRequest.cc @@ -0,0 +1,644 @@ +// -*- 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 "cls/rbd/cls_rbd_client.h" +#include "librbd/Operations.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/image/SetFlagsRequest.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/mirror/EnableRequest.h" +#include "librbd/journal/CreateRequest.h" +#include "librbd/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "librbd/object_map/CreateRequest.h" +#include "librbd/operation/EnableFeaturesRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(librbd::ImageCtx& image_ctx) + : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template<> +struct Journal<MockOperationImageCtx> { + static void get_work_queue(CephContext*, MockContextWQ**) { + } +}; + +namespace image { + +template<> +class SetFlagsRequest<MockOperationImageCtx> { +public: + static SetFlagsRequest *s_instance; + Context *on_finish = nullptr; + + static SetFlagsRequest *create(MockOperationImageCtx *image_ctx, uint64_t flags, + uint64_t mask, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetFlagsRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetFlagsRequest<MockOperationImageCtx> *SetFlagsRequest<MockOperationImageCtx>::s_instance; + +} // namespace image + +namespace journal { + +template<> +class CreateRequest<MockOperationImageCtx> { +public: + static CreateRequest *s_instance; + Context *on_finish = nullptr; + + static CreateRequest *create(IoCtx &ioctx, const std::string &imageid, + uint8_t order, uint8_t splay_width, + const std::string &object_pool, + uint64_t tag_class, TagData &tag_data, + const std::string &client_id, + MockContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + CreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +CreateRequest<MockOperationImageCtx> *CreateRequest<MockOperationImageCtx>::s_instance = nullptr; + +template <> +struct TypeTraits<MockOperationImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +} // namespace journal + +namespace mirror { + +template<> +class EnableRequest<MockOperationImageCtx> { +public: + static EnableRequest *s_instance; + Context *on_finish = nullptr; + + static EnableRequest *create(MockOperationImageCtx *image_ctx, + cls::rbd::MirrorImageMode mirror_image_mode, + const std::string& non_primary_global_image_id, + bool image_clean, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + EnableRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +EnableRequest<MockOperationImageCtx> *EnableRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace mirror + +namespace object_map { + +template<> +class CreateRequest<MockOperationImageCtx> { +public: + static CreateRequest *s_instance; + Context *on_finish = nullptr; + + static CreateRequest *create(MockOperationImageCtx *image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + CreateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +CreateRequest<MockOperationImageCtx> *CreateRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace object_map + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#include "librbd/operation/EnableFeaturesRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::_; + +class TestMockOperationEnableFeaturesRequest : public TestMockFixture { +public: + typedef librbd::image::SetFlagsRequest<MockOperationImageCtx> MockSetFlagsRequest; + typedef librbd::journal::CreateRequest<MockOperationImageCtx> MockCreateJournalRequest; + typedef librbd::mirror::EnableRequest<MockOperationImageCtx> MockEnableMirrorRequest; + typedef librbd::object_map::CreateRequest<MockOperationImageCtx> MockCreateObjectMapRequest; + typedef EnableFeaturesRequest<MockOperationImageCtx> MockEnableFeaturesRequest; + + class MirrorModeEnabler { + public: + MirrorModeEnabler(librados::IoCtx &ioctx, cls::rbd::MirrorMode mirror_mode) + : m_ioctx(ioctx), m_mirror_mode(mirror_mode) { + EXPECT_EQ(0, librbd::cls_client::mirror_uuid_set(&m_ioctx, "test-uuid")); + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set(&m_ioctx, m_mirror_mode)); + } + + ~MirrorModeEnabler() { + EXPECT_EQ(0, librbd::cls_client::mirror_mode_set( + &m_ioctx, cls::rbd::MIRROR_MODE_DISABLED)); + } + private: + librados::IoCtx &m_ioctx; + cls::rbd::MirrorMode m_mirror_mode; + }; + + void ensure_features_disabled(librbd::ImageCtx *ictx, + uint64_t features_to_disable) { + uint64_t features; + + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + features_to_disable &= features; + if (!features_to_disable) { + return; + } + ASSERT_EQ(0, ictx->operations->update_features(features_to_disable, false)); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & features_to_disable); + } + + void expect_prepare_lock(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, prepare_lock(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + expect_op_work_queue(mock_image_ctx); + } + + void expect_handle_prepare_lock_complete(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, handle_prepare_lock_complete()); + } + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, unblock_writes()).Times(1); + } + + void expect_verify_lock_ownership(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_block_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(0)).Times(1); + } + } + + void expect_unblock_requests(MockOperationImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests()).Times(1); + } + } + + void expect_set_flags_request_send( + MockOperationImageCtx &mock_image_ctx, + MockSetFlagsRequest &mock_set_flags_request, int r) { + EXPECT_CALL(mock_set_flags_request, send()) + .WillOnce(FinishRequest(&mock_set_flags_request, r, + &mock_image_ctx)); + } + + void expect_create_journal_request_send( + MockOperationImageCtx &mock_image_ctx, + MockCreateJournalRequest &mock_create_journal_request, int r) { + EXPECT_CALL(mock_create_journal_request, send()) + .WillOnce(FinishRequest(&mock_create_journal_request, r, + &mock_image_ctx)); + } + + void expect_enable_mirror_request_send( + MockOperationImageCtx &mock_image_ctx, + MockEnableMirrorRequest &mock_enable_mirror_request, int r) { + EXPECT_CALL(mock_enable_mirror_request, send()) + .WillOnce(FinishRequest(&mock_enable_mirror_request, r, + &mock_image_ctx)); + } + + void expect_create_object_map_request_send( + MockOperationImageCtx &mock_image_ctx, + MockCreateObjectMapRequest &mock_create_object_map_request, int r) { + EXPECT_CALL(mock_create_object_map_request, send()) + .WillOnce(FinishRequest(&mock_create_object_map_request, r, + &mock_image_ctx)); + } + + void expect_notify_update(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, notify_update(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + +}; + +TEST_F(TestMockOperationEnableFeaturesRequest, All) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + uint64_t features_to_enable = RBD_FEATURES_MUTABLE & features; + + REQUIRE(features_to_enable); + + ensure_features_disabled(ictx, features_to_enable); + + MockOperationImageCtx mock_image_ctx(*ictx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateJournalRequest mock_create_journal_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + if (features_to_enable & RBD_FEATURE_JOURNALING) { + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + } + if (features_to_enable & (RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF)) { + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + } + if (features_to_enable & RBD_FEATURE_OBJECT_MAP) { + expect_create_object_map_request_send(mock_image_ctx, + mock_create_object_map_request, 0); + } + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, features_to_enable); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, ObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_create_object_map_request_send(mock_image_ctx, + mock_create_object_map_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, ObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, 0); + expect_create_object_map_request_send( + mock_image_ctx, mock_create_object_map_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, SetFlagsError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled( + ictx, RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockSetFlagsRequest mock_set_flags_request; + MockCreateObjectMapRequest mock_create_object_map_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + if (mock_image_ctx.journal != nullptr) { + expect_is_journal_replaying(*mock_image_ctx.journal); + } + expect_block_requests(mock_image_ctx); + expect_append_op_event(mock_image_ctx, true, 0); + expect_set_flags_request_send(mock_image_ctx, + mock_set_flags_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_OBJECT_MAP); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, Mirroring) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + expect_enable_mirror_request_send(mock_image_ctx, + mock_enable_mirror_request, 0); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, JournalingError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, -EINVAL); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationEnableFeaturesRequest, MirroringError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + MirrorModeEnabler mirror_mode_enabler(m_ioctx, cls::rbd::MIRROR_MODE_POOL); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + + ensure_features_disabled(ictx, RBD_FEATURE_JOURNALING); + + MockOperationImageCtx 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_verify_lock_ownership(mock_image_ctx); + + MockCreateJournalRequest mock_create_journal_request; + MockEnableMirrorRequest mock_enable_mirror_request; + + ::testing::InSequence seq; + expect_prepare_lock(mock_image_ctx); + expect_block_writes(mock_image_ctx); + expect_block_requests(mock_image_ctx); + expect_create_journal_request_send(mock_image_ctx, + mock_create_journal_request, 0); + expect_enable_mirror_request_send(mock_image_ctx, + mock_enable_mirror_request, -EINVAL); + expect_notify_update(mock_image_ctx); + expect_unblock_requests(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_handle_prepare_lock_complete(mock_image_ctx); + + C_SaferCond cond_ctx; + MockEnableFeaturesRequest *req = new MockEnableFeaturesRequest( + mock_image_ctx, &cond_ctx, 0, RBD_FEATURE_JOURNALING); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_Request.cc b/src/test/librbd/operation/test_mock_Request.cc new file mode 100644 index 000000000..5c5e7a375 --- /dev/null +++ b/src/test/librbd/operation/test_mock_Request.cc @@ -0,0 +1,175 @@ +// -*- 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/librbd/mock/MockJournal.h" +#include "librbd/AsyncRequest.h" +#include "librbd/operation/Request.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template <> +struct AsyncRequest<librbd::MockTestImageCtx> { + librbd::MockTestImageCtx &m_image_ctx; + Context *m_on_finish; + + AsyncRequest(librbd::MockTestImageCtx &image_ctx, Context *on_finish) + : m_image_ctx(image_ctx), m_on_finish(on_finish) { + } + virtual ~AsyncRequest() { + } + + virtual void finish(int r) { + m_on_finish->complete(r); + } + virtual void finish_and_destroy(int r) { + finish(r); + delete this; + } +}; + +} // namespace librbd + +#include "librbd/operation/Request.cc" + +namespace librbd { +namespace journal { + +std::ostream& operator<<(std::ostream& os, const Event&) { + return os; +} + +} // namespace journal + +namespace operation { + +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; + +struct MockRequest : public Request<librbd::MockTestImageCtx> { + MockRequest(librbd::MockTestImageCtx &image_ctx, Context *on_finish, + uint64_t journal_op_tid) + : Request<librbd::MockTestImageCtx>(image_ctx, on_finish, journal_op_tid) { + } + + void complete(int r) { + finish_and_destroy(r); + } + + void send_op_impl(int r) { + bool appending = append_op_event< + MockRequest, &MockRequest::handle_send>(this); + if (!appending) { + complete(r); + } + } + MOCK_METHOD1(should_complete, bool(int)); + MOCK_METHOD0(send_op, void()); + MOCK_METHOD1(handle_send, Context*(int*)); + MOCK_CONST_METHOD0(can_affect_io, bool()); + MOCK_CONST_METHOD1(create_event, journal::Event(uint64_t)); +}; + +struct TestMockOperationRequest : public TestMockFixture { + void expect_can_affect_io(MockRequest &mock_request, bool can_affect) { + EXPECT_CALL(mock_request, can_affect_io()) + .WillOnce(Return(can_affect)); + } + + void expect_is_journal_replaying(MockJournal &mock_journal, bool replaying) { + EXPECT_CALL(mock_journal, is_journal_replaying()) + .WillOnce(Return(replaying)); + } + + void expect_is_journal_appending(MockJournal &mock_journal, bool appending) { + EXPECT_CALL(mock_journal, is_journal_appending()) + .WillOnce(Return(appending)); + } + + void expect_send_op(MockRequest &mock_request, int r) { + EXPECT_CALL(mock_request, send_op()) + .WillOnce(Invoke([&mock_request, r]() { + mock_request.complete(r); + })); + } + + void expect_send_op_affects_io(MockImageCtx &mock_image_ctx, + MockRequest &mock_request, int r) { + EXPECT_CALL(mock_request, send_op()) + .WillOnce(Invoke([&mock_image_ctx, &mock_request, r]() { + mock_image_ctx.image_ctx->op_work_queue->queue( + new LambdaContext([&mock_request, r](int _) { + mock_request.send_op_impl(r); + }), 0); + })); + } + +}; + +TEST_F(TestMockOperationRequest, SendJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + C_SaferCond ctx; + MockRequest *mock_request = new MockRequest(mock_image_ctx, &ctx, 0); + + InSequence seq; + expect_can_affect_io(*mock_request, false); + expect_is_journal_appending(mock_journal, false); + expect_send_op(*mock_request, 0); + + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + mock_request->send(); + } + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockOperationRequest, SendAffectsIOJournalDisabled) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + C_SaferCond ctx; + MockRequest *mock_request = new MockRequest(mock_image_ctx, &ctx, 0); + + InSequence seq; + expect_can_affect_io(*mock_request, true); + expect_send_op_affects_io(mock_image_ctx, *mock_request, 0); + expect_can_affect_io(*mock_request, true); + expect_is_journal_replaying(mock_journal, false); + expect_is_journal_appending(mock_journal, false); + + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + mock_request->send(); + } + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace operation +} // namespace librbd diff --git a/src/test/librbd/operation/test_mock_ResizeRequest.cc b/src/test/librbd/operation/test_mock_ResizeRequest.cc new file mode 100644 index 000000000..552ba5c97 --- /dev/null +++ b/src/test/librbd/operation/test_mock_ResizeRequest.cc @@ -0,0 +1,435 @@ +// -*- 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/librbd/mock/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/operation/ResizeRequest.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace util { + +inline ImageCtx* get_image_ctx(MockImageCtx* image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace operation { + +template <> +class TrimRequest<MockImageCtx> { +public: + static TrimRequest *s_instance; + static TrimRequest *create(MockImageCtx &image_ctx, Context *on_finish, + uint64_t original_size, uint64_t new_size, + ProgressContext &prog_ctx) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + TrimRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +TrimRequest<MockImageCtx> *TrimRequest<MockImageCtx>::s_instance = nullptr; + +} // namespace operation +} // namespace librbd + +// template definitions +#include "librbd/operation/ResizeRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationResizeRequest : public TestMockFixture { +public: + typedef ResizeRequest<MockImageCtx> MockResizeRequest; + typedef TrimRequest<MockImageCtx> MockTrimRequest; + + void expect_block_writes(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, 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.io_image_dispatcher, unblock_writes()) + .Times(1); + } + + void expect_is_lock_owner(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillOnce(Return(true)); + } + } + + void expect_grow_object_map(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.object_map != nullptr) { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_shrink_object_map(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.object_map != nullptr) { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_update_header(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + write(mock_image_ctx.header_oid, _, _, _, _)) + .WillOnce(Return(r)); + } else { + expect_is_lock_owner(mock_image_ctx); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("set_size"), _, _, _, _)) + .WillOnce(Return(r)); + } + } + + void expect_trim(MockImageCtx &mock_image_ctx, + MockTrimRequest &mock_trim_request, int r) { + EXPECT_CALL(mock_trim_request, send()) + .WillOnce(FinishRequest(&mock_trim_request, r, &mock_image_ctx)); + } + + void expect_flush_cache(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, r](io::ImageDispatchSpec* spec) { + ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>( + &spec->request) != nullptr); + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + auto aio_comp = spec->aio_comp; + auto ctx = new LambdaContext([aio_comp](int r) { + if (r < 0) { + aio_comp->fail(r); + } else { + aio_comp->set_request_count(1); + aio_comp->add_request(); + aio_comp->complete_request(r); + } + }); + mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r); + })); + } + + void expect_invalidate_cache(MockImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, invalidate_cache(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + expect_op_work_queue(mock_image_ctx); + } + + void expect_resize_object_map(MockImageCtx &mock_image_ctx, + uint64_t new_size) { + EXPECT_CALL(*mock_image_ctx.object_map, aio_resize(new_size, _, _)) + .WillOnce(WithArg<2>(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue))); + } + + int when_resize(MockImageCtx &mock_image_ctx, uint64_t new_size, + bool allow_shrink, uint64_t journal_op_tid, + bool disable_journal) { + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockResizeRequest *req = new MockResizeRequest( + mock_image_ctx, &cond_ctx, new_size, allow_shrink, prog_ctx, + journal_op_tid, disable_journal); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + return cond_ctx.wait(); + } +}; + +TEST_F(TestMockOperationResizeRequest, NoOpSuccess) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, GrowSuccess) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_grow_object_map(mock_image_ctx); + expect_update_header(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size * 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, ShrinkSuccess) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + expect_flush_cache(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + expect_trim(mock_image_ctx, mock_trim_request, 0); + expect_block_writes(mock_image_ctx, 0); + expect_update_header(mock_image_ctx, 0); + expect_shrink_object_map(mock_image_ctx); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, 0); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, ShrinkError) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, false, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, PreBlockWritesError) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, TrimError) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + expect_flush_cache(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, -EBUSY); + expect_trim(mock_image_ctx, mock_trim_request, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, FlushCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + REQUIRE(ictx->cache); + + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + expect_flush_cache(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, InvalidateCacheError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + REQUIRE(ictx->cache); + + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + expect_flush_cache(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, -EINVAL); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, PostBlockWritesError) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_unblock_writes(mock_image_ctx); + + MockTrimRequest mock_trim_request; + expect_flush_cache(mock_image_ctx, 0); + expect_invalidate_cache(mock_image_ctx, 0); + expect_trim(mock_image_ctx, mock_trim_request, 0); + expect_block_writes(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size / 2, true, 0, + false)); +} + +TEST_F(TestMockOperationResizeRequest, UpdateHeaderError) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, 0); + expect_grow_object_map(mock_image_ctx); + expect_update_header(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + expect_commit_op_event(mock_image_ctx, -EINVAL); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size * 2, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, JournalAppendError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_append_op_event(mock_image_ctx, true, -EINVAL); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(-EINVAL, when_resize(mock_image_ctx, ictx->size, true, 0, false)); +} + +TEST_F(TestMockOperationResizeRequest, JournalDisabled) { + 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); + + InSequence seq; + expect_block_writes(mock_image_ctx, 0); + expect_unblock_writes(mock_image_ctx); + ASSERT_EQ(0, when_resize(mock_image_ctx, ictx->size, true, 0, true)); +} + +} // namespace operation +} // namespace librbd 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 000000000..218fc6b04 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,495 @@ +// -*- 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/ObjectMap.h" +#include "librbd/mirror/snapshot/SetImageStateRequest.h" +#include "librbd/operation/SnapshotCreateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace mirror { +namespace snapshot { + +template<> +class SetImageStateRequest<MockImageCtx> { +public: + static SetImageStateRequest *s_instance; + Context *on_finish = nullptr; + + static SetImageStateRequest *create(MockImageCtx *image_ctx, uint64_t snap_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SetImageStateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SetImageStateRequest<MockImageCtx> *SetImageStateRequest<MockImageCtx>::s_instance; + +} // namespace snapshot +} // namespace mirror +} // namespace librbd + +// 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::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotCreateRequest : public TestMockFixture { +public: + typedef SnapshotCreateRequest<MockImageCtx> MockSnapshotCreateRequest; + typedef mirror::snapshot::SetImageStateRequest<MockImageCtx> MockSetImageStateRequest; + + void expect_notify_quiesce(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.image_watcher, notify_quiesce(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext( + r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_block_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, block_writes(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_verify_lock_ownership(MockImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, 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.data_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.data_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) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq(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) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, snapshot_add(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + 0, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_set_image_state( + MockImageCtx &mock_image_ctx, + MockSetImageStateRequest &mock_set_image_state_request, int r) { + EXPECT_CALL(mock_set_image_state_request, send()) + .WillOnce(FinishRequest(&mock_set_image_state_request, r, + &mock_image_ctx)); + } + + 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(static_cast<const librbd::SnapInfo*>(NULL))); + EXPECT_CALL(mock_image_ctx, add_snap(_, "snap1", _, _, _, _, _, _)); + } + + void expect_unblock_writes(MockImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, unblock_writes()) + .Times(1); + } + + void expect_notify_unquiesce(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.image_watcher, notify_unquiesce(_, _)) + .WillOnce(WithArg<1>( + CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } +}; + +TEST_F(TestMockOperationSnapshotCreateRequest, Success) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_notify_quiesce(mock_image_ctx, -EINVAL); + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, 0); + expect_object_map_snap_create(mock_image_ctx); + expect_update_snap_context(mock_image_ctx); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_unblock_writes(mock_image_ctx); + expect_notify_unquiesce(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, SNAP_CREATE_FLAG_IGNORE_NOTIFY_QUIESCE_ERROR, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, NotifyQuiesceError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_notify_quiesce(mock_image_ctx, -EINVAL); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, 0, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, AllocateSnapIdError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_notify_quiesce(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -EINVAL); + expect_unblock_writes(mock_image_ctx); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, 0, prog_ctx); + { + std::shared_lock 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); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_notify_quiesce(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, -ESTALE); + expect_snap_create(mock_image_ctx, -ESTALE); + expect_object_map_snap_create(mock_image_ctx); + expect_update_snap_context(mock_image_ctx); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_unblock_writes(mock_image_ctx); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, 0, prog_ctx); + { + std::shared_lock 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); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_notify_quiesce(mock_image_ctx, 0); + 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); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, 0, prog_ctx); + { + std::shared_lock 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); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + expect_notify_quiesce(mock_image_ctx, 0); + 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); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, 0, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, SkipObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_notify_quiesce(mock_image_ctx, 0); + 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_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_unblock_writes(mock_image_ctx); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, SNAP_CREATE_FLAG_SKIP_OBJECT_MAP, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, SkipNotifyQuiesce) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + 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_object_map_snap_create(mock_image_ctx); + expect_update_snap_context(mock_image_ctx); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), + "snap1", 0, SNAP_CREATE_FLAG_SKIP_NOTIFY_QUIESCE, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotCreateRequest, SetImageState) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_verify_lock_ownership(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_notify_quiesce(mock_image_ctx, 0); + expect_block_writes(mock_image_ctx); + expect_allocate_snap_id(mock_image_ctx, 0); + expect_snap_create(mock_image_ctx, 0); + expect_object_map_snap_create(mock_image_ctx); + MockSetImageStateRequest mock_set_image_state_request; + expect_set_image_state(mock_image_ctx, mock_set_image_state_request, 0); + expect_update_snap_context(mock_image_ctx); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_unblock_writes(mock_image_ctx); + expect_notify_unquiesce(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext prog_ctx; + MockSnapshotCreateRequest *req = new MockSnapshotCreateRequest( + mock_image_ctx, &cond_ctx, + cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP}, + "snap1", 0, 0, prog_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, 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 000000000..aa8c1e78d --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotProtectRequest.cc @@ -0,0 +1,193 @@ +// -*- 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/ImageState.h" +#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::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotProtectRequest : public TestMockFixture { +public: + typedef SnapshotProtectRequest<MockImageCtx> 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, _, StrEq("rbd"), + StrEq("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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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 000000000..4469cb80d --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,971 @@ +// -*- 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/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/image/DetachChildRequest.h" +#include "librbd/mirror/snapshot/RemoveImageStateRequest.h" +#include "librbd/operation/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace image { + +template <> +class DetachChildRequest<MockImageCtx> { +public: + static DetachChildRequest *s_instance; + static DetachChildRequest *create(MockImageCtx &image_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + DetachChildRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +DetachChildRequest<MockImageCtx> *DetachChildRequest<MockImageCtx>::s_instance; + +} // namespace image + +namespace mirror { +namespace snapshot { + +template<> +class RemoveImageStateRequest<MockImageCtx> { +public: + static RemoveImageStateRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveImageStateRequest *create(MockImageCtx *image_ctx, + uint64_t snap_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + RemoveImageStateRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveImageStateRequest<MockImageCtx> *RemoveImageStateRequest<MockImageCtx>::s_instance; + +} // namespace snapshot +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/operation/SnapshotRemoveRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotRemoveRequest : public TestMockFixture { +public: + typedef SnapshotRemoveRequest<MockImageCtx> MockSnapshotRemoveRequest; + typedef image::DetachChildRequest<MockImageCtx> MockDetachChildRequest; + typedef mirror::snapshot::RemoveImageStateRequest<MockImageCtx> MockRemoveImageStateRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + r = snap_protect(*ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_snapshot_trash_add(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + return; + } + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("snapshot_trash_add"), + _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_snapshot_get(MockImageCtx &mock_image_ctx, + const cls::rbd::SnapshotInfo& snap_info, int r) { + if (mock_image_ctx.old_format) { + return; + } + + using ceph::encode; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("snapshot_get"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([snap_info, r](bufferlist* bl) { + encode(snap_info, *bl); + return r; + }))); + } + + void expect_children_list(MockImageCtx &mock_image_ctx, + const cls::rbd::ChildImageSpecs& child_images, int r) { + if (mock_image_ctx.old_format) { + return; + } + + using ceph::encode; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("children_list"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([child_images, r](bufferlist* bl) { + encode(child_images, *bl); + return r; + }))); + } + + void expect_detach_stale_child(MockImageCtx &mock_image_ctx, int r) { + auto& parent_spec = mock_image_ctx.parent_md.spec; + + bufferlist bl; + encode(parent_spec.snap_id, bl); + encode(cls::rbd::ChildImageSpec{mock_image_ctx.md_ctx.get_id(), "", + mock_image_ctx.id}, bl); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(util::header_name(parent_spec.image_id), + _, StrEq("rbd"), StrEq("child_detach"), ContentsEqual(bl), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_object_map_snap_remove(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, snapshot_remove(_, _)) + .WillOnce(WithArg<1>(CompleteContext( + r, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_remove_image_state( + MockImageCtx &mock_image_ctx, + MockRemoveImageStateRequest &mock_remove_image_state_request, int r) { + EXPECT_CALL(mock_remove_image_state_request, send()) + .WillOnce(FinishRequest(&mock_remove_image_state_request, r, + &mock_image_ctx)); + } + + void expect_get_parent_spec(MockImageCtx &mock_image_ctx, int r) { + if (mock_image_ctx.old_format) { + return; + } + + auto &expect = EXPECT_CALL(mock_image_ctx, get_parent_spec(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + auto &parent_spec = mock_image_ctx.snap_info.rbegin()->second.parent.spec; + expect.WillOnce(DoAll(SetArgPointee<1>(parent_spec), + Return(0))); + } + } + + void expect_detach_child(MockImageCtx &mock_image_ctx, + MockDetachChildRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + 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, _, StrEq("rbd"), + StrEq(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.data_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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SuccessCloneParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, 0); + expect_get_parent_spec(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SuccessTrash) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, + {cls::rbd::TrashSnapshotNamespace{ + cls::rbd::SNAPSHOT_NAMESPACE_TYPE_USER, "snap1"}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, FlattenedCloneRemovesChild) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + REQUIRE(!is_feature_enabled(RBD_FEATURE_DEEP_FLATTEN)) + + 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, snap_create(*ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, flatten(*ictx, prog_ctx)); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(mock_image_ctx, mock_detach_child_request, -ENOENT); + + expect_object_map_snap_remove(mock_image_ctx, 0); + + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, TrashCloneParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + NoOpProgressContext prog_ctx; + ASSERT_EQ(0, ictx->operations->snap_create( + {cls::rbd::TrashSnapshotNamespace{}}, "snap1", 0, prog_ctx)); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, 0); + expect_get_parent_spec(mock_image_ctx, 0); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::TrashSnapshotNamespace{}, "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EBUSY, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, MirrorSnapshot) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + cls::rbd::MirrorSnapshotNamespace ns{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "mirror uuid", 123}; + expect_snapshot_get(mock_image_ctx, + {snap_id, {ns}, "mirror", 456, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + MockRemoveImageStateRequest mock_remove_image_state_request; + expect_remove_image_state(mock_image_ctx, mock_remove_image_state_request, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, ns, "mirror", snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, MirrorSnapshotOrphan) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + cls::rbd::MirrorSnapshotNamespace ns{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "", CEPH_NOSNAP}; + expect_snapshot_get(mock_image_ctx, + {snap_id, {ns}, "mirror", 456, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + MockRemoveImageStateRequest mock_remove_image_state_request; + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, ns, "mirror", snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotTrashAddNotSupported) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, -EOPNOTSUPP); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, 0); + expect_rm_snap(mock_image_ctx); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotTrashAddError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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_snapshot_trash_add(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, SnapshotGetError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, -EOPNOTSUPP); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EOPNOTSUPP, 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + + expect_object_map_snap_remove(mock_image_ctx, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, RemoveChildParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock 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)) { + GTEST_SKIP() << "Skipping due to enabled deep-flatten"; + } + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + + librbd::NoOpProgressContext prog_ctx; + ASSERT_EQ(0, flatten(*ictx, prog_ctx)); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_get_parent_spec(mock_image_ctx, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(mock_image_ctx, mock_detach_child_request, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + expect_get_parent_spec(mock_image_ctx, 0); + expect_object_map_snap_remove(mock_image_ctx, 0); + expect_release_snap_id(mock_image_ctx); + expect_snap_remove(mock_image_ctx, -ENOENT); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, MissingSnap) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + uint64_t snap_id = 456; + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, ListChildrenError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationSnapshotRemoveRequest, DetachStaleChildError) { + 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + MockImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + + ::testing::InSequence seq; + expect_snapshot_trash_add(mock_image_ctx, 0); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_snapshot_get(mock_image_ctx, + {snap_id, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + const cls::rbd::ChildImageSpecs child_images; + expect_children_list(mock_image_ctx, child_images, -EINVAL); + + C_SaferCond cond_ctx; + MockSnapshotRemoveRequest *req = new MockSnapshotRemoveRequest( + mock_image_ctx, &cond_ctx, cls::rbd::UserSnapshotNamespace(), "snap1", + snap_id); + { + std::shared_lock 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_SnapshotRollbackRequest.cc b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc new file mode 100644 index 000000000..65eac7a6d --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,367 @@ +// -*- 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/librbd/mock/io/MockObjectDispatch.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 { + +struct MockOperationImageCtx : public MockImageCtx { + MockOperationImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace operation { + +template <> +struct ResizeRequest<MockOperationImageCtx> { + static ResizeRequest *s_instance; + Context *on_finish = nullptr; + + static ResizeRequest* create(MockOperationImageCtx &image_ctx, Context *on_finish, + uint64_t new_size, bool allow_shrink, + ProgressContext &prog_ctx, uint64_t journal_op_tid, + bool disable_journal) { + ceph_assert(s_instance != nullptr); + ceph_assert(journal_op_tid == 0); + ceph_assert(disable_journal); + s_instance->on_finish = on_finish; + return s_instance; + } + + ResizeRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ResizeRequest<MockOperationImageCtx> *ResizeRequest<MockOperationImageCtx>::s_instance = nullptr; + +} // namespace operation + +template <> +struct AsyncRequest<MockOperationImageCtx> : public AsyncRequest<MockImageCtx> { + MockOperationImageCtx &m_image_ctx; + + AsyncRequest(MockOperationImageCtx &image_ctx, Context *on_finish) + : AsyncRequest<MockImageCtx>(image_ctx, on_finish), m_image_ctx(image_ctx) { + } +}; + +} // namespace librbd + +// template definitions +#include "librbd/AsyncRequest.cc" +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/Request.cc" +#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<MockOperationImageCtx> MockSnapshotRollbackRequest; + typedef ResizeRequest<MockOperationImageCtx> MockResizeRequest; + + void expect_block_writes(MockOperationImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, block_writes(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_unblock_writes(MockOperationImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, unblock_writes()) + .Times(1); + } + + void expect_get_image_size(MockOperationImageCtx &mock_image_ctx, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(CEPH_NOSNAP)) + .WillOnce(Return(size)); + } + + void expect_resize(MockOperationImageCtx &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_get_flags(MockOperationImageCtx &mock_image_ctx, + uint64_t snap_id, int r) { + EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _)) + .WillOnce(Return(r)); + } + + void expect_object_may_exist(MockOperationImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_snap_object_map(MockOperationImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map, uint64_t snap_id) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, create_object_map(snap_id)) + .WillOnce(Return(mock_object_map)); + EXPECT_CALL(*mock_object_map, open(_)) + .WillOnce(CompleteContext(0, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_rollback_object_map(MockOperationImageCtx &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(MockOperationImageCtx &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(MockOperationImageCtx &mock_image_ctx, uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_current_size()) + .WillOnce(Return(size)); + } + + void expect_rollback_snap_id(MockOperationImageCtx &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(MockOperationImageCtx &mock_image_ctx, int r) { + expect_get_current_size(mock_image_ctx, 1); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0); + expect_rollback_snap_id(mock_image_ctx, "object-name-0", r); + } + + void expect_create_object_map(MockOperationImageCtx &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(MockOperationImageCtx &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_refresh_object_map(MockOperationImageCtx &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(MockOperationImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, invalidate_cache(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + int when_snap_rollback(MockOperationImageCtx &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, cls::rbd::UserSnapshotNamespace(), snap_name, + snap_id, snap_size, prog_ctx); + { + std::shared_lock 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)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + MockObjectMap mock_snap_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, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, &mock_snap_object_map, 123); + 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)); + + MockOperationImageCtx 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, false, 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)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + MockObjectMap mock_snap_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, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_get_image_size(mock_image_ctx, 345); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, &mock_snap_object_map, 123); + 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)); + + MockOperationImageCtx 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, false, 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)); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + MockObjectMap mock_snap_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, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, &mock_snap_object_map, 123); + 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)); + REQUIRE(ictx->cache); + + MockOperationImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + MockObjectMap mock_snap_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, false, 0); + expect_block_writes(mock_image_ctx, 0); + expect_resize(mock_image_ctx, mock_resize_request, 0); + expect_get_flags(mock_image_ctx, 123, 0); + expect_get_snap_object_map(mock_image_ctx, &mock_snap_object_map, 123); + 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/operation/test_mock_SnapshotUnprotectRequest.cc b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc new file mode 100644 index 000000000..26b1be206 --- /dev/null +++ b/src/test/librbd/operation/test_mock_SnapshotUnprotectRequest.cc @@ -0,0 +1,277 @@ +// -*- 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/ImageState.h" +#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::StrEq; +using ::testing::WithArg; + +class TestMockOperationSnapshotUnprotectRequest : public TestMockFixture { +public: + typedef SnapshotUnprotectRequest<MockImageCtx> 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, _, StrEq("rbd"), + StrEq("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<std::pair<int64_t, std::string> > 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<std::string> children; + encode(children, bl); + + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), + StrEq("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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + 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, cls::rbd::UserSnapshotNamespace(), "snap1"); + { + std::shared_lock 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/operation/test_mock_TrimRequest.cc b/src/test/librbd/operation/test_mock_TrimRequest.cc new file mode 100644 index 000000000..1771e7413 --- /dev/null +++ b/src/test/librbd/operation/test_mock_TrimRequest.cc @@ -0,0 +1,493 @@ +// -*- 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/librbd/mock/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/AsyncRequest.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Utils.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <boost/variant.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template<> +struct AsyncRequest<librbd::MockTestImageCtx> { + librbd::MockTestImageCtx& m_image_ctx; + Context *on_finish; + + AsyncRequest(librbd::MockTestImageCtx& image_ctx, Context* on_finish) + : m_image_ctx(image_ctx), on_finish(on_finish) { + } + virtual ~AsyncRequest() { + } + + Context* create_callback_context() { + return util::create_context_callback(this); + } + + Context* create_async_callback_context() { + return util::create_context_callback<AsyncRequest, + &AsyncRequest::async_complete>(this); + } + + void complete(int r) { + if (should_complete(r)) { + async_complete(r); + } + } + + void async_complete(int r) { + on_finish->complete(r); + delete this; + } + + bool is_canceled() const { + return false; + } + + virtual void send() = 0; + virtual bool should_complete(int r) = 0; +}; + +namespace io { + +struct DiscardVisitor + : public boost::static_visitor<ObjectDispatchSpec::DiscardRequest*> { + ObjectDispatchSpec::DiscardRequest* + operator()(ObjectDispatchSpec::DiscardRequest& discard) const { + return &discard; + } + + template <typename T> + ObjectDispatchSpec::DiscardRequest* + operator()(T& t) const { + return nullptr; + } +}; + +} // namespace io +} // namespace librbd + +// template definitions +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/TrimRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationTrimRequest : public TestMockFixture { +public: + typedef TrimRequest<MockTestImageCtx> MockTrimRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + r = snap_protect(*ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_is_lock_owner(MockTestImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_object_map_update(MockTestImageCtx &mock_image_ctx, + uint64_t start_object, uint64_t end_object, + uint8_t state, uint8_t current_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + boost::optional<uint8_t>(current_state), _, false, _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx, + uint64_t overlap) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(CEPH_NOSNAP, _)) + .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) { + *o = overlap; + return 0; + }))); + } + + void expect_reduce_parent_overlap(MockTestImageCtx& mock_image_ctx, + uint64_t overlap) { + EXPECT_CALL(mock_image_ctx, reduce_parent_overlap(overlap, false)) + .WillOnce(Return(std::make_pair(overlap, io::ImageArea::DATA))); + } + + void expect_get_area_size(MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_area_size(io::ImageArea::CRYPTO_HEADER)) + .WillOnce(Return(0)); + } + + void expect_object_may_exist(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_object_name(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, const std::string& oid) { + EXPECT_CALL(mock_image_ctx, get_object_name(object_no)) + .WillOnce(Return(oid)); + } + + void expect_aio_remove(MockTestImageCtx &mock_image_ctx, + const std::string& oid, int ret_val) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), remove(oid, _)) + .WillOnce(Return(ret_val)); + } + + void expect_object_discard(MockImageCtx &mock_image_ctx, + io::MockObjectDispatch& mock_io_object_dispatch, + uint64_t offset, uint64_t length, + bool update_object_map, int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, offset, length, update_object_map, r] + (io::ObjectDispatchSpec* spec) { + auto discard = boost::apply_visitor(io::DiscardVisitor(), spec->request); + ASSERT_TRUE(discard != nullptr); + ASSERT_EQ(offset, discard->object_off); + ASSERT_EQ(length, discard->object_len); + int flags = 0; + if (!update_object_map) { + flags = io::OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE; + } + ASSERT_EQ(flags, discard->discard_flags); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + mock_image_ctx.op_work_queue->queue(&spec->dispatcher_ctx, r); + })); + } +}; + +TEST_F(TestMockOperationTrimRequest, SuccessRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, 0); + expect_reduce_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessCopyUp) { + 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, snap_create(*ictx, "snap")); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_reduce_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 1, true); + expect_get_object_name(mock_image_ctx, 1, "object1"); + expect_aio_remove(mock_image_ctx, "object1", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessBoundary) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessNoOp) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx 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); +} + +TEST_F(TestMockOperationTrimRequest, RemoveError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, 0); + expect_reduce_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", -EPERM); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EPERM, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, CopyUpError) { + 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, snap_create(*ictx, "snap")); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_reduce_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, BoundaryError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx 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); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd |