This commit adds a new object dispatch layer to enable encryption of RBD images.
Signed-off-by: Or Ozeri <oro@il.ibm.com>
cache/rwl/InitRequest.cc
cache/rwl/ShutdownRequest.cc
cache/WriteAroundObjectDispatch.cc
+ crypto/CryptoObjectDispatch.cc
deep_copy/ImageCopyRequest.cc
deep_copy/MetadataCopyRequest.cc
deep_copy/ObjectCopyRequest.cc
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
+
+#include "common/RefCountedObj.h"
+#include "include/buffer.h"
+
+namespace librbd {
+namespace crypto {
+
+class CryptoInterface : public RefCountedObject {
+
+public:
+ virtual void encrypt(ceph::bufferlist&& data,
+ uint64_t image_offset) const = 0;
+ virtual void decrypt(ceph::bufferlist&& data,
+ uint64_t image_offset) const = 0;
+
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "include/ceph_assert.h"
+#include "common/dout.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::CryptoObjectDispatch: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+
+using librbd::util::data_object_name;
+
+template <typename I>
+struct C_EncryptedObjectReadRequest : public Context {
+
+ I* image_ctx;
+ CryptoInterface* crypto;
+ uint64_t object_no;
+ uint64_t object_off;
+ ceph::bufferlist* read_data;
+ Context* onfinish;
+ io::ReadResult::C_ObjectReadRequest* req_comp;
+ io::ObjectDispatchSpec* req;
+
+ C_EncryptedObjectReadRequest(
+ I* image_ctx, CryptoInterface* crypto, uint64_t object_no,
+ uint64_t object_off, uint64_t object_len, librados::snap_t snap_id,
+ int op_flags, const ZTracer::Trace &parent_trace,
+ ceph::bufferlist* read_data, int* object_dispatch_flags,
+ Context** on_finish,
+ Context* on_dispatched) : image_ctx(image_ctx),
+ crypto(crypto),
+ object_no(object_no),
+ object_off(object_off),
+ read_data(read_data),
+ onfinish(on_dispatched) {
+ *on_finish = util::create_context_callback<
+ Context, &Context::complete>(*on_finish, crypto);
+
+ auto aio_comp = io::AioCompletion::create_and_start(
+ (Context*)this, util::get_image_ctx(image_ctx),
+ io::AIO_TYPE_READ);
+ aio_comp->read_result = io::ReadResult{read_data};
+ aio_comp->set_request_count(1);
+
+ auto req_comp = new io::ReadResult::C_ObjectReadRequest(
+ aio_comp, object_off, object_len, {{0, object_len}});
+
+ req = io::ObjectDispatchSpec::create_read(
+ image_ctx, io::OBJECT_DISPATCH_LAYER_CRYPTO, object_no,
+ {{object_off, object_len}}, snap_id, op_flags, parent_trace,
+ &req_comp->bl, &req_comp->extent_map, nullptr, req_comp);
+ }
+
+ void send() {
+ req->send();
+ }
+
+ void finish(int r) override {
+ ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+ if (r > 0) {
+ crypto->decrypt(
+ std::move(*read_data),
+ Striper::get_file_offset(
+ image_ctx->cct, &image_ctx->layout, object_no,
+ object_off));
+ }
+ onfinish->complete(r);
+ }
+};
+
+template <typename I>
+CryptoObjectDispatch<I>::CryptoObjectDispatch(
+ I* image_ctx, CryptoInterface *crypto)
+ : m_image_ctx(image_ctx), m_crypto(crypto) {
+}
+
+template <typename I>
+void CryptoObjectDispatch<I>::init(Context* on_finish) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 5) << dendl;
+
+ // need to initialize m_crypto here using image header object
+
+ m_image_ctx->io_object_dispatcher->register_dispatch(this);
+
+ on_finish->complete(0);
+}
+
+template <typename I>
+void CryptoObjectDispatch<I>::shut_down(Context* on_finish) {
+ if (m_crypto != nullptr) {
+ m_crypto->put();
+ m_crypto = nullptr;
+ }
+ on_finish->complete(0);
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::read(
+ uint64_t object_no, const io::Extents &extents,
+ librados::snap_t snap_id, int op_flags, const ZTracer::Trace &parent_trace,
+ ceph::bufferlist* read_data, io::Extents* extent_map, uint64_t* version,
+ int* object_dispatch_flags, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << extents << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ if (version != nullptr || extents.size() != 1) {
+ // there's currently no need to support multiple extents
+ // as well as returning object version
+ return false;
+ }
+
+ auto [object_off, object_len] = extents.front();
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = new C_EncryptedObjectReadRequest<I>(
+ m_image_ctx, m_crypto, object_no, object_off, object_len, snap_id,
+ op_flags, parent_trace, read_data, object_dispatch_flags, on_finish,
+ on_dispatched);
+ req->send();
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << data.length() << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ m_crypto->encrypt(
+ std::move(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(0);
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::write_same(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ io::LightweightBufferExtents&& buffer_extents, ceph::bufferlist&& data,
+ const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << object_len << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ // convert to regular write
+ io::LightweightObjectExtent extent(object_no, object_off, object_len, 0);
+ extent.buffer_extents = std::move(buffer_extents);
+
+ bufferlist ws_data;
+ io::util::assemble_write_same_extent(extent, data, &ws_data, true);
+
+ auto ctx = new LambdaContext(
+ [on_finish_ctx=on_dispatched](int r) {
+ on_finish_ctx->complete(r);
+ });
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = io::ObjectDispatchSpec::create_write(
+ m_image_ctx, io::OBJECT_DISPATCH_LAYER_NONE, object_no,
+ object_off, std::move(ws_data), snapc, op_flags, 0,
+ parent_trace, ctx);
+ req->send();
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::compare_and_write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& cmp_data,
+ ceph::bufferlist&& write_data, const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t* mismatch_offset,
+ int* object_dispatch_flags, uint64_t* journal_tid,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << write_data.length()
+ << 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);
+ m_crypto->encrypt(std::move(cmp_data), image_offset);
+ m_crypto->encrypt(std::move(write_data), image_offset);
+ *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
+ on_dispatched->complete(0);
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::discard(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ const ::SnapContext &snapc, int discard_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << object_len << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ // convert to write-same
+ auto ctx = new LambdaContext(
+ [on_finish_ctx=on_dispatched](int r) {
+ on_finish_ctx->complete(r);
+ });
+
+ bufferlist bl;
+ const int buffer_size = 4096;
+ bl.append_zero(buffer_size);
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = io::ObjectDispatchSpec::create_write_same(
+ m_image_ctx, io::OBJECT_DISPATCH_LAYER_NONE, object_no, object_off,
+ object_len, {{0, buffer_size}}, std::move(bl), snapc,
+ *object_dispatch_flags, 0, parent_trace, ctx);
+ req->send();
+ return true;
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::CryptoObjectDispatch<librbd::ImageCtx>;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
+
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/io/Types.h"
+#include "librbd/io/ObjectDispatchInterface.h"
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace crypto {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class CryptoObjectDispatch : public io::ObjectDispatchInterface {
+public:
+ static CryptoObjectDispatch* create(ImageCtxT* image_ctx) {
+ return new CryptoObjectDispatch(image_ctx, nullptr);
+ }
+
+ CryptoObjectDispatch(ImageCtxT* image_ctx, CryptoInterface *crypto);
+
+ io::ObjectDispatchLayer get_dispatch_layer() const override {
+ return io::OBJECT_DISPATCH_LAYER_CRYPTO;
+ }
+
+ void init(Context* on_finish);
+
+ void shut_down(Context* on_finish) override;
+
+ bool read(
+ uint64_t object_no, const io::Extents &extents,
+ librados::snap_t snap_id, int op_flags,
+ const ZTracer::Trace &parent_trace, ceph::bufferlist* read_data,
+ io::Extents* extent_map, uint64_t* version, int* object_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override;
+
+ bool discard(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ const ::SnapContext &snapc, int discard_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool write_same(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ io::LightweightBufferExtents&& buffer_extents, ceph::bufferlist&& data,
+ const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool compare_and_write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& cmp_data,
+ ceph::bufferlist&& write_data, const ::SnapContext &snapc, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t* mismatch_offset,
+ int* object_dispatch_flags, uint64_t* journal_tid,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override;
+
+ bool flush(
+ io::FlushSource flush_source, const ZTracer::Trace &parent_trace,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override {
+ return false;
+ }
+
+ bool invalidate_cache(Context* on_finish) override {
+ return false;
+ }
+ bool reset_existence_cache(Context* on_finish) override {
+ return false;
+ }
+
+ void extent_overwritten(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ uint64_t journal_tid, uint64_t new_journal_tid) override {
+ }
+
+private:
+
+ ImageCtxT* m_image_ctx;
+ CryptoInterface *m_crypto;
+
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::CryptoObjectDispatch<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
enum ObjectDispatchLayer {
OBJECT_DISPATCH_LAYER_NONE = 0,
OBJECT_DISPATCH_LAYER_CACHE,
+ OBJECT_DISPATCH_LAYER_CRYPTO,
OBJECT_DISPATCH_LAYER_JOURNAL,
OBJECT_DISPATCH_LAYER_PARENT_CACHE,
OBJECT_DISPATCH_LAYER_SCHEDULER,
test_mock_Watcher.cc
cache/test_mock_WriteAroundObjectDispatch.cc
cache/test_mock_ParentCacheObjectDispatch.cc
+ crypto/test_mock_CryptoObjectDispatch.cc
deep_copy/test_mock_ImageCopyRequest.cc
deep_copy/test_mock_MetadataCopyRequest.cc
deep_copy/test_mock_ObjectCopyRequest.cc
--- /dev/null
+// -*- 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/crypto/MockCryptoInterface.h"
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+
+namespace librbd {
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/CryptoObjectDispatch.cc"
+
+namespace librbd {
+namespace crypto {
+
+using ::testing::_;
+using ::testing::Invoke;
+
+struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
+ typedef CryptoObjectDispatch<librbd::MockImageCtx> MockCryptoObjectDispatch;
+
+ 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;
+
+ 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("X");
+ }
+
+ 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_image_ctx;
+
+ TestMockFixture::TearDown();
+ }
+
+ void expect_object_read() {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
+ .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) {
+ auto* read = boost::get<io::ObjectDispatchSpec::ReadRequest>(
+ &spec->request);
+ ASSERT_TRUE(read != nullptr);
+
+ spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ dispatcher_ctx = &spec->dispatcher_ctx;
+ }));
+ }
+
+ void expect_object_write() {
+ EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, send(_))
+ .WillOnce(Invoke([this](io::ObjectDispatchSpec* spec) {
+ auto* write = boost::get<io::ObjectDispatchSpec::WriteRequest>(
+ &spec->request);
+ ASSERT_TRUE(write != nullptr);
+
+ 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_encrypt(int count = 1) {
+ EXPECT_CALL(*crypto, encrypt(_, _)).Times(count);
+ }
+
+ void expect_decrypt() {
+ EXPECT_CALL(*crypto, decrypt(_, _));
+ }
+};
+
+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->snapc, 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, ReadFail) {
+ expect_object_read();
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, {{0, 4096}}, CEPH_NOSNAP, 0, {}, &data, &extent_map, 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(ETIMEDOUT, dispatched_cond.wait_for(0));
+ dispatcher_ctx->complete(-EIO);
+ ASSERT_EQ(-EIO, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, Read) {
+ expect_object_read();
+ ASSERT_TRUE(mock_crypto_object_dispatch->read(
+ 0, {{0, 4096}}, CEPH_NOSNAP, 0, {}, &data, &extent_map, 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(ETIMEDOUT, dispatched_cond.wait_for(0));
+
+ expect_decrypt();
+ dispatcher_ctx->complete(0);
+ ASSERT_EQ(4096, dispatched_cond.wait());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, Write) {
+ expect_encrypt();
+ ASSERT_TRUE(mock_crypto_object_dispatch->write(
+ 0, 0, std::move(data), mock_image_ctx->snapc, 0, {}, 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, CompareAndWrite) {
+ ceph::bufferlist cmp_data(data);
+
+ expect_encrypt(2);
+ ASSERT_TRUE(mock_crypto_object_dispatch->compare_and_write(
+ 0, 0, std::move(cmp_data), std::move(data),
+ mock_image_ctx->snapc, 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());
+}
+
+TEST_F(TestMockCryptoCryptoObjectDispatch, WriteSame) {
+ io::LightweightBufferExtents buffer_extents;
+
+ expect_object_write();
+ ASSERT_TRUE(mock_crypto_object_dispatch->write_same(
+ 0, 0, 4096, std::move(buffer_extents), std::move(data),
+ mock_image_ctx->snapc, 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());
+}
+
+} // namespace io
+} // namespace librbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H
+#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H
+
+#include "include/buffer.h"
+#include "gmock/gmock.h"
+#include "librbd/crypto/CryptoInterface.h"
+
+namespace librbd {
+namespace crypto {
+
+struct MockCryptoInterface : CryptoInterface {
+
+ MOCK_CONST_METHOD2(encrypt, void(ceph::bufferlist&&, uint64_t));
+ MOCK_CONST_METHOD2(decrypt, void(ceph::bufferlist&&, uint64_t));
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_CRYPTO_INTERFACE_H