]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: crypto alignment support 37680/head
authorOr Ozeri <oro@il.ibm.com>
Sun, 18 Oct 2020 16:10:13 +0000 (19:10 +0300)
committerOr Ozeri <oro@il.ibm.com>
Sun, 25 Oct 2020 08:13:09 +0000 (10:13 +0200)
This commit extents the recently added CryptoObjectDispatch layer to support block ciphers (as opposed to stream ciphers).
In other words, the crypto layer now handles the necessary data alignments before data is passed on to the crypto engine.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
src/librbd/crypto/BlockCrypto.cc
src/librbd/crypto/BlockCrypto.h
src/librbd/crypto/CryptoInterface.h
src/librbd/crypto/CryptoObjectDispatch.cc
src/librbd/crypto/CryptoObjectDispatch.h
src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
src/test/librbd/mock/crypto/MockCryptoInterface.h

index 5bb4cfee498ad6511a6f8c7c58dda3c5b160ea7d..a8203e13647621fc427cb2045902f4762cf6eeb7 100644 (file)
@@ -12,9 +12,10 @@ namespace crypto {
 
 template <typename T>
 BlockCrypto<T>::BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
-                            uint32_t block_size)
+                            uint64_t block_size)
      : m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_size),
        m_iv_size(data_cryptor->get_iv_size()) {
+  ceph_assert(isp2(block_size));
   ceph_assert((block_size % data_cryptor->get_block_size()) == 0);
 }
 
index fa51a6192e255cf9c373065cb75641a0ebfc9bcc..8dec0c5fd02ae17a5c409c0b5d4cc795172e74cc 100644 (file)
@@ -16,19 +16,19 @@ class BlockCrypto : public CryptoInterface {
 
 public:
     BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
-                uint32_t block_size);
+                uint64_t block_size);
 
     int encrypt(ceph::bufferlist* data, uint64_t image_offset) override;
     int decrypt(ceph::bufferlist* data, uint64_t image_offset) override;
 
-    uint32_t get_block_size() const {
+    uint64_t get_block_size() const override {
       return m_block_size;
     }
 
 private:
     CephContext* m_cct;
     DataCryptor<T>* m_data_cryptor;
-    uint32_t m_block_size;
+    uint64_t m_block_size;
     uint32_t m_iv_size;
 
     int crypt(ceph::bufferlist* data, uint64_t image_offset, CipherMode mode);
index e0e4f9f56272bd9a81e704d56e08e272e0309b75..cf60874aa315a8f9d60e5a8cdd150718d6741b9c 100644 (file)
@@ -6,6 +6,8 @@
 
 #include "common/RefCountedObj.h"
 #include "include/buffer.h"
+#include "include/intarith.h"
+#include "librbd/io/Types.h"
 
 namespace librbd {
 namespace crypto {
@@ -15,7 +17,102 @@ class CryptoInterface : public RefCountedObject {
 public:
   virtual int encrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
   virtual int decrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
+  virtual uint64_t get_block_size() const = 0;
 
+  inline std::pair<uint64_t, uint64_t> get_pre_and_post_align(
+          uint64_t off, uint64_t len) {
+    if (len == 0) {
+      return std::make_pair(0, 0);
+    }
+    auto block_size = get_block_size();
+    return std::make_pair(p2phase(off, block_size),
+                          p2nphase(off + len, block_size));
+  }
+
+  inline std::pair<uint64_t, uint64_t> align(uint64_t off, uint64_t len) {
+    auto aligns = get_pre_and_post_align(off, len);
+    return std::make_pair(off - aligns.first,
+                          len + aligns.first + aligns.second);
+  }
+
+  inline bool is_aligned(uint64_t off, uint64_t len) {
+    auto aligns = get_pre_and_post_align(off, len);
+    return aligns.first == 0 && aligns.second == 0;
+  }
+
+  inline bool is_aligned(const io::ReadExtents& extents) {
+    for (const auto& extent: extents) {
+      if (!is_aligned(extent.offset, extent.length)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  inline void align_extents(const io::ReadExtents& extents,
+                            io::ReadExtents* aligned_extents) {
+    for (const auto& extent: extents) {
+      auto aligned = align(extent.offset, extent.length);
+      aligned_extents->emplace_back(aligned.first, aligned.second);
+    }
+  }
+
+  inline int decrypt_aligned_extent(io::ReadExtent& extent,
+                                    uint64_t image_offset) {
+    if (extent.length == 0) {
+      return 0;
+    }
+
+    if (extent.extent_map.empty()) {
+      extent.extent_map.emplace_back(extent.offset, extent.bl.length());
+    }
+
+    ceph::bufferlist result_bl;
+    io::Extents result_extent_map;
+
+    ceph::bufferlist curr_block_bl;
+    auto curr_offset = extent.offset;
+    auto curr_block_start_offset = curr_offset;
+    auto curr_block_end_offset = curr_offset;
+
+    // this will add a final loop iteration for decrypting the last extent
+    extent.extent_map.emplace_back(
+            extent.offset + extent.length + get_block_size(), 0);
+
+    for (auto [off, len]: extent.extent_map) {
+      auto [aligned_off, aligned_len] = align(off, len);
+      if (aligned_off > curr_block_end_offset) {
+        curr_block_bl.append_zero(curr_block_end_offset - curr_offset);
+        auto curr_block_length = curr_block_bl.length();
+        if (curr_block_length > 0) {
+          auto r = decrypt(
+                  &curr_block_bl,
+                  image_offset + curr_block_start_offset - extent.offset);
+          if (r != 0) {
+            return r;
+          }
+
+          curr_block_bl.splice(0, curr_block_length, &result_bl);
+          result_extent_map.emplace_back(
+                  curr_block_start_offset, curr_block_length);
+        }
+
+        curr_block_start_offset = aligned_off;
+        curr_block_end_offset = aligned_off + aligned_len;
+        curr_offset = aligned_off;
+      }
+
+      curr_block_bl.append_zero(off - curr_offset);
+      extent.bl.splice(0, len, &curr_block_bl);
+      curr_offset = off + len;
+      curr_block_end_offset = aligned_off + aligned_len;
+    }
+
+    extent.bl = std::move(result_bl);
+    extent.extent_map = std::move(result_extent_map);
+
+    return 0;
+  }
 };
 
 } // namespace crypto
index c5c02a372bf2760ad95edf90008dd743867289d2..d17dcbcc6de7175a2ddc54060d69252335bc6dbe 100644 (file)
@@ -3,6 +3,7 @@
 
 #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() {
@@ -58,34 +67,332 @@ struct C_EncryptedObjectReadRequest : public Context {
     }
 
     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) {
 }
 
@@ -123,11 +430,20 @@ bool CryptoObjectDispatch<I>::read(
   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;
 }
 
@@ -144,13 +460,24 @@ bool CryptoObjectDispatch<I>::write(
                  << 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;
 }
 
@@ -202,14 +529,14 @@ bool CryptoObjectDispatch<I>::compare_and_write(
                  << 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;
 }
 
index 661142b0716e89e7e6bd46f55424adbdf3e9240b..a6cd87a9468fdccb8fbf368f9b03dac5942d6eb0 100644 (file)
@@ -21,7 +21,8 @@ public:
     return new CryptoObjectDispatch(image_ctx, nullptr);
   }
 
-  CryptoObjectDispatch(ImageCtxT* image_ctx, CryptoInterface *crypto);
+  CryptoObjectDispatch(ImageCtxT* image_ctx,
+                       ceph::ref_t<CryptoInterface> crypto);
 
   io::ObjectDispatchLayer get_dispatch_layer() const override {
     return io::OBJECT_DISPATCH_LAYER_CRYPTO;
@@ -105,7 +106,7 @@ public:
 private:
 
   ImageCtxT* m_image_ctx;
-  CryptoInterface *m_crypto;
+  ceph::ref_t<CryptoInterface> m_crypto;
 
 };
 
index 658b442f466794820f7209520f990cb986747e93..181cc2caf943548343c101e863d1f62adc13847e 100644 (file)
@@ -7,6 +7,7 @@
 #include "test/librbd/mock/crypto/MockCryptoInterface.h"
 #include "librbd/crypto/CryptoObjectDispatch.h"
 #include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/Utils.h"
 
 namespace librbd {
 
@@ -17,6 +18,40 @@ inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
 }
 
 } // 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"
@@ -25,10 +60,14 @@ namespace librbd {
 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;
@@ -43,6 +82,7 @@ struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
   io::DispatchResult dispatch_result;
   io::Extents extent_map;
   int object_dispatch_flags = 0;
+  MockUtils mock_utils;
 
   void SetUp() override {
     TestMockFixture::SetUp();
@@ -52,7 +92,7 @@ struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
     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 {
@@ -66,25 +106,54 @@ struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
     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;
             }));
@@ -133,38 +202,122 @@ TEST_F(TestMockCryptoCryptoObjectDispatch, Discard) {
   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,
@@ -176,26 +329,220 @@ TEST_F(TestMockCryptoCryptoObjectDispatch, Write) {
   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);
index 7c5869226d9251ac69ef0e7881c43bb1b354d2e3..0745583624ea8a088db151005f41618f8b6e64f8 100644 (file)
@@ -15,6 +15,10 @@ struct MockCryptoInterface : CryptoInterface {
 
   MOCK_METHOD2(encrypt, int(ceph::bufferlist*, uint64_t));
   MOCK_METHOD2(decrypt, int(ceph::bufferlist*, uint64_t));
+
+  uint64_t get_block_size() const override {
+    return 4096;
+  }
 };
 
 } // namespace crypto