#include "librbd/crypto/CryptoObjectDispatch.h"
#include "include/ceph_assert.h"
+#include "include/neorados/RADOS.hpp"
#include "common/dout.h"
#include "librbd/ImageCtx.h"
#include "librbd/Utils.h"
namespace librbd {
namespace crypto {
+using librbd::util::create_context_callback;
using librbd::util::data_object_name;
template <typename I>
-struct C_EncryptedObjectReadRequest : public Context {
-
+struct C_AlignedObjectReadRequest : public Context {
I* image_ctx;
- CryptoInterface* crypto;
+ ceph::ref_t<CryptoInterface> crypto;
uint64_t object_no;
io::ReadExtents* extents;
- Context* onfinish;
+ IOContext io_context;
+ const ZTracer::Trace parent_trace;
+ uint64_t* version;
+ Context* on_finish;
io::ObjectDispatchSpec* req;
+ bool disable_read_from_parent;
- C_EncryptedObjectReadRequest(
- I* image_ctx, CryptoInterface* crypto, uint64_t object_no,
- io::ReadExtents* extents, IOContext io_context,
+ C_AlignedObjectReadRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
uint64_t* version, int* object_dispatch_flags,
- Context** on_finish,
- Context* on_dispatched) : image_ctx(image_ctx),
- crypto(crypto),
- object_no(object_no),
- extents(extents),
- onfinish(on_dispatched) {
- *on_finish = util::create_context_callback<
- Context, &Context::complete>(*on_finish, crypto);
+ Context* on_dispatched
+ ) : image_ctx(image_ctx), crypto(crypto), object_no(object_no),
+ extents(extents), io_context(io_context),
+ parent_trace(parent_trace), version(version),
+ on_finish(on_dispatched) {
+ disable_read_from_parent =
+ ((read_flags & io::READ_FLAG_DISABLE_READ_FROM_PARENT) != 0);
+ read_flags |= io::READ_FLAG_DISABLE_READ_FROM_PARENT;
+
+ auto ctx = create_context_callback<
+ C_AlignedObjectReadRequest<I>,
+ &C_AlignedObjectReadRequest<I>::handle_read>(this);
req = io::ObjectDispatchSpec::create_read(
image_ctx, io::OBJECT_DISPATCH_LAYER_CRYPTO, object_no,
extents, io_context, op_flags, read_flags, parent_trace,
- version, this);
+ version, ctx);
}
void send() {
}
void finish(int r) override {
+ on_finish->complete(r);
+ }
+
+ void handle_read(int r) {
auto cct = image_ctx->cct;
ldout(cct, 20) << "r=" << r << dendl;
if (r == 0) {
for (auto& extent: *extents) {
- io::util::unsparsify(cct, &extent.bl, extent.extent_map,
- extent.offset, extent.length);
-
- auto crypto_ret = crypto->decrypt(
- &extent.bl,
+ auto crypto_ret = crypto->decrypt_aligned_extent(
+ extent,
Striper::get_file_offset(
- cct, &image_ctx->layout, object_no,
- extent.offset));
+ cct, &image_ctx->layout, object_no, extent.offset));
if (crypto_ret != 0) {
ceph_assert(crypto_ret < 0);
r = crypto_ret;
break;
}
+ r += extent.length;
+ }
+ }
+
+ if (r == -ENOENT && !disable_read_from_parent) {
+ io::util::read_parent<I>(
+ image_ctx, object_no, extents,
+ io_context->read_snap().value_or(CEPH_NOSNAP),
+ parent_trace, this);
+ } else {
+ complete(r);
+ }
+ }
+};
+
+template <typename I>
+struct C_UnalignedObjectReadRequest : public Context {
+ CephContext* cct;
+ io::ReadExtents* extents;
+ Context* on_finish;
+ io::ReadExtents aligned_extents;
+ io::ObjectDispatchSpec* req;
+
+ C_UnalignedObjectReadRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
+ uint64_t* version, int* object_dispatch_flags,
+ Context* on_dispatched) : cct(image_ctx->cct), extents(extents),
+ on_finish(on_dispatched) {
+ crypto->align_extents(*extents, &aligned_extents);
+
+ // send the aligned read back to get decrypted
+ req = io::ObjectDispatchSpec::create_read(
+ image_ctx, io::OBJECT_DISPATCH_LAYER_NONE, object_no,
+ &aligned_extents, io_context, op_flags, read_flags, parent_trace,
+ version, this);
+ }
+
+ void send() {
+ req->send();
+ }
+
+ void remove_alignment_data() {
+ for (uint64_t i = 0; i < extents->size(); ++i) {
+ auto& extent = (*extents)[i];
+ auto& aligned_extent = aligned_extents[i];
+ if (aligned_extent.extent_map.empty()) {
+ aligned_extent.bl.splice(extent.offset - aligned_extent.offset,
+ extent.length, &extent.bl);
+ } else {
+ for (auto [off, len]: aligned_extent.extent_map) {
+ ceph::bufferlist tmp;
+ aligned_extent.bl.splice(0, len, &tmp);
+
+ uint64_t bytes_to_skip = 0;
+ if (off < extent.offset) {
+ bytes_to_skip = extent.offset - off;
+ if (len <= bytes_to_skip) {
+ continue;
+ }
+ off += bytes_to_skip;
+ len -= bytes_to_skip;
+ }
+
+ len = std::min(len, extent.offset + extent.length - off);
+ if (len == 0) {
+ continue;
+ }
+
+ if (len > 0) {
+ tmp.splice(bytes_to_skip, len, &extent.bl);
+ extent.extent_map.emplace_back(off, len);
+ }
+ }
+ }
+ }
+ }
+ void finish(int r) override {
+ ldout(cct, 20) << "r=" << r << dendl;
+ if (r >= 0) {
+ remove_alignment_data();
+
+ r = 0;
+ for (auto& extent: *extents) {
r += extent.length;
}
}
- onfinish->complete(r);
+ on_finish->complete(r);
+ }
+};
+
+template <typename I>
+struct C_UnalignedObjectWriteRequest : public Context {
+ I* image_ctx;
+ ceph::ref_t<CryptoInterface> crypto;
+ uint64_t object_no;
+ uint64_t object_off;
+ ceph::bufferlist data;
+ ceph::bufferlist cmp_data;
+ uint64_t* mismatch_offset;
+ IOContext io_context;
+ int op_flags;
+ int write_flags;
+ std::optional<uint64_t> assert_version;
+ const ZTracer::Trace parent_trace;
+ int* object_dispatch_flags;
+ uint64_t* journal_tid;
+ Context* on_finish;
+ ceph::bufferlist aligned_data;
+ io::ReadExtents extents;
+ uint64_t version;
+ C_UnalignedObjectReadRequest<I>* read_req;
+ bool object_exists;
+
+ C_UnalignedObjectWriteRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ ceph::bufferlist&& cmp_data, uint64_t* mismatch_offset,
+ IOContext io_context, int op_flags, int write_flags,
+ std::optional<uint64_t> assert_version,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid,Context* on_dispatched
+ ) : image_ctx(image_ctx), crypto(crypto), object_no(object_no),
+ object_off(object_off), data(data), cmp_data(cmp_data),
+ mismatch_offset(mismatch_offset), io_context(io_context),
+ op_flags(op_flags), write_flags(write_flags),
+ assert_version(assert_version), parent_trace(parent_trace),
+ object_dispatch_flags(object_dispatch_flags),
+ journal_tid(journal_tid), on_finish(on_dispatched) {
+ // build read extents
+ auto [pre_align, post_align] = crypto->get_pre_and_post_align(
+ object_off, data.length());
+ if (pre_align != 0) {
+ extents.emplace_back(object_off - pre_align, pre_align);
+ }
+ if (post_align != 0) {
+ extents.emplace_back(object_off + data.length(), post_align);
+ }
+ if (cmp_data.length() != 0) {
+ extents.emplace_back(object_off, cmp_data.length());
+ }
+
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_read>(this);
+
+ read_req = new C_UnalignedObjectReadRequest<I>(
+ image_ctx, crypto, object_no, &extents, io_context,
+ 0, 0, parent_trace, &version, 0, ctx);
+ }
+
+ void send() {
+ read_req->send();
+ }
+
+ bool check_cmp_data() {
+ if (cmp_data.length() == 0) {
+ return true;
+ }
+
+ auto& cmp_extent = extents.back();
+ io::util::unsparsify(image_ctx->cct, &cmp_extent.bl,
+ cmp_extent.extent_map, cmp_extent.offset,
+ cmp_extent.length);
+
+ std::optional<uint64_t> found_mismatch = std::nullopt;
+
+ auto it1 = cmp_data.cbegin();
+ auto it2 = cmp_extent.bl.cbegin();
+ for (uint64_t idx = 0; idx < cmp_data.length(); ++idx) {
+ if (*it1 != *it2) {
+ found_mismatch = std::make_optional(idx);
+ break;
+ }
+ ++it1;
+ ++it2;
+ }
+
+ extents.pop_back();
+
+ if (found_mismatch.has_value()) {
+ if (mismatch_offset != nullptr) {
+ *mismatch_offset = found_mismatch.value();
+ }
+ complete(-EILSEQ);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool check_create_exclusive() {
+ bool exclusive =
+ ((write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE) != 0);
+ if (exclusive && object_exists) {
+ complete(-EEXIST);
+ return false;
+ }
+ return true;
+ }
+
+ bool check_version() {
+ int r = 0;
+ if (assert_version.has_value()) {
+ if (!object_exists) {
+ r = -ENOENT;
+ } else if (assert_version.value() < version) {
+ r = -ERANGE;
+ } else if (assert_version.value() > version) {
+ r = -EOVERFLOW;
+ }
+ }
+
+ if (r != 0) {
+ complete(r);
+ return false;
+ }
+ return true;
+ }
+
+ void build_aligned_data() {
+ auto [pre_align, post_align] = crypto->get_pre_and_post_align(
+ object_off, data.length());
+ if (pre_align != 0) {
+ auto &extent = extents.front();
+ io::util::unsparsify(image_ctx->cct, &extent.bl, extent.extent_map,
+ extent.offset, extent.length);
+ extent.bl.splice(0, pre_align, &aligned_data);
+ }
+ aligned_data.append(data);
+ if (post_align != 0) {
+ auto &extent = extents.back();
+ io::util::unsparsify(image_ctx->cct, &extent.bl, extent.extent_map,
+ extent.offset, extent.length);
+ extent.bl.splice(0, post_align, &aligned_data);
+ }
+ }
+
+ void handle_read(int r) {
+ ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (r == -ENOENT) {
+ object_exists = false;
+ } else if (r < 0) {
+ complete(r);
+ return;
+ } else {
+ object_exists = true;
+ }
+
+ if (!check_create_exclusive() || !check_version() || !check_cmp_data()) {
+ return;
+ }
+
+ build_aligned_data();
+
+ auto aligned_off = crypto->align(object_off, data.length()).first;
+ auto new_write_flags = write_flags;
+ auto new_assert_version = std::make_optional(version);
+ if (!object_exists) {
+ new_write_flags |= io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE;
+ new_assert_version = std::nullopt;
+ }
+
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_write>(this);
+
+ // send back aligned write back to get encrypted and committed
+ auto write_req = io::ObjectDispatchSpec::create_write(
+ image_ctx, io::OBJECT_DISPATCH_LAYER_NONE, object_no,
+ aligned_off, std::move(aligned_data), io_context, op_flags,
+ new_write_flags, new_assert_version,
+ journal_tid == nullptr ? 0 : *journal_tid, parent_trace, ctx);
+ write_req->send();
+ }
+
+ void handle_write(int r) {
+ bool exclusive = write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE;
+ bool restart = false;
+ if (r == -ERANGE && !assert_version.has_value()) {
+ restart = true;
+ } else if (r == -EEXIST && !exclusive) {
+ restart = true;
+ }
+
+ if (restart) {
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ image_ctx, crypto, object_no, object_off,
+ std::move(data), std::move(cmp_data),
+ mismatch_offset, io_context, op_flags, write_flags,
+ assert_version, parent_trace,
+ object_dispatch_flags, journal_tid, this);
+ req->send();
+ } else {
+ complete(r);
+ }
+ }
+
+ void finish(int r) override {
+ on_finish->complete(r);
}
};
template <typename I>
CryptoObjectDispatch<I>::CryptoObjectDispatch(
- I* image_ctx, CryptoInterface *crypto)
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto)
: m_image_ctx(image_ctx), m_crypto(crypto) {
}
ceph_assert(m_crypto != nullptr);
*dispatch_result = io::DISPATCH_RESULT_COMPLETE;
- auto req = new C_EncryptedObjectReadRequest<I>(
- m_image_ctx, m_crypto, object_no, extents, io_context,
- op_flags, read_flags, parent_trace, version, object_dispatch_flags,
- on_finish, on_dispatched);
- req->send();
+ if (m_crypto->is_aligned(*extents)) {
+ auto req = new C_AlignedObjectReadRequest<I>(
+ m_image_ctx, m_crypto, object_no, extents, io_context,
+ op_flags, read_flags, parent_trace, version, object_dispatch_flags,
+ on_dispatched);
+ req->send();
+ } else {
+ auto req = new C_UnalignedObjectReadRequest<I>(
+ m_image_ctx, m_crypto, object_no, extents, io_context,
+ op_flags, read_flags, parent_trace, version, object_dispatch_flags,
+ on_dispatched);
+ req->send();
+ }
+
return true;
}
<< object_off << "~" << data.length() << dendl;
ceph_assert(m_crypto != nullptr);
- auto r = m_crypto->encrypt(
- &data,
- Striper::get_file_offset(
- m_image_ctx->cct, &m_image_ctx->layout, object_no,
- object_off));
- *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
- on_dispatched->complete(r);
+ if (m_crypto->is_aligned(object_off, data.length())) {
+ auto r = m_crypto->encrypt(
+ &data,
+ Striper::get_file_offset(
+ m_image_ctx->cct, &m_image_ctx->layout, object_no,
+ object_off));
+ *dispatch_result = r == 0 ? io::DISPATCH_RESULT_CONTINUE
+ : io::DISPATCH_RESULT_COMPLETE;
+ on_dispatched->complete(r);
+ } else {
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ m_image_ctx, m_crypto, object_no, object_off, std::move(data), {},
+ nullptr, io_context, op_flags, write_flags, assert_version,
+ parent_trace, object_dispatch_flags, journal_tid, on_dispatched);
+ req->send();
+ }
+
return true;
}
<< dendl;
ceph_assert(m_crypto != nullptr);
- uint64_t image_offset = Striper::get_file_offset(
- m_image_ctx->cct, &m_image_ctx->layout, object_no, object_off);
- auto r = m_crypto->encrypt(&cmp_data, image_offset);
- if (r == 0) {
- r = m_crypto->encrypt(&write_data, image_offset);
- }
- *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
- on_dispatched->complete(r);
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ m_image_ctx, m_crypto, object_no, object_off, std::move(write_data),
+ std::move(cmp_data), mismatch_offset, io_context, op_flags, 0,
+ std::nullopt, parent_trace, object_dispatch_flags, journal_tid,
+ on_dispatched);
+ req->send();
+
return true;
}
#include "test/librbd/mock/crypto/MockCryptoInterface.h"
#include "librbd/crypto/CryptoObjectDispatch.h"
#include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/Utils.h"
namespace librbd {
}
} // namespace util
+
+namespace io {
+namespace util {
+
+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 crypto {
using ::testing::_;
+using ::testing::ElementsAre;
using ::testing::Invoke;
+using ::testing::Pair;
+using ::testing::WithArg;
struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
typedef CryptoObjectDispatch<librbd::MockImageCtx> MockCryptoObjectDispatch;
+ typedef io::util::Mock MockUtils;
MockCryptoInterface* crypto;
MockImageCtx* mock_image_ctx;
io::DispatchResult dispatch_result;
io::Extents extent_map;
int object_dispatch_flags = 0;
+ MockUtils mock_utils;
void SetUp() override {
TestMockFixture::SetUp();
crypto = new MockCryptoInterface();
mock_image_ctx = new MockImageCtx(*ictx);
mock_crypto_object_dispatch = new MockCryptoObjectDispatch(mock_image_ctx, crypto);
- data.append("X");
+ data.append(std::string(4096, '1'));
}
void TearDown() override {
TestMockFixture::TearDown();
}
- void expect_object_read() {
+ void expect_object_read(io::ReadExtents* extents, uint64_t version = 0) {
EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
- .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) {
+ .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_object_write() {
+ 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](io::ObjectDispatchSpec* spec) {
+ .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;
}));
ASSERT_EQ(0, dispatched_cond.wait());
}
-TEST_F(TestMockCryptoCryptoObjectDispatch, ReadFail) {
- expect_object_read();
+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_NE(on_finish, &finished_cond);
+ 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, Read) {
- expect_object_read();
- io::ReadExtents extents = {{0, 4096}, {8192, 4096}};
+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_NE(on_finish, &finished_cond);
+ ASSERT_EQ(on_finish, &finished_cond);
ASSERT_EQ(ETIMEDOUT, dispatched_cond.wait_for(0));
- expect_decrypt(2);
+ 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, Write) {
+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(0);
+ 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,
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(0); // 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);
+
+ 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);
+
+ 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(0); // 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, 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(0); // 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(0); // 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(0); // 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(0); // complete read
+ ASSERT_EQ(-EEXIST, dispatched_cond.wait());
+}
+
TEST_F(TestMockCryptoCryptoObjectDispatch, CompareAndWrite) {
- ceph::bufferlist cmp_data(data);
+ 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);
- expect_encrypt(2);
ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write(
- 0, 0, std::move(cmp_data), std::move(data),
+ 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_CONTINUE);
- ASSERT_EQ(on_finish, &finished_cond); // not modified
- on_finish->complete(0);
- ASSERT_EQ(0, finished_cond.wait());
+ 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(0); // 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(0); // complete read
+ ASSERT_EQ(-EILSEQ, dispatched_cond.wait());
+ ASSERT_EQ(mismatch_offset, 4094);
}
TEST_F(TestMockCryptoCryptoObjectDispatch, WriteSame) {
io::LightweightBufferExtents buffer_extents;
-
- expect_object_write();
+ 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, 4096, std::move(buffer_extents), std::move(data),
+ 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);