summaryrefslogtreecommitdiffstats
path: root/src/test/librbd/image
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/librbd/image')
-rw-r--r--src/test/librbd/image/test_mock_AttachChildRequest.cc275
-rw-r--r--src/test/librbd/image/test_mock_AttachParentRequest.cc155
-rw-r--r--src/test/librbd/image/test_mock_CloneRequest.cc960
-rw-r--r--src/test/librbd/image/test_mock_DetachChildRequest.cc454
-rw-r--r--src/test/librbd/image/test_mock_DetachParentRequest.cc135
-rw-r--r--src/test/librbd/image/test_mock_ListWatchersRequest.cc212
-rw-r--r--src/test/librbd/image/test_mock_PreRemoveRequest.cc465
-rw-r--r--src/test/librbd/image/test_mock_RefreshRequest.cc1757
-rw-r--r--src/test/librbd/image/test_mock_RemoveRequest.cc480
-rw-r--r--src/test/librbd/image/test_mock_ValidatePoolRequest.cc223
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