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