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;
<< "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);
}
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
// 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;
}
}
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) << ")"
}
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;
// 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,
}
}
- 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);
}
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);
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();
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);
}
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));
}
}
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));
}
}
}
}
+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);
}
}
+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);
};
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));
}
}
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));
}
}
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));
}
}
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);
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));
}
}
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));
}
}
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));
}
}
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));
}
}
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));
}
}
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));
}
}
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);
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));
}
}
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));
}
}
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));
}
}
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));
}
}
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);
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));
}
}
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));
}
}
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));
}
}