#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
#include "test/librados_test_stub/MockTestMemRadosClient.h"
#include "include/rbd/librbd.hpp"
+#include "librbd/api/Io.h"
#include "librbd/deep_copy/ObjectCopyRequest.h"
#include "librbd/io/CopyupRequest.h"
-#include "librbd/io/ImageRequest.h"
-#include "librbd/io/ImageRequestWQ.h"
+#include "librbd/io/ImageDispatchSpec.h"
#include "librbd/io/ObjectRequest.h"
#include "librbd/io/ReadResult.h"
+#include "librbd/io/Utils.h"
namespace librbd {
namespace {
: MockImageCtx(image_ctx) {
parent = mock_parent_image_ctx;
}
+ ~MockTestImageCtx() override {
+ // copyups need to complete prior to attempting to delete this object
+ wait_for_async_ops();
+ }
std::map<uint64_t, librbd::io::CopyupRequest<librbd::MockTestImageCtx>*> copyup_list;
};
namespace io {
+namespace util {
+
+template <> void file_to_extents(
+ MockTestImageCtx* image_ctx, uint64_t offset, uint64_t length,
+ uint64_t buffer_offset,
+ striper::LightweightObjectExtents* object_extents) {
+ Striper::file_to_extents(image_ctx->cct, &image_ctx->layout, offset, length,
+ 0, buffer_offset, object_extents);
+}
+
+template <> void extent_to_file(
+ MockTestImageCtx* image_ctx, uint64_t object_no, uint64_t offset,
+ uint64_t length,
+ std::vector<std::pair<uint64_t, uint64_t> >& extents) {
+ Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, object_no,
+ offset, length, extents);
+}
+
+} // namespace util
+
template <>
struct ObjectRequest<librbd::MockTestImageCtx> {
static void add_write_hint(librbd::MockTestImageCtx&,
- librados::ObjectWriteOperation*) {
+ neorados::WriteOp*) {
}
};
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;
- }
+ MOCK_METHOD1(add_copyup_ops, void(neorados::WriteOp*));
};
-ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
-
} // namespace io
} // namespace librbd
#include "librbd/AsyncObjectThrottle.cc"
#include "librbd/io/CopyupRequest.cc"
+MATCHER_P(IsRead, image_extents, "") {
+ auto req = boost::get<librbd::io::ImageDispatchSpec::Read>(&arg->request);
+ return (req != nullptr && image_extents == arg->image_extents);
+}
+
namespace librbd {
namespace io {
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 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;
- })));
- }
+ 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;
- })));
- }
+ 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,
+ void expect_read_parent(librbd::MockTestImageCtx& mock_image_ctx,
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);
+ EXPECT_CALL(*mock_image_ctx.io_image_dispatcher,
+ send(IsRead(image_extents)))
+ .WillOnce(Invoke(
+ [&mock_image_ctx, image_extents, data, r](io::ImageDispatchSpec* spec) {
+ auto req = boost::get<librbd::io::ImageDispatchSpec::Read>(
+ &spec->request);
+ ASSERT_TRUE(req != nullptr);
+
+ if (r < 0) {
+ spec->fail(r);
+ return;
+ }
+
+ spec->dispatch_result = DISPATCH_RESULT_COMPLETE;
+
+ auto aio_comp = spec->aio_comp;
+ aio_comp->read_result = std::move(req->read_result);
+ aio_comp->read_result.set_image_extents(image_extents);
aio_comp->set_request_count(1);
- auto ctx = new ReadResult::C_ImageReadRequest(aio_comp,
+ auto ctx = new ReadResult::C_ImageReadRequest(aio_comp, 0,
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,
snapc = mock_image_ctx.snapc;
}
- EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx,
exec(oid, _, StrEq("rbd"), StrEq("copyup"),
- ContentsEqual(in_bl), _, snapc))
+ ContentsEqual(in_bl), _, _, snapc))
.WillOnce(Return(r));
}
snapc = mock_image_ctx.snapc;
}
- EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx,
exec(oid, _, StrEq("rbd"), StrEq("sparse_copyup"),
- ContentsEqual(in_bl), _, snapc))
+ ContentsEqual(in_bl), _, _, snapc))
.WillOnce(Return(r));
}
snapc = mock_image_ctx.snapc;
}
- EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
- write(oid, _, 0, 0, snapc))
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx, write(oid, _, 0, 0, snapc))
.WillOnce(Return(r));
}
void expect_add_copyup_ops(MockAbstractObjectWriteRequest& mock_write_request) {
EXPECT_CALL(mock_write_request, add_copyup_ops(_))
- .WillOnce(Invoke([](librados::ObjectWriteOperation* op) {
+ .WillOnce(Invoke([](neorados::WriteOp* op) {
op->write(0, bufferlist{});
}));
}
}));
}
+ void expect_prepare_copyup(MockTestImageCtx& mock_image_ctx, int r = 0) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ prepare_copyup(_, _)).WillOnce(Return(r));
+ }
+
+ void expect_prepare_copyup(MockTestImageCtx& mock_image_ctx,
+ const SparseBufferlist& in_sparse_bl,
+ const SparseBufferlist& out_sparse_bl) {
+ EXPECT_CALL(*mock_image_ctx.io_object_dispatcher,
+ prepare_copyup(_, _))
+ .WillOnce(WithArg<1>(Invoke(
+ [in_sparse_bl, out_sparse_bl]
+ (SnapshotSparseBufferlist* snap_sparse_bl) {
+ auto& sparse_bl = (*snap_sparse_bl)[0];
+ EXPECT_EQ(in_sparse_bl, sparse_bl);
+
+ sparse_bl = out_sparse_bl;
+ return 0;
+ })));
+ }
+
void flush_async_operations(librbd::ImageCtx* ictx) {
- ictx->io_work_queue->flush();
+ api::Io<>::flush(*ictx);
}
std::string m_parent_image_name;
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
MockAbstractObjectWriteRequest mock_write_request;
MockObjectCopyRequest mock_object_copy_request;
- mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ 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);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
InSequence seq;
MockObjectCopyRequest mock_object_copy_request;
- mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size,
+ 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_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
0);
- expect_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
- {}, "", 0);
-
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
MockAbstractObjectWriteRequest mock_write_request;
MockObjectCopyRequest mock_object_copy_request;
- mock_image_ctx.migration_info = {1, "", "", "image id",
+ mock_image_ctx.migration_info = {1, "", "", "image id", "",
{{CEPH_NOSNAP, {2, 1}}},
ictx->size, true};
expect_is_empty_write_op(mock_write_request, false);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
MockAbstractObjectWriteRequest mock_write_request;
MockObjectCopyRequest mock_object_copy_request;
- mock_image_ctx.migration_info = {1, "", "", "image id",
+ 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);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
InSequence seq;
MockAbstractObjectWriteRequest mock_write_request;
+ expect_prepare_copyup(mock_image_ctx);
expect_is_empty_write_op(mock_write_request, false);
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
OBJECT_EXISTS);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
InSequence seq;
- MockImageRequest mock_image_request;
- expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
- "", -ENOENT);
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, "", -ENOENT);
+
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_is_empty_write_op(mock_write_request, true);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request1;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request1,
{{0, 4096}}, data, 0);
MockAbstractObjectWriteRequest mock_write_request2;
- EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
- write(ictx->get_object_name(0), _, 0, 0, _))
+ auto& mock_io_ctx = librados::get_mock_io_ctx(
+ mock_image_ctx.rados_api, *mock_image_ctx.get_data_io_context());
+ EXPECT_CALL(mock_io_ctx, write(ictx->get_object_name(0), _, 0, 0, _))
.WillOnce(WithoutArgs(Invoke([req, &mock_write_request2]() {
- req->append_request(&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->append_request(&mock_write_request1, {});
req->send();
ASSERT_EQ(0, mock_write_request1.ctx.wait());
InSequence seq;
- MockImageRequest mock_image_request;
- expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
- "", -EPERM);
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, "", -EPERM);
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
MockAbstractObjectWriteRequest mock_write_request;
- expect_is_empty_write_op(mock_write_request, false);
+ req->append_request(&mock_write_request, {});
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, PrepareCopyupError) {
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx, -EIO);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ MockAbstractObjectWriteRequest mock_write_request;
+ req->append_request(&mock_write_request, {});
req->send();
- ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+ ASSERT_EQ(-EIO, mock_write_request.ctx.wait());
}
TEST_F(TestMockIoCopyupRequest, DeepCopyError) {
MockAbstractObjectWriteRequest mock_write_request;
MockObjectCopyRequest mock_object_copy_request;
- mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ 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);
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(-EINVAL, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
auto req = new MockCopyupRequest(&mock_image_ctx, 0,
{{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
req->send();
ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
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_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+ expect_prepare_copyup(mock_image_ctx);
MockAbstractObjectWriteRequest mock_write_request;
expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
auto req = new MockCopyupRequest(&mock_image_ctx, 0, {{0, 4096}}, {});
mock_image_ctx.copyup_list[0] = req;
- req->append_request(&mock_write_request);
+ req->append_request(&mock_write_request, {});
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ProcessCopyup) {
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+
+ bufferlist in_prepare_bl;
+ in_prepare_bl.append(std::string(3072, '1'));
+ bufferlist out_prepare_bl;
+ out_prepare_bl.substr_of(in_prepare_bl, 0, 1024);
+ expect_prepare_copyup(
+ mock_image_ctx,
+ {{1024U, {3072U, {SPARSE_EXTENT_STATE_DATA, 3072,
+ std::move(in_prepare_bl)}}}},
+ {{2048U, {1024U, {SPARSE_EXTENT_STATE_DATA, 1024,
+ std::move(out_prepare_bl)}}}});
+
+ 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_sparse_copyup(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0),
+ {{2048, 1024}}, data.substr(0, 1024), 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request, {{0, 1024}});
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ProcessCopyupOverwrite) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->image_lock.lock();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->image_lock.unlock();
+
+ 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;
+
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, {{0, 4096}}, data, 0);
+
+ bufferlist in_prepare_bl;
+ in_prepare_bl.append(data);
+ bufferlist out_prepare_bl;
+ out_prepare_bl.substr_of(in_prepare_bl, 0, 1024);
+ expect_prepare_copyup(
+ mock_image_ctx,
+ {{0, {4096, {SPARSE_EXTENT_STATE_DATA, 4096,
+ std::move(in_prepare_bl)}}}},
+ {{0, {1024, {SPARSE_EXTENT_STATE_DATA, 1024, bufferlist{out_prepare_bl}}}},
+ {2048, {1024, {SPARSE_EXTENT_STATE_DATA, 1024,
+ bufferlist{out_prepare_bl}}}}});
+
+ 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_sparse_copyup(mock_image_ctx, 0, ictx->get_object_name(0),
+ {{0, 1024}, {2048, 1024}}, data.substr(0, 2048), 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, ictx->get_object_name(0), 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request, {{0, 1024}});
req->send();
ASSERT_EQ(0, mock_write_request.ctx.wait());