]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: do not invalidate object map if update races with copyup
authorJason Dillaman <dillaman@redhat.com>
Mon, 24 Sep 2018 18:45:09 +0000 (14:45 -0400)
committerJason Dillaman <dillaman@redhat.com>
Wed, 26 Sep 2018 12:34:32 +0000 (08:34 -0400)
The copyup state machine needs to iterate over all object maps to update
the existence for the object. If an snapshot is being removed concurrently,
it's possible to invalidate the object map for the image.

Fixes: http://tracker.ceph.com/issues/24516
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
14 files changed:
src/librbd/ObjectMap.cc
src/librbd/ObjectMap.h
src/librbd/deep_copy/ObjectCopyRequest.cc
src/librbd/io/CopyupRequest.cc
src/librbd/io/ObjectRequest.cc
src/librbd/object_map/UpdateRequest.cc
src/librbd/object_map/UpdateRequest.h
src/librbd/operation/TrimRequest.cc
src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc
src/test/librbd/io/test_mock_ObjectRequest.cc
src/test/librbd/mock/MockObjectMap.h
src/test/librbd/object_map/test_mock_UpdateRequest.cc
src/test/librbd/operation/test_mock_TrimRequest.cc
src/test/librbd/test_mock_ObjectMap.cc

index 870049be71da1f0360b1ca7111d0f48e55b85411..50cbfda89220edc23747aa1a8763401754ecd6f7 100644 (file)
@@ -286,7 +286,7 @@ void ObjectMap<I>::detained_aio_update(UpdateOperation &&op) {
       handle_detained_aio_update(cell, r, on_finish);
     });
   aio_update(CEPH_NOSNAP, op.start_object_no, op.end_object_no, op.new_state,
-             op.current_state, op.parent_trace, ctx);
+             op.current_state, op.parent_trace, op.ignore_enoent, ctx);
 }
 
 template <typename I>
@@ -314,7 +314,7 @@ void ObjectMap<I>::aio_update(uint64_t snap_id, uint64_t start_object_no,
                               uint64_t end_object_no, uint8_t new_state,
                               const boost::optional<uint8_t> &current_state,
                               const ZTracer::Trace &parent_trace,
-                              Context *on_finish) {
+                              bool ignore_enoent, Context *on_finish) {
   ceph_assert(m_image_ctx.snap_lock.is_locked());
   ceph_assert((m_image_ctx.features & RBD_FEATURE_OBJECT_MAP) != 0);
   ceph_assert(m_image_ctx.image_watcher != nullptr);
@@ -354,7 +354,7 @@ void ObjectMap<I>::aio_update(uint64_t snap_id, uint64_t start_object_no,
 
   auto req = object_map::UpdateRequest<I>::create(
     m_image_ctx, &m_object_map, snap_id, start_object_no, end_object_no,
-    new_state, current_state, parent_trace, on_finish);
+    new_state, current_state, parent_trace, ignore_enoent, on_finish);
   req->send();
 }
 
index ad393ef15d9e7dc6e0aae347ccfbbaff7bc9a9f4..bfde572a1d18deef9be9041839c3c24522c4466d 100644 (file)
@@ -57,17 +57,19 @@ public:
   template <typename T, void(T::*MF)(int) = &T::complete>
   bool aio_update(uint64_t snap_id, uint64_t start_object_no, uint8_t new_state,
                   const boost::optional<uint8_t> &current_state,
-                  const ZTracer::Trace &parent_trace, T *callback_object) {
+                  const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                  T *callback_object) {
     return aio_update<T, MF>(snap_id, start_object_no, start_object_no + 1,
                              new_state, current_state, parent_trace,
-                             callback_object);
+                             ignore_enoent, callback_object);
   }
 
   template <typename T, void(T::*MF)(int) = &T::complete>
   bool aio_update(uint64_t snap_id, uint64_t start_object_no,
                   uint64_t end_object_no, uint8_t new_state,
                   const boost::optional<uint8_t> &current_state,
-                  const ZTracer::Trace &parent_trace, T *callback_object) {
+                  const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                  T *callback_object) {
     ceph_assert(start_object_no < end_object_no);
     if (snap_id == CEPH_NOSNAP) {
       end_object_no = std::min(end_object_no, m_object_map.size());
@@ -89,12 +91,13 @@ public:
 
       UpdateOperation update_operation(start_object_no, end_object_no,
                                        new_state, current_state, parent_trace,
+                                       ignore_enoent,
                                        util::create_context_callback<T, MF>(
                                          callback_object));
       detained_aio_update(std::move(update_operation));
     } else {
       aio_update(snap_id, start_object_no, end_object_no, new_state,
-                 current_state, parent_trace,
+                 current_state, parent_trace, ignore_enoent,
                  util::create_context_callback<T, MF>(callback_object));
     }
     return true;
@@ -111,15 +114,18 @@ private:
     uint8_t new_state;
     boost::optional<uint8_t> current_state;
     ZTracer::Trace parent_trace;
+    bool ignore_enoent;
     Context *on_finish;
 
     UpdateOperation(uint64_t start_object_no, uint64_t end_object_no,
                     uint8_t new_state,
                     const boost::optional<uint8_t> &current_state,
-                    const ZTracer::Trace &parent_trace, Context *on_finish)
+                    const ZTracer::Trace &parent_trace,
+                    bool ignore_enoent, Context *on_finish)
       : start_object_no(start_object_no), end_object_no(end_object_no),
         new_state(new_state), current_state(current_state),
-        parent_trace(parent_trace), on_finish(on_finish) {
+        parent_trace(parent_trace), ignore_enoent(ignore_enoent),
+        on_finish(on_finish) {
     }
   };
 
@@ -138,7 +144,8 @@ private:
   void aio_update(uint64_t snap_id, uint64_t start_object_no,
                   uint64_t end_object_no, uint8_t new_state,
                   const boost::optional<uint8_t> &current_state,
-                  const ZTracer::Trace &parent_trace, Context *on_finish);
+                  const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                  Context *on_finish);
   bool update_required(const ceph::BitVector<2>::Iterator &it,
                        uint8_t new_state);
 
index 78ef97fb4716338eb77b08c154bb49fb27a15ae4..096b3cb1a3a7a5c2132e81ecd2d313a87e387423 100644 (file)
@@ -473,7 +473,7 @@ void ObjectCopyRequest<I>::send_update_object_map() {
   m_dst_image_ctx->object_map_lock.get_write();
   bool sent = m_dst_image_ctx->object_map->template aio_update<
     Context, &Context::complete>(dst_snap_id, m_dst_object_number, object_state,
-                                 {}, {}, ctx);
+                                 {}, {}, false, ctx);
   m_dst_image_ctx->object_map_lock.put_write();
   m_dst_image_ctx->snap_lock.put_read();
   m_dst_image_ctx->owner_lock.put_read();
index dbb3f0f89e5d6994554f140f67449477e586b6b2..0def795f1a845bdeb7c09183b85be28958266cc1 100644 (file)
@@ -50,7 +50,7 @@ public:
       ceph_assert(m_image_ctx.exclusive_lock->is_lock_owner());
       ceph_assert(m_image_ctx.object_map != nullptr);
       bool sent = m_image_ctx.object_map->aio_update<Context>(
-        CEPH_NOSNAP, m_object_no, OBJECT_EXISTS, {}, m_trace, this);
+        CEPH_NOSNAP, m_object_no, OBJECT_EXISTS, {}, m_trace, false, this);
       return (sent ? 0 : 1);
     }
 
@@ -67,7 +67,7 @@ public:
     }
 
     bool sent = m_image_ctx.object_map->aio_update<Context>(
-      snap_id, m_object_no, state, {}, m_trace, this);
+      snap_id, m_object_no, state, {}, m_trace, true, this);
     ceph_assert(sent);
     return 0;
   }
@@ -454,7 +454,7 @@ bool CopyupRequest<I>::send_object_map_head() {
       if (may_update && (new_state != current_state) &&
           m_ictx->object_map->aio_update<CopyupRequest>(
             CEPH_NOSNAP, m_object_no, new_state, current_state, m_trace,
-            this)) {
+            false, this)) {
         return false;
       }
     }
index 1250e03cdff7c2688f7e3cb6f61ca18be7e16b43..a66a06f4da237ba5d62f69957f7f18fbd2bf5b11 100644 (file)
@@ -459,7 +459,8 @@ void AbstractObjectWriteRequest<I>::pre_write_object_map_update() {
   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)) {
+          CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, false,
+          this)) {
     image_ctx->object_map_lock.put_write();
     image_ctx->snap_lock.put_read();
     return;
@@ -611,7 +612,7 @@ void AbstractObjectWriteRequest<I>::post_write_object_map_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)) {
+          this->m_trace, false, this)) {
     image_ctx->object_map_lock.put_write();
     image_ctx->snap_lock.put_read();
     return;
index 36f72dd40a7a989506d0c0d9b5265c258a4c5727..4b30533498a3ebb16682bfe6c51883ee8d4a6a15 100644 (file)
@@ -72,6 +72,13 @@ template <typename I>
 void UpdateRequest<I>::handle_update_object_map(int r) {
   ldout(m_image_ctx.cct, 20) << "r=" << r << dendl;
 
+  if (r == -ENOENT && m_ignore_enoent) {
+    r = 0;
+  }
+  if (r < 0 && m_ret_val == 0) {
+    m_ret_val = r;
+  }
+
   {
     RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
     RWLock::WLocker object_map_locker(m_image_ctx.object_map_lock);
@@ -85,7 +92,7 @@ void UpdateRequest<I>::handle_update_object_map(int r) {
   }
 
   // no more batch updates to send
-  complete(r);
+  complete(m_ret_val);
 }
 
 template <typename I>
index cb9804d07c9f0e09dc2f2110ab5fd70c7383d857..f5dcce15310278f5fef8e85587c46925a087e92e 100644 (file)
@@ -28,22 +28,24 @@ public:
                                uint64_t end_object_no, uint8_t new_state,
                                const boost::optional<uint8_t> &current_state,
                                const ZTracer::Trace &parent_trace,
-                               Context *on_finish) {
+                               bool ignore_enoent, Context *on_finish) {
     return new UpdateRequest(image_ctx, object_map, snap_id, start_object_no,
                              end_object_no, new_state, current_state,
-                             parent_trace, on_finish);
+                             parent_trace, ignore_enoent, on_finish);
   }
 
   UpdateRequest(ImageCtx &image_ctx, ceph::BitVector<2> *object_map,
                 uint64_t snap_id, uint64_t start_object_no,
                 uint64_t end_object_no, uint8_t new_state,
                 const boost::optional<uint8_t> &current_state,
-               const ZTracer::Trace &parent_trace, Context *on_finish)
+               const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                Context *on_finish)
     : Request(image_ctx, snap_id, on_finish), m_object_map(*object_map),
       m_start_object_no(start_object_no), m_end_object_no(end_object_no),
       m_update_start_object_no(start_object_no), m_new_state(new_state),
       m_current_state(current_state),
-      m_trace(util::create_trace(image_ctx, "update object map", parent_trace))
+      m_trace(util::create_trace(image_ctx, "update object map", parent_trace)),
+      m_ignore_enoent(ignore_enoent)
   {
     m_trace.event("start");
   }
@@ -80,6 +82,9 @@ private:
   uint8_t m_new_state;
   boost::optional<uint8_t> m_current_state;
   ZTracer::Trace m_trace;
+  bool m_ignore_enoent;
+
+  int m_ret_val = 0;
 
   void update_object_map();
   void handle_update_object_map(int r);
index 9ba563589dcd3f468c0a096df26e0e81fba3df93..8609235e558ef986f3443672b3a440656ab82d2c 100644 (file)
@@ -200,7 +200,7 @@ void TrimRequest<I>::send_pre_trim() {
       RWLock::WLocker object_map_locker(image_ctx.object_map_lock);
       if (image_ctx.object_map->template aio_update<AsyncRequest<I> >(
             CEPH_NOSNAP, m_delete_start_min, m_num_objects, OBJECT_PENDING,
-            OBJECT_EXISTS, {}, this)) {
+            OBJECT_EXISTS, {}, false, this)) {
         return;
       }
     }
@@ -294,7 +294,7 @@ void TrimRequest<I>::send_post_trim() {
       RWLock::WLocker object_map_locker(image_ctx.object_map_lock);
       if (image_ctx.object_map->template aio_update<AsyncRequest<I> >(
             CEPH_NOSNAP, m_delete_start_min, m_num_objects, OBJECT_NONEXISTENT,
-            OBJECT_PENDING, {}, this)) {
+            OBJECT_PENDING, {}, false, this)) {
         return;
       }
     }
index f004291b820a96014ed3baad09fc2f93bbb66f38..6b4b8929af389a42cfd4992bcacd1688b6975a78 100644 (file)
@@ -284,18 +284,18 @@ public:
                                 librados::snap_t snap_id, uint8_t state,
                                 int r) {
     if (mock_image_ctx.image_ctx->object_map != nullptr) {
-      auto &expect = EXPECT_CALL(mock_object_map, aio_update(snap_id, 0, 1, state, _, _, _));
+      auto &expect = EXPECT_CALL(mock_object_map, aio_update(snap_id, 0, 1, state, _, _, false, _));
       if (r < 0) {
-        expect.WillOnce(DoAll(WithArg<6>(Invoke([this, r](Context *ctx) {
+        expect.WillOnce(DoAll(WithArg<7>(Invoke([this, r](Context *ctx) {
                                   m_work_queue->queue(ctx, r);
                                 })),
                               Return(true)));
       } else {
-        expect.WillOnce(DoAll(WithArg<6>(Invoke([&mock_image_ctx, snap_id, state](Context *ctx) {
+        expect.WillOnce(DoAll(WithArg<7>(Invoke([&mock_image_ctx, snap_id, state](Context *ctx) {
                                   ceph_assert(mock_image_ctx.image_ctx->snap_lock.is_locked());
                                   ceph_assert(mock_image_ctx.image_ctx->object_map_lock.is_wlocked());
                                   mock_image_ctx.image_ctx->object_map->aio_update<Context>(
-                                    snap_id, 0, 1, state, boost::none, {}, ctx);
+                                    snap_id, 0, 1, state, boost::none, {}, false, ctx);
                                 })),
                               Return(true)));
       }
index 0a4aeda733f14b123c7f47fac64173a11c702e35..72ae152517264ca3bfb96d32c74fc7be573babab 100644 (file)
@@ -215,8 +215,8 @@ struct TestMockIoObjectRequest : public TestMockFixture {
     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) {
+                             current_state, _, false, _))
+        .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) {
                                if (updated) {
                                  mock_image_ctx.op_work_queue->queue(ctx, ret_val);
                                }
index 61dcedc38c3e1a8d792797253ae600f63cb805af..e756178b3a94644812ca08a6bb9aba679d55a8c7 100644 (file)
@@ -24,30 +24,33 @@ struct MockObjectMap {
   template <typename T, void(T::*MF)(int) = &T::complete>
   bool aio_update(uint64_t snap_id, uint64_t start_object_no, uint8_t new_state,
                   const boost::optional<uint8_t> &current_state,
-                  const ZTracer::Trace &parent_trace, T *callback_object) {
+                  const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                  T *callback_object) {
     return aio_update<T, MF>(snap_id, start_object_no, start_object_no + 1,
                              new_state, current_state, parent_trace,
-                             callback_object);
+                             ignore_enoent, callback_object);
   }
 
   template <typename T, void(T::*MF)(int) = &T::complete>
   bool aio_update(uint64_t snap_id, uint64_t start_object_no,
                   uint64_t end_object_no, uint8_t new_state,
                   const boost::optional<uint8_t> &current_state,
-                  const ZTracer::Trace &parent_trace, T *callback_object) {
+                  const ZTracer::Trace &parent_trace, bool ignore_enoent,
+                  T *callback_object) {
     auto ctx = util::create_context_callback<T, MF>(callback_object);
     bool updated = aio_update(snap_id, start_object_no, end_object_no,
-                              new_state, current_state, parent_trace, ctx);
+                              new_state, current_state, parent_trace,
+                              ignore_enoent, ctx);
     if (!updated) {
       delete ctx;
     }
     return updated;
   }
-  MOCK_METHOD7(aio_update, bool(uint64_t snap_id, uint64_t start_object_no,
+  MOCK_METHOD8(aio_update, bool(uint64_t snap_id, uint64_t start_object_no,
                                 uint64_t end_object_no, uint8_t new_state,
                                 const boost::optional<uint8_t> &current_state,
                                 const ZTracer::Trace &parent_trace,
-                                Context *on_finish));
+                                bool ignore_enoent, Context *on_finish));
 
   MOCK_METHOD2(snapshot_add, void(uint64_t snap_id, Context *on_finish));
   MOCK_METHOD2(snapshot_remove, void(uint64_t snap_id, Context *on_finish));
index 380a0350ad884c9d9f8f74416acce6d31cfa5daf..b7de4d1868ea83386aaf5c56a4423ef1884ccb4e 100644 (file)
@@ -81,7 +81,7 @@ TEST_F(TestMockObjectMapUpdateRequest, UpdateInMemory) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT,
-    OBJECT_EXISTS, {}, &cond_ctx);
+    OBJECT_EXISTS, {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -113,7 +113,7 @@ TEST_F(TestMockObjectMapUpdateRequest, UpdateHeadOnDisk) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT,
-    OBJECT_EXISTS, {}, &cond_ctx);
+    OBJECT_EXISTS, {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -143,7 +143,7 @@ TEST_F(TestMockObjectMapUpdateRequest, UpdateSnapOnDisk) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_NONEXISTENT,
-    OBJECT_EXISTS, {}, &cond_ctx);
+    OBJECT_EXISTS, {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -171,7 +171,7 @@ TEST_F(TestMockObjectMapUpdateRequest, UpdateOnDiskError) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT,
-    OBJECT_EXISTS, {}, &cond_ctx);
+    OBJECT_EXISTS, {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -202,7 +202,7 @@ TEST_F(TestMockObjectMapUpdateRequest, RebuildSnapOnDisk) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_EXISTS_CLEAN,
-    boost::optional<uint8_t>(), {}, &cond_ctx);
+    boost::optional<uint8_t>(), {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -240,7 +240,7 @@ TEST_F(TestMockObjectMapUpdateRequest, BatchUpdate) {
   C_SaferCond cond_ctx;
   AsyncRequest<> *req = new UpdateRequest<>(
     *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT,
-    OBJECT_EXISTS, {}, &cond_ctx);
+    OBJECT_EXISTS, {}, false, &cond_ctx);
   {
     RWLock::RLocker snap_locker(ictx->snap_lock);
     RWLock::WLocker object_map_locker(ictx->object_map_lock);
@@ -249,5 +249,32 @@ TEST_F(TestMockObjectMapUpdateRequest, BatchUpdate) {
   ASSERT_EQ(0, cond_ctx.wait());
 }
 
+TEST_F(TestMockObjectMapUpdateRequest, IgnoreMissingObjectMap) {
+  REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, acquire_exclusive_lock(*ictx));
+
+  expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS,
+                -ENOENT);
+
+  ceph::BitVector<2> object_map;
+  object_map.resize(1);
+
+  C_SaferCond cond_ctx;
+  AsyncRequest<> *req = new UpdateRequest<>(
+    *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT,
+    OBJECT_EXISTS, {}, true, &cond_ctx);
+  {
+    RWLock::RLocker snap_locker(ictx->snap_lock);
+    RWLock::WLocker object_map_locker(ictx->object_map_lock);
+    req->send();
+  }
+  ASSERT_EQ(0, cond_ctx.wait());
+
+  expect_unlock_exclusive_lock(*ictx);
+}
+
 } // namespace object_map
 } // namespace librbd
index 0203395929add30b3608b383d28e528a42de17ff..15e9bdd055e29583b39816498e1e8ea526e8bab5 100644 (file)
@@ -137,8 +137,8 @@ public:
     if (mock_image_ctx.object_map != nullptr) {
       EXPECT_CALL(*mock_image_ctx.object_map,
                   aio_update(CEPH_NOSNAP, start_object, end_object, state,
-                             boost::optional<uint8_t>(current_state), _, _))
-        .WillOnce(WithArg<6>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) {
+                             boost::optional<uint8_t>(current_state), _, false, _))
+        .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) {
                                if (updated) {
                                  mock_image_ctx.op_work_queue->queue(ctx, ret_val);
                                }
index 1d739a7c858c5c1253ed7669f48cbdbe84808e43..41672c73f33f1e198d858dc932a0474ba3eeb523 100644 (file)
@@ -71,17 +71,18 @@ struct UpdateRequest<MockTestImageCtx> {
                                uint8_t new_state,
                                const boost::optional<uint8_t> &current_state,
                                const ZTracer::Trace &parent_trace,
-                               Context *on_finish) {
+                               bool ignore_enoent, Context *on_finish) {
     ceph_assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
     s_instance->construct(snap_id, start_object_no, end_object_no, new_state,
-                          current_state);
+                          current_state, ignore_enoent);
     return s_instance;
   }
 
-  MOCK_METHOD5(construct, void(uint64_t snap_id, uint64_t start_object_no,
+  MOCK_METHOD6(construct, void(uint64_t snap_id, uint64_t start_object_no,
                                uint64_t end_object_no, uint8_t new_state,
-                               const boost::optional<uint8_t> &current_state));
+                               const boost::optional<uint8_t> &current_state,
+                               bool ignore_enoent));
   MOCK_METHOD0(send, void());
   UpdateRequest() {
     s_instance = this;
@@ -133,10 +134,10 @@ public:
                      uint64_t snap_id, uint64_t start_object_no,
                      uint64_t end_object_no, uint8_t new_state,
                      const boost::optional<uint8_t> &current_state,
-                     Context **on_finish) {
+                     bool ignore_enoent, Context **on_finish) {
     EXPECT_CALL(mock_update_request, construct(snap_id, start_object_no,
                                                end_object_no, new_state,
-                                               current_state))
+                                               current_state, ignore_enoent))
       .Times(1);
     EXPECT_CALL(mock_update_request, send())
       .WillOnce(Invoke([&mock_update_request, on_finish]() {
@@ -163,10 +164,10 @@ TEST_F(TestMockObjectMap, NonDetainedUpdate) {
   MockUpdateRequest mock_update_request;
   Context *finish_update_1;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                0, 1, 1, {}, &finish_update_1);
+                0, 1, 1, {}, false, &finish_update_1);
   Context *finish_update_2;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                1, 2, 1, {}, &finish_update_2);
+                1, 2, 1, {}, false, &finish_update_2);
 
   MockUnlockRequest mock_unlock_request;
   expect_unlock(mock_image_ctx, mock_unlock_request, 0);
@@ -181,8 +182,8 @@ TEST_F(TestMockObjectMap, NonDetainedUpdate) {
   {
     RWLock::RLocker snap_locker(mock_image_ctx.snap_lock);
     RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock);
-    mock_object_map.aio_update(CEPH_NOSNAP, 0, 1, {}, {}, &update_ctx1);
-    mock_object_map.aio_update(CEPH_NOSNAP, 1, 1, {}, {}, &update_ctx2);
+    mock_object_map.aio_update(CEPH_NOSNAP, 0, 1, {}, {}, false, &update_ctx1);
+    mock_object_map.aio_update(CEPH_NOSNAP, 1, 1, {}, {}, false, &update_ctx2);
   }
 
   finish_update_2->complete(0);
@@ -213,16 +214,16 @@ TEST_F(TestMockObjectMap, DetainedUpdate) {
   MockUpdateRequest mock_update_request;
   Context *finish_update_1;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                1, 4, 1, {}, &finish_update_1);
+                1, 4, 1, {}, false, &finish_update_1);
   Context *finish_update_2 = nullptr;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                1, 3, 1, {}, &finish_update_2);
+                1, 3, 1, {}, false, &finish_update_2);
   Context *finish_update_3 = nullptr;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                2, 3, 1, {}, &finish_update_3);
+                2, 3, 1, {}, false, &finish_update_3);
   Context *finish_update_4 = nullptr;
   expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP,
-                0, 2, 1, {}, &finish_update_4);
+                0, 2, 1, {}, false, &finish_update_4);
 
   MockUnlockRequest mock_unlock_request;
   expect_unlock(mock_image_ctx, mock_unlock_request, 0);
@@ -239,10 +240,14 @@ TEST_F(TestMockObjectMap, DetainedUpdate) {
   {
     RWLock::RLocker snap_locker(mock_image_ctx.snap_lock);
     RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock);
-    mock_object_map.aio_update(CEPH_NOSNAP, 1, 4, 1, {}, {}, &update_ctx1);
-    mock_object_map.aio_update(CEPH_NOSNAP, 1, 3, 1, {}, {}, &update_ctx2);
-    mock_object_map.aio_update(CEPH_NOSNAP, 2, 3, 1, {}, {}, &update_ctx3);
-    mock_object_map.aio_update(CEPH_NOSNAP, 0, 2, 1, {}, {}, &update_ctx4);
+    mock_object_map.aio_update(CEPH_NOSNAP, 1, 4, 1, {}, {}, false,
+                               &update_ctx1);
+    mock_object_map.aio_update(CEPH_NOSNAP, 1, 3, 1, {}, {}, false,
+                               &update_ctx2);
+    mock_object_map.aio_update(CEPH_NOSNAP, 2, 3, 1, {}, {}, false,
+                               &update_ctx3);
+    mock_object_map.aio_update(CEPH_NOSNAP, 0, 2, 1, {}, {}, false,
+                               &update_ctx4);
   }
 
   // updates 2, 3, and 4 are blocked on update 1