summaryrefslogtreecommitdiffstats
path: root/src/test/librbd/io
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
commit483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch)
treee5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/test/librbd/io
parentInitial commit. (diff)
downloadceph-upstream.tar.xz
ceph-upstream.zip
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/librbd/io')
-rw-r--r--src/test/librbd/io/test_mock_CopyupRequest.cc1068
-rw-r--r--src/test/librbd/io/test_mock_ImageRequest.cc468
-rw-r--r--src/test/librbd/io/test_mock_ImageRequestWQ.cc465
-rw-r--r--src/test/librbd/io/test_mock_ObjectRequest.cc1394
4 files changed, 3395 insertions, 0 deletions
diff --git a/src/test/librbd/io/test_mock_CopyupRequest.cc b/src/test/librbd/io/test_mock_CopyupRequest.cc
new file mode 100644
index 00000000..7a30f59a
--- /dev/null
+++ b/src/test/librbd/io/test_mock_CopyupRequest.cc
@@ -0,0 +1,1068 @@
+// -*- 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/MockExclusiveLock.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockJournal.h"
+#include "test/librbd/mock/MockObjectMap.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/deep_copy/ObjectCopyRequest.h"
+#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/io/ImageRequestWQ.h"
+#include "librbd/io/ObjectRequest.h"
+#include "librbd/io/ReadResult.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx,
+ MockTestImageCtx* mock_parent_image_ctx = nullptr)
+ : MockImageCtx(image_ctx) {
+ parent = mock_parent_image_ctx;
+ }
+
+ std::map<uint64_t, librbd::io::CopyupRequest<librbd::MockTestImageCtx>*> copyup_list;
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+
+namespace deep_copy {
+
+template <>
+struct ObjectCopyRequest<librbd::MockTestImageCtx> {
+ static ObjectCopyRequest* s_instance;
+ static ObjectCopyRequest* create(librbd::MockImageCtx* parent_image_ctx,
+ librbd::MockTestImageCtx* image_ctx,
+ librados::snap_t src_snap_id_start,
+ librados::snap_t dst_snap_id_start,
+ const SnapMap &snap_map,
+ uint64_t object_number, bool flatten,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->object_number = object_number;
+ s_instance->flatten = flatten;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ uint64_t object_number;
+ bool flatten;
+ Context *on_finish;
+
+ ObjectCopyRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+
+namespace io {
+
+template <>
+struct ObjectRequest<librbd::MockTestImageCtx> {
+ static void add_write_hint(librbd::MockTestImageCtx&,
+ librados::ObjectWriteOperation*) {
+ }
+};
+
+template <>
+struct AbstractObjectWriteRequest<librbd::MockTestImageCtx> {
+ C_SaferCond ctx;
+ void handle_copyup(int r) {
+ ctx.complete(r);
+ }
+
+ MOCK_CONST_METHOD0(get_pre_write_object_map_state, uint8_t());
+ MOCK_CONST_METHOD0(is_empty_write_op, bool());
+
+ MOCK_METHOD1(add_copyup_ops, void(librados::ObjectWriteOperation*));
+};
+
+template <>
+struct ImageRequest<librbd::MockTestImageCtx> {
+ static ImageRequest *s_instance;
+ static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c,
+ Extents &&image_extents, ReadResult &&read_result,
+ int op_flags, const ZTracer::Trace &parent_trace) {
+ s_instance->aio_read(c, image_extents, &read_result);
+ }
+
+ MOCK_METHOD3(aio_read, void(AioCompletion *, const Extents&, ReadResult*));
+
+ ImageRequest() {
+ s_instance = this;
+ }
+};
+
+ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace io
+} // namespace librbd
+
+static bool operator==(const SnapContext& rhs, const SnapContext& lhs) {
+ return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps);
+}
+
+#include "librbd/AsyncObjectThrottle.cc"
+#include "librbd/io/CopyupRequest.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+using ::testing::WithoutArgs;
+
+struct TestMockIoCopyupRequest : public TestMockFixture {
+ typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
+ typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest;
+ typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+ typedef deep_copy::ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+ if (!is_feature_enabled(RBD_FEATURE_LAYERING)) {
+ return;
+ }
+
+ m_parent_image_name = m_image_name;
+ m_image_name = get_temp_image_name();
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_parent_image_name.c_str(),
+ nullptr));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ image.close();
+
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_parent_image_name.c_str(), "one", m_ioctx,
+ m_image_name.c_str(), features, &order));
+ }
+
+ void expect_get_parent_overlap(MockTestImageCtx& mock_image_ctx,
+ librados::snap_t snap_id, uint64_t overlap,
+ int r) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _))
+ .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) {
+ *o = overlap;
+ return r;
+ })));
+ }
+ }
+
+ void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx,
+ uint64_t overlap, uint64_t object_overlap) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+ .WillOnce(WithoutArgs(Invoke([object_overlap]() {
+ return object_overlap;
+ })));
+ }
+ }
+
+ void expect_read_parent(MockTestImageCtx& mock_image_ctx,
+ MockImageRequest& mock_image_request,
+ const Extents& image_extents,
+ const std::string& data, int r) {
+ EXPECT_CALL(mock_image_request, aio_read(_, image_extents, _))
+ .WillOnce(WithArgs<0, 2>(Invoke(
+ [&mock_image_ctx, image_extents, data, r](
+ AioCompletion* aio_comp, ReadResult* read_result) {
+ aio_comp->read_result = std::move(*read_result);
+ aio_comp->set_request_count(1);
+ auto ctx = new ReadResult::C_ImageReadRequest(aio_comp,
+ image_extents);
+ ctx->bl.append(data);
+ mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r);
+ })));
+ }
+
+ void expect_copyup(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const std::string& oid, const std::string& data, int r) {
+ bufferlist in_bl;
+ in_bl.append(data);
+
+ SnapContext snapc;
+ if (snap_id == CEPH_NOSNAP) {
+ snapc = mock_image_ctx.snapc;
+ }
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ exec(oid, _, StrEq("rbd"), StrEq("copyup"),
+ ContentsEqual(in_bl), _, snapc))
+ .WillOnce(Return(r));
+ }
+
+ void expect_write(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const std::string& oid, int r) {
+ SnapContext snapc;
+ if (snap_id == CEPH_NOSNAP) {
+ snapc = mock_image_ctx.snapc;
+ }
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write(oid, _, 0, 0, snapc))
+ .WillOnce(Return(r));
+ }
+
+ void expect_test_features(MockTestImageCtx& mock_image_ctx) {
+ EXPECT_CALL(mock_image_ctx, test_features(_, _))
+ .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) {
+ return (mock_image_ctx.features & features) != 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_is_empty_write_op(MockAbstractObjectWriteRequest& mock_write_request,
+ bool is_empty) {
+ EXPECT_CALL(mock_write_request, is_empty_write_op())
+ .WillOnce(Return(is_empty));
+ }
+
+ void expect_add_copyup_ops(MockAbstractObjectWriteRequest& mock_write_request) {
+ EXPECT_CALL(mock_write_request, add_copyup_ops(_))
+ .WillOnce(Invoke([](librados::ObjectWriteOperation* op) {
+ op->write(0, bufferlist{});
+ }));
+ }
+
+ void expect_get_pre_write_object_map_state(MockTestImageCtx& mock_image_ctx,
+ MockAbstractObjectWriteRequest& mock_write_request,
+ uint8_t state) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_write_request, get_pre_write_object_map_state())
+ .WillOnce(Return(state));
+ }
+ }
+
+ void expect_object_map_at(MockTestImageCtx& mock_image_ctx,
+ uint64_t object_no, uint8_t state) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(*mock_image_ctx.object_map, at(object_no))
+ .WillOnce(Return(state));
+ }
+ }
+
+ void expect_object_map_update(MockTestImageCtx& mock_image_ctx,
+ uint64_t snap_id, uint64_t object_no,
+ uint8_t state, bool updated, int ret_val) {
+ if (mock_image_ctx.object_map != nullptr) {
+ if (!mock_image_ctx.image_ctx->test_features(RBD_FEATURE_FAST_DIFF) &&
+ state == OBJECT_EXISTS_CLEAN) {
+ state = OBJECT_EXISTS;
+ }
+
+ EXPECT_CALL(*mock_image_ctx.object_map,
+ aio_update(snap_id, object_no, object_no + 1, state,
+ boost::optional<uint8_t>(), _,
+ (snap_id != CEPH_NOSNAP), _))
+ .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_object_copy(MockTestImageCtx& mock_image_ctx,
+ MockObjectCopyRequest& mock_object_copy_request,
+ bool flatten, int r) {
+ EXPECT_CALL(mock_object_copy_request, send())
+ .WillOnce(Invoke(
+ [&mock_image_ctx, &mock_object_copy_request, flatten, r]() {
+ ASSERT_EQ(flatten, mock_object_copy_request.flatten);
+ mock_image_ctx.op_work_queue->queue(
+ mock_object_copy_request.on_finish, r);
+ }));
+ }
+
+ void flush_async_operations(librbd::ImageCtx* ictx) {
+ ictx->io_work_queue->flush();
+ }
+
+ std::string m_parent_image_name;
+};
+
+TEST_F(TestMockIoCopyupRequest, Standard) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, StandardWithSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {2, {2, 1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS_CLEAN, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, 0, "oid", data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ flush_async_operations(ictx);
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyOnReadWithSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS_CLEAN,
+ true, 0);
+
+ expect_copyup(mock_image_ctx, 0, "oid", data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ flush_async_operations(ictx);
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopy) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size,
+ false};
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ flush_async_operations(ictx);
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyWithPostSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {3, {3, 2, 1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id",
+ {{CEPH_NOSNAP, {2, 1}}},
+ ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_parent_overlap(mock_image_ctx, 1, 0, 0);
+ expect_get_parent_overlap(mock_image_ctx, 2, 1, 0);
+ expect_prune_parent_extents(mock_image_ctx, 1, 1);
+ expect_get_parent_overlap(mock_image_ctx, 3, 1, 0);
+ expect_prune_parent_extents(mock_image_ctx, 1, 1);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS_CLEAN, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyWithPreAndPostSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "4", 4, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {4, {4, 3, 2, 1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id",
+ {{CEPH_NOSNAP, {2, 1}}, {10, {1}}},
+ ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_parent_overlap(mock_image_ctx, 2, 0, 0);
+ expect_get_parent_overlap(mock_image_ctx, 3, 1, 0);
+ expect_prune_parent_extents(mock_image_ctx, 1, 1);
+ expect_get_parent_overlap(mock_image_ctx, 4, 1, 0);
+ expect_prune_parent_extents(mock_image_ctx, 1, 1);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS_CLEAN, true, 0);
+ expect_object_map_update(mock_image_ctx, 4, 0, OBJECT_EXISTS_CLEAN, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ZeroedCopyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ 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;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ZeroedCopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '\0');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ flush_async_operations(ictx);
+}
+
+TEST_F(TestMockIoCopyupRequest, NoOpCopyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ "", -ENOENT);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, true);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, RestartWrite) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request1;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request1,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ expect_add_copyup_ops(mock_write_request1);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request2;
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write("oid", _, 0, 0, _))
+ .WillOnce(WithoutArgs(Invoke([req, &mock_write_request2]() {
+ req->append_request(&mock_write_request2);
+ return 0;
+ })));
+
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request1);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request1.ctx.wait());
+ ASSERT_EQ(-ERESTART, mock_write_request2.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ReadFromParentError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ "", -EPERM);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, false);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, -EPERM);
+
+ expect_is_empty_write_op(mock_write_request, false);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, UpdateObjectMapError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ -EINVAL);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyupError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ 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_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, 0, "oid", data, -EPERM);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+ flush_async_operations(ictx);
+}
+
+} // namespace io
+} // namespace librbd
diff --git a/src/test/librbd/io/test_mock_ImageRequest.cc b/src/test/librbd/io/test_mock_ImageRequest.cc
new file mode 100644
index 00000000..03405802
--- /dev/null
+++ b/src/test/librbd/io/test_mock_ImageRequest.cc
@@ -0,0 +1,468 @@
+// -*- 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 "test/librbd/mock/cache/MockImageCache.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx;
+
+struct MockTestJournal : public MockJournal {
+ MOCK_METHOD4(append_write_event, uint64_t(uint64_t, size_t,
+ const bufferlist &, bool));
+ MOCK_METHOD5(append_io_event_mock, uint64_t(const journal::EventEntry&,
+ uint64_t, size_t, bool, int));
+ uint64_t append_io_event(journal::EventEntry &&event_entry,
+ uint64_t offset, size_t length,
+ bool flush_entry, int filter_ret_val) {
+ // googlemock doesn't support move semantics
+ return append_io_event_mock(event_entry, offset, length, flush_entry,
+ filter_ret_val);
+ }
+
+ MOCK_METHOD2(commit_io_event, void(uint64_t, int));
+};
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+
+ MockTestJournal* journal;
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/io/ImageRequest.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+using ::testing::WithoutArgs;
+using ::testing::Exactly;
+
+struct TestMockIoImageRequest : public TestMockFixture {
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
+ typedef ImageReadRequest<librbd::MockTestImageCtx> MockImageReadRequest;
+ typedef ImageWriteRequest<librbd::MockTestImageCtx> MockImageWriteRequest;
+ typedef ImageDiscardRequest<librbd::MockTestImageCtx> MockImageDiscardRequest;
+ typedef ImageFlushRequest<librbd::MockTestImageCtx> MockImageFlushRequest;
+ typedef ImageWriteSameRequest<librbd::MockTestImageCtx> MockImageWriteSameRequest;
+ typedef ImageCompareAndWriteRequest<librbd::MockTestImageCtx> MockImageCompareAndWriteRequest;
+
+ void expect_is_journal_appending(MockTestJournal &mock_journal, bool appending) {
+ EXPECT_CALL(mock_journal, is_journal_appending())
+ .WillOnce(Return(appending));
+ }
+
+ void expect_get_modify_timestamp(MockTestImageCtx &mock_image_ctx,
+ bool needs_update) {
+ if (needs_update) {
+ mock_image_ctx.mtime_update_interval = 5;
+ EXPECT_CALL(mock_image_ctx, get_modify_timestamp())
+ .WillOnce(Return(ceph_clock_now() - utime_t(10,0)));
+ } else {
+ mock_image_ctx.mtime_update_interval = 600;
+ EXPECT_CALL(mock_image_ctx, get_modify_timestamp())
+ .WillOnce(Return(ceph_clock_now()));
+ }
+ }
+
+ void expect_object_discard_request(MockTestImageCtx &mock_image_ctx,
+ uint64_t object_no, uint64_t offset,
+ uint32_t length, int r) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_))
+ .WillOnce(Invoke([&mock_image_ctx, object_no, offset, length, r]
+ (ObjectDispatchSpec* spec) {
+ auto* discard_spec = boost::get<ObjectDispatchSpec::DiscardRequest>(&spec->request);
+ ASSERT_TRUE(discard_spec != nullptr);
+ ASSERT_EQ(object_no, discard_spec->object_no);
+ ASSERT_EQ(offset, discard_spec->object_off);
+ ASSERT_EQ(length, discard_spec->object_len);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ mock_image_ctx.image_ctx->op_work_queue->queue(&spec->dispatcher_ctx, r);
+ }));
+ }
+
+ void expect_object_request_send(MockTestImageCtx &mock_image_ctx,
+ int r) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_))
+ .WillOnce(Invoke([&mock_image_ctx, r](ObjectDispatchSpec* spec) {
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ mock_image_ctx.image_ctx->op_work_queue->queue(&spec->dispatcher_ctx, r);
+ }));
+ }
+};
+
+TEST_F(TestMockIoImageRequest, AioWriteModifyTimestamp) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ mock_image_ctx.mtime_update_interval = 5;
+
+ utime_t dummy = ceph_clock_now();
+ dummy -= utime_t(10,0);
+
+ EXPECT_CALL(mock_image_ctx, get_modify_timestamp())
+ .Times(Exactly(3))
+ .WillOnce(Return(dummy))
+ .WillOnce(Return(dummy))
+ .WillOnce(Return(dummy + utime_t(10,0)));
+
+ EXPECT_CALL(mock_image_ctx, set_modify_timestamp(_))
+ .Times(Exactly(1));
+
+ InSequence seq;
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx_1, aio_comp_ctx_2;
+ AioCompletion *aio_comp_1 = AioCompletion::create_and_start(
+ &aio_comp_ctx_1, ictx, AIO_TYPE_WRITE);
+
+ AioCompletion *aio_comp_2 = AioCompletion::create_and_start(
+ &aio_comp_ctx_2, ictx, AIO_TYPE_WRITE);
+
+ bufferlist bl;
+ bl.append("1");
+ MockImageWriteRequest mock_aio_image_write_1(mock_image_ctx, aio_comp_1,
+ {{0, 1}}, std::move(bl), 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_write_1.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx_1.wait());
+
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ bl.append("1");
+ MockImageWriteRequest mock_aio_image_write_2(mock_image_ctx, aio_comp_2,
+ {{0, 1}}, std::move(bl), 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_write_2.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx_2.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioReadAccessTimestamp) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ mock_image_ctx.atime_update_interval = 5;
+
+ utime_t dummy = ceph_clock_now();
+ dummy -= utime_t(10,0);
+
+ EXPECT_CALL(mock_image_ctx, get_access_timestamp())
+ .Times(Exactly(3))
+ .WillOnce(Return(dummy))
+ .WillOnce(Return(dummy))
+ .WillOnce(Return(dummy + utime_t(10,0)));
+
+ EXPECT_CALL(mock_image_ctx, set_access_timestamp(_))
+ .Times(Exactly(1));
+
+ InSequence seq;
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx_1, aio_comp_ctx_2;
+ AioCompletion *aio_comp_1 = AioCompletion::create_and_start(
+ &aio_comp_ctx_1, ictx, AIO_TYPE_READ);
+
+
+ ReadResult rr;
+ MockImageReadRequest mock_aio_image_read_1(mock_image_ctx, aio_comp_1,
+ {{0, 1}}, std::move(rr), 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_read_1.send();
+ }
+ ASSERT_EQ(1, aio_comp_ctx_1.wait());
+
+ AioCompletion *aio_comp_2 = AioCompletion::create_and_start(
+ &aio_comp_ctx_2, ictx, AIO_TYPE_READ);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ MockImageReadRequest mock_aio_image_read_2(mock_image_ctx, aio_comp_2,
+ {{0, 1}}, std::move(rr), 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_read_2.send();
+ }
+ ASSERT_EQ(1, aio_comp_ctx_2.wait());
+}
+
+TEST_F(TestMockIoImageRequest, PartialDiscard) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->discard_granularity_bytes = 0;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.journal = nullptr;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_object_discard_request(mock_image_ctx, 0, 16, 63, 0);
+ expect_object_discard_request(mock_image_ctx, 0, 84, 100, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_DISCARD);
+ MockImageDiscardRequest mock_aio_image_discard(
+ mock_image_ctx, aio_comp, {{16, 63}, {84, 100}},
+ ictx->discard_granularity_bytes, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_discard.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, TailDiscard) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, resize(ictx, ictx->layout.object_size));
+ ictx->discard_granularity_bytes = 2 * ictx->layout.object_size;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.journal = nullptr;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_object_discard_request(
+ mock_image_ctx, 0, ictx->layout.object_size - 1024, 1024, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_DISCARD);
+ MockImageDiscardRequest mock_aio_image_discard(
+ mock_image_ctx, aio_comp,
+ {{ictx->layout.object_size - 1024, 1024}},
+ ictx->discard_granularity_bytes, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_discard.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, DiscardGranularity) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ASSERT_EQ(0, resize(ictx, ictx->layout.object_size));
+ ictx->discard_granularity_bytes = 32;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.journal = nullptr;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_object_discard_request(mock_image_ctx, 0, 32, 32, 0);
+ expect_object_discard_request(mock_image_ctx, 0, 96, 64, 0);
+ expect_object_discard_request(
+ mock_image_ctx, 0, ictx->layout.object_size - 32, 32, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_DISCARD);
+ MockImageDiscardRequest mock_aio_image_discard(
+ mock_image_ctx, aio_comp,
+ {{16, 63}, {96, 31}, {84, 100}, {ictx->layout.object_size - 33, 33}},
+ ictx->discard_granularity_bytes, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_discard.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioWriteJournalAppendDisabled) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_WRITE);
+
+ bufferlist bl;
+ bl.append("1");
+ MockImageWriteRequest mock_aio_image_write(mock_image_ctx, aio_comp,
+ {{0, 1}}, std::move(bl), 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_write.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioDiscardJournalAppendDisabled) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->discard_granularity_bytes = 0;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_DISCARD);
+ MockImageDiscardRequest mock_aio_image_discard(
+ mock_image_ctx, aio_comp, {{0, 1}}, ictx->discard_granularity_bytes, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_discard.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioFlushJournalAppendDisabled) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_FLUSH);
+ MockImageFlushRequest mock_aio_image_flush(mock_image_ctx, aio_comp,
+ FLUSH_SOURCE_USER, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_flush.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioWriteSameJournalAppendDisabled) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_WRITESAME);
+
+ bufferlist bl;
+ bl.append("1");
+ MockImageWriteSameRequest mock_aio_image_writesame(mock_image_ctx, aio_comp,
+ {{0, 1}}, std::move(bl), 0,
+ {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_writesame.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+TEST_F(TestMockIoImageRequest, AioCompareAndWriteJournalAppendDisabled) {
+ REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockTestJournal mock_journal;
+ mock_image_ctx.journal = &mock_journal;
+
+ InSequence seq;
+ expect_get_modify_timestamp(mock_image_ctx, false);
+ expect_is_journal_appending(mock_journal, false);
+ expect_object_request_send(mock_image_ctx, 0);
+
+ C_SaferCond aio_comp_ctx;
+ AioCompletion *aio_comp = AioCompletion::create_and_start(
+ &aio_comp_ctx, ictx, AIO_TYPE_COMPARE_AND_WRITE);
+
+ bufferlist cmp_bl;
+ cmp_bl.append("1");
+ bufferlist write_bl;
+ write_bl.append("1");
+ uint64_t mismatch_offset;
+ MockImageCompareAndWriteRequest mock_aio_image_write(mock_image_ctx, aio_comp,
+ {{0, 1}}, std::move(cmp_bl),
+ std::move(write_bl),
+ &mismatch_offset,
+ 0, {});
+ {
+ RWLock::RLocker owner_locker(mock_image_ctx.owner_lock);
+ mock_aio_image_write.send();
+ }
+ ASSERT_EQ(0, aio_comp_ctx.wait());
+}
+
+} // namespace io
+} // namespace librbd
diff --git a/src/test/librbd/io/test_mock_ImageRequestWQ.cc b/src/test/librbd/io/test_mock_ImageRequestWQ.cc
new file mode 100644
index 00000000..7b91d68f
--- /dev/null
+++ b/src/test/librbd/io/test_mock_ImageRequestWQ.cc
@@ -0,0 +1,465 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/exclusive_lock/MockPolicy.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ImageRequestWQ.h"
+#include "librbd/io/ImageRequest.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace io {
+
+template <>
+struct ImageRequest<librbd::MockTestImageCtx> {
+ static ImageRequest* s_instance;
+ AioCompletion *aio_comp = nullptr;
+
+ static void aio_write(librbd::MockTestImageCtx *ictx, AioCompletion *c,
+ Extents &&image_extents, bufferlist &&bl, int op_flags,
+ const ZTracer::Trace &parent_trace) {
+ }
+
+ ImageRequest() {
+ s_instance = this;
+ }
+};
+
+template <>
+struct ImageDispatchSpec<librbd::MockTestImageCtx> {
+ static ImageDispatchSpec* s_instance;
+ AioCompletion *aio_comp = nullptr;
+
+ static ImageDispatchSpec* create_write_request(
+ librbd::MockTestImageCtx &image_ctx, AioCompletion *aio_comp,
+ Extents &&image_extents, bufferlist &&bl, int op_flags,
+ const ZTracer::Trace &parent_trace) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->aio_comp = aio_comp;
+ return s_instance;
+ }
+
+ static ImageDispatchSpec* create_flush_request(
+ librbd::MockTestImageCtx &image_ctx, AioCompletion *aio_comp,
+ FlushSource flush_source, const ZTracer::Trace &parent_trace) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->aio_comp = aio_comp;
+ return s_instance;
+ }
+
+ MOCK_CONST_METHOD0(is_write_op, bool());
+ MOCK_CONST_METHOD0(start_op, void());
+ MOCK_CONST_METHOD0(send, void());
+ MOCK_CONST_METHOD1(fail, void(int));
+ MOCK_CONST_METHOD1(was_throttled, bool(uint64_t));
+ MOCK_CONST_METHOD0(were_all_throttled, bool());
+ MOCK_CONST_METHOD1(set_throttled, void(uint64_t));
+ MOCK_CONST_METHOD2(tokens_requested, bool(uint64_t, uint64_t *));
+
+ ImageDispatchSpec() {
+ s_instance = this;
+ }
+};
+} // namespace io
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+
+} // namespace librbd
+
+template <>
+struct ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>> {
+ typedef librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx> ImageDispatchSpec;
+ static PointerWQ* s_instance;
+
+ ceph::mutex m_lock;
+
+ PointerWQ(const std::string &name, time_t, int, ThreadPool *)
+ : m_lock(ceph::make_mutex(name)) {
+ s_instance = this;
+ }
+ virtual ~PointerWQ() {
+ }
+
+ MOCK_METHOD0(drain, void());
+ MOCK_METHOD0(empty, bool());
+ MOCK_METHOD0(mock_empty, bool());
+ MOCK_METHOD0(signal, void());
+ MOCK_METHOD1(signal, void(const std::lock_guard<ceph::mutex>&));
+ MOCK_METHOD0(process_finish, void());
+
+ MOCK_METHOD0(front, ImageDispatchSpec*());
+ MOCK_METHOD1(requeue_front, void(ImageDispatchSpec*));
+ MOCK_METHOD2(requeue_back, void(const std::lock_guard<ceph::mutex>&,
+ ImageDispatchSpec*));
+
+ MOCK_METHOD0(dequeue, void*());
+ MOCK_METHOD1(queue, void(ImageDispatchSpec*));
+
+ void register_work_queue() {
+ // no-op
+ }
+ ceph::mutex &get_pool_lock() {
+ return m_lock;
+ }
+
+ void* invoke_dequeue() {
+ std::lock_guard locker{m_lock};
+ return _void_dequeue();
+ }
+ void invoke_process(ImageDispatchSpec *image_request) {
+ process(image_request);
+ }
+
+ virtual void *_void_dequeue() {
+ return dequeue();
+ }
+ virtual void process(ImageDispatchSpec *req) = 0;
+ virtual bool _empty() {
+ return mock_empty();
+ }
+
+};
+
+ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>>*
+ ThreadPool::PointerWQ<librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>>::s_instance = nullptr;
+librbd::io::ImageRequest<librbd::MockTestImageCtx>*
+ librbd::io::ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>*
+ librbd::io::ImageDispatchSpec<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+#include "librbd/io/ImageRequestWQ.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+
+struct TestMockIoImageRequestWQ : public TestMockFixture {
+ typedef ImageRequestWQ<librbd::MockTestImageCtx> MockImageRequestWQ;
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
+ typedef ImageDispatchSpec<librbd::MockTestImageCtx> MockImageDispatchSpec;
+
+ void expect_is_write_op(MockImageDispatchSpec &image_request,
+ bool write_op) {
+ EXPECT_CALL(image_request, is_write_op()).WillOnce(Return(write_op));
+ }
+
+ void expect_signal(MockImageRequestWQ &image_request_wq) {
+ EXPECT_CALL(image_request_wq, signal());
+ }
+
+ void expect_signal_locked(MockImageRequestWQ &image_request_wq) {
+ EXPECT_CALL(image_request_wq, signal(_));
+ }
+
+ void expect_queue(MockImageRequestWQ &image_request_wq) {
+ EXPECT_CALL(image_request_wq, queue(_));
+ }
+
+ void expect_requeue_back(MockImageRequestWQ &image_request_wq) {
+ EXPECT_CALL(image_request_wq, requeue_back(_, _));
+ }
+
+ void expect_front(MockImageRequestWQ &image_request_wq,
+ MockImageDispatchSpec *image_request) {
+ EXPECT_CALL(image_request_wq, front()).WillOnce(Return(image_request));
+ }
+
+ void expect_is_refresh_request(MockTestImageCtx &mock_image_ctx,
+ bool required) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required()).WillOnce(
+ Return(required));
+ }
+
+ void expect_dequeue(MockImageRequestWQ &image_request_wq,
+ MockImageDispatchSpec *image_request) {
+ EXPECT_CALL(image_request_wq, dequeue()).WillOnce(Return(image_request));
+ }
+
+ void expect_get_exclusive_lock_policy(MockTestImageCtx &mock_image_ctx,
+ librbd::exclusive_lock::MockPolicy &policy) {
+ EXPECT_CALL(mock_image_ctx,
+ get_exclusive_lock_policy()).WillOnce(Return(&policy));
+ }
+
+ void expect_may_auto_request_lock(librbd::exclusive_lock::MockPolicy &policy,
+ bool value) {
+ EXPECT_CALL(policy, may_auto_request_lock()).WillOnce(Return(value));
+ }
+
+ void expect_acquire_lock(MockExclusiveLock &mock_exclusive_lock,
+ Context **on_finish) {
+ EXPECT_CALL(mock_exclusive_lock, acquire_lock(_))
+ .WillOnce(Invoke([on_finish](Context *ctx) {
+ *on_finish = ctx;
+ }));
+ }
+
+ void expect_process_finish(MockImageRequestWQ &mock_image_request_wq) {
+ EXPECT_CALL(mock_image_request_wq, process_finish()).Times(1);
+ }
+
+ void expect_fail(MockImageDispatchSpec &mock_image_request, int r) {
+ EXPECT_CALL(mock_image_request, fail(r))
+ .WillOnce(Invoke([&mock_image_request](int r) {
+ mock_image_request.aio_comp->get();
+ mock_image_request.aio_comp->fail(r);
+ }));
+ }
+
+ void expect_refresh(MockTestImageCtx &mock_image_ctx, Context **on_finish) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(Invoke([on_finish](Context *ctx) {
+ *on_finish = ctx;
+ }));
+ }
+
+ void expect_set_throttled(MockImageDispatchSpec &mock_image_request) {
+ EXPECT_CALL(mock_image_request, set_throttled(_)).Times(6);
+ }
+
+ void expect_was_throttled(MockImageDispatchSpec &mock_image_request, bool value) {
+ EXPECT_CALL(mock_image_request, was_throttled(_)).Times(6).WillRepeatedly(Return(value));
+ }
+
+ void expect_tokens_requested(MockImageDispatchSpec &mock_image_request,
+ uint64_t tokens, bool r) {
+ EXPECT_CALL(mock_image_request, tokens_requested(_, _))
+ .WillOnce(WithArg<1>(Invoke([tokens, r](uint64_t *t) {
+ *t = tokens;
+ return r;
+ })));
+ }
+
+ void expect_all_throttled(MockImageDispatchSpec &mock_image_request, bool value) {
+ EXPECT_CALL(mock_image_request, were_all_throttled()).WillOnce(Return(value));
+ }
+
+ void expect_start_op(MockImageDispatchSpec &mock_image_request) {
+ EXPECT_CALL(mock_image_request, start_op()).Times(1);
+ }
+};
+
+TEST_F(TestMockIoImageRequestWQ, AcquireLockError) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+ auto mock_queued_image_request = new MockImageDispatchSpec();
+ expect_was_throttled(*mock_queued_image_request, false);
+ expect_set_throttled(*mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+ expect_signal(mock_image_request_wq);
+ mock_image_request_wq.set_require_lock(DIRECTION_WRITE, true);
+
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_queue(mock_image_request_wq);
+ auto *aio_comp = new librbd::io::AioCompletion();
+ mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0);
+
+ librbd::exclusive_lock::MockPolicy mock_exclusive_lock_policy;
+ expect_front(mock_image_request_wq, mock_queued_image_request);
+ expect_is_refresh_request(mock_image_ctx, false);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_dequeue(mock_image_request_wq, mock_queued_image_request);
+ expect_get_exclusive_lock_policy(mock_image_ctx, mock_exclusive_lock_policy);
+ expect_may_auto_request_lock(mock_exclusive_lock_policy, true);
+ Context *on_acquire = nullptr;
+ expect_acquire_lock(mock_exclusive_lock, &on_acquire);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr);
+ ASSERT_TRUE(on_acquire != nullptr);
+
+ expect_process_finish(mock_image_request_wq);
+ expect_fail(*mock_queued_image_request, -EPERM);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_signal(mock_image_request_wq);
+ on_acquire->complete(-EPERM);
+
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ ASSERT_EQ(-EPERM, aio_comp->get_return_value());
+ aio_comp->release();
+}
+
+TEST_F(TestMockIoImageRequestWQ, AcquireLockBlacklisted) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+ auto mock_queued_image_request = new MockImageDispatchSpec();
+ expect_was_throttled(*mock_queued_image_request, false);
+ expect_set_throttled(*mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+ expect_signal(mock_image_request_wq);
+ mock_image_request_wq.set_require_lock(DIRECTION_WRITE, true);
+
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_queue(mock_image_request_wq);
+ auto *aio_comp = new librbd::io::AioCompletion();
+ mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0);
+
+ librbd::exclusive_lock::MockPolicy mock_exclusive_lock_policy;
+ expect_front(mock_image_request_wq, mock_queued_image_request);
+ expect_is_refresh_request(mock_image_ctx, false);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_dequeue(mock_image_request_wq, mock_queued_image_request);
+ expect_get_exclusive_lock_policy(mock_image_ctx, mock_exclusive_lock_policy);
+ expect_may_auto_request_lock(mock_exclusive_lock_policy, false);
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, get_unlocked_op_error())
+ .WillOnce(Return(-EBLACKLISTED));
+ expect_process_finish(mock_image_request_wq);
+ expect_fail(*mock_queued_image_request, -EBLACKLISTED);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_signal(mock_image_request_wq);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr);
+
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ ASSERT_EQ(-EBLACKLISTED, aio_comp->get_return_value());
+ aio_comp->release();
+}
+
+TEST_F(TestMockIoImageRequestWQ, RefreshError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ auto mock_queued_image_request = new MockImageDispatchSpec();
+ expect_was_throttled(*mock_queued_image_request, false);
+ expect_set_throttled(*mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_queue(mock_image_request_wq);
+ auto *aio_comp = new librbd::io::AioCompletion();
+ mock_image_request_wq.aio_write(aio_comp, 0, 0, {}, 0);
+
+ expect_front(mock_image_request_wq, mock_queued_image_request);
+ expect_is_refresh_request(mock_image_ctx, true);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_dequeue(mock_image_request_wq, mock_queued_image_request);
+ Context *on_refresh = nullptr;
+ expect_refresh(mock_image_ctx, &on_refresh);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr);
+ ASSERT_TRUE(on_refresh != nullptr);
+
+ expect_process_finish(mock_image_request_wq);
+ expect_fail(*mock_queued_image_request, -EPERM);
+ expect_is_write_op(*mock_queued_image_request, true);
+ expect_signal(mock_image_request_wq);
+ on_refresh->complete(-EPERM);
+
+ ASSERT_EQ(0, aio_comp->wait_for_complete());
+ ASSERT_EQ(-EPERM, aio_comp->get_return_value());
+ aio_comp->release();
+}
+
+TEST_F(TestMockIoImageRequestWQ, QosNoLimit) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockImageDispatchSpec mock_queued_image_request;
+ expect_was_throttled(mock_queued_image_request, false);
+ expect_set_throttled(mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+
+ mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 0, 0);
+
+ expect_front(mock_image_request_wq, &mock_queued_image_request);
+ expect_is_refresh_request(mock_image_ctx, false);
+ expect_is_write_op(mock_queued_image_request, true);
+ expect_dequeue(mock_image_request_wq, &mock_queued_image_request);
+ expect_start_op(mock_queued_image_request);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == &mock_queued_image_request);
+}
+
+TEST_F(TestMockIoImageRequestWQ, BPSQosNoBurst) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockImageDispatchSpec mock_queued_image_request;
+ expect_was_throttled(mock_queued_image_request, false);
+ expect_set_throttled(mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+
+ mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 1, 0);
+
+ expect_front(mock_image_request_wq, &mock_queued_image_request);
+ expect_tokens_requested(mock_queued_image_request, 2, true);
+ expect_dequeue(mock_image_request_wq, &mock_queued_image_request);
+ expect_all_throttled(mock_queued_image_request, true);
+ expect_requeue_back(mock_image_request_wq);
+ expect_signal_locked(mock_image_request_wq);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr);
+}
+
+TEST_F(TestMockIoImageRequestWQ, BPSQosWithBurst) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockImageDispatchSpec mock_queued_image_request;
+ expect_was_throttled(mock_queued_image_request, false);
+ expect_set_throttled(mock_queued_image_request);
+
+ InSequence seq;
+ MockImageRequestWQ mock_image_request_wq(&mock_image_ctx, "io", 60, nullptr);
+
+ mock_image_request_wq.apply_qos_limit(RBD_QOS_BPS_THROTTLE, 1, 1);
+
+ expect_front(mock_image_request_wq, &mock_queued_image_request);
+ expect_tokens_requested(mock_queued_image_request, 2, true);
+ expect_dequeue(mock_image_request_wq, &mock_queued_image_request);
+ expect_all_throttled(mock_queued_image_request, true);
+ expect_requeue_back(mock_image_request_wq);
+ expect_signal_locked(mock_image_request_wq);
+ ASSERT_TRUE(mock_image_request_wq.invoke_dequeue() == nullptr);
+}
+
+} // namespace io
+} // namespace librbd
diff --git a/src/test/librbd/io/test_mock_ObjectRequest.cc b/src/test/librbd/io/test_mock_ObjectRequest.cc
new file mode 100644
index 00000000..2f1d8777
--- /dev/null
+++ b/src/test/librbd/io/test_mock_ObjectRequest.cc
@@ -0,0 +1,1394 @@
+// -*- 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/MockObjectMap.h"
+#include "test/librbd/mock/cache/MockImageCache.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/io/ObjectRequest.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+
+namespace io {
+
+template <>
+struct CopyupRequest<librbd::MockImageCtx> {
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD1(append_request, void(AbstractObjectWriteRequest<librbd::MockTestImageCtx>*));
+};
+
+template <>
+struct CopyupRequest<librbd::MockTestImageCtx> : public CopyupRequest<librbd::MockImageCtx> {
+ static CopyupRequest* s_instance;
+ static CopyupRequest* create(librbd::MockTestImageCtx *ictx,
+ const std::string &oid, uint64_t objectno,
+ Extents &&image_extents,
+ const ZTracer::Trace &parent_trace) {
+ return s_instance;
+ }
+
+ CopyupRequest() {
+ s_instance = this;
+ }
+};
+
+template <>
+struct ImageRequest<librbd::MockTestImageCtx> {
+ static ImageRequest *s_instance;
+ static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c,
+ Extents &&image_extents, ReadResult &&read_result,
+ int op_flags, const ZTracer::Trace &parent_trace) {
+ s_instance->aio_read(c, image_extents);
+ }
+
+ MOCK_METHOD2(aio_read, void(AioCompletion *, const Extents&));
+
+ ImageRequest() {
+ s_instance = this;
+ }
+};
+
+CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace io
+} // namespace librbd
+
+#include "librbd/io/ObjectRequest.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::DoDefault;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+struct TestMockIoObjectRequest : public TestMockFixture {
+ typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest;
+ typedef ObjectReadRequest<librbd::MockTestImageCtx> MockObjectReadRequest;
+ typedef ObjectWriteRequest<librbd::MockTestImageCtx> MockObjectWriteRequest;
+ typedef ObjectDiscardRequest<librbd::MockTestImageCtx> MockObjectDiscardRequest;
+ typedef ObjectWriteSameRequest<librbd::MockTestImageCtx> MockObjectWriteSameRequest;
+ typedef ObjectCompareAndWriteRequest<librbd::MockTestImageCtx> MockObjectCompareAndWriteRequest;
+ typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+ typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
+
+ 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_size(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(mock_image_ctx, get_object_size()).WillRepeatedly(Return(
+ mock_image_ctx.layout.object_size));
+ }
+
+ void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx,
+ librados::snap_t snap_id, uint64_t overlap,
+ int r) {
+ EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _))
+ .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) {
+ *o = overlap;
+ return r;
+ })));
+ }
+
+ void expect_prune_parent_extents(MockTestImageCtx &mock_image_ctx,
+ const Extents& extents,
+ uint64_t overlap, uint64_t object_overlap) {
+ EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+ .WillOnce(WithArg<0>(Invoke([extents, object_overlap](Extents& e) {
+ e = extents;
+ return object_overlap;
+ })));
+ }
+
+ void expect_get_read_flags(MockTestImageCtx &mock_image_ctx,
+ librados::snap_t snap_id, int flags) {
+ EXPECT_CALL(mock_image_ctx, get_read_flags(snap_id))
+ .WillOnce(Return(flags));
+ }
+
+ void expect_is_lock_owner(MockExclusiveLock& mock_exclusive_lock) {
+ EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly(
+ Return(true));
+ }
+
+ void expect_read(MockTestImageCtx &mock_image_ctx,
+ const std::string& oid, uint64_t off, uint64_t len,
+ const std::string& data, int r) {
+ bufferlist bl;
+ bl.append(data);
+
+ auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ read(oid, len, off, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(WithArg<3>(Invoke([bl](bufferlist *out_bl) {
+ out_bl->append(bl);
+ return bl.length();
+ })));
+ }
+ }
+
+ void expect_sparse_read(MockTestImageCtx &mock_image_ctx,
+ const std::string& oid, uint64_t off, uint64_t len,
+ const std::string& data, int r) {
+ bufferlist bl;
+ bl.append(data);
+
+ auto& expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ sparse_read(oid, off, len, _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(WithArg<4>(Invoke([bl](bufferlist *out_bl) {
+ out_bl->append(bl);
+ return bl.length();
+ })));
+ }
+ }
+
+ void expect_aio_read(MockTestImageCtx &mock_image_ctx,
+ MockImageRequest& mock_image_request,
+ Extents&& extents, int r) {
+ EXPECT_CALL(mock_image_request, aio_read(_, extents))
+ .WillOnce(WithArg<0>(Invoke([&mock_image_ctx, r](AioCompletion* aio_comp) {
+ aio_comp->set_request_count(1);
+ mock_image_ctx.image_ctx->op_work_queue->queue(new C_AioRequest(aio_comp), r);
+ })));
+ }
+
+ void expect_copyup(MockCopyupRequest& mock_copyup_request, int r) {
+ EXPECT_CALL(mock_copyup_request, send())
+ .WillOnce(Invoke([]() {}));
+ }
+
+ void expect_copyup(MockCopyupRequest& mock_copyup_request,
+ MockAbstractObjectWriteRequest** write_request, int r) {
+ EXPECT_CALL(mock_copyup_request, append_request(_))
+ .WillOnce(Invoke([write_request](MockAbstractObjectWriteRequest *req) {
+ *write_request = req;
+ }));
+ EXPECT_CALL(mock_copyup_request, send())
+ .WillOnce(Invoke([write_request, r]() {
+ (*write_request)->handle_copyup(r);
+ }));
+ }
+
+ void expect_object_map_update(MockTestImageCtx &mock_image_ctx,
+ uint64_t start_object, uint64_t end_object,
+ uint8_t state,
+ const boost::optional<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,
+ 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_assert_exists(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), assert_exists(_))
+ .WillOnce(Return(r));
+ }
+
+ void expect_write(MockTestImageCtx &mock_image_ctx,
+ uint64_t offset, uint64_t length, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write(_, _, length, offset, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_write_full(MockTestImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write_full(_, _, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_writesame(MockTestImageCtx &mock_image_ctx,
+ uint64_t offset, uint64_t length, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ writesame(_, _, length, offset, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_remove(MockTestImageCtx &mock_image_ctx, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ remove(_, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_create(MockTestImageCtx &mock_image_ctx, bool exclusive) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, exclusive))
+ .Times(1);
+ }
+
+ void expect_truncate(MockTestImageCtx &mock_image_ctx, int offset, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ truncate(_, offset, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_zero(MockTestImageCtx &mock_image_ctx, int offset, int length,
+ int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ zero(_, offset, length, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+
+ void expect_cmpext(MockTestImageCtx &mock_image_ctx, int offset, int r) {
+ auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ cmpext(_, offset, _));
+ if (r < 0) {
+ expect.WillOnce(Return(r));
+ } else {
+ expect.WillOnce(DoDefault());
+ }
+ }
+};
+
+TEST_F(TestMockIoObjectRequest, Read) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->sparse_read_threshold_bytes = 8096;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096,
+ std::string(4096, '1'), 0);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {},
+ &bl, &extent_map, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, SparseReadThreshold) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->sparse_read_threshold_bytes = ictx->get_object_size();
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_sparse_read(mock_image_ctx, ictx->get_object_name(0), 0,
+ ictx->sparse_read_threshold_bytes,
+ std::string(ictx->sparse_read_threshold_bytes, '1'), 0);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ ictx->sparse_read_threshold_bytes, CEPH_NOSNAP, 0, {}, &bl, &extent_map,
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, ReadError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->sparse_read_threshold_bytes = 8096;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -EPERM);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {},
+ &bl, &extent_map, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, ParentRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+ ictx->sparse_read_threshold_bytes = 8096;
+ ictx->clone_copy_on_read = false;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
+
+ MockImageRequest mock_image_request;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, 0);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {},
+ &bl, &extent_map, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, ParentReadError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+ ictx->sparse_read_threshold_bytes = 8096;
+ ictx->clone_copy_on_read = false;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
+
+ MockImageRequest mock_image_request;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, -EPERM);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {},
+ &bl, &extent_map, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+ ictx->sparse_read_threshold_bytes = 8096;
+ ictx->clone_copy_on_read = true;
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
+ expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
+
+ MockImageRequest mock_image_request;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_aio_read(mock_image_ctx, mock_image_request, {{0, 4096}}, 0);
+
+ MockCopyupRequest mock_copyup_request;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_copyup(mock_copyup_request, 0);
+
+ bufferlist bl;
+ ExtentMap extent_map;
+ C_SaferCond ctx;
+ auto req = MockObjectReadRequest::create(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, {},
+ &bl, &extent_map, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, Write) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_write(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteFull) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist bl;
+ bl.append(std::string(ictx->get_object_size(), '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_write_full(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteObjectMap) {
+ REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+
+ MockObjectMap mock_object_map;
+ mock_image_ctx.object_map = &mock_object_map;
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, true, 0);
+ expect_write(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_write(mock_image_ctx, 0, 4096, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, Copyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_assert_exists(mock_image_ctx, -ENOENT);
+
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ MockCopyupRequest mock_copyup_request;
+ expect_copyup(mock_copyup_request, &write_request, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyupRestart) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_assert_exists(mock_image_ctx, -ENOENT);
+
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ MockCopyupRequest mock_copyup_request;
+ expect_copyup(mock_copyup_request, &write_request, -ERESTART);
+ expect_assert_exists(mock_image_ctx, 0);
+ expect_write(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyupOptimization) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+
+ MockObjectMap mock_object_map;
+ mock_image_ctx.object_map = &mock_object_map;
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, false);
+
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ MockCopyupRequest mock_copyup_request;
+ expect_copyup(mock_copyup_request, &write_request, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyupError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_assert_exists(mock_image_ctx, -ENOENT);
+
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ MockCopyupRequest mock_copyup_request;
+ expect_copyup(mock_copyup_request, &write_request, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardRemove) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, {}, false, 0);
+ expect_remove(mock_image_ctx, 0);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT,
+ OBJECT_PENDING, false, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ mock_image_ctx.get_object_size(), mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardRemoveTruncate) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), features, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, false);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_create(mock_image_ctx, false);
+ expect_truncate(mock_image_ctx, 0, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ mock_image_ctx.get_object_size(), mock_image_ctx.snapc,
+ OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardTruncateAssertExists) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), features, &order));
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, clone_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ image.close();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_assert_exists(mock_image_ctx, 0);
+ expect_truncate(mock_image_ctx, 0, 0);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, _)).Times(0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ mock_image_ctx.get_object_size(), mock_image_ctx.snapc,
+ OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardTruncate) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_truncate(mock_image_ctx, 1, 0);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), create(_, _)).Times(0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 1,
+ mock_image_ctx.get_object_size() - 1, mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardZero) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_zero(mock_image_ctx, 1, 1, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 1, 1, mock_image_ctx.snapc,
+ 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardDisableObjectMapUpdate) {
+ REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+
+ MockObjectMap mock_object_map;
+ mock_image_ctx.object_map = &mock_object_map;
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_remove(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ mock_image_ctx.get_object_size(), mock_image_ctx.snapc,
+ OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE |
+ OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardNoOp) {
+ REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+
+ MockObjectMap mock_object_map;
+ mock_image_ctx.object_map = &mock_object_map;
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, false);
+
+ C_SaferCond ctx;
+ auto req = MockObjectDiscardRequest::create_discard(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+ mock_image_ctx.get_object_size(), mock_image_ctx.snapc,
+ OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE |
+ OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE, {},
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteSame) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_writesame(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteSameRequest::create_write_same(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWrite) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist cmp_bl;
+ cmp_bl.append_zero(4096);
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_cmpext(mock_image_ctx, 0, 0);
+ expect_write(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ uint64_t mismatch_offset;
+ auto req = MockObjectWriteSameRequest::create_compare_and_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl),
+ std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteFull) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist cmp_bl;
+ cmp_bl.append_zero(ictx->get_object_size());
+
+ bufferlist bl;
+ bl.append(std::string(ictx->get_object_size(), '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_cmpext(mock_image_ctx, 0, 0);
+ expect_write_full(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ uint64_t mismatch_offset;
+ auto req = MockObjectWriteSameRequest::create_compare_and_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl),
+ std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteCopyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+ image.close();
+
+ std::string clone_name = get_temp_image_name();
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+ clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist cmp_bl;
+ cmp_bl.append_zero(4096);
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_assert_exists(mock_image_ctx, -ENOENT);
+
+ MockAbstractObjectWriteRequest *write_request = nullptr;
+ MockCopyupRequest mock_copyup_request;
+ expect_copyup(mock_copyup_request, &write_request, 0);
+
+ expect_assert_exists(mock_image_ctx, 0);
+ expect_cmpext(mock_image_ctx, 0, 0);
+ expect_write(mock_image_ctx, 0, 4096, 0);
+
+ C_SaferCond ctx;
+ uint64_t mismatch_offset;
+ auto req = MockObjectWriteSameRequest::create_compare_and_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl),
+ std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteMismatch) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+ }
+
+ MockObjectMap mock_object_map;
+ if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+ mock_image_ctx.object_map = &mock_object_map;
+ }
+
+ bufferlist cmp_bl;
+ cmp_bl.append_zero(4096);
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+ expect_cmpext(mock_image_ctx, 0, -MAX_ERRNO - 1);
+
+ C_SaferCond ctx;
+ uint64_t mismatch_offset;
+ auto req = MockObjectWriteSameRequest::create_compare_and_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(cmp_bl),
+ std::move(bl), mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EILSEQ, ctx.wait());
+ ASSERT_EQ(1ULL, mismatch_offset);
+}
+
+TEST_F(TestMockIoObjectRequest, ObjectMapError) {
+ REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_get_object_size(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ expect_is_lock_owner(mock_exclusive_lock);
+
+ MockObjectMap mock_object_map;
+ mock_image_ctx.object_map = &mock_object_map;
+
+ bufferlist bl;
+ bl.append(std::string(4096, '1'));
+
+ InSequence seq;
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+ expect_object_may_exist(mock_image_ctx, 0, true);
+ expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, true,
+ -EBLACKLISTED);
+
+ C_SaferCond ctx;
+ auto req = MockObjectWriteRequest::create_write(
+ &mock_image_ctx, ictx->get_object_name(0), 0, 0, std::move(bl),
+ mock_image_ctx.snapc, 0, {}, &ctx);
+ req->send();
+ ASSERT_EQ(-EBLACKLISTED, ctx.wait());
+}
+
+} // namespace io
+} // namespace librbd
+