]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd/object_map: add support for ranged diff-iterate
authorIlya Dryomov <idryomov@gmail.com>
Wed, 27 Dec 2023 17:07:05 +0000 (18:07 +0100)
committerIlya Dryomov <idryomov@gmail.com>
Sat, 20 Jan 2024 15:06:53 +0000 (16:06 +0100)
Currently diff-iterate in fast-diff mode is performed on the entire
image no matter what image extent is passed to the API.  Then, unused
diff just gets discarded as DiffIterate ends up querying only objects
that the passed image extent maps to.  This hasn't been an issue for
internal consumers ("rbd du", "rbd diff", etc) because they work on the
entire image, but turns out to lead to quadratic slowdown in some QEMU
use cases.

0..UINT64_MAX range is carved out for deep-copy which is unranged by
definition.  To get effectively unranged diff-iterate, 0..UINT64_MAX-1
range can be used.

Fixes: https://tracker.ceph.com/issues/63341
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
src/librbd/api/DiffIterate.cc
src/librbd/api/DiffIterate.h
src/librbd/deep_copy/ImageCopyRequest.cc
src/librbd/object_map/DiffRequest.cc
src/librbd/object_map/DiffRequest.h
src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc
src/test/librbd/object_map/test_mock_DiffRequest.cc

index 4bbc8ebdc98ae3207176b5b0a386ef175917b5fe..a464b942ae091ddf41d9a1d40801cf1f56a9a5a3 100644 (file)
@@ -211,6 +211,29 @@ int DiffIterate<I>::diff_iterate(I *ictx,
   return r;
 }
 
+template <typename I>
+std::pair<uint64_t, uint64_t> DiffIterate<I>::calc_object_diff_range() {
+  uint64_t period = m_image_ctx.get_stripe_period();
+  uint64_t first_period_off = round_down_to(m_offset, period);
+  uint64_t last_period_off = round_down_to(m_offset + m_length - 1, period);
+
+  striper::LightweightObjectExtents object_extents;
+  if (first_period_off != last_period_off) {
+    // map only the tail of the first period and the front of the last
+    // period instead of the entire range for efficiency
+    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout,
+                             m_offset, first_period_off + period - m_offset,
+                             0, 0, &object_extents);
+    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout,
+                        last_period_off, m_offset + m_length - last_period_off,
+                        0, 0, &object_extents);
+  } else {
+    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout, m_offset,
+                             m_length, 0, 0, &object_extents);
+  }
+  return {object_extents.front().object_no, object_extents.back().object_no + 1};
+}
+
 template <typename I>
 int DiffIterate<I>::execute() {
   CephContext* cct = m_image_ctx.cct;
@@ -245,20 +268,24 @@ int DiffIterate<I>::execute() {
 
   int r;
   bool fast_diff_enabled = false;
+  uint64_t start_object_no, end_object_no;
   BitVector<2> object_diff_state;
   interval_set<uint64_t> parent_diff;
   if (m_whole_object) {
+    std::tie(start_object_no, end_object_no) = calc_object_diff_range();
+
     C_SaferCond ctx;
     auto req = object_map::DiffRequest<I>::create(&m_image_ctx, from_snap_id,
-                                                  end_snap_id, true,
+                                                  end_snap_id, start_object_no,
+                                                  end_object_no,
                                                   &object_diff_state, &ctx);
     req->send();
-
     r = ctx.wait();
     if (r < 0) {
       ldout(cct, 5) << "fast diff disabled" << dendl;
     } else {
       ldout(cct, 5) << "fast diff enabled" << dendl;
+      ceph_assert(object_diff_state.size() == end_object_no - start_object_no);
       fast_diff_enabled = true;
 
       // check parent overlap only if we are comparing to the beginning of time
@@ -308,7 +335,8 @@ int DiffIterate<I>::execute() {
       io::SparseExtents aggregate_sparse_extents;
       for (auto& [object, extents] : object_extents) {
         const uint64_t object_no = extents.front().objectno;
-        uint8_t diff_state = object_diff_state[object_no];
+        ceph_assert(object_no >= start_object_no && object_no < end_object_no);
+        uint8_t diff_state = object_diff_state[object_no - start_object_no];
         ldout(cct, 20) << "object " << object << ": diff_state="
                        << (int)diff_state << dendl;
 
index e6074d9cb3d66dbb1e8588441cc055b665420e8d..8ce7f6afd7c30a684cb1ce8dd7ed130f0c9631a8 100644 (file)
@@ -7,6 +7,7 @@
 #include "include/int_types.h"
 #include "common/bit_vector.hpp"
 #include "cls/rbd/cls_rbd_types.h"
+#include <utility>
 
 namespace librbd {
 
@@ -51,6 +52,8 @@ private:
   {
   }
 
+  std::pair<uint64_t, uint64_t> calc_object_diff_range();
+
   int execute();
 
   int diff_object_map(uint64_t from_snap_id, uint64_t to_snap_id,
index 0ba9bc82f18d817473d5b7e972954041eb307805..7f684c5b0a5378a9dc01c81adf61d370de08ace5 100644 (file)
@@ -110,7 +110,7 @@ void ImageCopyRequest<I>::compute_diff() {
     ImageCopyRequest<I>, &ImageCopyRequest<I>::handle_compute_diff>(this);
   auto req = object_map::DiffRequest<I>::create(m_src_image_ctx,
                                                 m_src_snap_id_start,
-                                                m_src_snap_id_end, false,
+                                                m_src_snap_id_end, 0, UINT64_MAX,
                                                 &m_object_diff_state, ctx);
   req->send();
 }
index 4d23bcbad4da1075dab4bff7c281f64464071dff..b8666fb9809addf8b4715545cdb46d329d72d0ee 100644 (file)
@@ -20,6 +20,29 @@ namespace object_map {
 
 using util::create_rados_callback;
 
+template <typename I>
+DiffRequest<I>::DiffRequest(I* image_ctx,
+                            uint64_t snap_id_start, uint64_t snap_id_end,
+                            uint64_t start_object_no, uint64_t end_object_no,
+                            BitVector<2>* object_diff_state,
+                            Context* on_finish)
+    : m_image_ctx(image_ctx), m_snap_id_start(snap_id_start),
+      m_snap_id_end(snap_id_end), m_start_object_no(start_object_no),
+      m_end_object_no(end_object_no), m_object_diff_state(object_diff_state),
+      m_on_finish(on_finish) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 10) << "snap_id_start=" << m_snap_id_start
+                 << ", snap_id_end=" << m_snap_id_end
+                 << ", start_object_no=" << m_start_object_no
+                 << ", end_object_no=" << m_end_object_no
+                 << dendl;
+}
+
+template <typename I>
+bool DiffRequest<I>::is_diff_iterate() const {
+  return m_start_object_no != 0 || m_end_object_no != UINT64_MAX;
+}
+
 template <typename I>
 void DiffRequest<I>::send() {
   auto cct = m_image_ctx->cct;
@@ -30,19 +53,34 @@ void DiffRequest<I>::send() {
                << "snap_id_end=" << m_snap_id_end << dendl;
     finish(-EINVAL);
     return;
-  } else if (m_snap_id_start == m_snap_id_end) {
-    // no delta between the same snapshot
-    finish(0);
+  }
+  if (m_start_object_no == UINT64_MAX || m_start_object_no > m_end_object_no ||
+      (m_start_object_no != 0 && m_end_object_no == UINT64_MAX)) {
+    lderr(cct) << "invalid start/end object numbers: "
+               << "start_object_no=" << m_start_object_no << ", "
+               << "end_object_no=" << m_end_object_no << dendl;
+    finish(-EINVAL);
     return;
   }
 
   m_object_diff_state->clear();
 
+  if (m_snap_id_start == m_snap_id_end) {
+    // no delta between the same snapshot
+    finish(0);
+    return;
+  }
+  if (m_start_object_no == m_end_object_no) {
+    // no objects in the provided range (half-open)
+    finish(0);
+    return;
+  }
+
   // collect all the snap ids in the provided range (inclusive) unless
   // this is diff-iterate against the beginning of time, in which case
   // only the end version matters
   std::shared_lock image_locker{m_image_ctx->image_lock};
-  if (!m_diff_iterate_range || m_snap_id_start != 0) {
+  if (!is_diff_iterate() || m_snap_id_start != 0) {
     if (m_snap_id_start != 0) {
       m_snap_ids.insert(m_snap_id_start);
     }
@@ -178,10 +216,14 @@ void DiffRequest<I>::handle_load_object_map(int r) {
     m_object_map.resize(num_objs);
   }
 
+  uint64_t start_object_no, end_object_no;
   uint64_t prev_object_diff_state_size = m_object_diff_state->size();
-  if (m_diff_iterate_range) {
-    if (m_object_diff_state->size() != m_object_map.size()) {
-      m_object_diff_state->resize(m_object_map.size());
+  if (is_diff_iterate()) {
+    start_object_no = std::min(m_start_object_no, m_object_map.size());
+    end_object_no = std::min(m_end_object_no, m_object_map.size());
+    uint64_t num_objs_in_range = end_object_no - start_object_no;
+    if (m_object_diff_state->size() != num_objs_in_range) {
+      m_object_diff_state->resize(num_objs_in_range);
     }
   } else {
     // for deep-copy, the object diff state should be the largest of
@@ -194,21 +236,23 @@ void DiffRequest<I>::handle_load_object_map(int r) {
       // diff state
       m_object_map.resize(m_object_diff_state->size());
     }
+    start_object_no = 0;
+    end_object_no = m_object_diff_state->size();
   }
 
-  uint64_t overlap = std::min(m_object_map.size(), prev_object_diff_state_size);
-  auto it = m_object_map.begin();
-  auto overlap_end_it = it + overlap;
+  uint64_t overlap = std::min(m_object_diff_state->size(),
+                              prev_object_diff_state_size);
+  auto it = m_object_map.begin() + start_object_no;
   auto diff_it = m_object_diff_state->begin();
-  uint64_t i = 0;
-  for (; it != overlap_end_it; ++it, ++diff_it, ++i) {
+  uint64_t ono = start_object_no;
+  for (; ono < start_object_no + overlap; ++it, ++diff_it, ++ono) {
     uint8_t object_map_state = *it;
     uint8_t prev_object_diff_state = *diff_it;
     switch (prev_object_diff_state) {
     case DIFF_STATE_HOLE:
       if (object_map_state != OBJECT_NONEXISTENT) {
         // stay in HOLE on intermediate snapshots for diff-iterate
-        if (!m_diff_iterate_range || m_current_snap_id == m_snap_id_end) {
+        if (!is_diff_iterate() || m_current_snap_id == m_snap_id_end) {
           *diff_it = DIFF_STATE_DATA_UPDATED;
         }
       }
@@ -234,7 +278,7 @@ void DiffRequest<I>::handle_load_object_map(int r) {
       ceph_abort();
     }
 
-    ldout(cct, 20) << "object state: " << i << " "
+    ldout(cct, 20) << "object state: " << ono << " "
                    << static_cast<uint32_t>(prev_object_diff_state)
                    << "->" << static_cast<uint32_t>(*diff_it) << " ("
                    << static_cast<uint32_t>(object_map_state) << ")"
@@ -242,8 +286,9 @@ void DiffRequest<I>::handle_load_object_map(int r) {
   }
   ldout(cct, 20) << "computed overlap diffs" << dendl;
 
-  auto end_it = m_object_map.end();
-  for (; it != end_it; ++it, ++diff_it, ++i) {
+  ceph_assert(diff_it == m_object_diff_state->end() ||
+              end_object_no <= m_object_map.size());
+  for (; ono < end_object_no; ++it, ++diff_it, ++ono) {
     uint8_t object_map_state = *it;
     if (object_map_state == OBJECT_NONEXISTENT) {
       *diff_it = DIFF_STATE_HOLE;
@@ -251,7 +296,7 @@ void DiffRequest<I>::handle_load_object_map(int r) {
       // diffing against the beginning of time or image was grown
       // (implicit) starting state is HOLE, this is the first object
       // map after
-      if (m_diff_iterate_range) {
+      if (is_diff_iterate()) {
         // for diff-iterate, if the object is discarded prior to or
         // in the end version, result should be HOLE
         // since DATA_UPDATED can transition only to HOLE_UPDATED,
@@ -278,12 +323,13 @@ void DiffRequest<I>::handle_load_object_map(int r) {
       }
     }
 
-    ldout(cct, 20) << "object state: " << i << " "
+    ldout(cct, 20) << "object state: " << ono << " "
                    << "->" << static_cast<uint32_t>(*diff_it) << " ("
                    << static_cast<uint32_t>(*it) << ")" << dendl;
   }
   ldout(cct, 20) << "computed resize diffs" << dendl;
 
+  ceph_assert(diff_it == m_object_diff_state->end());
   std::shared_lock image_locker{m_image_ctx->image_lock};
   load_object_map(&image_locker);
 }
index f2fb9975f751024bb49aa7f8630b63de5d97fe6c..6a8ecb9dc93067d0ab29f5ce2cfae4b2808fb70b 100644 (file)
@@ -21,21 +21,20 @@ namespace object_map {
 template <typename ImageCtxT>
 class DiffRequest {
 public:
-  static DiffRequest* create(ImageCtxT* image_ctx, uint64_t snap_id_start,
-                             uint64_t snap_id_end, bool diff_iterate_range,
+  static DiffRequest* create(ImageCtxT* image_ctx,
+                             uint64_t snap_id_start, uint64_t snap_id_end,
+                             uint64_t start_object_no, uint64_t end_object_no,
                              BitVector<2>* object_diff_state,
                              Context* on_finish) {
     return new DiffRequest(image_ctx, snap_id_start, snap_id_end,
-                           diff_iterate_range, object_diff_state, on_finish);
+                           start_object_no, end_object_no, object_diff_state,
+                           on_finish);
   }
 
-  DiffRequest(ImageCtxT* image_ctx, uint64_t snap_id_start,
-              uint64_t snap_id_end, bool diff_iterate_range,
-              BitVector<2>* object_diff_state, Context* on_finish)
-    : m_image_ctx(image_ctx), m_snap_id_start(snap_id_start),
-      m_snap_id_end(snap_id_end), m_diff_iterate_range(diff_iterate_range),
-      m_object_diff_state(object_diff_state), m_on_finish(on_finish) {
-  }
+  DiffRequest(ImageCtxT* image_ctx,
+              uint64_t snap_id_start, uint64_t snap_id_end,
+              uint64_t start_object_no, uint64_t end_object_no,
+              BitVector<2>* object_diff_state, Context* on_finish);
 
   void send();
 
@@ -58,7 +57,8 @@ private:
   ImageCtxT* m_image_ctx;
   uint64_t m_snap_id_start;
   uint64_t m_snap_id_end;
-  bool m_diff_iterate_range;
+  uint64_t m_start_object_no;
+  uint64_t m_end_object_no;
   BitVector<2>* m_object_diff_state;
   Context* m_on_finish;
 
@@ -72,6 +72,8 @@ private:
 
   bufferlist m_out_bl;
 
+  bool is_diff_iterate() const;
+
   void load_object_map(std::shared_lock<ceph::shared_mutex>* image_locker);
   void handle_load_object_map(int r);
 
index 634cabfa79e3ece5e3deb33f58b21cde1b8c9e1f..2c42d5075ccfced2dad80647e45af1047fdbd37b 100644 (file)
@@ -92,7 +92,7 @@ struct DiffRequest<MockTestImageCtx> {
   static DiffRequest* s_instance;
   static DiffRequest* create(MockTestImageCtx *image_ctx,
                              uint64_t snap_id_start, uint64_t snap_id_end,
-                             bool diff_iterate_range,
+                             uint64_t start_object_no, uint64_t end_object_no,
                              BitVector<2>* object_diff_state,
                              Context* on_finish) {
     ceph_assert(s_instance != nullptr);
index 9be4995e00a77ebc85a9936fa45ad5a6429fb4b7..c7afbe939c296714bf9280900b08e4c87e26c41f 100644 (file)
@@ -209,7 +209,7 @@ public:
 
   template <typename F>
   int do_diff(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
-              bool diff_iterate_range) {
+              uint64_t start_object_no, uint64_t end_object_no) {
     InSequence seq;
 
     MockTestImageCtx mock_image_ctx(*m_image_ctx);
@@ -217,7 +217,7 @@ public:
 
     C_SaferCond ctx;
     auto req = new MockDiffRequest(&mock_image_ctx, start_snap_id,
-                                   end_snap_id, diff_iterate_range,
+                                   end_snap_id, start_object_no, end_object_no,
                                    &m_diff_state, &ctx);
     req->send();
     return ctx.wait();
@@ -226,14 +226,29 @@ public:
   template <typename F>
   void test_diff_iterate(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
                          const BitVector<2>& expected_diff_state) {
-    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id, true));
+    // ranged -- run through all ranges (substrings) in expected_diff_state
+    for (uint64_t i = 0; i < expected_diff_state.size(); i++) {
+      for (uint64_t j = i + 1; j <= expected_diff_state.size(); j++) {
+        ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
+                             i, j));
+        ASSERT_EQ(j - i, m_diff_state.size());
+        for (uint64_t k = 0; k < m_diff_state.size(); k++) {
+          ASSERT_EQ(expected_diff_state[i + k], m_diff_state[k]);
+        }
+      }
+    }
+
+    // unranged -- equivalent to i=0, j=expected_diff_state.size() range
+    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
+                         0, UINT64_MAX - 1));
     ASSERT_EQ(expected_diff_state, m_diff_state);
   }
 
   template <typename F>
   void test_deep_copy(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
                       const BitVector<2>& expected_diff_state) {
-    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id, false));
+    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
+                         0, UINT64_MAX));
     ASSERT_EQ(expected_diff_state, m_diff_state);
   }
 
@@ -243,9 +258,17 @@ public:
 
 TEST_P(TestMockObjectMapDiffRequest, InvalidStartSnap) {
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, true));
+    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456));
+  } else {
+    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX));
+  }
+}
+
+TEST_P(TestMockObjectMapDiffRequest, InvalidEndSnap) {
+  if (is_diff_iterate()) {
+    ASSERT_EQ(-EINVAL, do_diff(noop, 2, 1, 123, 456));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(noop, 2, 1, 0, UINT64_MAX));
   }
 }
 
@@ -253,21 +276,48 @@ TEST_P(TestMockObjectMapDiffRequest, StartEndSnapEqual) {
   BitVector<2> expected_diff_state;
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(0, do_diff(noop, 1, 1, true));
+    ASSERT_EQ(0, do_diff(noop, 1, 1, 123, 456));
   } else {
-    ASSERT_EQ(0, do_diff(noop, 1, 1, false));
+    ASSERT_EQ(0, do_diff(noop, 1, 1, 0, UINT64_MAX));
   }
   ASSERT_EQ(expected_diff_state, m_diff_state);
 }
 
+TEST_P(TestMockObjectMapDiffRequest, InvalidStartObject) {
+  if (is_diff_iterate()) {
+    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, UINT64_MAX, UINT64_MAX));
+  } else {
+    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, 123, UINT64_MAX));
+  }
+}
+
+TEST_P(TestMockObjectMapDiffRequest, InvalidEndObject) {
+  if (is_diff_iterate()) {
+    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, 456, 123));
+  } else {
+    SUCCEED();
+  }
+}
+
+TEST_P(TestMockObjectMapDiffRequest, StartEndObjectEqual) {
+  BitVector<2> expected_diff_state;
+
+  if (is_diff_iterate()) {
+    ASSERT_EQ(0, do_diff(noop, 0, 1, 123, 123));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
+  } else {
+    SUCCEED();
+  }
+}
+
 TEST_P(TestMockObjectMapDiffRequest, FastDiffDisabled) {
   // negative test -- object-map implicitly enables fast-diff
   REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP));
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, true));
+    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, 123, 456));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -301,6 +351,28 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnap) {
   }
 }
 
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapEmpty) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  m_image_ctx->size = 0;
+  m_image_ctx->snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}
+  };
+
+  BitVector<2> object_map_1;
+  BitVector<2> expected_diff_state;
+
+  auto load = [&](MockTestImageCtx& mock_image_ctx) {
+    expect_get_flags(mock_image_ctx, 1, 0, 0);
+    expect_load_map(mock_image_ctx, 1, object_map_1, 0);
+  };
+  if (is_diff_iterate()) {
+    test_diff_iterate(load, 0, 1, expected_diff_state);
+  } else {
+    test_deep_copy(load, 0, 1, expected_diff_state);
+  }
+}
+
 TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnap) {
   REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
 
@@ -558,6 +630,25 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHead) {
   }
 }
 
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadEmpty) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  m_image_ctx->size = 0;
+
+  BitVector<2> object_map_head;
+  BitVector<2> expected_diff_state;
+
+  auto load = [&](MockTestImageCtx& mock_image_ctx) {
+    expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+  };
+  if (is_diff_iterate()) {
+    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+  } else {
+    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+  }
+}
+
 TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnap) {
   REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
 
@@ -1274,9 +1365,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartSnapDNE) {
   };
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, true));
+    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, false));
+    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1294,13 +1385,13 @@ TEST_P(TestMockObjectMapDiffRequest, EndSnapDNE) {
   object_map_1.resize(object_count);
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 0, 2, true));
+    ASSERT_EQ(-ENOENT, do_diff(noop, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, false));
+    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1397,9 +1488,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapDNE) {
     expect_load_map(mock_image_ctx, 1, object_map_1, -ENOENT);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, true));
+    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, false));
+    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1429,7 +1520,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapDNE) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, true));
+    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1437,7 +1528,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapDNE) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, false));
+    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1529,9 +1620,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartFastDiffInvalid) {
     expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, true));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, false));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1554,14 +1645,14 @@ TEST_P(TestMockObjectMapDiffRequest, EndFastDiffInvalid) {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, true));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, 0, object_count));
   } else {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
       expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, false));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1592,7 +1683,7 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromBeginning) {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1617,9 +1708,9 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromSnap) {
     expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, true));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1642,9 +1733,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapLoadError) {
     expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, true));
+    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, false));
+    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1674,7 +1765,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapLoadError) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, true));
+    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1682,7 +1773,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapLoadError) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, false));
+    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1715,7 +1806,7 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromBeginning
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EPERM, do_diff(load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1742,9 +1833,9 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromSnap) {
     expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, true));
+    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1768,9 +1859,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapTooSmall) {
     expect_load_map(mock_image_ctx, 1, object_map_1, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, true));
+    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, false));
+    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1796,7 +1887,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapTooSmall) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, true));
+    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1804,7 +1895,7 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapTooSmall) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, false));
+    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1838,7 +1929,7 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromBeginning)
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1866,9 +1957,9 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromSnap) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, true));
+    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, false));
+    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }