]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: refactor object write request state machines
authorJason Dillaman <dillaman@redhat.com>
Thu, 9 Nov 2017 17:10:30 +0000 (12:10 -0500)
committerJason Dillaman <dillaman@redhat.com>
Thu, 16 Nov 2017 12:31:59 +0000 (07:31 -0500)
Fixes: http://tracker.ceph.com/issues/20789
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/librbd/Journal.cc
src/librbd/io/ObjectRequest.cc
src/librbd/io/ObjectRequest.h
src/test/librbd/io/test_mock_ImageRequest.cc
src/test/librbd/io/test_mock_ObjectRequest.cc
src/test/librbd/mock/MockImageCtx.h
src/test/librbd/operation/test_mock_TrimRequest.cc

index 8203390808d15341c50c9e97e1e2196508647aff..64031efe473b98b0b25b606dcc8e5024c9939dfc 100644 (file)
@@ -1496,7 +1496,7 @@ void Journal<I>::handle_io_event_safe(int r, uint64_t tid) {
        it != aio_object_requests.end(); ++it) {
     if (r < 0) {
       // don't send aio requests if the journal fails -- bubble error up
-      (*it)->complete(r);
+      (*it)->fail(r);
     } else {
       // send any waiting aio requests now that journal entry is safe
       (*it)->send();
index 2d17793ba80f11f310a67d9cbf9ea1da0c2f60a6..15babcaeff655f2c1d774c6ef15d68ad397962d7 100644 (file)
@@ -108,12 +108,11 @@ template <typename I>
 ObjectRequest<I>::ObjectRequest(I *ictx, const std::string &oid,
                                 uint64_t objectno, uint64_t off,
                                 uint64_t len, librados::snap_t snap_id,
-                                bool hide_enoent, const char *trace_name,
-                               const ZTracer::Trace &trace,
+                                const char *trace_name,
+                                const ZTracer::Trace &trace,
                                Context *completion)
   : m_ictx(ictx), m_oid(oid), m_object_no(objectno), m_object_off(off),
     m_object_len(len), m_snap_id(snap_id), m_completion(completion),
-    m_hide_enoent(hide_enoent),
     m_trace(util::create_trace(*ictx, "", trace)) {
   if (m_trace.valid()) {
     m_trace.copy_name(trace_name + std::string(" ") + oid);
@@ -130,19 +129,6 @@ void ObjectRequest<I>::add_write_hint(I& image_ctx,
   }
 }
 
-template <typename I>
-void ObjectRequest<I>::complete(int r)
-{
-  if (this->should_complete(r)) {
-    ldout(m_ictx->cct, 20) << dendl;
-    if (m_hide_enoent && r == -ENOENT) {
-      r = 0;
-    }
-    m_completion->complete(r);
-    delete this;
-  }
-}
-
 template <typename I>
 bool ObjectRequest<I>::compute_parent_extents(Extents *parent_extents) {
   assert(m_ictx->snap_lock.is_locked());
@@ -199,7 +185,7 @@ ObjectReadRequest<I>::ObjectReadRequest(I *ictx, const std::string &oid,
                                         int op_flags, bool cache_initiated,
                                         const ZTracer::Trace &parent_trace,
                                         Context *completion)
-  : ObjectRequest<I>(ictx, oid, objectno, offset, len, snap_id, false, "read",
+  : ObjectRequest<I>(ictx, oid, objectno, offset, len, snap_id, "read",
                      parent_trace, completion),
     m_op_flags(op_flags), m_cache_initiated(cache_initiated) {
 }
@@ -405,29 +391,34 @@ void ObjectReadRequest<I>::copyup() {
   this->finish(0);
 }
 
-template <typename I>
-bool ObjectReadRequest<I>::should_complete(int r) {
-  // TODO remove once fully converted
-  assert(false);
-}
-
 /** write **/
 
 template <typename I>
 AbstractObjectWriteRequest<I>::AbstractObjectWriteRequest(
     I *ictx, const std::string &oid, uint64_t object_no, uint64_t object_off,
-    uint64_t len, const ::SnapContext &snapc, bool hide_enoent,
-    const char *trace_name, const ZTracer::Trace &parent_trace,
-    Context *completion)
+    uint64_t len, const ::SnapContext &snapc, const char *trace_name,
+    const ZTracer::Trace &parent_trace, Context *completion)
   : ObjectRequest<I>(ictx, oid, object_no, object_off, len, CEPH_NOSNAP,
-                     hide_enoent, trace_name, parent_trace, completion),
-    m_state(LIBRBD_AIO_WRITE_FLAT), m_snap_seq(snapc.seq.val)
+                     trace_name, parent_trace, completion),
+    m_snap_seq(snapc.seq.val)
 {
   m_snaps.insert(m_snaps.end(), snapc.snaps.begin(), snapc.snaps.end());
 
-  RWLock::RLocker snap_locker(ictx->snap_lock);
-  RWLock::RLocker parent_locker(ictx->parent_lock);
-  this->compute_parent_extents(&this->m_parent_extents);
+  {
+    RWLock::RLocker snap_locker(ictx->snap_lock);
+    RWLock::RLocker parent_locker(ictx->parent_lock);
+    this->compute_parent_extents(&m_parent_extents);
+  }
+
+  if (this->m_object_off == 0 &&
+      this->m_object_len == ictx->get_object_size()) {
+    m_full_object = true;
+  }
+
+  if (!this->has_parent() ||
+      (m_full_object && m_snaps.empty() && !is_post_copyup_write_required())) {
+    this->m_copyup_enabled = false;
+  }
 }
 
 template <typename I>
@@ -440,100 +431,6 @@ void AbstractObjectWriteRequest<I>::add_write_hint(
   }
 }
 
-template <typename I>
-void AbstractObjectWriteRequest<I>::handle_copyup(int r) {
-  I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << "r=" << r << dendl;
-
-  // TODO
-  assert(m_state == LIBRBD_AIO_WRITE_COPYUP);
-  this->complete(r);
-}
-
-template <typename I>
-void AbstractObjectWriteRequest<I>::guard_write()
-{
-  I *image_ctx = this->m_ictx;
-  if (this->has_parent()) {
-    m_state = LIBRBD_AIO_WRITE_GUARD;
-    m_write.assert_exists();
-    ldout(image_ctx->cct, 20) << "guarding write" << dendl;
-  }
-}
-
-template <typename I>
-bool AbstractObjectWriteRequest<I>::should_complete(int r)
-{
-  I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << this->get_op_type() << this->m_oid << " "
-                            << this->m_object_off << "~" << this->m_object_len
-                            << " r = " << r << dendl;
-
-  bool finished = true;
-  switch (m_state) {
-  case LIBRBD_AIO_WRITE_PRE:
-    ldout(image_ctx->cct, 20) << "WRITE_PRE" << dendl;
-    if (r < 0) {
-      return true;
-    }
-
-    send_write_op();
-    finished = false;
-    break;
-
-  case LIBRBD_AIO_WRITE_POST:
-    ldout(image_ctx->cct, 20) << "WRITE_POST" << dendl;
-    finished = true;
-    break;
-
-  case LIBRBD_AIO_WRITE_GUARD:
-    ldout(image_ctx->cct, 20) << "WRITE_CHECK_GUARD" << dendl;
-
-    if (r == -ENOENT) {
-      handle_write_guard();
-      finished = false;
-      break;
-    } else if (r < 0) {
-      // pass the error code to the finish context
-      m_state = LIBRBD_AIO_WRITE_ERROR;
-      this->complete(r);
-      finished = false;
-      break;
-    }
-
-    finished = send_post_object_map_update();
-    break;
-
-  case LIBRBD_AIO_WRITE_COPYUP:
-    ldout(image_ctx->cct, 20) << "WRITE_COPYUP" << dendl;
-    if (r < 0) {
-      m_state = LIBRBD_AIO_WRITE_ERROR;
-      this->complete(r);
-      finished = false;
-    } else {
-      finished = send_post_object_map_update();
-    }
-    break;
-
-  case LIBRBD_AIO_WRITE_FLAT:
-    ldout(image_ctx->cct, 20) << "WRITE_FLAT" << dendl;
-
-    finished = send_post_object_map_update();
-    break;
-
-  case LIBRBD_AIO_WRITE_ERROR:
-    assert(r < 0);
-    lderr(image_ctx->cct) << "WRITE_ERROR: " << cpp_strerror(r) << dendl;
-    break;
-
-  default:
-    lderr(image_ctx->cct) << "invalid request state: " << m_state << dendl;
-    ceph_abort();
-  }
-
-  return finished;
-}
-
 template <typename I>
 void AbstractObjectWriteRequest<I>::send() {
   I *image_ctx = this->m_ictx;
@@ -552,83 +449,119 @@ void AbstractObjectWriteRequest<I>::send() {
     }
   }
 
-  send_write();
+  if (!m_object_may_exist && is_no_op_for_nonexistent_object()) {
+    ldout(image_ctx->cct, 20) << "skipping no-op on nonexistent object"
+                              << dendl;
+    this->async_finish(0);
+    return;
+  }
+
+  pre_write_object_map_update();
 }
 
 template <typename I>
-void AbstractObjectWriteRequest<I>::send_pre_object_map_update() {
+void AbstractObjectWriteRequest<I>::pre_write_object_map_update() {
   I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << dendl;
 
-  {
-    RWLock::RLocker snap_lock(image_ctx->snap_lock);
-    if (image_ctx->object_map != nullptr) {
-      uint8_t new_state = this->get_pre_write_object_map_state();
-      RWLock::WLocker object_map_locker(image_ctx->object_map_lock);
-      ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off
-                                << "~" << this->m_object_len << dendl;
-      m_state = LIBRBD_AIO_WRITE_PRE;
-
-      if (image_ctx->object_map->template aio_update<ObjectRequest<I>>(
-            CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace,
-            this)) {
-        return;
-      }
-    }
+  image_ctx->snap_lock.get_read();
+  if (image_ctx->object_map == nullptr || !is_object_map_update_enabled()) {
+    image_ctx->snap_lock.put_read();
+    write_object();
+    return;
+  }
+
+  if (!m_object_may_exist && m_copyup_enabled) {
+    // optimization: copyup required
+    image_ctx->snap_lock.put_read();
+    copyup();
+    return;
+  }
+
+  uint8_t new_state = this->get_pre_write_object_map_state();
+  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off
+                            << "~" << this->m_object_len << dendl;
+
+  image_ctx->object_map_lock.get_write();
+  if (image_ctx->object_map->template aio_update<
+        AbstractObjectWriteRequest<I>,
+        &AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update>(
+          CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, this)) {
+    image_ctx->object_map_lock.put_write();
+    image_ctx->snap_lock.put_read();
+    return;
   }
 
-  send_write_op();
+  image_ctx->object_map_lock.put_write();
+  image_ctx->snap_lock.put_read();
+  write_object();
 }
 
 template <typename I>
-bool AbstractObjectWriteRequest<I>::send_post_object_map_update() {
+void AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update(int r) {
   I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << dendl;
-
-  RWLock::RLocker snap_locker(image_ctx->snap_lock);
-  if (image_ctx->object_map == nullptr || !post_object_map_update()) {
-    return true;
-  }
+  ldout(image_ctx->cct, 20) << "r=" << r << dendl;
 
-  // should have been flushed prior to releasing lock
-  assert(image_ctx->exclusive_lock->is_lock_owner());
+  assert(r == 0);
+  write_object();
+}
 
-  RWLock::WLocker object_map_locker(image_ctx->object_map_lock);
-  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off
-                            << "~" << this->m_object_len << dendl;
-  m_state = LIBRBD_AIO_WRITE_POST;
+template <typename I>
+void AbstractObjectWriteRequest<I>::write_object() {
+  I *image_ctx = this->m_ictx;
+  ldout(image_ctx->cct, 20) << dendl;
 
-  if (image_ctx->object_map->template aio_update<ObjectRequest<I>>(
-        CEPH_NOSNAP, this->m_object_no, OBJECT_NONEXISTENT, OBJECT_PENDING,
-        this->m_trace, this)) {
-    return false;
+  librados::ObjectWriteOperation write;
+  if (m_copyup_enabled) {
+    ldout(image_ctx->cct, 20) << "guarding write" << dendl;
+    write.assert_exists();
   }
 
-  return true;
+  add_write_hint(&write);
+  add_write_ops(&write);
+  assert(write.size() != 0);
+
+  librados::AioCompletion *rados_completion = util::create_rados_callback<
+    AbstractObjectWriteRequest<I>,
+    &AbstractObjectWriteRequest<I>::handle_write_object>(this);
+  int r = image_ctx->data_ctx.aio_operate(
+    this->m_oid, rados_completion, &write, m_snap_seq, m_snaps,
+    (this->m_trace.valid() ? this->m_trace.get_info() : nullptr));
+  assert(r == 0);
+  rados_completion->release();
 }
 
 template <typename I>
-void AbstractObjectWriteRequest<I>::send_write() {
+void AbstractObjectWriteRequest<I>::handle_write_object(int r) {
   I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off << "~"
-                            << this->m_object_len << " object exist "
-                            << m_object_may_exist << dendl;
+  ldout(image_ctx->cct, 20) << "r=" << r << dendl;
 
-  if (!m_object_may_exist && this->has_parent()) {
-    m_state = LIBRBD_AIO_WRITE_GUARD;
-    handle_write_guard();
-  } else {
-    send_pre_object_map_update();
+  r = filter_write_result(r);
+  if (r == -ENOENT) {
+    if (m_copyup_enabled) {
+      copyup();
+      return;
+    }
+  } else if (r == -EILSEQ) {
+    ldout(image_ctx->cct, 10) << "failed to write object" << dendl;
+    this->finish(r);
+    return;
+  } else if (r < 0) {
+    lderr(image_ctx->cct) << "failed to write object: " << cpp_strerror(r)
+                          << dendl;
+    this->finish(r);
+    return;
   }
+
+  post_write_object_map_update();
 }
 
 template <typename I>
-void AbstractObjectWriteRequest<I>::send_copyup()
-{
+void AbstractObjectWriteRequest<I>::copyup() {
   I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off
-                            << "~" << this->m_object_len << dendl;
-  m_state = LIBRBD_AIO_WRITE_COPYUP;
+  ldout(image_ctx->cct, 20) << dendl;
+
+  assert(!m_copyup_in_progress);
+  m_copyup_in_progress = true;
 
   image_ctx->copyup_list_lock.Lock();
   auto it = image_ctx->copyup_list.find(this->m_object_no);
@@ -651,98 +584,77 @@ void AbstractObjectWriteRequest<I>::send_copyup()
 }
 
 template <typename I>
-void AbstractObjectWriteRequest<I>::send_write_op()
-{
+void AbstractObjectWriteRequest<I>::handle_copyup(int r) {
   I *image_ctx = this->m_ictx;
-  m_state = LIBRBD_AIO_WRITE_FLAT;
-  if (m_guard) {
-    guard_write();
+  ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+
+  assert(m_copyup_in_progress);
+  m_copyup_in_progress = false;
+
+  if (r < 0) {
+    lderr(image_ctx->cct) << "failed to copyup object: " << cpp_strerror(r)
+                          << dendl;
+    this->finish(r);
+    return;
   }
 
-  add_write_hint(&m_write);
-  add_write_ops(&m_write);
-  assert(m_write.size() != 0);
+  if (is_post_copyup_write_required()) {
+    write_object();
+    return;
+  }
 
-  librados::AioCompletion *rados_completion =
-    util::create_rados_callback(this);
-  int r = image_ctx->data_ctx.aio_operate(
-    this->m_oid, rados_completion, &m_write, m_snap_seq, m_snaps,
-    (this->m_trace.valid() ? this->m_trace.get_info() : nullptr));
-  assert(r == 0);
-  rados_completion->release();
+  post_write_object_map_update();
 }
 
 template <typename I>
-void AbstractObjectWriteRequest<I>::handle_write_guard()
-{
+void AbstractObjectWriteRequest<I>::post_write_object_map_update() {
   I *image_ctx = this->m_ictx;
-  bool has_parent;
-  {
-    RWLock::RLocker snap_locker(image_ctx->snap_lock);
-    RWLock::RLocker parent_locker(image_ctx->parent_lock);
-    has_parent = this->compute_parent_extents(&this->m_parent_extents);
-  }
-  // If parent still exists, overlap might also have changed.
-  if (has_parent) {
-    send_copyup();
-  } else {
-    // parent may have disappeared -- send original write again
-    ldout(image_ctx->cct, 20) << "should_complete(" << this
-                              << "): parent overlap now 0" << dendl;
-    send_write();
+
+  image_ctx->snap_lock.get_read();
+  if (image_ctx->object_map == nullptr || !is_object_map_update_enabled() ||
+      !is_non_existent_post_write_object_map_state()) {
+    image_ctx->snap_lock.put_read();
+    this->finish(0);
+    return;
   }
-}
 
-template <typename I>
-void ObjectWriteRequest<I>::add_write_ops(librados::ObjectWriteOperation *wr) {
-  I *image_ctx = this->m_ictx;
+  ldout(image_ctx->cct, 20) << dendl;
 
-  if (this->m_object_off == 0 &&
-      this->m_object_len == image_ctx->get_object_size()) {
-    wr->write_full(m_write_data);
-  } else {
-    wr->write(this->m_object_off, m_write_data);
+  // should have been flushed prior to releasing lock
+  assert(image_ctx->exclusive_lock->is_lock_owner());
+  image_ctx->object_map_lock.get_write();
+  if (image_ctx->object_map->template aio_update<
+        AbstractObjectWriteRequest<I>,
+        &AbstractObjectWriteRequest<I>::handle_post_write_object_map_update>(
+          CEPH_NOSNAP, this->m_object_no, OBJECT_NONEXISTENT, OBJECT_PENDING,
+          this->m_trace, this)) {
+    image_ctx->object_map_lock.put_write();
+    image_ctx->snap_lock.put_read();
+    return;
   }
-  wr->set_op_flags2(m_op_flags);
+
+  image_ctx->object_map_lock.put_write();
+  image_ctx->snap_lock.put_read();
+  this->finish(0);
 }
 
 template <typename I>
-void ObjectWriteRequest<I>::send_write() {
+void AbstractObjectWriteRequest<I>::handle_post_write_object_map_update(int r) {
   I *image_ctx = this->m_ictx;
-  bool write_full = (this->m_object_off == 0 &&
-                     this->m_object_len == image_ctx->get_object_size());
-  ldout(image_ctx->cct, 20) << this->m_oid << " "
-                            << this->m_object_off << "~" << this->m_object_len
-                            << " object exist " << this->m_object_may_exist
-                            << " write_full " << write_full << dendl;
-  if (write_full && !this->has_parent()) {
-    this->m_guard = false;
-  }
+  ldout(image_ctx->cct, 20) << "r=" << r << dendl;
 
-  AbstractObjectWriteRequest<I>::send_write();
+  assert(r == 0);
+  this->finish(0);
 }
 
 template <typename I>
-void ObjectDiscardRequest<I>::send_write() {
-  I *image_ctx = this->m_ictx;
-  ldout(image_ctx->cct, 20) << this->m_oid << " " << get_op_type() << " "
-                            << this->m_object_off << "~"
-                            << this->m_object_len << ", "
-                            << "object exist " << this->m_object_may_exist
-                            << dendl;
-
-  if (!this->m_object_may_exist && !this->has_parent()) {
-    // optimization: nothing to discard
-    this->m_state = AbstractObjectWriteRequest<I>::LIBRBD_AIO_WRITE_FLAT;
-    Context *ctx = util::create_context_callback<ObjectRequest<I>>(this);
-    image_ctx->op_work_queue->queue(ctx, 0);
-  } else if (m_discard_action == DISCARD_ACTION_REMOVE ||
-             m_discard_action == DISCARD_ACTION_REMOVE_TRUNCATE) {
-    // optimization: skip the copyup path for removals
-    this->send_pre_object_map_update();
+void ObjectWriteRequest<I>::add_write_ops(librados::ObjectWriteOperation *wr) {
+  if (this->m_full_object) {
+    wr->write_full(m_write_data);
   } else {
-    AbstractObjectWriteRequest<I>::send_write();
+    wr->write(this->m_object_off, m_write_data);
   }
+  wr->set_op_flags2(m_op_flags);
 }
 
 template <typename I>
@@ -752,31 +664,12 @@ void ObjectWriteSameRequest<I>::add_write_ops(
   wr->set_op_flags2(m_op_flags);
 }
 
-template <typename I>
-void ObjectWriteSameRequest<I>::send_write() {
-  I *image_ctx = this->m_ictx;
-  bool write_full = (this->m_object_off == 0 &&
-                     this->m_object_len == image_ctx->get_object_size());
-  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off << "~"
-                            << this->m_object_len << " write_full "
-                            << write_full << dendl;
-  if (write_full && !this->has_parent()) {
-    this->m_guard = false;
-  }
-
-  AbstractObjectWriteRequest<I>::send_write();
-}
-
 template <typename I>
 void ObjectCompareAndWriteRequest<I>::add_write_ops(
     librados::ObjectWriteOperation *wr) {
-  I *image_ctx = this->m_ictx;
-
-  // add cmpext ops
   wr->cmpext(this->m_object_off, m_cmp_bl, nullptr);
 
-  if (this->m_object_off == 0 &&
-      this->m_object_len == image_ctx->get_object_size()) {
+  if (this->m_full_object) {
     wr->write_full(m_write_bl);
   } else {
     wr->write(this->m_object_off, m_write_bl);
@@ -785,52 +678,24 @@ void ObjectCompareAndWriteRequest<I>::add_write_ops(
 }
 
 template <typename I>
-void ObjectCompareAndWriteRequest<I>::send_write() {
-  I *image_ctx = this->m_ictx;
-  bool write_full = (this->m_object_off == 0 &&
-                     this->m_object_len == image_ctx->get_object_size());
-  ldout(image_ctx->cct, 20) << this->m_oid << " "
-                            << this->m_object_off << "~" << this->m_object_len
-                            << " object exist " << this->m_object_may_exist
-                            << " write_full " << write_full << dendl;
-  if (write_full && !this->has_parent()) {
-    this->m_guard = false;
-  }
+int ObjectCompareAndWriteRequest<I>::filter_write_result(int r) const {
+  if (r <= -MAX_ERRNO) {
+    I *image_ctx = this->m_ictx;
+    Extents image_extents;
 
-  AbstractObjectWriteRequest<I>::send_write();
-}
-
-template <typename I>
-void ObjectCompareAndWriteRequest<I>::complete(int r)
-{
-  I *image_ctx = this->m_ictx;
-  if (this->should_complete(r)) {
-    ldout(image_ctx->cct, 20) << "complete " << this << dendl;
-
-    if (this->m_hide_enoent && r == -ENOENT) {
-      r = 0;
-    }
-
-    vector<pair<uint64_t,uint64_t> > file_extents;
-    if (r <= -MAX_ERRNO) {
-      // object extent compare mismatch
-      uint64_t offset = -MAX_ERRNO - r;
-      Striper::extent_to_file(image_ctx->cct, &image_ctx->layout,
-                              this->m_object_no, offset, this->m_object_len,
-                              file_extents);
-
-      assert(file_extents.size() == 1);
+    // object extent compare mismatch
+    uint64_t offset = -MAX_ERRNO - r;
+    Striper::extent_to_file(image_ctx->cct, &image_ctx->layout,
+                            this->m_object_no, offset, this->m_object_len,
+                            image_extents);
+    assert(image_extents.size() == 1);
 
-      uint64_t mismatch_offset = file_extents[0].first;
-      if (this->m_mismatch_offset)
-        *this->m_mismatch_offset = mismatch_offset;
-      r = -EILSEQ;
+    if (m_mismatch_offset) {
+      *m_mismatch_offset = image_extents[0].first;
     }
-
-    //compare and write object extent error
-    this->m_completion->complete(r);
-    delete this;
+    r = -EILSEQ;
   }
+  return r;
 }
 
 } // namespace io
index f8d6945fc36bf581323dbf38cf4ce9d9359bf178..a015fb199ec9508580ce5f995037b008a82b20f0 100644 (file)
@@ -23,16 +23,12 @@ namespace io {
 
 struct AioCompletion;
 template <typename> class CopyupRequest;
-template <typename> class ObjectRemoveRequest;
-template <typename> class ObjectTruncateRequest;
-template <typename> class ObjectWriteRequest;
-template <typename> class ObjectZeroRequest;
 
 struct ObjectRequestHandle {
   virtual ~ObjectRequestHandle() {
   }
 
-  virtual void complete(int r) = 0;
+  virtual void fail(int r) = 0;
   virtual void send() = 0;
 };
 
@@ -82,9 +78,8 @@ public:
 
   ObjectRequest(ImageCtxT *ictx, const std::string &oid,
                 uint64_t objectno, uint64_t off, uint64_t len,
-                librados::snap_t snap_id, bool hide_enoent,
-               const char *trace_name, const ZTracer::Trace &parent_trace,
-               Context *completion);
+                librados::snap_t snap_id, const char *trace_name,
+                const ZTracer::Trace &parent_trace, Context *completion);
   ~ObjectRequest() override {
     m_trace.event("finish");
   }
@@ -92,9 +87,10 @@ public:
   static void add_write_hint(ImageCtxT& image_ctx,
                              librados::ObjectWriteOperation *wr);
 
-  virtual void complete(int r);
+  void fail(int r) {
+    finish(r);
+  }
 
-  virtual bool should_complete(int r) = 0;
   void send() override = 0;
 
   bool has_parent() const {
@@ -111,8 +107,6 @@ protected:
   uint64_t m_object_no, m_object_off, m_object_len;
   librados::snap_t m_snap_id;
   Context *m_completion;
-  Extents m_parent_extents;
-  bool m_hide_enoent;
   ZTracer::Trace m_trace;
 
   void async_finish(int r);
@@ -144,7 +138,6 @@ public:
                     bool cache_initiated, const ZTracer::Trace &parent_trace,
                     Context *completion);
 
-  bool should_complete(int r) override;
   void send() override;
 
   inline uint64_t get_offset() const {
@@ -215,7 +208,7 @@ public:
   AbstractObjectWriteRequest(ImageCtxT *ictx, const std::string &oid,
                              uint64_t object_no, uint64_t object_off,
                              uint64_t len, const ::SnapContext &snapc,
-                            bool hide_enoent, const char *trace_name,
+                            const char *trace_name,
                             const ZTracer::Trace &parent_trace,
                              Context *completion);
 
@@ -233,78 +226,82 @@ public:
 
   void handle_copyup(int r);
 
-  bool should_complete(int r) override;
   void send() override;
 
+protected:
+  bool m_full_object = false;
+
+  virtual bool is_no_op_for_nonexistent_object() const {
+    return false;
+  }
+  virtual bool is_object_map_update_enabled() const {
+    return true;
+  }
+  virtual bool is_post_copyup_write_required() const {
+    return false;
+  }
+  virtual bool is_non_existent_post_write_object_map_state() const {
+    return false;
+  }
+
+  virtual void add_write_hint(librados::ObjectWriteOperation *wr);
+  virtual void add_write_ops(librados::ObjectWriteOperation *wr) = 0;
+
+  virtual int filter_write_result(int r) const {
+    return r;
+  }
+
+private:
   /**
-   * Writes go through the following state machine to deal with
-   * layering and the object map:
+   * @verbatim
    *
-   *   <start>
-   *      |
-   *      |\
-   *      | \       -or-
-   *      |  ---------------------------------> LIBRBD_AIO_WRITE_PRE
-   *      |                          .                            |
-   *      |                          .                            |
-   *      |                          .                            v
-   *      |                          . . .  . > LIBRBD_AIO_WRITE_FLAT. . .
-   *      |                                                       |      .
-   *      |                                                       |      .
-   *      |                                                       |      .
-   *      v                need copyup   (copyup performs pre)    |      .
-   * LIBRBD_AIO_WRITE_GUARD -----------> LIBRBD_AIO_WRITE_COPYUP  |      .
-   *  .       |                               |        .          |      .
-   *  .       |                               |        .          |      .
-   *  .       |                         /-----/        .          |      .
-   *  .       |                         |              .          |      .
-   *  .       \-------------------\     |     /-------------------/      .
-   *  .                           |     |     |        .                 .
-   *  .                           v     v     v        .                 .
-   *  .                       LIBRBD_AIO_WRITE_POST    .                 .
-   *  .                               |                .                 .
-   *  .                               |  . . . . . . . .                 .
-   *  .                               |  .                               .
-   *  .                               v  v                               .
-   *  . . . . . . . . . . . . . . > <finish> < . . . . . . . . . . . . . .
+   * <start>
+   *    |
+   *    v           (no-op write request)
+   * DETECT_NO_OP . . . . . . . . . . . . . . . . . . .
+   *    |                                             .
+   *    v (skip if not required/disabled)             .
+   * PRE_UPDATE_OBJECT_MAP                            .
+   *    |          .                                  .
+   *    |          . (child dne)                      .
+   *    |          . . . . . . . . .                  .
+   *    |                          .                  .
+   *    |   (post-copyup write)    .                  .
+   *    | . . . . . . . . . . . .  .                  .
+   *    | .                     .  .                  .
+   *    v v                     .  v                  .
+   *   WRITE . . . . . . . . > COPYUP (if required)   .
+   *    |                       |                     .
+   *    |/----------------------/                     .
+   *    |                                             .
+   *    v (skip if not required/disabled)             .
+   * POST_UPDATE_OBJECT_MAP                           .
+   *    |                                             .
+   *    v                                             .
+   * <finish> < . . . . . . . . . . . . . . . . . . . .
    *
-   * The _PRE/_POST states are skipped if the object map is disabled.
-   * The write starts in _WRITE_GUARD or _FLAT depending on whether or not
-   * there is a parent overlap.
+   * @endverbatim
    */
-protected:
-  enum write_state_d {
-    LIBRBD_AIO_WRITE_GUARD,
-    LIBRBD_AIO_WRITE_COPYUP,
-    LIBRBD_AIO_WRITE_FLAT,
-    LIBRBD_AIO_WRITE_PRE,
-    LIBRBD_AIO_WRITE_POST,
-    LIBRBD_AIO_WRITE_ERROR
-  };
 
-  write_state_d m_state;
-  librados::ObjectWriteOperation m_write;
   uint64_t m_snap_seq;
   std::vector<librados::snap_t> m_snaps;
+
+  Extents m_parent_extents;
   bool m_object_may_exist = false;
-  bool m_guard = true;
+  bool m_copyup_enabled = true;
+  bool m_copyup_in_progress = false;
 
-  virtual void add_write_hint(librados::ObjectWriteOperation *wr);
-  virtual void add_write_ops(librados::ObjectWriteOperation *wr) = 0;
+  void pre_write_object_map_update();
+  void handle_pre_write_object_map_update(int r);
 
-  virtual void guard_write();
-  virtual bool post_object_map_update() {
-    return false;
-  }
-  virtual void send_write();
-  virtual void send_write_op();
-  virtual void handle_write_guard();
+  void write_object();
+  void handle_write_object(int r);
 
-  void send_pre_object_map_update();
+  void copyup();
+
+  void post_write_object_map_update();
+  void handle_post_write_object_map_update(int r);
 
-private:
-  bool send_post_object_map_update();
-  void send_copyup();
 };
 
 template <typename ImageCtxT = ImageCtx>
@@ -316,8 +313,8 @@ public:
                      int op_flags, const ZTracer::Trace &parent_trace,
                      Context *completion)
     : AbstractObjectWriteRequest<ImageCtxT>(ictx, oid, object_no, object_off,
-                                            data.length(), snapc, false,
-                                            "write", parent_trace, completion),
+                                            data.length(), snapc, "write",
+                                            parent_trace, completion),
       m_write_data(data), m_op_flags(op_flags) {
   }
 
@@ -332,8 +329,6 @@ public:
 protected:
   void add_write_ops(librados::ObjectWriteOperation *wr) override;
 
-  void send_write() override;
-
 private:
   ceph::bufferlist m_write_data;
   int m_op_flags;
@@ -348,10 +343,10 @@ public:
                        bool disable_clone_remove, bool update_object_map,
                        const ZTracer::Trace &parent_trace, Context *completion)
     : AbstractObjectWriteRequest<ImageCtxT>(ictx, oid, object_no, object_off,
-                                            object_len, snapc, true, "discard",
+                                            object_len, snapc, "discard",
                                             parent_trace, completion),
       m_update_object_map(update_object_map) {
-    if (object_off == 0 && object_len == ictx->layout.object_size) {
+    if (this->m_full_object) {
       if (disable_clone_remove && this->has_parent()) {
         // need to hide the parent object instead of child object
         m_discard_action = DISCARD_ACTION_REMOVE_TRUNCATE;
@@ -359,7 +354,6 @@ public:
       } else {
         m_discard_action = DISCARD_ACTION_REMOVE;
       }
-      this->m_guard = (!snapc.snaps.empty());
     } else if (object_off + object_len == ictx->layout.object_size) {
       m_discard_action = DISCARD_ACTION_TRUNCATE;
     } else {
@@ -390,6 +384,16 @@ public:
   }
 
 protected:
+  bool is_no_op_for_nonexistent_object() const override {
+    return (!this->has_parent());
+  }
+  bool is_object_map_update_enabled() const override {
+    return m_update_object_map;
+  }
+  bool is_non_existent_post_write_object_map_state() const override {
+    return (m_discard_action == DISCARD_ACTION_REMOVE);
+  }
+
   void add_write_hint(librados::ObjectWriteOperation *wr) override {
     // no hint for discard
   }
@@ -412,13 +416,6 @@ protected:
     }
   }
 
-  bool post_object_map_update() override {
-    // trim operation updates the object map in batches
-    return (m_update_object_map && m_discard_action == DISCARD_ACTION_REMOVE);
-  }
-
-  void send_write() override;
-
 private:
   enum DiscardAction {
     DISCARD_ACTION_REMOVE,
@@ -442,9 +439,8 @@ public:
                         const ZTracer::Trace &parent_trace,
                         Context *completion)
     : AbstractObjectWriteRequest<ImageCtxT>(ictx, oid, object_no, object_off,
-                                            object_len, snapc, false,
-                                            "writesame", parent_trace,
-                                            completion),
+                                            object_len, snapc, "writesame",
+                                            parent_trace, completion),
       m_write_data(data), m_op_flags(op_flags) {
   }
 
@@ -455,8 +451,6 @@ public:
 protected:
   void add_write_ops(librados::ObjectWriteOperation *wr) override;
 
-  void send_write() override;
-
 private:
   ceph::bufferlist m_write_data;
   int m_op_flags;
@@ -474,7 +468,7 @@ public:
                                const ZTracer::Trace &parent_trace,
                                Context *completion)
    : AbstractObjectWriteRequest<ImageCtxT>(ictx, oid, object_no, object_off,
-                                           cmp_bl.length(), snapc, false,
+                                           cmp_bl.length(), snapc,
                                            "compare_and_write", parent_trace,
                                            completion),
     m_cmp_bl(cmp_bl), m_write_bl(write_bl),
@@ -485,12 +479,18 @@ public:
     return "compare_and_write";
   }
 
-  void complete(int r) override;
+  void add_copyup_ops(librados::ObjectWriteOperation *wr) override {
+    // no-op on copyup
+  }
 
 protected:
+  virtual bool is_post_copyup_write_required() const {
+    return true;
+  }
+
   void add_write_ops(librados::ObjectWriteOperation *wr) override;
 
-  void send_write() override;
+  int filter_write_result(int r) const override;
 
 private:
   ceph::bufferlist m_cmp_bl;
index bd1ff6795309165bac1fc30057db9493db74ecfb..e85ae259cd0210a2ff0c81790fb145ac73438c2d 100644 (file)
@@ -102,8 +102,8 @@ struct ObjectRequest<librbd::MockTestImageCtx> : public ObjectRequestHandle {
     s_instance = nullptr;
   }
 
-  MOCK_METHOD1(complete, void(int));
   MOCK_METHOD0(send, void());
+  MOCK_METHOD1(fail, void(int));
 };
 
 template <>
index 7e6eb15d1a8a25d88da2c4ed443e72fff53b975a..6f0425029ec06b6b743d158227bef3da8d8d2327 100644 (file)
@@ -35,23 +35,27 @@ namespace io {
 
 template <>
 struct CopyupRequest<librbd::MockImageCtx> {
+  MOCK_METHOD0(send, void());
+  MOCK_METHOD1(append_request, void(AbstractObjectWriteRequest<librbd::MockTestImageCtx>*));
+};
+
+template <>
+struct CopyupRequest<librbd::MockTestImageCtx> : public CopyupRequest<librbd::MockImageCtx> {
   static CopyupRequest* s_instance;
-  static CopyupRequest* create(librbd::MockImageCtx *ictx,
+  static CopyupRequest* create(librbd::MockTestImageCtx *ictx,
                                const std::string &oid, uint64_t objectno,
                                Extents &&image_extents,
                                const ZTracer::Trace &parent_trace) {
     return s_instance;
   }
 
-  MOCK_METHOD0(send, void());
-
   CopyupRequest() {
     s_instance = this;
   }
 };
 
 template <>
-struct ImageRequest<librbd::MockImageCtx> {
+struct ImageRequest<librbd::MockTestImageCtx> {
   static ImageRequest *s_instance;
   static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c,
                        Extents &&image_extents, ReadResult &&read_result,
@@ -66,8 +70,8 @@ struct ImageRequest<librbd::MockImageCtx> {
   }
 };
 
-CopyupRequest<librbd::MockImageCtx>* CopyupRequest<librbd::MockImageCtx>::s_instance = nullptr;
-ImageRequest<librbd::MockImageCtx>* ImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
+CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
 
 } // namespace io
 } // namespace librbd
@@ -86,10 +90,15 @@ using ::testing::WithArg;
 using ::testing::WithArgs;
 
 struct TestMockIoObjectRequest : public TestMockFixture {
-  typedef ObjectRequest<librbd::MockImageCtx> MockObjectRequest;
-  typedef ObjectReadRequest<librbd::MockImageCtx> MockObjectReadRequest;
-  typedef CopyupRequest<librbd::MockImageCtx> MockCopyupRequest;
-  typedef ImageRequest<librbd::MockImageCtx> MockImageRequest;
+  typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest;
+  typedef ObjectReadRequest<librbd::MockTestImageCtx> MockObjectReadRequest;
+  typedef ObjectWriteRequest<librbd::MockTestImageCtx> MockObjectWriteRequest;
+  typedef ObjectDiscardRequest<librbd::MockTestImageCtx> MockObjectDiscardRequest;
+  typedef ObjectWriteSameRequest<librbd::MockTestImageCtx> MockObjectWriteSameRequest;
+  typedef ObjectCompareAndWriteRequest<librbd::MockTestImageCtx> MockObjectCompareAndWriteRequest;
+  typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+  typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+  typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
 
   void expect_object_may_exist(MockTestImageCtx &mock_image_ctx,
                                uint64_t object_no, bool exists) {
@@ -99,6 +108,11 @@ struct TestMockIoObjectRequest : public TestMockFixture {
     }
   }
 
+  void expect_get_object_size(MockTestImageCtx &mock_image_ctx) {
+    EXPECT_CALL(mock_image_ctx, get_object_size()).WillRepeatedly(Return(
+      mock_image_ctx.layout.object_size));
+  }
+
   void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx,
                                  librados::snap_t snap_id, uint64_t overlap,
                                  int r) {
@@ -125,6 +139,11 @@ struct TestMockIoObjectRequest : public TestMockFixture {
       .WillOnce(Return(flags));
   }
 
+  void expect_is_lock_owner(MockExclusiveLock& mock_exclusive_lock) {
+    EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly(
+      Return(true));
+  }
+
   void expect_read(MockTestImageCtx &mock_image_ctx,
                    const std::string& oid, uint64_t off, uint64_t len,
                    const std::string& data, int r) {
@@ -192,6 +211,114 @@ struct TestMockIoObjectRequest : public TestMockFixture {
       .WillOnce(Invoke([&mock_copyup_request, r]() {
                 }));
   }
+
+  void expect_copyup(MockCopyupRequest& mock_copyup_request,
+                     MockAbstractObjectWriteRequest** write_request, int r) {
+    EXPECT_CALL(mock_copyup_request, append_request(_))
+      .WillOnce(Invoke([write_request](MockAbstractObjectWriteRequest *req) {
+                  *write_request = req;
+                }));
+    EXPECT_CALL(mock_copyup_request, send())
+      .WillOnce(Invoke([write_request, r]() {
+                  (*write_request)->handle_copyup(r);
+                }));
+  }
+
+  void expect_object_map_update(MockTestImageCtx &mock_image_ctx,
+                                uint64_t start_object, uint64_t end_object,
+                                uint8_t state,
+                                const boost::optional<uint8_t> &current_state,
+                                bool updated, int ret_val) {
+    if (mock_image_ctx.object_map != nullptr) {
+      EXPECT_CALL(*mock_image_ctx.object_map,
+                  aio_update(CEPH_NOSNAP, start_object, end_object, state,
+                             current_state, _, _))
+        .WillOnce(WithArg<6>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) {
+                               if (updated) {
+                                 mock_image_ctx.op_work_queue->queue(ctx, ret_val);
+                               }
+                               return updated;
+                             })));
+    }
+  }
+
+  void expect_assert_exists(MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), assert_exists(_))
+      .WillOnce(Return(r));
+  }
+
+  void expect_write(MockTestImageCtx &mock_image_ctx,
+                    uint64_t offset, uint64_t length, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               write(_, _, length, offset, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_write_full(MockTestImageCtx &mock_image_ctx, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               write_full(_, _, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_writesame(MockTestImageCtx &mock_image_ctx,
+                        uint64_t offset, uint64_t length, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               writesame(_, _, length, offset, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_remove(MockTestImageCtx &mock_image_ctx, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               remove(_, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_truncate(MockTestImageCtx &mock_image_ctx, int offset, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               truncate(_, offset, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_zero(MockTestImageCtx &mock_image_ctx, int offset, int length,
+                   int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               zero(_, offset, length, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_cmpext(MockTestImageCtx &mock_image_ctx, int offset, int r) {
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+                               cmpext(_, offset, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
 };
 
 TEST_F(TestMockIoObjectRequest, Read) {
@@ -210,11 +337,13 @@ TEST_F(TestMockIoObjectRequest, Read) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, std::string(4096, '1'), 0);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096,
+              std::string(4096, '1'), 0);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -235,14 +364,14 @@ TEST_F(TestMockIoObjectRequest, SparseReadThreshold) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_sparse_read(mock_image_ctx, "object0", 0,
+  expect_sparse_read(mock_image_ctx, ictx->get_object_name(0), 0,
                      ictx->sparse_read_threshold_bytes,
                      std::string(ictx->sparse_read_threshold_bytes, '1'), 0);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, ictx->sparse_read_threshold_bytes,
-    CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+    ictx->sparse_read_threshold_bytes, CEPH_NOSNAP, 0, false, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -263,11 +392,12 @@ TEST_F(TestMockIoObjectRequest, ReadError) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, "", -EPERM);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -EPERM);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
   req->send();
   ASSERT_EQ(-EPERM, ctx.wait());
 }
@@ -286,12 +416,13 @@ TEST_F(TestMockIoObjectRequest, CacheRead) {
   expect_op_work_queue(mock_image_ctx);
 
   InSequence seq;
-  expect_cache_read(mock_image_ctx, "object0", 0, 0, 4096,
+  expect_cache_read(mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096,
                     std::string(4096, '1'), 0);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -310,12 +441,13 @@ TEST_F(TestMockIoObjectRequest, CacheReadError) {
   expect_op_work_queue(mock_image_ctx);
 
   InSequence seq;
-  expect_cache_read(mock_image_ctx, "object0", 0, 0, 4096,
+  expect_cache_read(mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096,
                     "", -EPERM);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
   req->send();
   ASSERT_EQ(-EPERM, ctx.wait());
 }
@@ -352,7 +484,7 @@ TEST_F(TestMockIoObjectRequest, ParentRead) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
 
   MockImageRequest mock_image_request;
   expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
@@ -361,7 +493,8 @@ TEST_F(TestMockIoObjectRequest, ParentRead) {
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
@@ -398,7 +531,7 @@ TEST_F(TestMockIoObjectRequest, ParentReadError) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
 
   MockImageRequest mock_image_request;
   expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
@@ -407,7 +540,8 @@ TEST_F(TestMockIoObjectRequest, ParentReadError) {
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false,  {}, &ctx);
   req->send();
   ASSERT_EQ(-EPERM, ctx.wait());
 }
@@ -443,11 +577,12 @@ TEST_F(TestMockIoObjectRequest, CacheInitiated) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, true, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, true,
+    {}, &ctx);
   req->send();
   ASSERT_EQ(-ENOENT, ctx.wait());
 }
@@ -484,7 +619,7 @@ TEST_F(TestMockIoObjectRequest, CopyOnRead) {
   InSequence seq;
   expect_object_may_exist(mock_image_ctx, 0, true);
   expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0);
-  expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT);
+  expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT);
 
   MockImageRequest mock_image_request;
   expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
@@ -498,10 +633,705 @@ TEST_F(TestMockIoObjectRequest, CopyOnRead) {
 
   C_SaferCond ctx;
   auto req = MockObjectReadRequest::create(
-    &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx);
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0,
+    false, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, Write) {
+  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;
+  }
+
+  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, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteFull) {
+  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;
+  }
+
+  bufferlist bl;
+  bl.append(std::string(ictx->get_object_size(), '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_full(mock_image_ctx, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteObjectMap) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_op_work_queue(mock_image_ctx);
+  expect_get_object_size(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  expect_is_lock_owner(mock_exclusive_lock);
+
+  MockObjectMap mock_object_map;
+  mock_image_ctx.object_map = &mock_object_map;
+
+  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, {}, true, 0);
+  expect_write(mock_image_ctx, 0, 4096, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteError) {
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_get_object_size(mock_image_ctx);
+
+  bufferlist bl;
+  bl.append(std::string(4096, '1'));
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_write(mock_image_ctx, 0, 4096, -EPERM);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, Copyup) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  librbd::Image image;
+  librbd::RBD rbd;
+  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+  ASSERT_EQ(0, image.snap_create("one"));
+  ASSERT_EQ(0, image.snap_protect("one"));
+  image.close();
+
+  std::string clone_name = get_temp_image_name();
+  int order = 0;
+  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(clone_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;
+  }
+
+  bufferlist bl;
+  bl.append(std::string(4096, '1'));
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+  expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+  expect_object_may_exist(mock_image_ctx, 0, true);
+  expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+  expect_assert_exists(mock_image_ctx, -ENOENT);
+
+  MockAbstractObjectWriteRequest *write_request = nullptr;
+  MockCopyupRequest mock_copyup_request;
+  expect_copyup(mock_copyup_request, &write_request, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyupOptimization) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP);
+
+  librbd::Image image;
+  librbd::RBD rbd;
+  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+  ASSERT_EQ(0, image.snap_create("one"));
+  ASSERT_EQ(0, image.snap_protect("one"));
+  image.close();
+
+  std::string clone_name = get_temp_image_name();
+  int order = 0;
+  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_get_object_size(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  expect_is_lock_owner(mock_exclusive_lock);
+
+  MockObjectMap mock_object_map;
+  mock_image_ctx.object_map = &mock_object_map;
+
+  bufferlist bl;
+  bl.append(std::string(4096, '1'));
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+  expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+  expect_object_may_exist(mock_image_ctx, 0, false);
+
+  MockAbstractObjectWriteRequest *write_request = nullptr;
+  MockCopyupRequest mock_copyup_request;
+  expect_copyup(mock_copyup_request, &write_request, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CopyupError) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  librbd::Image image;
+  librbd::RBD rbd;
+  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+  ASSERT_EQ(0, image.snap_create("one"));
+  ASSERT_EQ(0, image.snap_protect("one"));
+  image.close();
+
+  std::string clone_name = get_temp_image_name();
+  int order = 0;
+  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(clone_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_get_object_size(mock_image_ctx);
+
+  bufferlist bl;
+  bl.append(std::string(4096, '1'));
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+  expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+  expect_assert_exists(mock_image_ctx, -ENOENT);
+
+  MockAbstractObjectWriteRequest *write_request = nullptr;
+  MockCopyupRequest mock_copyup_request;
+  expect_copyup(mock_copyup_request, &write_request, -EPERM);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteRequest::create_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc,
+    0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardRemove) {
+  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;
+  }
+
+  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_PENDING, {}, false, 0);
+  expect_remove(mock_image_ctx, 0);
+  expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT,
+                           OBJECT_PENDING, false, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+    mock_image_ctx.get_object_size(), mock_image_ctx.snapc, false, true, {},
+    &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 }
 
+TEST_F(TestMockIoObjectRequest, DiscardRemoveTruncate) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  librbd::Image image;
+  librbd::RBD rbd;
+  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+  ASSERT_EQ(0, image.snap_create("one"));
+  ASSERT_EQ(0, image.snap_protect("one"));
+  image.close();
+
+  std::string clone_name = get_temp_image_name();
+  int order = 0;
+  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(clone_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;
+  }
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+  expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+  expect_object_may_exist(mock_image_ctx, 0, true);
+  expect_truncate(mock_image_ctx, 0, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+    mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, true, {},
+    &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardTruncate) {
+  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;
+  }
+
+  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_truncate(mock_image_ctx, 1, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 1,
+    mock_image_ctx.get_object_size() - 1, mock_image_ctx.snapc, false, true, {},
+    &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardZero) {
+  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;
+  }
+
+  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_zero(mock_image_ctx, 1, 1, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 1, 1, mock_image_ctx.snapc,
+    false, true, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardDisableObjectMapUpdate) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  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;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  expect_is_lock_owner(mock_exclusive_lock);
+
+  MockObjectMap mock_object_map;
+  mock_image_ctx.object_map = &mock_object_map;
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_object_may_exist(mock_image_ctx, 0, true);
+  expect_remove(mock_image_ctx, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+    mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, false, {},
+    &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, DiscardNoOp) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_op_work_queue(mock_image_ctx);
+  expect_get_object_size(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  expect_is_lock_owner(mock_exclusive_lock);
+
+  MockObjectMap mock_object_map;
+  mock_image_ctx.object_map = &mock_object_map;
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_object_may_exist(mock_image_ctx, 0, false);
+
+  C_SaferCond ctx;
+  auto req = MockObjectDiscardRequest::create_discard(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0,
+    mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, false, {},
+    &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, WriteSame) {
+  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;
+  }
+
+  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_writesame(mock_image_ctx, 0, 4096, 0);
+
+  C_SaferCond ctx;
+  auto req = MockObjectWriteSameRequest::create_writesame(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, bl,
+    mock_image_ctx.snapc, 0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWrite) {
+  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;
+  }
+
+  bufferlist cmp_bl;
+  cmp_bl.append_zero(4096);
+
+  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_cmpext(mock_image_ctx, 0, 0);
+  expect_write(mock_image_ctx, 0, 4096, 0);
+
+  C_SaferCond ctx;
+  uint64_t mismatch_offset;
+  auto req = MockObjectWriteSameRequest::create_compare_and_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl,
+    mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteFull) {
+  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;
+  }
+
+  bufferlist cmp_bl;
+  cmp_bl.append_zero(ictx->get_object_size());
+
+  bufferlist bl;
+  bl.append(std::string(ictx->get_object_size(), '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_cmpext(mock_image_ctx, 0, 0);
+  expect_write_full(mock_image_ctx, 0);
+
+  C_SaferCond ctx;
+  uint64_t mismatch_offset;
+  auto req = MockObjectWriteSameRequest::create_compare_and_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl,
+    mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteCopyup) {
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  librbd::Image image;
+  librbd::RBD rbd;
+  ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL));
+  ASSERT_EQ(0, image.snap_create("one"));
+  ASSERT_EQ(0, image.snap_protect("one"));
+  image.close();
+
+  std::string clone_name = get_temp_image_name();
+  int order = 0;
+  ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx,
+                         clone_name.c_str(), RBD_FEATURE_LAYERING, &order));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(clone_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;
+  }
+
+  bufferlist cmp_bl;
+  cmp_bl.append_zero(4096);
+
+  bufferlist bl;
+  bl.append(std::string(4096, '1'));
+
+  InSequence seq;
+  expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+  expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+  expect_object_may_exist(mock_image_ctx, 0, true);
+  expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0);
+  expect_assert_exists(mock_image_ctx, -ENOENT);
+
+  MockAbstractObjectWriteRequest *write_request = nullptr;
+  MockCopyupRequest mock_copyup_request;
+  expect_copyup(mock_copyup_request, &write_request, 0);
+
+  expect_assert_exists(mock_image_ctx, 0);
+  expect_cmpext(mock_image_ctx, 0, 0);
+  expect_write(mock_image_ctx, 0, 4096, 0);
+
+  C_SaferCond ctx;
+  uint64_t mismatch_offset;
+  auto req = MockObjectWriteSameRequest::create_compare_and_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl,
+    mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockIoObjectRequest, CompareAndWriteMismatch) {
+  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;
+  }
+
+  bufferlist cmp_bl;
+  cmp_bl.append_zero(4096);
+
+  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_cmpext(mock_image_ctx, 0, -MAX_ERRNO - 1);
+
+  C_SaferCond ctx;
+  uint64_t mismatch_offset;
+  auto req = MockObjectWriteSameRequest::create_compare_and_write(
+    &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl,
+    mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx);
+  req->send();
+  ASSERT_EQ(-EILSEQ, ctx.wait());
+  ASSERT_EQ(1ULL, mismatch_offset);
+}
+
 } // namespace io
 } // namespace librbd
+
index ee020c1350982cdbc06720d6fa50059a1711e497..17697a12d19c9d8177f9f171bdebb0e68d6fd68e 100644 (file)
@@ -108,7 +108,8 @@ struct MockImageCtx {
           image_ctx.mirroring_resync_after_disconnect),
       mirroring_replay_delay(image_ctx.mirroring_replay_delay),
       non_blocking_aio(image_ctx.non_blocking_aio),
-      blkin_trace_all(image_ctx.blkin_trace_all)
+      blkin_trace_all(image_ctx.blkin_trace_all),
+      enable_alloc_hint(image_ctx.enable_alloc_hint)
   {
     md_ctx.dup(image_ctx.md_ctx);
     data_ctx.dup(image_ctx.data_ctx);
@@ -147,6 +148,7 @@ struct MockImageCtx {
   MOCK_METHOD0(init_layout, void());
 
   MOCK_CONST_METHOD1(get_object_name, std::string(uint64_t));
+  MOCK_CONST_METHOD0(get_object_size, uint64_t());
   MOCK_CONST_METHOD0(get_current_size, uint64_t());
   MOCK_CONST_METHOD1(get_image_size, uint64_t(librados::snap_t));
   MOCK_CONST_METHOD1(get_object_count, uint64_t(librados::snap_t));
@@ -315,6 +317,7 @@ struct MockImageCtx {
   int mirroring_replay_delay;
   bool non_blocking_aio;
   bool blkin_trace_all;
+  bool enable_alloc_hint;
 };
 
 } // namespace librbd
index 8821a7cb9ed625b2205e089808ed6c34eed5e52a..9509b203fda1a050d3771764c3be722b61648469 100644 (file)
@@ -94,7 +94,7 @@ struct ObjectRequest<librbd::MockTestImageCtx> : public ObjectRequestHandle {
 
   MOCK_METHOD3(construct, void(uint64_t, uint64_t, bool));
   MOCK_METHOD0(send, void());
-  MOCK_METHOD1(complete, void(int));
+  MOCK_METHOD1(fail, void(int));
 };
 
 ObjectRequest<librbd::MockTestImageCtx>* ObjectRequest<librbd::MockTestImageCtx>::s_instance = nullptr;