diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc | |
parent | Initial commit. (diff) | |
download | ceph-upstream/16.2.11+ds.tar.xz ceph-upstream/16.2.11+ds.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc')
-rw-r--r-- | src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc new file mode 100644 index 000000000..04b6b8962 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc @@ -0,0 +1,798 @@ +// -*- 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/crypto/MockCryptoInterface.h" +#include "librbd/crypto/CryptoObjectDispatch.h" +#include "librbd/io/ObjectDispatchSpec.h" +#include "librbd/io/Utils.h" + +#include "librbd/io/Utils.cc" +template bool librbd::io::util::trigger_copyup( + MockImageCtx *image_ctx, uint64_t object_no, IOContext io_context, + Context* on_finish); + +template class librbd::io::ObjectWriteRequest<librbd::MockImageCtx>; +template class librbd::io::AbstractObjectWriteRequest<librbd::MockImageCtx>; +#include "librbd/io/ObjectRequest.cc" + +namespace librbd { + +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_METHOD2(append_request, void( + AbstractObjectWriteRequest<librbd::MockImageCtx>*, + const Extents&)); + + static CopyupRequest* s_instance; + static CopyupRequest* create(librbd::MockImageCtx *ictx, + uint64_t objectno, Extents &&image_extents, + const ZTracer::Trace &parent_trace) { + return s_instance; + } + + CopyupRequest() { + s_instance = this; + } +}; + +CopyupRequest<librbd::MockImageCtx>* CopyupRequest< + librbd::MockImageCtx>::s_instance = nullptr; + +namespace util { + +template <> uint64_t get_file_offset( + MockImageCtx *image_ctx, uint64_t object_no, uint64_t offset) { + return Striper::get_file_offset(image_ctx->cct, &image_ctx->layout, + object_no, offset); +} + +namespace { + +struct Mock { + static Mock* s_instance; + + Mock() { + s_instance = this; + } + + MOCK_METHOD6(read_parent, + void(MockImageCtx*, uint64_t, io::ReadExtents*, + librados::snap_t, const ZTracer::Trace &, Context*)); +}; + +Mock *Mock::s_instance = nullptr; + +} // anonymous namespace + +template <> void read_parent( + MockImageCtx *image_ctx, uint64_t object_no, + io::ReadExtents* extents, librados::snap_t snap_id, + const ZTracer::Trace &trace, Context* on_finish) { + + Mock::s_instance->read_parent(image_ctx, object_no, extents, snap_id, trace, + on_finish); +} + +} // namespace util +} // namespace io + +} // namespace librbd + +#include "librbd/crypto/CryptoObjectDispatch.cc" + +namespace librbd { +namespace crypto { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Pair; +using ::testing::Return; +using ::testing::WithArg; + +struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture { + typedef CryptoObjectDispatch<librbd::MockImageCtx> MockCryptoObjectDispatch; + typedef io::AbstractObjectWriteRequest<librbd::MockImageCtx> + MockAbstractObjectWriteRequest; + typedef io::CopyupRequest<librbd::MockImageCtx> MockCopyupRequest; + typedef io::util::Mock MockUtils; + + MockCryptoInterface* crypto; + MockImageCtx* mock_image_ctx; + MockCryptoObjectDispatch* mock_crypto_object_dispatch; + + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + C_SaferCond dispatched_cond; + Context *on_dispatched = &dispatched_cond; + Context *dispatcher_ctx; + ceph::bufferlist data; + io::DispatchResult dispatch_result; + io::Extents extent_map; + int object_dispatch_flags = 0; + MockUtils mock_utils; + MockCopyupRequest copyup_request; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + crypto = new MockCryptoInterface(); + mock_image_ctx = new MockImageCtx(*ictx); + mock_crypto_object_dispatch = new MockCryptoObjectDispatch( + mock_image_ctx, crypto); + data.append(std::string(4096, '1')); + } + + void TearDown() override { + C_SaferCond cond; + Context *on_finish = &cond; + mock_crypto_object_dispatch->shut_down(on_finish); + ASSERT_EQ(0, cond.wait()); + + delete mock_crypto_object_dispatch; + delete mock_image_ctx; + + TestMockFixture::TearDown(); + } + + void expect_object_read(io::ReadExtents* extents, uint64_t version = 0) { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this, extents, + version](io::ObjectDispatchSpec* spec) { + auto* read = boost::get<io::ObjectDispatchSpec::ReadRequest>( + &spec->request); + ASSERT_TRUE(read != nullptr); + + ASSERT_EQ(extents->size(), read->extents->size()); + for (uint64_t i = 0; i < extents->size(); ++i) { + ASSERT_EQ((*extents)[i].offset, (*read->extents)[i].offset); + ASSERT_EQ((*extents)[i].length, (*read->extents)[i].length); + (*read->extents)[i].bl = (*extents)[i].bl; + (*read->extents)[i].extent_map = (*extents)[i].extent_map; + } + + if (read->version != nullptr) { + *(read->version) = version; + } + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_read_parent(MockUtils &mock_utils, uint64_t object_no, + io::ReadExtents* extents, librados::snap_t snap_id, + int r) { + EXPECT_CALL(mock_utils, + read_parent(_, object_no, extents, snap_id, _, _)) + .WillOnce(WithArg<5>(CompleteContext( + r, static_cast<asio::ContextWQ*>(nullptr)))); + } + + void expect_object_write(uint64_t object_off, const std::string& data, + int write_flags, + std::optional<uint64_t> assert_version) { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this, object_off, data, write_flags, + assert_version](io::ObjectDispatchSpec* spec) { + auto* write = boost::get<io::ObjectDispatchSpec::WriteRequest>( + &spec->request); + ASSERT_TRUE(write != nullptr); + + ASSERT_EQ(object_off, write->object_off); + ASSERT_TRUE(data == write->data.to_str()); + ASSERT_EQ(write_flags, write->write_flags); + ASSERT_EQ(assert_version, write->assert_version); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_object_write_same() { + EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_)) + .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) { + auto* write_same = boost::get< + io::ObjectDispatchSpec::WriteSameRequest>( + &spec->request); + ASSERT_TRUE(write_same != nullptr); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + dispatcher_ctx = &spec->dispatcher_ctx; + })); + } + + void expect_get_object_size() { + EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return( + mock_image_ctx->layout.object_size)); + } + + void expect_remap_extents(uint64_t offset, uint64_t length) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, remap_extents( + ElementsAre(Pair(offset, length)), + io::IMAGE_EXTENTS_MAP_TYPE_PHYSICAL_TO_LOGICAL)); + } + + void expect_get_parent_overlap(uint64_t overlap) { + EXPECT_CALL(*mock_image_ctx, get_parent_overlap(_, _)) + .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) { + *o = overlap; + return 0; + }))); + } + + void expect_prune_parent_extents(uint64_t object_overlap) { + EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _)) + .WillOnce(Return(object_overlap)); + } + + void expect_copyup(MockAbstractObjectWriteRequest** write_request, int r) { + EXPECT_CALL(copyup_request, append_request(_, _)) + .WillOnce(WithArg<0>( + Invoke([write_request]( + MockAbstractObjectWriteRequest *req) { + *write_request = req; + }))); + EXPECT_CALL(copyup_request, send()) + .WillOnce(Invoke([write_request, r]() { + (*write_request)->handle_copyup(r); + })); + } + + void expect_encrypt(int count = 1) { + EXPECT_CALL(*crypto, encrypt(_, _)).Times(count); + } + + void expect_decrypt(int count = 1) { + EXPECT_CALL(*crypto, decrypt(_, _)).Times(count); + } +}; + +TEST_F(TestMockCryptoCryptoObjectDispatch, Flush) { + ASSERT_FALSE(mock_crypto_object_dispatch->flush( + io::FLUSH_SOURCE_USER, {}, nullptr, nullptr, &on_finish, nullptr)); + ASSERT_EQ(on_finish, &finished_cond); // not modified + on_finish->complete(0); + ASSERT_EQ(0, finished_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, Discard) { + expect_object_write_same(); + ASSERT_TRUE(mock_crypto_object_dispatch->discard( + 0, 0, 4096, mock_image_ctx->get_data_io_context(), 0, {}, + &object_dispatch_flags, nullptr, &dispatch_result, &on_finish, + on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedReadFail) { + io::ReadExtents extents = {{0, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(-EIO); + ASSERT_EQ(-EIO, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedRead) { + io::ReadExtents extents = {{0, 16384}, {32768, 4096}}; + extents[0].bl.append(std::string(1024, '1') + std::string(1024, '2') + + std::string(1024, '3') + std::string(1024, '4')); + extents[0].extent_map = {{1024, 1024}, {3072, 2048}, {16384 - 1024, 1024}}; + extents[1].bl.append(std::string(4096, '0')); + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + expect_decrypt(3); + dispatcher_ctx->complete(0); + ASSERT_EQ(16384 + 4096, dispatched_cond.wait()); + + auto expected_bl_data = ( + std::string(1024, '\0') + std::string(1024, '1') + + std::string(1024, '\0') + std::string(1024, '2') + + std::string(1024, '3') + std::string(3072, '\0') + + std::string(3072, '\0') + std::string(1024, '4')); + ASSERT_TRUE(extents[0].bl.to_str() == expected_bl_data); + ASSERT_THAT(extents[0].extent_map, + ElementsAre(Pair(0, 8192), Pair(16384 - 4096, 4096))); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParent) { + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + expect_read_parent(mock_utils, 0, &extents, CEPH_NOSNAP, 8192); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + // no decrypt + dispatcher_ctx->complete(-ENOENT); + ASSERT_EQ(8192, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFromParentDisabled) { + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, + io::READ_FLAG_DISABLE_READ_FROM_PARENT, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + // no decrypt + dispatcher_ctx->complete(-ENOENT); + ASSERT_EQ(-ENOENT, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedRead) { + io::ReadExtents extents = {{0, 1}, {8191, 1}, {8193, 1}, + {16384 + 1, 4096 * 5 - 2}}; + io::ReadExtents aligned_extents = {{0, 4096}, {4096, 4096}, {8192, 4096}, + {16384, 4096 * 5}}; + aligned_extents[0].bl.append(std::string("1") + std::string(4096, '0')); + aligned_extents[1].bl.append(std::string(4095, '0') + std::string("2")); + aligned_extents[2].bl.append(std::string("03") + std::string(4094, '0')); + aligned_extents[3].bl.append(std::string("0") + std::string(4095, '4') + + std::string(4096, '5') + + std::string(4095, '6') + std::string("0")); + aligned_extents[3].extent_map = {{16384, 4096}, {16384 + 2 * 4096, 4096}, + {16384 + 4 * 4096, 4096}}; + + expect_object_read(&aligned_extents); + ASSERT_TRUE(mock_crypto_object_dispatch->read( + 0, &extents, mock_image_ctx->get_data_io_context(), 0, 0, {}, + nullptr, &object_dispatch_flags, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + dispatcher_ctx->complete(4096*8); + ASSERT_EQ(3 + 4096 * 5 - 2, dispatched_cond.wait()); + ASSERT_TRUE(extents[0].bl.to_str() == std::string("1")); + ASSERT_TRUE(extents[1].bl.to_str() == std::string("2")); + ASSERT_TRUE(extents[2].bl.to_str() == std::string("3")); + + auto expected_bl_data = (std::string(4095, '4') + std::string(4096, '5') + + std::string(4095, '6')); + ASSERT_TRUE(extents[3].bl.to_str() == expected_bl_data); + ASSERT_THAT(extents[3].extent_map, + ElementsAre(Pair(16384 + 1, 4095), Pair(16384 + 2 * 4096, 4096), + Pair(16384 + 4 * 4096, 4095))); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, AlignedWrite) { + expect_encrypt(); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 0, std::move(data), mock_image_ctx->get_data_io_context(), 0, 0, + std::nullopt, {}, nullptr, nullptr, &dispatch_result, &on_finish, + on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_CONTINUE); + ASSERT_EQ(on_finish, &finished_cond); // not modified + on_finish->complete(0); + ASSERT_EQ(0, finished_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWrite) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithNoObject) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(0); + auto expected_data = (std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0')); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailCreate) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(0); + auto expected_data = (std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0')); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + uint64_t version = 1234; + expect_object_read(&extents, version); + dispatcher_ctx->complete(-EEXIST); // complete write, request will restart + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data2 = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data2, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteCopyup) { + MockObjectMap mock_object_map; + mock_image_ctx->object_map = &mock_object_map; + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx->exclusive_lock = &mock_exclusive_lock; + + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(mock_image_ctx->layout.object_size); + expect_remap_extents(0, mock_image_ctx->layout.object_size); + expect_prune_parent_extents(mock_image_ctx->layout.object_size); + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce( + Return(false)); + MockAbstractObjectWriteRequest *write_request = nullptr; + expect_copyup(&write_request, 0); + + // unaligned write restarted + uint64_t version = 1234; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + dispatcher_ctx->complete(-ENOENT); // complete first read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete second read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteEmptyCopyup) { + MockObjectMap mock_object_map; + mock_image_ctx->object_map = &mock_object_map; + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx->exclusive_lock = &mock_exclusive_lock; + + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + expect_get_object_size(); + expect_get_parent_overlap(mock_image_ctx->layout.object_size); + expect_remap_extents(0, mock_image_ctx->layout.object_size); + expect_prune_parent_extents(mock_image_ctx->layout.object_size); + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + EXPECT_CALL(*mock_image_ctx->object_map, object_may_exist(0)).WillOnce( + Return(false)); + MockAbstractObjectWriteRequest *write_request = nullptr; + expect_copyup(&write_request, 0); + + // unaligned write restarted + expect_object_read(&extents); + dispatcher_ctx->complete(-ENOENT); // complete first read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + auto expected_data = + std::string(1, '\0') + std::string(8192, '1') + + std::string(4095, '\0'); + expect_object_write(0, expected_data, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, + std::nullopt); + dispatcher_ctx->complete(-ENOENT); // complete second read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteFailVersionCheck) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::nullopt, {}, nullptr, nullptr, &dispatch_result, + &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + version = 1235; + expect_object_read(&extents, version); + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + dispatcher_ctx->complete(-ERANGE); // complete write, request will restart + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithAssertVersion) { + ceph::bufferlist write_data; + uint64_t version = 1234; + uint64_t assert_version = 1233; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents, version); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, 0, std::make_optional(assert_version), {}, nullptr, nullptr, + &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(-ERANGE, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, UnalignedWriteWithExclusiveCreate) { + ceph::bufferlist write_data; + write_data.append(std::string(8192, '1')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + expect_object_read(&extents); + ASSERT_TRUE(mock_crypto_object_dispatch->write( + 0, 1, std::move(write_data), mock_image_ctx->get_data_io_context(), + 0, io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, nullptr, + nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(8192); // complete read + ASSERT_EQ(-EEXIST, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWrite) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + ceph::bufferlist cmp_data; + cmp_data.append(std::string(4096, '2')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + extents[2].bl.append(std::string(8192, '2')); + expect_object_read(&extents, version); + + ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write( + 0, 1, std::move(cmp_data), std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr, + nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + auto expected_data = + std::string("2") + std::string(8192, '1') + std::string(4095, '3'); + expect_object_write(0, expected_data, 0, std::make_optional(version)); + dispatcher_ctx->complete(4096*4); // complete read + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); // complete write + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWriteFail) { + ceph::bufferlist write_data; + uint64_t version = 1234; + write_data.append(std::string(8192, '1')); + ceph::bufferlist cmp_data; + cmp_data.append(std::string(4094, '2') + std::string(2, '4')); + io::ReadExtents extents = {{0, 4096}, {8192, 4096}, {0, 8192}}; + extents[0].bl.append(std::string(4096, '2')); + extents[1].bl.append(std::string(4096, '3')); + extents[2].bl.append(std::string(8192, '2')); + expect_object_read(&extents, version); + + uint64_t mismatch_offset; + ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write( + 0, 1, std::move(cmp_data), std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, &mismatch_offset, + nullptr, nullptr, &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + ASSERT_EQ(on_finish, &finished_cond); + + dispatcher_ctx->complete(4096*4); // complete read + ASSERT_EQ(-EILSEQ, dispatched_cond.wait()); + ASSERT_EQ(mismatch_offset, 4094); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, WriteSame) { + io::LightweightBufferExtents buffer_extents; + ceph::bufferlist write_data; + write_data.append(std::string("12")); + expect_object_write(0, std::string("12121") , 0, std::nullopt); + ASSERT_TRUE(mock_crypto_object_dispatch->write_same( + 0, 0, 5, {{0, 5}}, std::move(write_data), + mock_image_ctx->get_data_io_context(), 0, {}, nullptr, nullptr, + &dispatch_result, &on_finish, on_dispatched)); + ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE); + + ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0)); + dispatcher_ctx->complete(0); + ASSERT_EQ(0, dispatched_cond.wait()); +} + +TEST_F(TestMockCryptoCryptoObjectDispatch, PrepareCopyup) { + char* data = (char*)"0123456789"; + io::SnapshotSparseBufferlist snapshot_sparse_bufferlist; + auto& snap1 = snapshot_sparse_bufferlist[0]; + auto& snap2 = snapshot_sparse_bufferlist[1]; + + snap1.insert(0, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 1, 1)}); + snap1.insert(8191, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 2, 1)}); + snap1.insert(8193, 3, {io::SPARSE_EXTENT_STATE_DATA, 3, + ceph::bufferlist::static_from_mem(data + 3, 3)}); + + snap2.insert(0, 2, {io::SPARSE_EXTENT_STATE_ZEROED, 2}); + snap2.insert(8191, 3, {io::SPARSE_EXTENT_STATE_DATA, 3, + ceph::bufferlist::static_from_mem(data + 6, 3)}); + snap2.insert(16384, 1, {io::SPARSE_EXTENT_STATE_DATA, 1, + ceph::bufferlist::static_from_mem(data + 9, 1)}); + + expect_get_object_size(); + expect_encrypt(6); + InSequence seq; + expect_remap_extents(0, 4096); + expect_remap_extents(4096, 4096); + expect_remap_extents(8192, 4096); + expect_remap_extents(0, 4096); + expect_remap_extents(4096, 8192); + expect_remap_extents(16384, 4096); + ASSERT_EQ(0, mock_crypto_object_dispatch->prepare_copyup( + 0, &snapshot_sparse_bufferlist)); + + ASSERT_EQ(2, snapshot_sparse_bufferlist.size()); + + auto& snap1_result = snapshot_sparse_bufferlist[0]; + auto& snap2_result = snapshot_sparse_bufferlist[1]; + + auto it = snap1_result.begin(); + ASSERT_NE(it, snap1_result.end()); + ASSERT_EQ(0, it.get_off()); + ASSERT_EQ(4096 * 3, it.get_len()); + + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string("1") + std::string(4095, '\0') + + std::string(4095, '\0') + std::string("2") + + std::string(1, '\0') + std::string("345") + std::string(4092, '\0')); + ASSERT_EQ(++it, snap1_result.end()); + + it = snap2_result.begin(); + ASSERT_NE(it, snap2_result.end()); + ASSERT_EQ(0, it.get_off()); + ASSERT_EQ(4096 * 3, it.get_len()); + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string(4096, '\0') + + std::string(4095, '\0') + std::string("6") + + std::string("7845") + std::string(4092, '\0')); + + ASSERT_NE(++it, snap2_result.end()); + ASSERT_EQ(16384, it.get_off()); + ASSERT_EQ(4096, it.get_len()); + ASSERT_TRUE(it.get_val().bl.to_str() == + std::string("9") + std::string(4095, '\0')); + ASSERT_EQ(++it, snap2_result.end()); +} + +} // namespace io +} // namespace librbd |