]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: add a crypto object dispatch layer
authorOr Ozeri <oro@il.ibm.com>
Thu, 2 Jul 2020 14:50:16 +0000 (17:50 +0300)
committerOr Ozeri <oro@il.ibm.com>
Wed, 5 Aug 2020 09:44:34 +0000 (12:44 +0300)
This commit adds a new object dispatch layer to enable encryption of RBD images.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
src/librbd/CMakeLists.txt
src/librbd/crypto/CryptoInterface.h [new file with mode: 0644]
src/librbd/crypto/CryptoObjectDispatch.cc [new file with mode: 0644]
src/librbd/crypto/CryptoObjectDispatch.h [new file with mode: 0644]
src/librbd/io/Types.h
src/test/librbd/CMakeLists.txt
src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc [new file with mode: 0644]
src/test/librbd/mock/crypto/MockCryptoInterface.h [new file with mode: 0644]

index 65e98ad1f369d0f21c721489cb40dec21a5445a8..2f8bb82fa1b195ed5a5b1ca81fc47485d0efefee 100644 (file)
@@ -46,6 +46,7 @@ set(librbd_internal_srcs
   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
diff --git a/src/librbd/crypto/CryptoInterface.h b/src/librbd/crypto/CryptoInterface.h
new file mode 100644 (file)
index 0000000..28f2baa
--- /dev/null
@@ -0,0 +1,26 @@
+// -*- 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
diff --git a/src/librbd/crypto/CryptoObjectDispatch.cc b/src/librbd/crypto/CryptoObjectDispatch.cc
new file mode 100644 (file)
index 0000000..99ccc49
--- /dev/null
@@ -0,0 +1,254 @@
+// -*- 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>;
diff --git a/src/librbd/crypto/CryptoObjectDispatch.h b/src/librbd/crypto/CryptoObjectDispatch.h
new file mode 100644 (file)
index 0000000..1d9edda
--- /dev/null
@@ -0,0 +1,103 @@
+// -*- 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
index 3d43bcc16ae11f920d475eb28392362c7ea0a40c..6a46788d2ed9d03cc072b4eb824098a772c8e601 100644 (file)
@@ -91,6 +91,7 @@ enum {
 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,
index 403c3c12ccdfe49c00a50fb51114c34c16309519..ae6966d5daf95410780da5b1da1a25d4fa2092ea 100644 (file)
@@ -53,6 +53,7 @@ set(unittest_librbd_srcs
   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
diff --git a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
new file mode 100644 (file)
index 0000000..d38d10d
--- /dev/null
@@ -0,0 +1,204 @@
+// -*- 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
diff --git a/src/test/librbd/mock/crypto/MockCryptoInterface.h b/src/test/librbd/mock/crypto/MockCryptoInterface.h
new file mode 100644 (file)
index 0000000..5bb1c7e
--- /dev/null
@@ -0,0 +1,23 @@
+// -*- 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