]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: support writing with version assertion on object dispatch
authorOr Ozeri <oro@il.ibm.com>
Wed, 22 Jul 2020 06:14:19 +0000 (09:14 +0300)
committerOr Ozeri <oro@il.ibm.com>
Sun, 9 Aug 2020 09:39:18 +0000 (12:39 +0300)
This commit extends the object dispatch write function to support RADOS object version assertion.
In addition, we add a write flag which allows to assert that an object exists before writing.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
29 files changed:
src/librbd/cache/ObjectCacherObjectDispatch.cc
src/librbd/cache/ObjectCacherObjectDispatch.h
src/librbd/cache/ObjectCacherWriteback.cc
src/librbd/cache/ParentCacheObjectDispatch.h
src/librbd/cache/WriteAroundObjectDispatch.cc
src/librbd/cache/WriteAroundObjectDispatch.h
src/librbd/crypto/CryptoObjectDispatch.cc
src/librbd/crypto/CryptoObjectDispatch.h
src/librbd/io/ImageRequest.cc
src/librbd/io/ObjectDispatch.cc
src/librbd/io/ObjectDispatch.h
src/librbd/io/ObjectDispatchInterface.h
src/librbd/io/ObjectDispatchSpec.h
src/librbd/io/ObjectDispatcher.cc
src/librbd/io/ObjectRequest.cc
src/librbd/io/ObjectRequest.h
src/librbd/io/SimpleSchedulerObjectDispatch.cc
src/librbd/io/SimpleSchedulerObjectDispatch.h
src/librbd/io/Types.h
src/librbd/journal/ObjectDispatch.cc
src/librbd/journal/ObjectDispatch.h
src/librbd/operation/FlattenRequest.cc
src/librbd/operation/MigrateRequest.cc
src/test/librbd/cache/test_mock_ParentCacheObjectDispatch.cc
src/test/librbd/cache/test_mock_WriteAroundObjectDispatch.cc
src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
src/test/librbd/io/test_mock_ObjectRequest.cc
src/test/librbd/io/test_mock_SimpleSchedulerObjectDispatch.cc
src/test/librbd/mock/io/MockObjectDispatch.h

index db0c71f186a428d8705b20967d135d9d0186bad4..524808a5097a98573eb7b64f1bd9e814c804a4ca 100644 (file)
@@ -267,7 +267,8 @@ bool ObjectCacherObjectDispatch<I>::discard(
 template <typename I>
 bool ObjectCacherObjectDispatch<I>::write(
     uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
     Context** on_finish, Context* on_dispatched) {
@@ -279,6 +280,23 @@ bool ObjectCacherObjectDispatch<I>::write(
   on_dispatched = util::create_async_context_callback(*m_image_ctx,
                                                       on_dispatched);
 
+  // cache layer does not handle version checking
+  if (assert_version.has_value() ||
+      (write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE) != 0) {
+    ObjectExtents object_extents;
+    object_extents.emplace_back(data_object_name(m_image_ctx, object_no),
+                                object_no, object_off, data.length(), 0);
+
+    *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
+
+    // ensure any in-flight writeback is complete before advancing
+    // the write request
+    std::lock_guard locker{m_cache_lock};
+    m_object_cacher->discard_writeback(m_object_set, object_extents,
+                                       on_dispatched);
+    return true;
+  }
+
   m_image_ctx->image_lock.lock_shared();
   ObjectCacher::OSDWrite *wr = m_object_cacher->prepare_write(
     snapc, data, ceph::real_time::min(), op_flags, *journal_tid);
@@ -317,8 +335,8 @@ bool ObjectCacherObjectDispatch<I>::write_same(
   bufferlist ws_data;
   io::util::assemble_write_same_extent(extent, data, &ws_data, true);
 
-  return write(object_no, object_off, std::move(ws_data), snapc, op_flags,
-               parent_trace, object_dispatch_flags, journal_tid,
+  return write(object_no, object_off, std::move(ws_data), snapc, op_flags, 0,
+               std::nullopt, parent_trace, object_dispatch_flags, journal_tid,
                dispatch_result, on_finish, on_dispatched);
 }
 
index 6e4ab23ae77e69c29e12e44c627fd790ae2dac21..4f3d96707eccc9ccc5880819d39c4a0921da56bf 100644 (file)
@@ -58,7 +58,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override;
index 529b6c54ad105e3b5c87c26ffc58fb8dd22ad444..cfa358d05ae31058be029e06b3535d381c0a67ff 100644 (file)
@@ -197,7 +197,7 @@ ceph_tid_t ObjectCacherWriteback::write(const object_t& oid,
 
   auto req = io::ObjectDispatchSpec::create_write(
     m_ictx, io::OBJECT_DISPATCH_LAYER_CACHE, object_no, off, std::move(bl_copy),
-    snapc, 0, journal_tid, trace, ctx);
+    snapc, 0, 0, std::nullopt, journal_tid, trace, ctx);
   req->object_dispatch_flags = (
     io::OBJECT_DISPATCH_FLAG_FLUSH |
     io::OBJECT_DISPATCH_FLAG_WILL_RETRY_ON_ERROR);
index 588d284f2fd1068408cb9f7ed7f483cf4c858e95..314c9a585def34445ed51ba5cd194b7ac2e00b17 100644 (file)
@@ -58,7 +58,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) {
index bbcf4a65b24f1f6aca82dab36baa467a47cb30cd..d206cc3f6c9155577cb8c17bae59d99f9d46958e 100644 (file)
@@ -85,7 +85,8 @@ bool WriteAroundObjectDispatch<I>::discard(
 template <typename I>
 bool WriteAroundObjectDispatch<I>::write(
     uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
     Context**on_finish, Context* on_dispatched) {
index bf1e1d985513fcf2f55950bc4709ce4fe50fad69..e08cdd43574717ace9fe1371a46591f3f6a7167c 100644 (file)
@@ -58,7 +58,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
       Context**on_finish, Context* on_dispatched) override;
index 99ccc49c1a1348ccdb2348cfb6d8a1edd5f18da9..12cc7e899ad6575b0737a835b0a4254836c96fa0 100644 (file)
@@ -141,7 +141,8 @@ bool CryptoObjectDispatch<I>::read(
 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 ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
     Context** on_finish, Context* on_dispatched) {
@@ -188,7 +189,7 @@ bool CryptoObjectDispatch<I>::write_same(
   *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,
+          object_off, std::move(ws_data), snapc, op_flags, 0, std::nullopt, 0,
           parent_trace, ctx);
   req->send();
   return true;
index 1d9edda909be901037f6c2c604113a0d99c42b65..6b95256f60df87c505b4456ca2e091afd01bc96c 100644 (file)
@@ -48,7 +48,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override;
index 3e9d1d99fd62424a84fbc3ac909b7ac62d268354..c98d00dd8ae715d932777bad2a4c5fbefb458901 100644 (file)
@@ -579,8 +579,8 @@ ObjectDispatchSpec *ImageWriteRequest<I>::create_object_request(
 
   auto req = ObjectDispatchSpec::create_write(
     &image_ctx, OBJECT_DISPATCH_LAYER_NONE, object_extent.object_no,
-    object_extent.offset, std::move(bl), snapc, m_op_flags, journal_tid,
-    this->m_trace, on_finish);
+    object_extent.offset, std::move(bl), snapc, m_op_flags, 0, std::nullopt,
+    journal_tid, this->m_trace, on_finish);
   return req;
 }
 
@@ -811,8 +811,8 @@ ObjectDispatchSpec *ImageWriteSameRequest<I>::create_object_request(
   }
   req = ObjectDispatchSpec::create_write(
     &image_ctx, OBJECT_DISPATCH_LAYER_NONE, object_extent.object_no,
-    object_extent.offset, std::move(bl), snapc, m_op_flags, journal_tid,
-    this->m_trace, on_finish);
+    object_extent.offset, std::move(bl), snapc, m_op_flags, 0, std::nullopt,
+    journal_tid, this->m_trace, on_finish);
   return req;
 }
 
index c6099316685a8aa4377320e1906a9f0969450515..1823be38222cf1c9e2ef1138a2b77bf701a23c12 100644 (file)
@@ -71,7 +71,8 @@ bool ObjectDispatch<I>::discard(
 template <typename I>
 bool ObjectDispatch<I>::write(
     uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, 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, DispatchResult* dispatch_result,
     Context** on_finish, Context* on_dispatched) {
@@ -82,6 +83,7 @@ bool ObjectDispatch<I>::write(
   *dispatch_result = DISPATCH_RESULT_COMPLETE;
   auto req = new ObjectWriteRequest<I>(m_image_ctx, object_no, object_off,
                                        std::move(data), snapc, op_flags,
+                                       write_flags, assert_version,
                                        parent_trace, on_dispatched);
   req->send();
   return true;
index 4a65ec417beefee3bd4aa2fd7a9377e6c51ce116..f06271f26dc571070170ec52aca87f5d0d443127 100644 (file)
@@ -50,7 +50,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override;
index 1ade6adbd518610473315f4b0f975084cb03c893..5774787ebdeebdc7a056a73dc0a6ca9a533552d9 100644 (file)
@@ -49,7 +49,8 @@ struct ObjectDispatchInterface {
 
   virtual bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, DispatchResult* dispatch_result,
       Context**on_finish, Context* on_dispatched) = 0;
index 942b1d719cf8620a7650b90359ca45d6bd5d0e9e..e4fa32b42e2719a30016b515cb32052b0eb98bd1 100644 (file)
@@ -83,12 +83,16 @@ public:
 
   struct WriteRequest : public WriteRequestBase {
     ceph::bufferlist data;
+    int write_flags;
+    std::optional<uint64_t> assert_version;
 
     WriteRequest(uint64_t object_no, uint64_t object_off,
                  ceph::bufferlist&& data, const ::SnapContext& snapc,
+                 int write_flags, std::optional<uint64_t> assert_version,
                  uint64_t journal_tid)
       : WriteRequestBase(object_no, object_off, snapc, journal_tid),
-        data(std::move(data)) {
+        data(std::move(data)), write_flags(write_flags),
+        assert_version(assert_version) {
     }
   };
 
@@ -182,12 +186,14 @@ public:
   static ObjectDispatchSpec* create_write(
       ImageCtxT* image_ctx, ObjectDispatchLayer object_dispatch_layer,
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags, uint64_t journal_tid,
+      const ::SnapContext &snapc, int op_flags, int write_flags,
+      std::optional<uint64_t> assert_version, uint64_t journal_tid,
       const ZTracer::Trace &parent_trace, Context *on_finish) {
     return new ObjectDispatchSpec(image_ctx->io_object_dispatcher,
                                   object_dispatch_layer,
                                   WriteRequest{object_no, object_off,
                                                std::move(data), snapc,
+                                               write_flags, assert_version,
                                                journal_tid},
                                   op_flags, parent_trace, on_finish);
   }
index 4732aa68e27c8b1a7ccf9513946a4c13f1e05414..6e0ee89673b10c27a734a210d3ffe7631d390ae1 100644 (file)
@@ -129,7 +129,8 @@ struct ObjectDispatcher<I>::SendVisitor : public boost::static_visitor<bool> {
   bool operator()(ObjectDispatchSpec::WriteRequest& write) const {
     return object_dispatch->write(
       write.object_no, write.object_off, std::move(write.data), write.snapc,
-      object_dispatch_spec->op_flags, object_dispatch_spec->parent_trace,
+      object_dispatch_spec->op_flags, write.write_flags, write.assert_version,
+      object_dispatch_spec->parent_trace,
       &object_dispatch_spec->object_dispatch_flags, &write.journal_tid,
       &object_dispatch_spec->dispatch_result,
       &object_dispatch_spec->dispatcher_ctx.on_finish,
index 87879eb8607a00ec74ac403f988a19c7f71a4ad6..60b05ea6115fe707ac9d9c02a3c435eb4853ce3c 100644 (file)
@@ -54,10 +54,12 @@ template <typename I>
 ObjectRequest<I>*
 ObjectRequest<I>::create_write(
     I *ictx, uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, int op_flags, int write_flags,
+    std::optional<uint64_t> assert_version,
     const ZTracer::Trace &parent_trace, Context *completion) {
   return new ObjectWriteRequest<I>(ictx, object_no, object_off,
                                    std::move(data), snapc, op_flags,
+                                   write_flags, assert_version,
                                    parent_trace, completion);
 }
 
@@ -642,6 +644,11 @@ void AbstractObjectWriteRequest<I>::handle_post_write_object_map_update(int r) {
 
 template <typename I>
 void ObjectWriteRequest<I>::add_write_ops(neorados::WriteOp* wr) {
+  if ((m_write_flags & OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE) != 0) {
+    wr->create(true);
+  } else if (m_assert_version.has_value()) {
+    wr->assert_version(m_assert_version.value());
+  }
   if (this->m_full_object) {
     wr->write_full(bufferlist{m_write_data});
   } else {
index c34a1f7f3efc2039d262ff61aa71c649681acb76..2750da98cd860839ebe90155c5d7e77bbd99a16f 100644 (file)
@@ -38,6 +38,7 @@ public:
   static ObjectRequest* create_write(
       ImageCtxT *ictx, uint64_t object_no, uint64_t object_off,
       ceph::bufferlist&& data, const ::SnapContext &snapc, int op_flags,
+      int write_flags, std::optional<uint64_t> assert_version,
       const ZTracer::Trace &parent_trace, Context *completion);
   static ObjectRequest* create_discard(
       ImageCtxT *ictx, uint64_t object_no, uint64_t object_off,
@@ -265,11 +266,13 @@ public:
   ObjectWriteRequest(
       ImageCtxT *ictx, uint64_t object_no, uint64_t object_off,
       ceph::bufferlist&& data, const ::SnapContext &snapc, int op_flags,
+      int write_flags, std::optional<uint64_t> assert_version,
       const ZTracer::Trace &parent_trace, Context *completion)
     : AbstractObjectWriteRequest<ImageCtxT>(ictx, object_no, object_off,
                                             data.length(), snapc, "write",
                                             parent_trace, completion),
-      m_write_data(std::move(data)), m_op_flags(op_flags) {
+      m_write_data(std::move(data)), m_op_flags(op_flags),
+      m_write_flags(write_flags), m_assert_version(assert_version) {
   }
 
   bool is_empty_write_op() const override {
@@ -286,6 +289,8 @@ protected:
 private:
   ceph::bufferlist m_write_data;
   int m_op_flags;
+  int m_write_flags;
+  std::optional<uint64_t> m_assert_version;
 };
 
 template <typename ImageCtxT = ImageCtx>
index 8b83468fe89af5f794fe64db6cb1cbbb5cb01e25..1f50da8c29bf004195813940dac2a9e7e443fa2b 100644 (file)
@@ -162,7 +162,7 @@ void SimpleSchedulerObjectDispatch<I>::ObjectRequests::dispatch_delayed_requests
     auto req = ObjectDispatchSpec::create_write(
         image_ctx, OBJECT_DISPATCH_LAYER_SCHEDULER,
         m_object_no, offset, std::move(merged_requests.data), m_snapc,
-        m_op_flags, 0, {}, ctx);
+        m_op_flags, 0, std::nullopt, 0, {}, ctx);
 
     req->object_dispatch_flags = m_object_dispatch_flags;
     req->send();
@@ -256,7 +256,8 @@ bool SimpleSchedulerObjectDispatch<I>::discard(
 template <typename I>
 bool SimpleSchedulerObjectDispatch<I>::write(
     uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, 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, DispatchResult* dispatch_result,
     Context** on_finish, Context* on_dispatched) {
@@ -264,6 +265,13 @@ bool SimpleSchedulerObjectDispatch<I>::write(
   ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
                  << object_off << "~" << data.length() << dendl;
 
+  // don't try to batch assert version writes
+  if (assert_version.has_value() ||
+      (write_flags & OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE) != 0) {
+    dispatch_delayed_requests(object_no);
+    return false;
+  }
+
   std::lock_guard locker{m_lock};
   if (try_delay_write(object_no, object_off, std::move(data), snapc, op_flags,
                       *object_dispatch_flags, on_dispatched)) {
index def70af5e906c23e23f24f08f09a63a75322fe7a..9b7d1fbad1c6a06fbc8d1e875ecf208705790f17 100644 (file)
@@ -66,7 +66,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override;
index 6a46788d2ed9d03cc072b4eb824098a772c8e601..66a7635cd53210cf5d3f6afab0c0dc3fb8e3361c 100644 (file)
@@ -99,6 +99,10 @@ enum ObjectDispatchLayer {
   OBJECT_DISPATCH_LAYER_LAST
 };
 
+enum {
+  OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE            = 1UL << 0
+};
+
 enum {
   OBJECT_DISCARD_FLAG_DISABLE_CLONE_REMOVE      = 1UL << 0,
   OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE = 1UL << 1
index 91dae20d76107e8a8800aff838e5c57a1e03b7e3..be47abbaa6e5be19eae8f16a05d3ce8ddfbe0c63 100644 (file)
@@ -107,7 +107,8 @@ bool ObjectDispatch<I>::discard(
 template <typename I>
 bool ObjectDispatch<I>::write(
     uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-    const ::SnapContext &snapc, int op_flags,
+    const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
     Context** on_finish, Context* on_dispatched) {
index cd03389c5c134eae4f0714ed1bd4f306c921aec0..f4426be3e28baf7226d14ee95a82cdc4aa7bf901 100644 (file)
@@ -56,7 +56,8 @@ public:
 
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, 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, io::DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override;
index d94e8421083f45c97a685906ee26730c9de78fbf..6d94b5da84722507ca76841adc7565ed3fc13ed0 100644 (file)
@@ -56,8 +56,8 @@ public:
 
     bufferlist bl;
     auto req = new io::ObjectWriteRequest<I>(&image_ctx, m_object_no, 0,
-                                             std::move(bl), m_snapc, 0, {},
-                                             this);
+                                             std::move(bl), m_snapc, 0, 0,
+                                             std::nullopt, {}, this);
     if (!req->has_parent()) {
       // stop early if the parent went away - it just means
       // another flatten finished first or the image was resized
index 0da4be9936a54f766f5064c32fe23f1c7ef23f42..1e5f9ff0f442e0e668ecd084998eefe764cb3ce1 100644 (file)
@@ -118,8 +118,8 @@ private:
     if (is_within_overlap_bounds()) {
       bufferlist bl;
       auto req = new io::ObjectWriteRequest<I>(&image_ctx, m_object_no, 0,
-                                               std::move(bl), m_snapc, 0, {},
-                                               ctx);
+                                               std::move(bl), m_snapc, 0, 0,
+                                               std::nullopt, {}, ctx);
 
       ldout(cct, 20) << "copyup object req " << req << ", object_no "
                      << m_object_no << dendl;
index c65c4974dcd9236a5cfdbd23b097073aed24a5e4..f66cf92e24ec00edc51e1bd791d84a845e6c1f46 100644 (file)
@@ -313,7 +313,7 @@ TEST_F(TestMockParentCacheObjectDispatch, test_disble_interface) {
 
   ASSERT_EQ(mock_parent_image_cache->discard(0, 0, 0, *temp_snapc, 0, *temp_trace, temp_op_flags,
             temp_journal_tid, temp_dispatch_result, temp_on_finish, temp_on_dispatched), false);
-  ASSERT_EQ(mock_parent_image_cache->write(0, 0, std::move(temp_bl), *temp_snapc, 0,
+  ASSERT_EQ(mock_parent_image_cache->write(0, 0, std::move(temp_bl), *temp_snapc, 0, 0, std::nullopt,
             *temp_trace, temp_op_flags, temp_journal_tid, temp_dispatch_result,
             temp_on_finish, temp_on_dispatched), false);
   ASSERT_EQ(mock_parent_image_cache->write_same(0, 0, 0, std::move(buffer_extents),
index ee7cfc5fd3911a8aacaa9d0a52ca9b65047377be..52d2bd295069f7dcf96498369ab9b1e9a197ac14 100644 (file)
@@ -72,9 +72,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteThrough) {
   MockContext finish_ctx;
   MockContext dispatch_ctx;
   Context* finish_ctx_ptr = &finish_ctx;
-  ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                     nullptr, nullptr, &dispatch_result,
-                                     &finish_ctx_ptr, &dispatch_ctx));
+  ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                     std::nullopt, {}, nullptr, nullptr,
+                                     &dispatch_result, &finish_ctx_ptr,
+                                     &dispatch_ctx));
   ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
 }
 
@@ -95,9 +96,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteThroughUntilFlushed) {
   MockContext finish_ctx;
   MockContext dispatch_ctx;
   Context* finish_ctx_ptr = &finish_ctx;
-  ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                     nullptr, nullptr, &dispatch_result,
-                                     &finish_ctx_ptr, &dispatch_ctx));
+  ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                     std::nullopt, {}, nullptr, nullptr,
+                                     &dispatch_result, &finish_ctx_ptr,
+                                     &dispatch_ctx));
   ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
 
   ASSERT_FALSE(object_dispatch.flush(io::FLUSH_SOURCE_USER, {}, nullptr,
@@ -107,9 +109,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, WriteThroughUntilFlushed) {
   expect_context_complete(dispatch_ctx, 0);
   expect_context_complete(finish_ctx, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                     nullptr, nullptr, &dispatch_result,
-                                     &finish_ctx_ptr, &dispatch_ctx));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr,
+                                    &dispatch_ctx));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr, &finish_ctx);
   ASSERT_EQ(0, dispatch_ctx.wait());
@@ -138,9 +141,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, DispatchIO) {
   expect_context_complete(dispatch_ctx, 0);
   expect_context_complete(finish_ctx, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr, &dispatch_ctx));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr,
+                                    &dispatch_ctx));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr, &finish_ctx);
 
@@ -170,9 +174,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0,  0,
+                                    std::nullopt,{}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
 
@@ -183,9 +188,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedIO) {
   expect_context_complete(dispatch_ctx2, 0);
   expect_context_complete(finish_ctx2, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr2, &dispatch_ctx2));
+  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr2,
+                                    &dispatch_ctx2));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
 
@@ -193,9 +199,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedIO) {
   MockContext dispatch_ctx3;
   Context* finish_ctx_ptr3 = &finish_ctx3;
 
-  ASSERT_TRUE(object_dispatch.write(0, 1024, std::move(data), {}, 0,
-                                    {}, nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr3, &dispatch_ctx3));
+  ASSERT_TRUE(object_dispatch.write(0, 1024, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr3,
+                                    &dispatch_ctx3));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr3, &finish_ctx3);
 
@@ -235,9 +242,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, QueuedIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
 
@@ -245,9 +253,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, QueuedIO) {
   MockContext dispatch_ctx2;
   Context* finish_ctx_ptr2 = &finish_ctx2;
 
-  ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr2, &dispatch_ctx2));
+  ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr2,
+                                    &dispatch_ctx2));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -283,9 +292,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedAndQueuedIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
 
@@ -296,9 +306,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedAndQueuedIO) {
   expect_context_complete(dispatch_ctx2, 0);
   expect_context_complete(finish_ctx2, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr2, &dispatch_ctx2));
+  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr2,
+                                    &dispatch_ctx2));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
 
@@ -306,9 +317,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, BlockedAndQueuedIO) {
   MockContext dispatch_ctx3;
   Context* finish_ctx_ptr3 = &finish_ctx3;
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0,
-                                    {}, nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr3, &dispatch_ctx3));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr3,
+                                    &dispatch_ctx3));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr3, &finish_ctx3);
 
@@ -368,9 +380,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushQueuedOnInFlightIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -413,9 +426,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushQueuedOnQueuedIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -424,9 +438,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushQueuedOnQueuedIO) {
   MockContext dispatch_ctx2;
   Context* finish_ctx_ptr2 = &finish_ctx2;
 
-  ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr2, &dispatch_ctx2));
+  ASSERT_TRUE(object_dispatch.write(0, 8192, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr2,
+                                    &dispatch_ctx2));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -477,9 +492,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, FlushError) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -547,9 +563,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIOInFlightIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -593,9 +610,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIOBlockedIO) {
   expect_context_complete(dispatch_ctx1, 0);
   expect_context_complete(finish_ctx1, 0);
 
-  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr1, &dispatch_ctx1));
+  ASSERT_TRUE(object_dispatch.write(0, 0, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr1,
+                                    &dispatch_ctx1));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr1, &finish_ctx1);
   ASSERT_EQ(0, dispatch_ctx1.wait());
@@ -604,9 +622,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, UnoptimizedIOBlockedIO) {
   MockContext finish_ctx2;
   MockContext dispatch_ctx2;
   Context* finish_ctx_ptr2 = &finish_ctx2;
-  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, {},
-                                    nullptr, nullptr, &dispatch_result,
-                                    &finish_ctx_ptr2, &dispatch_ctx2));
+  ASSERT_TRUE(object_dispatch.write(0, 4096, std::move(data), {}, 0, 0,
+                                    std::nullopt, {}, nullptr, nullptr,
+                                    &dispatch_result, &finish_ctx_ptr2,
+                                    &dispatch_ctx2));
   ASSERT_EQ(io::DISPATCH_RESULT_CONTINUE, dispatch_result);
   ASSERT_NE(finish_ctx_ptr2, &finish_ctx2);
 
@@ -649,9 +668,10 @@ TEST_F(TestMockCacheWriteAroundObjectDispatch, FUA) {
   MockContext dispatch_ctx;
   Context* finish_ctx_ptr = &finish_ctx;
   ASSERT_FALSE(object_dispatch.write(0, 0, std::move(data), {},
-                                     LIBRADOS_OP_FLAG_FADVISE_FUA, {},
-                                     nullptr, nullptr, &dispatch_result,
-                                     &finish_ctx_ptr, &dispatch_ctx));
+                                     LIBRADOS_OP_FLAG_FADVISE_FUA, 0,
+                                     std::nullopt, {}, nullptr, nullptr,
+                                     &dispatch_result, &finish_ctx_ptr,
+                                     &dispatch_ctx));
   ASSERT_EQ(finish_ctx_ptr, &finish_ctx);
 }
 
index d38d10dae07ee745425f18d862cd657303bc0d5f..6fc8491aae9560f6096d90fa9496acfdfb31e702 100644 (file)
@@ -163,8 +163,8 @@ TEST_F(TestMockCryptoCryptoObjectDispatch, Read) {
 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));
+        0, 0, std::move(data), mock_image_ctx->snapc, 0, 0, std::nullopt, {},
+        nullptr, nullptr, &dispatch_result, &on_finish, on_dispatched));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_CONTINUE);
   ASSERT_EQ(on_finish, &finished_cond); // not modified
   on_finish->complete(0);
index 123bf52c149d966d911ed78ab5a8f2d1ad36fcef..0803ec32d8d31ca6fde3793b33c6b6d8dc4c15b9 100644 (file)
@@ -606,11 +606,167 @@ TEST_F(TestMockIoObjectRequest, Write) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockIoObjectRequest, WriteWithCreateExclusiveFlag) {
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_get_object_size(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+    expect_is_lock_owner(mock_exclusive_lock);
+  }
+
+  MockObjectMap mock_object_map;
+  if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+    mock_image_ctx.object_map = &mock_object_map;
+  }
+
+  // exclusive create should succeed
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+    expect_write(mock_image_ctx, 0, 4096, 0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0,
+            OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, &ctx);
+    req->send();
+    ASSERT_EQ(0, ctx.wait());
+  }
+
+  // exclusive create should fail since object already exists
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0,
+            OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE, std::nullopt, {}, &ctx);
+    req->send();
+    ASSERT_EQ(-EEXIST, ctx.wait());
+  }
+}
+
+TEST_F(TestMockIoObjectRequest, WriteWithAssertVersion) {
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_get_object_size(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+    expect_is_lock_owner(mock_exclusive_lock);
+  }
+
+  MockObjectMap mock_object_map;
+  if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) {
+    mock_image_ctx.object_map = &mock_object_map;
+  }
+
+  // write an object
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+    expect_write(mock_image_ctx, 0, 4096, 0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+            std::nullopt, {}, &ctx);
+    req->send();
+    ASSERT_EQ(0, ctx.wait());
+  }
+
+  // assert version should succeed (version = 1)
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+    expect_write(mock_image_ctx, 0, 4096, 0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+            std::make_optional(1), {}, &ctx);
+    req->send();
+    ASSERT_EQ(0, ctx.wait());
+  }
+
+  // assert with wrong (lower) version (version = 2)
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+            std::make_optional(1), {}, &ctx);
+    req->send();
+    ASSERT_EQ(-ERANGE, ctx.wait());
+  }
+
+  // assert with wrong (higher) version (version = 2)
+  {
+    bufferlist bl;
+    bl.append(std::string(4096, '1'));
+
+    InSequence seq;
+    expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_object_may_exist(mock_image_ctx, 0, true);
+    expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false,
+                             0);
+
+    C_SaferCond ctx;
+    auto req = MockObjectWriteRequest::create_write(
+            &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+            std::make_optional(3), {}, &ctx);
+    req->send();
+    ASSERT_EQ(-EOVERFLOW, ctx.wait());
+  }
+}
+
 TEST_F(TestMockIoObjectRequest, WriteFull) {
   librbd::ImageCtx *ictx;
   ASSERT_EQ(0, open_image(m_image_name, &ictx));
@@ -640,7 +796,8 @@ TEST_F(TestMockIoObjectRequest, WriteFull) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -673,7 +830,8 @@ TEST_F(TestMockIoObjectRequest, WriteObjectMap) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -694,7 +852,8 @@ TEST_F(TestMockIoObjectRequest, WriteError) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(-EPERM, ctx.wait());
 }
@@ -747,7 +906,8 @@ TEST_F(TestMockIoObjectRequest, Copyup) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -802,7 +962,8 @@ TEST_F(TestMockIoObjectRequest, CopyupRestart) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -849,7 +1010,8 @@ TEST_F(TestMockIoObjectRequest, CopyupOptimization) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -889,7 +1051,8 @@ TEST_F(TestMockIoObjectRequest, CopyupError) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(-EPERM, ctx.wait());
 }
@@ -1407,7 +1570,8 @@ TEST_F(TestMockIoObjectRequest, ObjectMapError) {
 
   C_SaferCond ctx;
   auto req = MockObjectWriteRequest::create_write(
-    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, {}, &ctx);
+    &mock_image_ctx, 0, 0, std::move(bl), mock_image_ctx.snapc, 0, 0,
+    std::nullopt, {}, &ctx);
   req->send();
   ASSERT_EQ(-EBLACKLISTED, ctx.wait());
 }
index 9ce77ad0fb068694b1da2c42975914f35e033ae2..852dcc120f3a292788ac4f11fc6473da554f75ff 100644 (file)
@@ -171,7 +171,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Write) {
   C_SaferCond cond;
   Context *on_finish = &cond;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish, nullptr));
   ASSERT_NE(on_finish, &cond);
   on_finish->complete(0);
@@ -256,7 +256,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayed) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -268,7 +268,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayed) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
@@ -303,7 +303,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayedFlush) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -315,7 +315,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteDelayedFlush) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
@@ -359,7 +359,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -374,8 +374,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched2;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched2));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish2, &cond2);
@@ -388,8 +388,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   Context *on_finish3 = &cond3;
   C_SaferCond on_dispatched3;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3,
       &on_dispatched3));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish3, &cond3);
@@ -401,8 +401,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   Context *on_finish4 = &cond4;
   C_SaferCond on_dispatched4;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish4,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {},&object_dispatch_flags, nullptr, &dispatch_result, &on_finish4,
       &on_dispatched4));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish4, &cond4);
@@ -414,8 +414,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   Context *on_finish5 = &cond5;
   C_SaferCond on_dispatched5;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish5,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish5,
       &on_dispatched5));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish5, &cond5);
@@ -427,8 +427,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteMerged) {
   Context *on_finish6 = &cond6;
   C_SaferCond on_dispatched6;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish6,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish6,
       &on_dispatched6));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish6, &cond6);
@@ -475,7 +475,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteNonSequential) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -490,8 +490,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteNonSequential) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched2;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched2));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish2, &cond2);
@@ -506,8 +506,9 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, WriteNonSequential) {
   C_SaferCond cond3;
   Context *on_finish3 = &cond3;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3, nullptr));
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3,
+      nullptr));
   ASSERT_NE(on_finish3, &cond3);
 
   on_finish1->complete(0);
@@ -538,7 +539,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -554,8 +555,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched2;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched2));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish2, &cond2);
@@ -570,8 +571,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
   Context *on_finish3 = &cond3;
   C_SaferCond on_dispatched3;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish3,
       &on_dispatched3));
   ASSERT_NE(on_finish3, &cond3);
 
@@ -600,8 +601,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
   Context *on_finish5 = &cond5;
   C_SaferCond on_dispatched5;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish5,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish5,
       &on_dispatched5));
   ASSERT_NE(on_finish5, &cond5);
   ASSERT_NE(timer_task, nullptr);
@@ -630,8 +631,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Mixed) {
   Context *on_finish7 = &cond7;
   C_SaferCond on_dispatched7;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, object_off, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish7,
+      0, object_off, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish7,
       &on_dispatched7));
   ASSERT_NE(on_finish7, &cond7);
   ASSERT_NE(timer_task, nullptr);
@@ -693,8 +694,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, DispatchQueue) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
+      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
   Context *timer_task = nullptr;
@@ -706,8 +707,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, DispatchQueue) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched2;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
+      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched2));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish2, &cond2);
@@ -720,8 +721,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, DispatchQueue) {
   C_SaferCond cond3;
   Context *on_finish3 = &cond3;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, nullptr, &on_finish3, nullptr));
+      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, nullptr, &on_finish3, nullptr));
   ASSERT_NE(on_finish3, &cond3);
 
   data.clear();
@@ -729,8 +730,8 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, DispatchQueue) {
   Context *on_finish4 = &cond4;
   C_SaferCond on_dispatched4;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, {},
-      &object_dispatch_flags, nullptr, &dispatch_result, &on_finish4,
+      object_no, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt,
+      {}, &object_dispatch_flags, nullptr, &dispatch_result, &on_finish4,
       &on_dispatched4));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
   ASSERT_NE(on_finish4, &cond4);
@@ -776,7 +777,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Timer) {
   C_SaferCond cond1;
   Context *on_finish1 = &cond1;
   ASSERT_FALSE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, nullptr, &on_finish1, nullptr));
   ASSERT_NE(on_finish1, &cond1);
 
@@ -789,7 +790,7 @@ TEST_F(TestMockIoSimpleSchedulerObjectDispatch, Timer) {
   Context *on_finish2 = &cond2;
   C_SaferCond on_dispatched;
   ASSERT_TRUE(mock_simple_scheduler_object_dispatch.write(
-      0, 0, std::move(data), mock_image_ctx.snapc, 0, {},
+      0, 0, std::move(data), mock_image_ctx.snapc, 0, 0, std::nullopt, {},
       &object_dispatch_flags, nullptr, &dispatch_result, &on_finish2,
       &on_dispatched));
   ASSERT_EQ(dispatch_result, io::DISPATCH_RESULT_COMPLETE);
index 943b3a06a1e08365b0343abd4b7055274d3a6e3f..e16be79ef437d8a322c75e0159a5c9d40363f7c6 100644 (file)
@@ -52,18 +52,20 @@ public:
                            dispatch_result, on_dispatched);
   }
 
-  MOCK_METHOD8(execute_write,
+  MOCK_METHOD10(execute_write,
                bool(uint64_t, uint64_t, const ceph::bufferlist&,
-                    const ::SnapContext &, int*, uint64_t*, DispatchResult*,
-                    Context *));
+                    const ::SnapContext &, int, std::optional<uint64_t>, int*,
+                    uint64_t*, DispatchResult*, Context *));
   bool write(
       uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
-      const ::SnapContext &snapc, int op_flags,
+      const ::SnapContext &snapc, int op_flags, int write_flags,
+      std::optional<uint64_t> assert_version,
       const ZTracer::Trace &parent_trace, int* dispatch_flags,
       uint64_t* journal_tid, DispatchResult* dispatch_result,
       Context** on_finish, Context* on_dispatched) override {
-    return execute_write(object_no, object_off, data, snapc, dispatch_flags,
-                         journal_tid, dispatch_result, on_dispatched);
+    return execute_write(object_no, object_off, data, snapc, write_flags,
+                         assert_version, dispatch_flags, journal_tid,
+                         dispatch_result, on_dispatched);
   }
 
   MOCK_METHOD10(execute_write_same,