template <typename I>
void DiffRequest<I>::send() {
- std::shared_lock image_locker{m_image_ctx->image_lock};
+ auto cct = m_image_ctx->cct;
- m_diff_from_start = (m_snap_id_start == 0);
- if (m_snap_id_start == 0) {
- if (!m_image_ctx->snap_info.empty()) {
- m_snap_id_start = m_image_ctx->snap_info.begin()->first;
- } else {
- m_snap_id_start = CEPH_NOSNAP;
- }
+ if (m_snap_id_start == CEPH_NOSNAP || m_snap_id_start > m_snap_id_end) {
+ lderr(cct) << "invalid start/end snap ids: "
+ << "snap_id_start=" << m_snap_id_start << ", "
+ << "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);
+ return;
}
m_object_diff_state->clear();
- m_current_snap_id = m_snap_id_start;
- m_next_snap_id = m_snap_id_end;
+
+ // collect all the snap ids in the provided range (inclusive)
+ if (m_snap_id_start != 0) {
+ m_snap_ids.insert(m_snap_id_start);
+ }
+
+ std::shared_lock image_locker{m_image_ctx->image_lock};
+ auto snap_info_it = m_image_ctx->snap_info.upper_bound(m_snap_id_start);
+ auto snap_info_it_end = m_image_ctx->snap_info.lower_bound(m_snap_id_end);
+ for (; snap_info_it != snap_info_it_end; ++snap_info_it) {
+ m_snap_ids.insert(snap_info_it->first);
+ }
+ m_snap_ids.insert(m_snap_id_end);
load_object_map(&image_locker);
}
std::shared_lock<ceph::shared_mutex>* image_locker) {
ceph_assert(ceph_mutex_is_locked(m_image_ctx->image_lock));
- auto cct = m_image_ctx->cct;
- ldout(cct, 10) << dendl;
-
- m_current_size = m_image_ctx->size;
- if (m_current_snap_id != CEPH_NOSNAP) {
- auto snap_it = m_image_ctx->snap_info.find(m_current_snap_id);
- ceph_assert(snap_it != m_image_ctx->snap_info.end());
- m_current_size = snap_it->second.size;
+ if (m_snap_ids.empty()) {
+ image_locker->unlock();
- ++snap_it;
- if (snap_it != m_image_ctx->snap_info.end()) {
- m_next_snap_id = snap_it->first;
- } else {
- m_next_snap_id = CEPH_NOSNAP;
- }
+ finish(0);
+ return;
}
+ m_current_snap_id = *m_snap_ids.begin();
+ m_snap_ids.erase(m_current_snap_id);
+
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 10) << "snap_id=" << m_current_snap_id << dendl;
+
if ((m_image_ctx->features & RBD_FEATURE_FAST_DIFF) == 0) {
image_locker->unlock();
return;
}
+ // ignore ENOENT with intermediate snapshots since deleted
+ // snaps will get merged with later snapshots
+ m_ignore_enoent = (m_current_snap_id != m_snap_id_start &&
+ m_current_snap_id != m_snap_id_end);
+
+ if (m_current_snap_id == CEPH_NOSNAP) {
+ m_current_size = m_image_ctx->size;
+ } else {
+ auto snap_it = m_image_ctx->snap_info.find(m_current_snap_id);
+ if (snap_it == m_image_ctx->snap_info.end()) {
+ ldout(cct, 10) << "snapshot " << m_current_snap_id << " does not exist"
+ << dendl;
+ if (!m_ignore_enoent) {
+ image_locker->unlock();
+
+ finish(-ENOENT);
+ return;
+ }
+
+ load_object_map(image_locker);
+ return;
+ }
+
+ m_current_size = snap_it->second.size;
+ }
+
uint64_t flags = 0;
int r = m_image_ctx->get_flags(m_current_snap_id, &flags);
if (r < 0) {
finish(r);
return;
}
- if ((flags & RBD_FLAG_FAST_DIFF_INVALID) != 0) {
- image_locker->unlock();
+ image_locker->unlock();
+ if ((flags & RBD_FLAG_FAST_DIFF_INVALID) != 0) {
ldout(cct, 1) << "cannot perform fast diff on invalid object map"
<< dendl;
finish(-EINVAL);
std::string oid(ObjectMap<>::object_map_name(m_image_ctx->id,
m_current_snap_id));
- if (r < 0) {
+ if (r == -ENOENT && m_ignore_enoent) {
+ ldout(cct, 10) << "object map " << oid << " does not exist" << dendl;
+
+ std::shared_lock image_locker{m_image_ctx->image_lock};
+ load_object_map(&image_locker);
+ return;
+ } else if (r < 0) {
lderr(cct) << "failed to load object map: " << oid << dendl;
finish(r);
return;
}
ldout(cct, 20) << "computed overlap diffs" << dendl;
+ bool diff_from_start = (m_snap_id_start == 0);
auto end_it = m_object_map.end();
if (m_object_map.size() > m_prev_object_map.size() &&
- (m_diff_from_start || m_prev_object_map_valid)) {
+ (diff_from_start || m_prev_object_map_valid)) {
for (; it != end_it; ++it,++diff_it, ++i) {
ldout(cct, 20) << "object state: " << i << " "
<< "->" << static_cast<uint32_t>(*it) << dendl;
}
ldout(cct, 20) << "computed resize diffs" << dendl;
- if (m_current_snap_id == m_next_snap_id || m_next_snap_id > m_snap_id_end) {
- finish(0);
- return;
- }
-
- m_current_snap_id = m_next_snap_id;
m_prev_object_map = m_object_map;
m_prev_object_map_valid = true;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/object_map/DiffRequest.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/object_map/DiffRequest.cc"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+namespace librbd {
+namespace object_map {
+
+class TestMockObjectMapDiffRequest : public TestMockFixture {
+public:
+ typedef DiffRequest<MockTestImageCtx> MockDiffRequest;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+ }
+
+ void expect_get_flags(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ int32_t flags, int r) {
+ EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _))
+ .WillOnce(WithArg<1>(Invoke([flags, r](uint64_t *out_flags) {
+ *out_flags = flags;
+ return r;
+ })));
+ }
+
+ template <typename Lambda>
+ void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const BitVector<2>& object_map, int r,
+ Lambda&& lambda) {
+ std::string snap_oid(ObjectMap<>::object_map_name(mock_image_ctx.id,
+ snap_id));
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _,
+ _, _))
+ .WillOnce(WithArg<5>(Invoke([object_map, r, lambda=std::move(lambda)]
+ (bufferlist* out_bl) {
+ lambda();
+
+ auto out_object_map{object_map};
+ out_object_map.set_crc_enabled(false);
+ encode(out_object_map, *out_bl);
+ return r;
+ })));
+ }
+
+ void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const BitVector<2>& object_map, int r) {
+ expect_load_map(mock_image_ctx, snap_id, object_map, r, [](){});
+ }
+
+ librbd::ImageCtx* m_image_ctx = nullptr;
+ BitVector<2> m_object_diff_state;
+};
+
+TEST_F(TestMockObjectMapDiffRequest, InvalidStartSnap) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, CEPH_NOSNAP, 0,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, StartEndSnapEqual) {
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 1,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(0U, m_object_diff_state.size());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FastDiffDisabled) {
+ // negative test -- object-map implicitly enables fast-diff
+ REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP));
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FastDiffInvalid) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}
+ };
+
+ InSequence seq;
+ expect_get_flags(mock_image_ctx, 1U, RBD_FLAG_FAST_DIFF_INVALID, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, FullDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ object_map_head[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_UPDATED;
+ expected_diff_state[2] = DIFF_STATE_UPDATED;
+ expected_diff_state[3] = DIFF_STATE_HOLE;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, IntermediateDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS_CLEAN;
+ object_map_1[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 2,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[2] = DIFF_STATE_UPDATED;
+ expected_diff_state[3] = DIFF_STATE_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, EndDelta) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 2U, 0, 0);
+
+ BitVector<2> object_map_2;
+ object_map_2.resize(object_count);
+ object_map_2[1] = OBJECT_EXISTS_CLEAN;
+ object_map_2[2] = OBJECT_EXISTS;
+ object_map_2[3] = OBJECT_EXISTS;
+ expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ object_map_head[2] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 2, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[3] = DIFF_STATE_HOLE;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, StartSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, EndSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 1, 2,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, IntermediateSnapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}},
+ {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ object_map_1.resize(object_count);
+ object_map_1[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0,
+ [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); });
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, -ENOENT);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadIntermediateObjectMapDNE) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, -ENOENT);
+
+ expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+
+ BitVector<2> object_map_head;
+ object_map_head.resize(object_count);
+ object_map_head[1] = OBJECT_EXISTS_CLEAN;
+ expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ BitVector<2> expected_diff_state;
+ expected_diff_state.resize(object_count);
+ expected_diff_state[1] = DIFF_STATE_UPDATED;
+ ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapError) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockObjectMapDiffRequest, ObjectMapTooSmall) {
+ REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+ uint32_t object_count = 5;
+ m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+ MockTestImageCtx mock_image_ctx(*m_image_ctx);
+ mock_image_ctx.snap_info = {
+ {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+ {}, {}, {}}}
+ };
+
+ InSequence seq;
+
+ expect_get_flags(mock_image_ctx, 1U, 0, 0);
+
+ BitVector<2> object_map_1;
+ expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+ &m_object_diff_state, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace object_map
+} // librbd