diff options
Diffstat (limited to 'src/test/librbd/image')
-rw-r--r-- | src/test/librbd/image/test_mock_AttachChildRequest.cc | 275 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_AttachParentRequest.cc | 155 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_CloneRequest.cc | 960 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_DetachChildRequest.cc | 454 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_DetachParentRequest.cc | 135 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_ListWatchersRequest.cc | 212 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_PreRemoveRequest.cc | 465 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_RefreshRequest.cc | 1757 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_RemoveRequest.cc | 480 | ||||
-rw-r--r-- | src/test/librbd/image/test_mock_ValidatePoolRequest.cc | 223 |
10 files changed, 5116 insertions, 0 deletions
diff --git a/src/test/librbd/image/test_mock_AttachChildRequest.cc b/src/test/librbd/image/test_mock_AttachChildRequest.cc new file mode 100644 index 000000000..66d594eb0 --- /dev/null +++ b/src/test/librbd/image/test_mock_AttachChildRequest.cc @@ -0,0 +1,275 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/image/AttachChildRequest.h" +#include "librbd/image/RefreshRequest.h" +#include "librbd/internal.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct RefreshRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static RefreshRequest* s_instance; + static RefreshRequest* create(MockTestImageCtx &image_ctx, + bool acquiring_lock, bool skip_open_parent, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RefreshRequest() { + s_instance = this; + } +}; + +RefreshRequest<MockTestImageCtx>* RefreshRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +} // namespace librbd + +// template definitions +#include "librbd/image/AttachChildRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageAttachChildRequest : public TestMockFixture { +public: + typedef AttachChildRequest<MockTestImageCtx> MockAttachChildRequest; + typedef RefreshRequest<MockTestImageCtx> MockRefreshRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + NoOpProgressContext prog_ctx; + ASSERT_EQ(0, image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace{}, "snap", 0, prog_ctx)); + if (is_feature_enabled(RBD_FEATURE_LAYERING)) { + ASSERT_EQ(0, image_ctx->operations->snap_protect( + cls::rbd::UserSnapshotNamespace{}, "snap")); + + uint64_t snap_id = image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace{}, "snap"}]; + ASSERT_NE(CEPH_NOSNAP, snap_id); + + C_SaferCond ctx; + image_ctx->state->snap_set(snap_id, &ctx); + ASSERT_EQ(0, ctx.wait()); + } + } + + void expect_add_child(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("add_child"), _, _, _, + _)) + .WillOnce(Return(r)); + } + + void expect_refresh(MockRefreshRequest& mock_refresh_request, int r) { + EXPECT_CALL(mock_refresh_request, send()) + .WillOnce(Invoke([this, &mock_refresh_request, r]() { + image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r); + })); + } + + void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected, + int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _)) + .WillOnce(WithArg<1>(Invoke([is_protected, r](bool* is_prot) { + *is_prot = is_protected; + return r; + }))); + } + + void expect_op_features_set(MockImageCtx &mock_image_ctx, int r) { + bufferlist bl; + encode(static_cast<uint64_t>(RBD_OPERATION_FEATURE_CLONE_CHILD), bl); + encode(static_cast<uint64_t>(RBD_OPERATION_FEATURE_CLONE_CHILD), bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(util::header_name(mock_image_ctx.id), _, StrEq("rbd"), + StrEq("op_features_set"), ContentsEqual(bl), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_child_attach(MockImageCtx &mock_image_ctx, int r) { + bufferlist bl; + encode(mock_image_ctx.snap_id, bl); + encode(cls::rbd::ChildImageSpec{m_ioctx.get_id(), "", mock_image_ctx.id}, + bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("child_attach"), ContentsEqual(bl), _, _, _)) + .WillOnce(Return(r)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageAttachChildRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, 0); + expect_is_snap_protected(mock_image_ctx, true, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, 0); + expect_child_attach(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, AddChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, RefreshError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, ValidateProtectedFailed) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, 0); + expect_is_snap_protected(mock_image_ctx, false, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, SetCloneError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, AttachChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, 0); + expect_child_attach(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_AttachParentRequest.cc b/src/test/librbd/image/test_mock_AttachParentRequest.cc new file mode 100644 index 000000000..de5f64431 --- /dev/null +++ b/src/test/librbd/image/test_mock_AttachParentRequest.cc @@ -0,0 +1,155 @@ +// -*- 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/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/image/AttachParentRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/AttachParentRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImageAttachParentRequest : public TestMockFixture { +public: + typedef AttachParentRequest<MockTestImageCtx> MockAttachParentRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_parent_attach(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_attach"), _, _, _, _)) + .WillOnce(Return(r)); + } + + void expect_set_parent(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("set_parent"), _, _, _, _)) + .WillOnce(Return(r)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageAttachParentRequest, ParentAttachSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, 0); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "ns", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, SetParentSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + expect_set_parent(mock_image_ctx, 0); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, ParentAttachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EPERM); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, SetParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + expect_set_parent(mock_image_ctx, -EINVAL); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachParentRequest, NamespaceUnsupported) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_attach(mock_image_ctx, -EOPNOTSUPP); + + cls::rbd::ParentImageSpec parent_image_spec{ + 1, "ns", "image id", 123}; + + C_SaferCond ctx; + auto req = MockAttachParentRequest::create(mock_image_ctx, parent_image_spec, + 234, false, &ctx); + req->send(); + ASSERT_EQ(-EXDEV, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_CloneRequest.cc b/src/test/librbd/image/test_mock_CloneRequest.cc new file mode 100644 index 000000000..25de66dab --- /dev/null +++ b/src/test/librbd/image/test_mock_CloneRequest.cc @@ -0,0 +1,960 @@ +// -*- 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/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/deep_copy/MetadataCopyRequest.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/AttachChildRequest.h" +#include "librbd/image/AttachParentRequest.h" +#include "librbd/image/CreateRequest.h" +#include "librbd/image/RemoveRequest.h" +#include "librbd/mirror/EnableRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + librados::snap_t snap_id, IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +namespace deep_copy { + +template <> +struct MetadataCopyRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + + static MetadataCopyRequest* s_instance; + static MetadataCopyRequest* create(MockTestImageCtx* src_image_ctx, + MockTestImageCtx* dst_image_ctx, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MetadataCopyRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +MetadataCopyRequest<MockTestImageCtx>* MetadataCopyRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace deep_copy + +namespace image { + +template <> +struct AttachChildRequest<MockTestImageCtx> { + uint32_t clone_format; + Context* on_finish = nullptr; + static AttachChildRequest* s_instance; + static AttachChildRequest* create(MockTestImageCtx *, + MockTestImageCtx *, + const librados::snap_t &, + MockTestImageCtx *, + const librados::snap_t &, + uint32_t clone_format, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->clone_format = clone_format; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + AttachChildRequest() { + s_instance = this; + } +}; + +AttachChildRequest<MockTestImageCtx>* AttachChildRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct AttachParentRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static AttachParentRequest* s_instance; + static AttachParentRequest* create(MockTestImageCtx&, + const cls::rbd::ParentImageSpec& pspec, + uint64_t parent_overlap, bool reattach, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + AttachParentRequest() { + s_instance = this; + } +}; + +AttachParentRequest<MockTestImageCtx>* AttachParentRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct CreateRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static CreateRequest* s_instance; + static CreateRequest* create(const ConfigProxy& config, IoCtx &ioctx, + const std::string &image_name, + const std::string &image_id, uint64_t size, + const ImageOptions &image_options, + bool skip_mirror_enable, + cls::rbd::MirrorImageMode mode, + const std::string &non_primary_global_image_id, + const std::string &primary_mirror_uuid, + asio::ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + CreateRequest() { + s_instance = this; + } +}; + +CreateRequest<MockTestImageCtx>* CreateRequest<MockTestImageCtx>::s_instance = nullptr; + +template <> +struct RemoveRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static RemoveRequest* s_instance; + static RemoveRequest* create(librados::IoCtx &ioctx, + const std::string &image_name, + const std::string &image_id, + bool force, bool from_trash_remove, + ProgressContext &prog_ctx, + asio::ContextWQ *op_work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RemoveRequest() { + s_instance = this; + } +}; + +RemoveRequest<MockTestImageCtx>* RemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +namespace mirror { + +template <> +struct EnableRequest<MockTestImageCtx> { + Context* on_finish = nullptr; + static EnableRequest* s_instance; + static EnableRequest* create(MockTestImageCtx* image_ctx, + cls::rbd::MirrorImageMode mode, + const std::string &non_primary_global_image_id, + bool image_clean, Context *on_finish) { + ceph_assert(s_instance != nullptr); + EXPECT_TRUE(image_clean); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + EnableRequest() { + s_instance = this; + } +}; + +EnableRequest<MockTestImageCtx>* EnableRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/image/CloneRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageCloneRequest : public TestMockFixture { +public: + typedef CloneRequest<MockTestImageCtx> MockCloneRequest; + typedef AttachChildRequest<MockTestImageCtx> MockAttachChildRequest; + typedef AttachParentRequest<MockTestImageCtx> MockAttachParentRequest; + typedef CreateRequest<MockTestImageCtx> MockCreateRequest; + typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest; + typedef deep_copy::MetadataCopyRequest<MockTestImageCtx> MockMetadataCopyRequest; + typedef mirror::EnableRequest<MockTestImageCtx> MockMirrorEnableRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "2")); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + NoOpProgressContext prog_ctx; + ASSERT_EQ(0, image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace{}, "snap", 0, prog_ctx)); + if (is_feature_enabled(RBD_FEATURE_LAYERING)) { + ASSERT_EQ(0, image_ctx->operations->snap_protect( + cls::rbd::UserSnapshotNamespace{}, "snap")); + + uint64_t snap_id = image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace{}, "snap"}]; + ASSERT_NE(CEPH_NOSNAP, snap_id); + + C_SaferCond ctx; + image_ctx->state->snap_set(snap_id, &ctx); + ASSERT_EQ(0, ctx.wait()); + } + } + + void expect_get_min_compat_client(int8_t min_compat_client, int r) { + auto mock_rados_client = get_mock_io_ctx(m_ioctx).get_mock_rados_client(); + EXPECT_CALL(*mock_rados_client, get_min_compatible_client(_, _)) + .WillOnce(Invoke([min_compat_client, r](int8_t* min, int8_t* required_min) { + *min = min_compat_client; + *required_min = min_compat_client; + return r; + })); + } + + void expect_get_image_size(MockTestImageCtx &mock_image_ctx, uint64_t snap_id, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(snap_id)) + .WillOnce(Return(size)); + } + + void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected, + int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _)) + .WillOnce(WithArg<1>(Invoke([is_protected, r](bool* is_prot) { + *is_prot = is_protected; + return r; + }))); + } + + void expect_create(MockCreateRequest& mock_create_request, int r) { + EXPECT_CALL(mock_create_request, send()) + .WillOnce(Invoke([this, &mock_create_request, r]() { + image_ctx->op_work_queue->queue(mock_create_request.on_finish, r); + })); + } + + void expect_open(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + }))); + } + + void expect_attach_parent(MockAttachParentRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([this, &mock_request, r]() { + image_ctx->op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_attach_child(MockAttachChildRequest& mock_request, + uint32_t clone_format, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([this, &mock_request, clone_format, r]() { + EXPECT_EQ(mock_request.clone_format, clone_format); + image_ctx->op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_metadata_copy(MockMetadataCopyRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([this, &mock_request, r]() { + image_ctx->op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_test_features(MockTestImageCtx &mock_image_ctx, + uint64_t features, bool enabled) { + EXPECT_CALL(mock_image_ctx, test_features(features)) + .WillOnce(Return(enabled)); + } + + void expect_mirror_mode_get(MockTestImageCtx &mock_image_ctx, + cls::rbd::MirrorMode mirror_mode, int r) { + bufferlist out_bl; + encode(static_cast<uint32_t>(mirror_mode), out_bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_mode_get"), + _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([out_bl, r](bufferlist* out) { + *out = out_bl; + return r; + }))); + } + + void expect_mirror_enable(MockMirrorEnableRequest& mock_mirror_enable_request, + int r) { + EXPECT_CALL(mock_mirror_enable_request, send()) + .WillOnce(Invoke([this, &mock_mirror_enable_request, r]() { + image_ctx->op_work_queue->queue(mock_mirror_enable_request.on_finish, r); + })); + } + + void expect_close(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + })); + } + + void expect_remove(MockRemoveRequest& mock_remove_request, int r) { + EXPECT_CALL(mock_remove_request, send()) + .WillOnce(Invoke([this, &mock_remove_request, r]() { + image_ctx->op_work_queue->queue(mock_remove_request.on_finish, r); + })); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageCloneRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 1, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + expect_mirror_enable(mock_mirror_enable_request, 0); + } else { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + } + + expect_close(mock_image_ctx, 0); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "2")); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + expect_mirror_enable(mock_mirror_enable_request, 0); + } else { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + } + + expect_close(mock_image_ctx, 0); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, SuccessAuto) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "auto")); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + expect_mirror_enable(mock_mirror_enable_request, 0); + } else { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + } + + expect_close(mock_image_ctx, 0); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, OpenParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CreateError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, OpenError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, AttachParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, AttachChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, MetadataCopyError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, GetMirrorModeError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, MirrorEnableError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + expect_mirror_enable(mock_mirror_enable_request, -EINVAL); + + expect_close(mock_image_ctx, 0); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CloseError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + + expect_close(mock_image_ctx, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, RemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, -EPERM); + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, CloseParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, -EINVAL); + + MockRemoveRequest mock_remove_request; + expect_remove(mock_remove_request, 0); + + expect_close(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, SnapshotMirrorEnableNonPrimary) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_open(mock_image_ctx, 0); + + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + + MockAttachParentRequest mock_attach_parent_request; + expect_attach_parent(mock_attach_parent_request, 0); + + MockAttachChildRequest mock_attach_child_request; + expect_attach_child(mock_attach_child_request, 2, 0); + + MockMetadataCopyRequest mock_request; + expect_metadata_copy(mock_request, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + expect_mirror_enable(mock_mirror_enable_request, 0); + + expect_close(mock_image_ctx, 0); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(m_cct->_conf, m_ioctx, "parent id", "", {}, 123, + m_ioctx, "clone name", "clone id", clone_opts, + cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, + "global image id", "primary mirror uuid", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_DetachChildRequest.cc b/src/test/librbd/image/test_mock_DetachChildRequest.cc new file mode 100644 index 000000000..6812125cb --- /dev/null +++ b/src/test/librbd/image/test_mock_DetachChildRequest.cc @@ -0,0 +1,454 @@ +// -*- 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/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/DetachChildRequest.h" +#include "librbd/trash/RemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +namespace image { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +} // namespace image + +namespace trash { + +template <> +class RemoveRequest<MockTestImageCtx> { +private: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; +public: + static RemoveRequest *s_instance; + static RemoveRequest *create(librados::IoCtx &ioctx, + MockTestImageCtx *image_ctx, + ContextWQ *op_work_queue, bool force, + ProgressContext &prog_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockTestImageCtx> *RemoveRequest<MockTestImageCtx>::s_instance; + +} // namespace trash +} // namespace librbd + +// template definitions +#include "librbd/image/DetachChildRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageDetachChildRequest : public TestMockFixture { +public: + typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest; + typedef trash::RemoveRequest<MockTestImageCtx> MockTrashRemoveRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_test_op_features(MockTestImageCtx& mock_image_ctx, bool enabled) { + EXPECT_CALL(mock_image_ctx, + test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD)) + .WillOnce(Return(enabled)); + } + + void expect_create_ioctx(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl **io_ctx_impl) { + *io_ctx_impl = &get_mock_io_ctx(mock_image_ctx.md_ctx); + auto rados_client = (*io_ctx_impl)->get_mock_rados_client(); + + EXPECT_CALL(*rados_client, create_ioctx(_, _)) + .WillOnce(DoAll(GetReference(*io_ctx_impl), Return(*io_ctx_impl))); + } + + void expect_child_detach(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx_impl, + 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(mock_io_ctx_impl, + exec(util::header_name(parent_spec.image_id), + _, StrEq("rbd"), StrEq("child_detach"), ContentsEqual(bl), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_child(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("remove_child"), _, + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_snapshot_get(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx_impl, + const std::string& parent_header_name, + const cls::rbd::SnapshotInfo& snap_info, int r) { + + using ceph::encode; + EXPECT_CALL(mock_io_ctx_impl, + exec(parent_header_name, _, StrEq("rbd"), + StrEq("snapshot_get"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([snap_info, r](bufferlist* bl) { + encode(snap_info, *bl); + return r; + }))); + } + + void expect_open(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, &mock_image_ctx, r](Context* ctx) { + EXPECT_EQ(0U, mock_image_ctx.read_only_mask & + IMAGE_READ_ONLY_FLAG_NON_PRIMARY); + image_ctx->op_work_queue->queue(ctx, r); + }))); + if (r == 0) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillOnce(Return(false)); + } + } + + void expect_close(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + image_ctx->op_work_queue->queue(ctx, r); + })); + } + + void expect_snap_remove(MockImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, + snap_remove({cls::rbd::TrashSnapshotNamespace{}}, + StrEq(snap_name), _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context *ctx) { + image_ctx->op_work_queue->queue(ctx, r); + }))); + } + + void expect_trash_get(MockImageCtx &mock_image_ctx, + librados::MockTestMemIoCtxImpl &mock_io_ctx_impl, + const cls::rbd::TrashImageSpec& trash_spec, + int r) { + using ceph::encode; + EXPECT_CALL(mock_io_ctx_impl, + exec(RBD_TRASH, _, StrEq("rbd"), + StrEq("trash_get"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([trash_spec, r](bufferlist* bl) { + encode(trash_spec, *bl); + return r; + }))); + } + + void expect_trash_remove(MockTrashRemoveRequest& mock_trash_remove_request, + int r) { + EXPECT_CALL(mock_trash_remove_request, send()) + .WillOnce(Invoke([&mock_trash_remove_request, r]() { + mock_trash_remove_request.on_finish->complete(r); + })); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageDetachChildRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, false); + expect_remove_child(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::UserSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, 0); + expect_snap_remove(mock_image_ctx, "snap1", 0); + const cls::rbd::TrashImageSpec trash_spec; + expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, -ENOENT); + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, ParentAutoRemove) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, 0); + expect_snap_remove(mock_image_ctx, "snap1", 0); + const cls::rbd::TrashImageSpec trash_spec = + {cls::rbd::TRASH_IMAGE_SOURCE_USER_PARENT, "parent", {}, {}}; + + expect_trash_get(mock_image_ctx, *mock_io_ctx_impl, trash_spec, 0); + MockTrashRemoveRequest mock_trash_remove_request; + expect_trash_remove(mock_trash_remove_request, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotInUse) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 1}, 0); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotSnapshotGetError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotOpenParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, TrashedSnapshotRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, 0); + expect_snapshot_get(mock_image_ctx, *mock_io_ctx_impl, + "rbd_header.parent id", + {234, {cls::rbd::TrashSnapshotNamespace{}}, + "snap1", 123, {}, 0}, 0); + expect_open(mock_image_ctx, 0); + expect_snap_remove(mock_image_ctx, "snap1", -EPERM); + expect_close(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, ParentDNE) { + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, ChildDetachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, true); + + librados::MockTestMemIoCtxImpl *mock_io_ctx_impl; + expect_create_ioctx(mock_image_ctx, &mock_io_ctx_impl); + expect_child_detach(mock_image_ctx, *mock_io_ctx_impl, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDetachChildRequest, RemoveChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + mock_image_ctx.parent_md.spec = {m_ioctx.get_id(), "", "parent id", 234}; + + InSequence seq; + expect_test_op_features(mock_image_ctx, false); + expect_remove_child(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachChildRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_DetachParentRequest.cc b/src/test/librbd/image/test_mock_DetachParentRequest.cc new file mode 100644 index 000000000..4d9f012f8 --- /dev/null +++ b/src/test/librbd/image/test_mock_DetachParentRequest.cc @@ -0,0 +1,135 @@ +// -*- 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/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/image/DetachParentRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/DetachParentRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImageDetachParentRequest : public TestMockFixture { +public: + typedef DetachParentRequest<MockTestImageCtx> MockDetachParentRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_parent_detach(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("parent_detach"), _, _, _, _)) + .WillOnce(Return(r)); + } + + void expect_remove_parent(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("remove_parent"), _, _, _, _)) + .WillOnce(Return(r)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageDetachParentRequest, ParentDetachSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, RemoveParentSuccess) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EOPNOTSUPP); + expect_remove_parent(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, ParentDNE) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -ENOENT); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, ParentDetachError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDetachParentRequest, RemoveParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + expect_parent_detach(mock_image_ctx, -EOPNOTSUPP); + expect_remove_parent(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockDetachParentRequest::create(mock_image_ctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_ListWatchersRequest.cc b/src/test/librbd/image/test_mock_ListWatchersRequest.cc new file mode 100644 index 000000000..d90fc4ab0 --- /dev/null +++ b/src/test/librbd/image/test_mock_ListWatchersRequest.cc @@ -0,0 +1,212 @@ +// -*- 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 "librbd/image/ListWatchersRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockContextWQ.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/test_support.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/ListWatchersRequest.cc" +template class librbd::image::ListWatchersRequest<librbd::MockImageCtx>; + +namespace librbd { + +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class TestMockListWatchersRequest : public TestMockFixture { +public: + typedef ListWatchersRequest<MockImageCtx> MockListWatchersRequest; + + obj_watch_t watcher(const std::string &address, uint64_t watch_handle) { + obj_watch_t w; + strcpy(w.addr, address.c_str()); + w.watcher_id = 0; + w.cookie = watch_handle; + w.timeout_seconds = 0; + + return w; + } + + void expect_list_watchers(MockTestImageCtx &mock_image_ctx, + const std::string oid, + const std::list<obj_watch_t> &watchers, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + list_watchers(oid, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoAll(SetArgPointee<1>(watchers), Return(0))); + } + } + + void expect_list_image_watchers(MockTestImageCtx &mock_image_ctx, + const std::list<obj_watch_t> &watchers, + int r) { + expect_list_watchers(mock_image_ctx, mock_image_ctx.header_oid, + watchers, r); + } + + void expect_list_mirror_watchers(MockTestImageCtx &mock_image_ctx, + const std::list<obj_watch_t> &watchers, + int r) { + expect_list_watchers(mock_image_ctx, RBD_MIRRORING, watchers, r); + } + + void expect_get_watch_handle(MockImageWatcher &mock_watcher, + uint64_t watch_handle) { + EXPECT_CALL(mock_watcher, get_watch_handle()) + .WillOnce(Return(watch_handle)); + } +}; + +TEST_F(TestMockListWatchersRequest, NoImageWatchers) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, {}, 0); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(watchers.empty()); +} + +TEST_F(TestMockListWatchersRequest, Error) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, {}, -EINVAL); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockListWatchersRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create(mock_image_ctx, 0, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(2U, watchers.size()); + + auto w = watchers.begin(); + ASSERT_STREQ("a", w->addr); + ASSERT_EQ(123U, w->cookie); + + w++; + ASSERT_STREQ("b", w->addr); + ASSERT_EQ(456U, w->cookie); +} + +TEST_F(TestMockListWatchersRequest, FilterOutMyInstance) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create( + mock_image_ctx, LIST_WATCHERS_FILTER_OUT_MY_INSTANCE, &watchers, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(1U, watchers.size()); + + ASSERT_STREQ("b", watchers.begin()->addr); + ASSERT_EQ(456U, watchers.begin()->cookie); +} + +TEST_F(TestMockListWatchersRequest, FilterOutMirrorInstance) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockImageWatcher mock_watcher; + + InSequence seq; + expect_list_image_watchers(mock_image_ctx, + {watcher("a", 123), watcher("b", 456)}, 0); + expect_list_mirror_watchers(mock_image_ctx, {watcher("b", 789)}, 0); + expect_get_watch_handle(*mock_image_ctx.image_watcher, 123); + + std::list<obj_watch_t> watchers; + C_SaferCond ctx; + auto req = MockListWatchersRequest::create( + mock_image_ctx, LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES, &watchers, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(1U, watchers.size()); + + ASSERT_STREQ("a", watchers.begin()->addr); + ASSERT_EQ(123U, watchers.begin()->cookie); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_PreRemoveRequest.cc b/src/test/librbd/image/test_mock_PreRemoveRequest.cc new file mode 100644 index 000000000..faae4f201 --- /dev/null +++ b/src/test/librbd/image/test_mock_PreRemoveRequest.cc @@ -0,0 +1,465 @@ +// -*- 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 "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/image/ListWatchersRequest.h" +#include "librbd/image/PreRemoveRequest.h" +#include "librbd/image/RefreshParentRequest.h" +#include "librbd/operation/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> +#include <boost/scope_exit.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace operation { + +template <> +class SnapshotRemoveRequest<MockTestImageCtx> { +public: + static SnapshotRemoveRequest *s_instance; + static SnapshotRemoveRequest *create(MockTestImageCtx &image_ctx, + cls::rbd::SnapshotNamespace sn, + std::string name, + uint64_t id, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + SnapshotRemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +SnapshotRemoveRequest<MockTestImageCtx> *SnapshotRemoveRequest<MockTestImageCtx>::s_instance; + +} // namespace operation + +namespace image { + +template<> +class ListWatchersRequest<MockTestImageCtx> { +public: + static ListWatchersRequest *s_instance; + Context *on_finish = nullptr; + + static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags, + std::list<obj_watch_t> *watchers, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + ListWatchersRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ListWatchersRequest<MockTestImageCtx> *ListWatchersRequest<MockTestImageCtx>::s_instance; + +} // namespace image +} // namespace librbd + +// template definitions +#include "librbd/exclusive_lock/StandardPolicy.cc" +#include "librbd/image/PreRemoveRequest.cc" + +ACTION_P(TestFeatures, image_ctx) { + return ((image_ctx->features & arg0) != 0); +} + +ACTION_P(ShutDownExclusiveLock, image_ctx) { + // shutting down exclusive lock will close object map and journal + image_ctx->exclusive_lock = nullptr; + image_ctx->object_map = nullptr; + image_ctx->journal = nullptr; +} + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockImagePreRemoveRequest : public TestMockFixture { +public: + typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest; + typedef ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest; + typedef librbd::operation::SnapshotRemoveRequest<MockTestImageCtx> MockSnapshotRemoveRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_test_imctx)); + m_mock_imctx = new MockTestImageCtx(*m_test_imctx); + } + + void TearDown() override { + delete m_mock_imctx; + TestMockFixture::TearDown(); + } + + void expect_test_features(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_)) + .WillRepeatedly(TestFeatures(&mock_image_ctx)); + } + + void expect_set_exclusive_lock_policy(MockTestImageCtx& mock_image_ctx) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_image_ctx, set_exclusive_lock_policy(_)) + .WillOnce(Invoke([](exclusive_lock::Policy* policy) { + ASSERT_FALSE(policy->may_auto_request_lock()); + delete policy; + })); + } + } + + void expect_set_journal_policy(MockTestImageCtx &mock_image_ctx) { + if (m_test_imctx->test_features(RBD_FEATURE_JOURNALING)) { + EXPECT_CALL(mock_image_ctx, set_journal_policy(_)) + .WillOnce(Invoke([](journal::Policy* policy) { + ASSERT_TRUE(policy->journal_disabled()); + delete policy; + })); + } + } + + void expect_acquire_exclusive_lock(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, acquire_lock(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + } + + void expect_shut_down_exclusive_lock(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, shut_down(_)) + .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx), + CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + } + + void expect_is_exclusive_lock_owner(MockTestImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + bool is_owner) { + if (m_mock_imctx->exclusive_lock != nullptr) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillOnce(Return(is_owner)); + } + } + + void expect_list_image_watchers( + MockTestImageCtx &mock_image_ctx, + MockListWatchersRequest &mock_list_watchers_request, int r) { + EXPECT_CALL(mock_list_watchers_request, send()) + .WillOnce(FinishRequest(&mock_list_watchers_request, r, &mock_image_ctx)); + } + + void expect_get_group(MockTestImageCtx &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("image_group_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove_snap(MockTestImageCtx &mock_image_ctx, + MockSnapshotRemoveRequest& mock_snap_remove_request, + int r) { + EXPECT_CALL(mock_snap_remove_request, send()) + .WillOnce(FinishRequest(&mock_snap_remove_request, r, &mock_image_ctx)); + } + + librbd::ImageCtx *m_test_imctx = nullptr; + MockTestImageCtx *m_mock_imctx = nullptr; +}; + +TEST_F(TestMockImagePreRemoveRequest, Success) { + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, OperationsDisabled) { + REQUIRE_FORMAT_V2(); + + m_mock_imctx->operations_disabled = true; + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EROFS, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockTryAcquireFailed) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, + -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockTryAcquireNotLockOwner) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, false); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Force) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, + -EINVAL); + expect_shut_down_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockShutDownFailed) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + MockExclusiveLock mock_exclusive_lock; + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, -EINVAL); + expect_shut_down_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Migration) { + m_mock_imctx->features |= RBD_FEATURE_MIGRATING; + + expect_test_features(*m_mock_imctx); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Snapshots) { + m_mock_imctx->snap_info = { + {123, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}}; + + expect_test_features(*m_mock_imctx); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-ENOTEMPTY, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, Watchers) { + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, + -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, GroupError) { + REQUIRE_FORMAT_V2(); + + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImagePreRemoveRequest, AutoDeleteSnapshots) { + REQUIRE_FORMAT_V2(); + + MockExclusiveLock mock_exclusive_lock; + if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + m_mock_imctx->exclusive_lock = &mock_exclusive_lock; + } + + expect_op_work_queue(*m_mock_imctx); + expect_test_features(*m_mock_imctx); + + m_mock_imctx->snap_info = { + {123, {"snap1", {cls::rbd::TrashSnapshotNamespace{}}, {}, {}, {}, {}, {}}}}; + + InSequence seq; + expect_set_exclusive_lock_policy(*m_mock_imctx); + expect_set_journal_policy(*m_mock_imctx); + expect_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0); + expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true); + + MockListWatchersRequest mock_list_watchers_request; + expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0); + + expect_get_group(*m_mock_imctx, 0); + + MockSnapshotRemoveRequest mock_snap_remove_request; + expect_remove_snap(*m_mock_imctx, mock_snap_remove_request, 0); + + C_SaferCond ctx; + auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_RefreshRequest.cc b/src/test/librbd/image/test_mock_RefreshRequest.cc new file mode 100644 index 000000000..e60409615 --- /dev/null +++ b/src/test/librbd/image/test_mock_RefreshRequest.cc @@ -0,0 +1,1757 @@ +// -*- 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/MockImageWatcher.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockJournalPolicy.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/image/GetMetadataRequest.h" +#include "librbd/image/RefreshRequest.h" +#include "librbd/image/RefreshParentRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <queue> +#include <boost/scope_exit.hpp> + +namespace librbd { + +namespace { + +struct MockRefreshImageCtx : public MockImageCtx { + MockRefreshImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct GetMetadataRequest<MockRefreshImageCtx> { + std::string oid; + std::map<std::string, bufferlist>* pairs = nullptr; + Context* on_finish = nullptr; + + static GetMetadataRequest* s_instance; + static GetMetadataRequest* create(librados::IoCtx&, + const std::string& oid, + bool filter_internal, + const std::string& filter_key_prefix, + const std::string& last_key, + uint32_t max_results, + std::map<std::string, bufferlist>* pairs, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + EXPECT_EQ("conf_", filter_key_prefix); + EXPECT_EQ("conf_", last_key); + s_instance->oid = oid; + s_instance->pairs = pairs; + s_instance->on_finish = on_finish; + return s_instance; + } + + GetMetadataRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +struct RefreshParentRequest<MockRefreshImageCtx> { + static std::queue<RefreshParentRequest*> s_instances; + static RefreshParentRequest* create(MockRefreshImageCtx &mock_image_ctx, + const ParentImageInfo &parent_md, + const MigrationInfo &migration_info, + Context *on_finish) { + ceph_assert(!s_instances.empty()); + auto instance = s_instances.front(); + instance->on_finish = on_finish; + return instance; + } + static bool is_refresh_required(MockRefreshImageCtx &mock_image_ctx, + const ParentImageInfo& parent_md, + const MigrationInfo &migration_info) { + ceph_assert(!s_instances.empty()); + return s_instances.front()->is_refresh_required(); + } + + Context *on_finish = nullptr; + + RefreshParentRequest() { + s_instances.push(this); + } + + ~RefreshParentRequest() { + ceph_assert(this == s_instances.front()); + s_instances.pop(); + } + + MOCK_CONST_METHOD0(is_refresh_required, bool()); + MOCK_METHOD0(send, void()); + MOCK_METHOD0(apply, void()); + MOCK_METHOD1(finalize, void(Context *)); +}; + +GetMetadataRequest<MockRefreshImageCtx>* GetMetadataRequest<MockRefreshImageCtx>::s_instance = nullptr; +std::queue<RefreshParentRequest<MockRefreshImageCtx>*> RefreshParentRequest<MockRefreshImageCtx>::s_instances; + +} // namespace image + +namespace util { + +inline ImageCtx *get_image_ctx(librbd::MockRefreshImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +// template definitions +#include "librbd/image/RefreshRequest.cc" + +ACTION_P(TestFeatures, image_ctx) { + return ((image_ctx->features & arg0) != 0); +} + +ACTION_P(ShutDownExclusiveLock, image_ctx) { + // shutting down exclusive lock will close object map and journal + image_ctx->exclusive_lock = nullptr; + image_ctx->object_map = nullptr; + image_ctx->journal = nullptr; +} + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::StrEq; + +class TestMockImageRefreshRequest : public TestMockFixture { +public: + typedef GetMetadataRequest<MockRefreshImageCtx> MockGetMetadataRequest; + typedef RefreshRequest<MockRefreshImageCtx> MockRefreshRequest; + typedef RefreshParentRequest<MockRefreshImageCtx> MockRefreshParentRequest; + typedef std::map<std::string, bufferlist> Metadata; + + void set_v1_migration_header(ImageCtx *ictx) { + bufferlist hdr; + ASSERT_EQ(0, read_header_bl(ictx->md_ctx, ictx->header_oid, hdr, nullptr)); + ASSERT_TRUE(hdr.length() >= sizeof(rbd_obj_header_ondisk)); + ASSERT_EQ(0, memcmp(RBD_HEADER_TEXT, hdr.c_str(), sizeof(RBD_HEADER_TEXT))); + + bufferlist::iterator it = hdr.begin(); + it.copy_in(sizeof(RBD_MIGRATE_HEADER_TEXT), RBD_MIGRATE_HEADER_TEXT); + ASSERT_EQ(0, ictx->md_ctx.write(ictx->header_oid, hdr, hdr.length(), 0)); + } + + void expect_set_require_lock(MockExclusiveLock &mock_exclusive_lock, + librbd::io::Direction direction) { + EXPECT_CALL(mock_exclusive_lock, set_require_lock(true, direction, _)) + .WillOnce(WithArg<2>(Invoke([](Context* ctx) { ctx->complete(0); }))); + } + + void expect_unset_require_lock(MockExclusiveLock &mock_exclusive_lock, + librbd::io::Direction direction) { + EXPECT_CALL(mock_exclusive_lock, unset_require_lock(direction)); + } + + void expect_v1_read_header(MockRefreshImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + read(mock_image_ctx.header_oid, _, _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_v1_get_snapshots(MockRefreshImageCtx &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("snap_list"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_v1_get_locks(MockRefreshImageCtx &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("lock"), + StrEq("get_info"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_mutable_metadata(MockRefreshImageCtx &mock_image_ctx, + uint64_t features, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_size"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + uint64_t incompatible = ( + mock_image_ctx.read_only ? features & RBD_FEATURES_INCOMPATIBLE : + features & RBD_FEATURES_RW_INCOMPATIBLE); + + expect.WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_features"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([features, incompatible](bufferlist* out_bl) { + encode(features, *out_bl); + encode(incompatible, *out_bl); + return 0; + }))); + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_snapcontext"), _, _, _, _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("lock"), + StrEq("get_info"), _, _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_parent_overlap_get(MockRefreshImageCtx &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("parent_overlap_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_parent(MockRefreshImageCtx &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("parent_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + expect_parent_overlap_get(mock_image_ctx, 0); + } + } + + void expect_get_parent_legacy(MockRefreshImageCtx &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("get_parent"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_migration_header(MockRefreshImageCtx &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("migration_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_metadata(MockRefreshImageCtx& mock_image_ctx, + MockGetMetadataRequest& mock_request, + const std::string& oid, + const Metadata& metadata, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([&mock_image_ctx, &mock_request, oid, metadata, r]() { + ASSERT_EQ(oid, mock_request.oid); + *mock_request.pairs = metadata; + mock_image_ctx.image_ctx->op_work_queue->queue( + mock_request.on_finish, r); + })); + } + + void expect_get_flags(MockRefreshImageCtx &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("get_flags"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_op_features(MockRefreshImageCtx &mock_image_ctx, + uint64_t op_features, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("op_features_get"), _, _, _, _)) + .WillOnce(WithArg<5>(Invoke([op_features, r](bufferlist* out_bl) { + encode(op_features, *out_bl); + return r; + }))); + } + + void expect_get_group(MockRefreshImageCtx &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("image_group_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_get_snapshots(MockRefreshImageCtx &mock_image_ctx, + bool legacy_parent, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("snapshot_get"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + if (legacy_parent) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_parent"), _, _, _, _)) + .WillOnce(DoDefault()); + } else { + expect_parent_overlap_get(mock_image_ctx, 0); + } + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_protection_status"), _, _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_get_snapshots_legacy(MockRefreshImageCtx &mock_image_ctx, + bool include_timestamp, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_snapshot_name"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_size"), _, _, _, _)) + .WillOnce(DoDefault()); + if (include_timestamp) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_snapshot_timestamp"), _, _, _, _)) + .WillOnce(DoDefault()); + } + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_parent"), _, _, _, _)) + .WillOnce(DoDefault()); + expect_get_flags(mock_image_ctx, 0); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("get_protection_status"), _, _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_apply_metadata(MockRefreshImageCtx &mock_image_ctx, + int r) { + EXPECT_CALL(*mock_image_ctx.image_watcher, is_unregistered()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_image_ctx, apply_metadata(_, false)) + .WillOnce(Return(r)); + } + + void expect_add_snap(MockRefreshImageCtx &mock_image_ctx, + const std::string &snap_name, uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, add_snap(_, snap_name, snap_id, _, _, _, _, _)); + } + + void expect_init_exclusive_lock(MockRefreshImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + EXPECT_CALL(mock_image_ctx, create_exclusive_lock()) + .WillOnce(Return(&mock_exclusive_lock)); + EXPECT_CALL(mock_exclusive_lock, init(mock_image_ctx.features, _)) + .WillOnce(WithArg<1>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_shut_down_exclusive_lock(MockRefreshImageCtx &mock_image_ctx, + MockExclusiveLock &mock_exclusive_lock, + int r) { + EXPECT_CALL(mock_exclusive_lock, shut_down(_)) + .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx), + CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + void expect_init_layout(MockRefreshImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, init_layout(_)); + } + + void expect_test_features(MockRefreshImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(TestFeatures(&mock_image_ctx)); + } + + void expect_refresh_parent_is_required(MockRefreshParentRequest &mock_refresh_parent_request, + bool required) { + EXPECT_CALL(mock_refresh_parent_request, is_refresh_required()) + .WillRepeatedly(Return(required)); + } + + void expect_refresh_parent_send(MockRefreshImageCtx &mock_image_ctx, + MockRefreshParentRequest &mock_refresh_parent_request, + int r) { + EXPECT_CALL(mock_refresh_parent_request, send()) + .WillOnce(FinishRequest(&mock_refresh_parent_request, r, + &mock_image_ctx)); + } + + void expect_refresh_parent_apply(MockRefreshParentRequest &mock_refresh_parent_request) { + EXPECT_CALL(mock_refresh_parent_request, apply()); + } + + void expect_refresh_parent_finalize(MockRefreshImageCtx &mock_image_ctx, + MockRefreshParentRequest &mock_refresh_parent_request, + int r) { + EXPECT_CALL(mock_refresh_parent_request, finalize(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_is_exclusive_lock_owner(MockExclusiveLock &mock_exclusive_lock, + bool is_owner) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillOnce(Return(is_owner)); + } + + void expect_get_journal_policy(MockImageCtx &mock_image_ctx, + MockJournalPolicy &mock_journal_policy) { + EXPECT_CALL(mock_image_ctx, get_journal_policy()) + .WillOnce(Return(&mock_journal_policy)); + } + + void expect_journal_disabled(MockJournalPolicy &mock_journal_policy, + bool disabled) { + EXPECT_CALL(mock_journal_policy, journal_disabled()) + .WillOnce(Return(disabled)); + } + + void expect_open_journal(MockRefreshImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_image_ctx, create_journal()) + .WillOnce(Return(&mock_journal)); + EXPECT_CALL(mock_journal, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_journal(MockRefreshImageCtx &mock_image_ctx, + MockJournal &mock_journal, int r) { + EXPECT_CALL(mock_journal, close(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_open_object_map(MockRefreshImageCtx &mock_image_ctx, + MockObjectMap *mock_object_map, int r) { + EXPECT_CALL(mock_image_ctx, create_object_map(_)) + .WillOnce(Return(mock_object_map)); + EXPECT_CALL(*mock_object_map, open(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_close_object_map(MockRefreshImageCtx &mock_image_ctx, + MockObjectMap &mock_object_map, int r) { + EXPECT_CALL(mock_object_map, close(_)) + .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)); + } + + void expect_get_snap_id(MockRefreshImageCtx &mock_image_ctx, + const std::string &snap_name, + uint64_t snap_id) { + EXPECT_CALL(mock_image_ctx, + get_snap_id(_, snap_name)).WillOnce(Return(snap_id)); + } + + 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_image_flush(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.io_image_dispatcher, send(_)) + .WillOnce(Invoke([r](io::ImageDispatchSpec* spec) { + ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>( + &spec->request) != nullptr); + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + spec->aio_comp->set_request_count(1); + spec->aio_comp->add_request(); + spec->aio_comp->complete_request(r); + })); + } + +}; + +TEST_F(TestMockImageRefreshRequest, SuccessV1) { + REQUIRE_FORMAT_V1(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_v1_read_header(mock_image_ctx, 0); + expect_v1_get_snapshots(mock_image_ctx, 0); + expect_v1_get_locks(mock_image_ctx, 0); + expect_init_layout(mock_image_ctx); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV1) { + REQUIRE_FORMAT_V1(); + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_v1_read_header(mock_image_ctx, 0); + expect_v1_get_snapshots(mock_image_ctx, 0); + expect_v1_get_locks(mock_image_ctx, 0); + expect_init_layout(mock_image_ctx); + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, -EOPNOTSUPP); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessSnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessLegacySnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, -EOPNOTSUPP); + expect_get_parent_legacy(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, true, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessLegacySnapshotNoTimestampV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, -EOPNOTSUPP); + expect_get_parent_legacy(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, true, -EOPNOTSUPP); + expect_get_snapshots_legacy(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessSetSnapshotV2) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockObjectMap mock_object_map; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, false, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + expect_open_object_map(mock_image_ctx, &mock_object_map, 0); + } + expect_add_snap(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + expect_get_snap_id(mock_image_ctx, "snap", ictx->snap_ids.begin()->second); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SnapshotV2EnoentRetriesLimit) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockGetMetadataRequest mock_get_metadata_request; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + for (int i = 0; i < RefreshRequest<>::MAX_ENOENT_RETRIES + 1; ++i) { + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_get_snapshots(mock_image_ctx, false, -ENOENT); + } + + C_SaferCond ctx; + auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessChild) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + MockRefreshParentRequest *mock_refresh_parent_request = new MockRefreshParentRequest(); + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features &= ~RBD_FEATURE_OPERATIONS; + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(*mock_refresh_parent_request, true); + expect_refresh_parent_send(mock_image_ctx, *mock_refresh_parent_request, 0); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + expect_refresh_parent_apply(*mock_refresh_parent_request); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_refresh_parent_finalize(mock_image_ctx, *mock_refresh_parent_request, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessChildDontOpenParent) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect(cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features &= ~RBD_FEATURE_OPERATIONS; + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, true, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessChildBeingFlattened) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect( + cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + auto mock_refresh_parent_request = new MockRefreshParentRequest(); + MockRefreshParentRequest mock_refresh_parent_request_ext; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features &= ~RBD_FEATURE_OPERATIONS; + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(*mock_refresh_parent_request, true); + expect_refresh_parent_send(mock_image_ctx, *mock_refresh_parent_request, + -ENOENT); + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request_ext, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, ChildEnoentRetriesLimit) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + librbd::ImageCtx *ictx2 = nullptr; + std::string clone_name = get_temp_image_name(); + + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + ASSERT_EQ(0, snap_protect(*ictx, "snap")); + BOOST_SCOPE_EXIT_ALL((&)) { + if (ictx2 != nullptr) { + close_image(ictx2); + } + + librbd::NoOpProgressContext no_op; + ASSERT_EQ(0, librbd::api::Image<>::remove(m_ioctx, clone_name, no_op)); + ASSERT_EQ(0, ictx->operations->snap_unprotect( + cls::rbd::UserSnapshotNamespace(), "snap")); + }; + + int order = ictx->order; + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap", m_ioctx, + clone_name.c_str(), ictx->features, &order, 0, 0)); + + ASSERT_EQ(0, open_image(clone_name, &ictx2)); + + MockRefreshImageCtx mock_image_ctx(*ictx2); + constexpr int num_tries = RefreshRequest<>::MAX_ENOENT_RETRIES + 1; + MockRefreshParentRequest* mock_refresh_parent_requests[num_tries]; + for (auto& mock_refresh_parent_request : mock_refresh_parent_requests) { + mock_refresh_parent_request = new MockRefreshParentRequest(); + } + MockGetMetadataRequest mock_get_metadata_request; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features &= ~RBD_FEATURE_OPERATIONS; + + InSequence seq; + for (auto mock_refresh_parent_request : mock_refresh_parent_requests) { + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(*mock_refresh_parent_request, true); + expect_refresh_parent_send(mock_image_ctx, *mock_refresh_parent_request, + -ENOENT); + } + expect_refresh_parent_apply(*mock_refresh_parent_requests[num_tries - 1]); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_refresh_parent_finalize( + mock_image_ctx, *mock_refresh_parent_requests[num_tries - 1], 0); + + C_SaferCond ctx; + auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, SuccessOpFeatures) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + mock_image_ctx.features |= RBD_FEATURE_OPERATIONS; + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, mock_image_ctx.features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_op_features(mock_image_ctx, 4096, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(4096U, mock_image_ctx.op_features); + ASSERT_TRUE(mock_image_ctx.operations_disabled); +} + +TEST_F(TestMockImageRefreshRequest, DisableExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_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; + } + + MockJournal mock_journal; + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_EXCLUSIVE_LOCK, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify that exclusive lock is properly handled when object map + // and journaling were never enabled (or active) + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_shut_down_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableExclusiveLockWhileAcquiringLock) { + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_EXCLUSIVE_LOCK, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify that exclusive lock is properly handled when object map + // and journaling were never enabled (or active) + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, true, false, &ctx); + req->send(); + + ASSERT_EQ(-ERESTART, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, JournalDisabledByPolicy) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + MockJournalPolicy mock_journal_policy; + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, true); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableJournalWithExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockJournal mock_journal; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // journal should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + + MockJournalPolicy mock_journal_policy; + expect_get_journal_policy(mock_image_ctx, mock_journal_policy); + expect_journal_disabled(mock_journal_policy, false); + expect_open_journal(mock_image_ctx, mock_journal, 0); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableJournalWithoutExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_OBJECT_MAP, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, false); + + // do not open the journal if exclusive lock is not owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_set_require_lock(mock_exclusive_lock, librbd::io::DIRECTION_BOTH); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableJournal) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_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; + } + + MockJournal mock_journal; + mock_image_ctx.journal = &mock_journal; + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify journal is closed if feature disabled + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_block_writes(mock_image_ctx, 0); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + if (!mock_image_ctx.clone_copy_on_read) { + expect_unset_require_lock(mock_exclusive_lock, librbd::io::DIRECTION_READ); + } + expect_close_journal(mock_image_ctx, mock_journal, 0); + expect_unblock_writes(mock_image_ctx); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, &mock_object_map, 0); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, EnableObjectMapWithoutExclusiveLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, false); + + // do not open the object map if exclusive lock is not owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, DisableObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + MockJournal mock_journal; + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + mock_image_ctx.journal = &mock_journal; + } + + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_FAST_DIFF, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + // verify object map is closed if feature disabled + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + expect_close_object_map(mock_image_ctx, mock_object_map, 0); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, OpenObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, &mock_object_map, -EBLOCKLISTED); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, + &ctx); + req->send(); + + ASSERT_EQ(-EBLOCKLISTED, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +TEST_F(TestMockImageRefreshRequest, OpenObjectMapTooLarge) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + if (ictx->test_features(RBD_FEATURE_JOURNALING)) { + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + } + + ASSERT_EQ(0, ictx->state->refresh()); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + MockObjectMap mock_object_map; + + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + expect_is_exclusive_lock_owner(mock_exclusive_lock, true); + + // object map should be immediately opened if exclusive lock owned + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + expect_open_object_map(mock_image_ctx, &mock_object_map, -EFBIG); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(nullptr, mock_image_ctx.object_map); +} + +TEST_F(TestMockImageRefreshRequest, ApplyMetadataError) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, -EINVAL); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRefreshRequest, NonPrimaryFeature) { + REQUIRE_FORMAT_V2(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockRefreshImageCtx mock_image_ctx(*ictx); + MockRefreshParentRequest mock_refresh_parent_request; + MockExclusiveLock mock_exclusive_lock; + expect_op_work_queue(mock_image_ctx); + expect_test_features(mock_image_ctx); + + InSequence seq; + + // ensure the image is put into read-only mode + expect_get_mutable_metadata(mock_image_ctx, + ictx->features | RBD_FEATURE_NON_PRIMARY, 0); + expect_get_parent(mock_image_ctx, 0); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx1; + auto req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx1); + req->send(); + + ASSERT_EQ(0, ctx1.wait()); + ASSERT_TRUE(mock_image_ctx.read_only); + ASSERT_EQ(IMAGE_READ_ONLY_FLAG_NON_PRIMARY, mock_image_ctx.read_only_flags); + + // try again but permit R/W against non-primary image + mock_image_ctx.read_only_mask = ~IMAGE_READ_ONLY_FLAG_NON_PRIMARY; + + expect_get_mutable_metadata(mock_image_ctx, + ictx->features | RBD_FEATURE_NON_PRIMARY, 0); + expect_get_parent(mock_image_ctx, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, + mock_image_ctx.header_oid, {}, 0); + expect_get_metadata(mock_image_ctx, mock_get_metadata_request, RBD_INFO, {}, + 0); + expect_apply_metadata(mock_image_ctx, 0); + expect_get_group(mock_image_ctx, 0); + expect_refresh_parent_is_required(mock_refresh_parent_request, false); + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0); + } + EXPECT_CALL(mock_image_ctx, rebuild_data_io_context()); + + C_SaferCond ctx2; + req = new MockRefreshRequest(mock_image_ctx, false, false, &ctx2); + req->send(); + + ASSERT_EQ(0, ctx2.wait()); + ASSERT_FALSE(mock_image_ctx.read_only); + ASSERT_EQ(0U, mock_image_ctx.read_only_flags); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_RemoveRequest.cc b/src/test/librbd/image/test_mock_RemoveRequest.cc new file mode 100644 index 000000000..9700202d6 --- /dev/null +++ b/src/test/librbd/image/test_mock_RemoveRequest.cc @@ -0,0 +1,480 @@ +// -*- 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/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/image/TypeTraits.h" +#include "librbd/image/DetachChildRequest.h" +#include "librbd/image/PreRemoveRequest.h" +#include "librbd/image/RemoveRequest.h" +#include "librbd/journal/RemoveRequest.h" +#include "librbd/journal/TypeTraits.h" +#include "librbd/mirror/DisableRequest.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <arpa/inet.h> +#include <list> +#include <boost/scope_exit.hpp> + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + static MockTestImageCtx* s_instance; + static MockTestImageCtx* create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx* MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +template<> +struct Journal<MockTestImageCtx> { + static void get_work_queue(CephContext*, MockContextWQ**) { + } +}; + +namespace image { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +template <> +class DetachChildRequest<MockTestImageCtx> { +public: + static DetachChildRequest *s_instance; + static DetachChildRequest *create(MockTestImageCtx &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<MockTestImageCtx> *DetachChildRequest<MockTestImageCtx>::s_instance; + +template <> +class PreRemoveRequest<MockTestImageCtx> { +public: + static PreRemoveRequest *s_instance; + static PreRemoveRequest *create(MockTestImageCtx* image_ctx, bool force, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + PreRemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +PreRemoveRequest<MockTestImageCtx> *PreRemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace image + +namespace journal { + +template <> +struct TypeTraits<MockTestImageCtx> { + typedef librbd::MockContextWQ ContextWQ; +}; + +} // namespace journal + +namespace operation { + +template <> +class TrimRequest<MockTestImageCtx> { +public: + static TrimRequest *s_instance; + static TrimRequest *create(MockTestImageCtx &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<MockTestImageCtx> *TrimRequest<MockTestImageCtx>::s_instance; + +} // namespace operation + +namespace journal { + +template <> +class RemoveRequest<MockTestImageCtx> { +private: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; +public: + static RemoveRequest *s_instance; + static RemoveRequest *create(IoCtx &ioctx, const std::string &imageid, + const std::string &client_id, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + RemoveRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +RemoveRequest<MockTestImageCtx> *RemoveRequest<MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal + +namespace mirror { + +template<> +class DisableRequest<MockTestImageCtx> { +public: + static DisableRequest *s_instance; + Context *on_finish = nullptr; + + static DisableRequest *create(MockTestImageCtx *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<MockTestImageCtx> *DisableRequest<MockTestImageCtx>::s_instance; + +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/image/RemoveRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::WithArg; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class TestMockImageRemoveRequest : public TestMockFixture { +public: + typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits; + typedef typename TypeTraits::ContextWQ ContextWQ; + typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest; + typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest; + typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest; + typedef librbd::operation::TrimRequest<MockTestImageCtx> MockTrimRequest; + typedef librbd::journal::RemoveRequest<MockTestImageCtx> MockJournalRemoveRequest; + typedef librbd::mirror::DisableRequest<MockTestImageCtx> MockMirrorDisableRequest; + + librbd::ImageCtx *m_test_imctx = NULL; + MockTestImageCtx *m_mock_imctx = NULL; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &m_test_imctx)); + m_mock_imctx = new MockTestImageCtx(*m_test_imctx); + librbd::MockTestImageCtx::s_instance = m_mock_imctx; + } + void TearDown() override { + librbd::MockTestImageCtx::s_instance = NULL; + delete m_mock_imctx; + TestMockFixture::TearDown(); + } + + void expect_state_open(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(_, _)) + .WillOnce(Invoke([r](bool open_parent, Context *on_ready) { + on_ready->complete(r); + })); + } + + void expect_state_close(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([](Context *on_ready) { + on_ready->complete(0); + })); + } + + void expect_wq_queue(ContextWQ &wq, int r) { + EXPECT_CALL(wq, queue(_, r)) + .WillRepeatedly(Invoke([](Context *on_ready, int r) { + on_ready->complete(r); + })); + } + + void expect_pre_remove_image(MockTestImageCtx &mock_image_ctx, + MockPreRemoveRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } + + void expect_trim(MockTestImageCtx &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_journal_remove(MockTestImageCtx &mock_image_ctx, + MockJournalRemoveRequest &mock_journal_remove_request, int r) { + EXPECT_CALL(mock_journal_remove_request, send()) + .WillOnce(FinishRequest(&mock_journal_remove_request, r, &mock_image_ctx)); + } + + void expect_mirror_disable(MockTestImageCtx &mock_image_ctx, + MockMirrorDisableRequest &mock_mirror_disable_request, int r) { + EXPECT_CALL(mock_mirror_disable_request, send()) + .WillOnce(FinishRequest(&mock_mirror_disable_request, r, &mock_image_ctx)); + } + + void expect_remove_mirror_image(librados::IoCtx &ioctx, int r) { + EXPECT_CALL(get_mock_io_ctx(ioctx), + exec(StrEq("rbd_mirroring"), _, StrEq("rbd"), + StrEq("mirror_image_remove"), _, _, _, _)) + .WillOnce(Return(r)); + } + + void expect_dir_remove_image(librados::IoCtx &ioctx, int r) { + EXPECT_CALL(get_mock_io_ctx(ioctx), + exec(RBD_DIRECTORY, _, StrEq("rbd"), StrEq("dir_remove_image"), + _, _, _, _)) + .WillOnce(Return(r)); + } + + void expect_detach_child(MockTestImageCtx &mock_image_ctx, + MockDetachChildRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx)); + } +}; + +TEST_F(TestMockImageRemoveRequest, SuccessV1) { + REQUIRE_FORMAT_V1(); + expect_op_work_queue(*m_mock_imctx); + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + expect_state_close(*m_mock_imctx); + + ContextWQ op_work_queue; + expect_wq_queue(op_work_queue, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + MockRemoveRequest *req = MockRemoveRequest::create(m_ioctx, m_image_name, "", + true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, OpenFailV1) { + REQUIRE_FORMAT_V1(); + + InSequence seq; + expect_state_open(*m_mock_imctx, -ENOENT); + + ContextWQ op_work_queue; + expect_wq_queue(op_work_queue, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + MockRemoveRequest *req = MockRemoveRequest::create(m_ioctx, m_image_name, "", + true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, 0); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageRemoveRequest, NotExistsV2) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + expect_op_work_queue(*m_mock_imctx); + + m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id(); + m_mock_imctx->parent_md.spec.image_id = "parent id"; + m_mock_imctx->parent_md.spec.snap_id = 234; + + InSequence seq; + expect_state_open(*m_mock_imctx, 0); + + MockPreRemoveRequest mock_pre_remove_request; + expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0); + + MockTrimRequest mock_trim_request; + expect_trim(*m_mock_imctx, mock_trim_request, 0); + + MockDetachChildRequest mock_detach_child_request; + expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0); + + MockMirrorDisableRequest mock_mirror_disable_request; + expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0); + + expect_state_close(*m_mock_imctx); + + MockJournalRemoveRequest mock_journal_remove_request; + expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0); + + expect_remove_mirror_image(m_ioctx, 0); + expect_dir_remove_image(m_ioctx, -ENOENT); + + C_SaferCond ctx; + librbd::NoOpProgressContext no_op; + ContextWQ op_work_queue; + MockRemoveRequest *req = MockRemoveRequest::create( + m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +} // namespace image +} // namespace librbd diff --git a/src/test/librbd/image/test_mock_ValidatePoolRequest.cc b/src/test/librbd/image/test_mock_ValidatePoolRequest.cc new file mode 100644 index 000000000..f5204ac20 --- /dev/null +++ b/src/test/librbd/image/test_mock_ValidatePoolRequest.cc @@ -0,0 +1,223 @@ +// -*- 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 "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "librbd/image/ValidatePoolRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageValidatePoolRequest : public TestMockFixture { +public: + typedef ValidatePoolRequest<MockTestImageCtx> MockValidatePoolRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + m_ioctx.remove(RBD_INFO); + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + } + + void expect_clone(librados::MockTestMemIoCtxImpl &mock_io_ctx) { + EXPECT_CALL(mock_io_ctx, clone()) + .WillOnce(Invoke([&mock_io_ctx]() { + mock_io_ctx.get(); + return &mock_io_ctx; + })); + } + + void expect_read_rbd_info(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const std::string& data, int r) { + auto& expect = EXPECT_CALL( + mock_io_ctx, read(StrEq(RBD_INFO), 0, 0, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(WithArg<3>(Invoke([data](bufferlist* bl) { + bl->append(data); + return 0; + }))); + } + } + + void expect_write_rbd_info(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const std::string& data, int r) { + bufferlist bl; + bl.append(data); + + EXPECT_CALL(mock_io_ctx, write(StrEq(RBD_INFO), ContentsEqual(bl), + data.length(), 0, _)) + .WillOnce(Return(r)); + } + + void expect_allocate_snap_id(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, selfmanaged_snap_create(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_release_snap_id(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + auto &expect = EXPECT_CALL(mock_io_ctx, selfmanaged_snap_remove(_)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageValidatePoolRequest, Success) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, AlreadyValidated) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, SnapshotsValidated) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "validate", 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, ReadError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -EPERM); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, CreateSnapshotError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", 0); + expect_allocate_snap_id(mock_io_ctx, -EPERM); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, WriteError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", -EPERM); + expect_release_snap_id(mock_io_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, RemoveSnapshotError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, -EPERM); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", 0); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageValidatePoolRequest, OverwriteError) { + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_ioctx)); + + InSequence seq; + expect_clone(mock_io_ctx); + expect_read_rbd_info(mock_io_ctx, "", -ENOENT); + expect_allocate_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "validate", 0); + expect_release_snap_id(mock_io_ctx, 0); + expect_write_rbd_info(mock_io_ctx, "overwrite validated", -EOPNOTSUPP); + + C_SaferCond ctx; + auto req = new MockValidatePoolRequest(m_ioctx, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd |