From: Jason Dillaman Date: Fri, 11 Dec 2020 01:11:34 +0000 (-0500) Subject: librbd/object_map: don't assert if a snapshot doesn't exist X-Git-Tag: v15.2.9~84^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=301d212961f1731218f7099563aaec1e3f9bbdf7;p=ceph.git librbd/object_map: don't assert if a snapshot doesn't exist When compute a fast-diff, if an snapshot no longer exists or fails to load, ignore the error as long as it wasn't the start or end snapshot. Fixes: https://tracker.ceph.com/issues/48526 Signed-off-by: Jason Dillaman (cherry picked from commit abab5f143b80ff876948307f00ab767bc1b6ba22) Conflicts: src/test/librbd/object_map/test_mock_DiffRequest.cc: exec() mock only takes 7 params --- diff --git a/src/librbd/object_map/DiffRequest.cc b/src/librbd/object_map/DiffRequest.cc index 1f80eb155cb2b..1058288a6bac3 100644 --- a/src/librbd/object_map/DiffRequest.cc +++ b/src/librbd/object_map/DiffRequest.cc @@ -22,20 +22,34 @@ using util::create_rados_callback; template void DiffRequest::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); } @@ -45,23 +59,19 @@ void DiffRequest::load_object_map( std::shared_lock* 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(); @@ -70,6 +80,32 @@ void DiffRequest::load_object_map( 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) { @@ -80,9 +116,9 @@ void DiffRequest::load_object_map( 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); @@ -115,7 +151,13 @@ void DiffRequest::handle_load_object_map(int r) { 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; @@ -167,9 +209,10 @@ void DiffRequest::handle_load_object_map(int r) { } 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(*it) << dendl; @@ -182,12 +225,6 @@ void DiffRequest::handle_load_object_map(int r) { } 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; diff --git a/src/librbd/object_map/DiffRequest.h b/src/librbd/object_map/DiffRequest.h index e80a02d99e6f6..31a7a1bea2505 100644 --- a/src/librbd/object_map/DiffRequest.h +++ b/src/librbd/object_map/DiffRequest.h @@ -8,6 +8,7 @@ #include "common/bit_vector.hpp" #include "common/ceph_mutex.h" #include "librbd/object_map/Types.h" +#include struct Context; @@ -60,9 +61,9 @@ private: BitVector<2>* m_object_diff_state; Context* m_on_finish; - bool m_diff_from_start = false; + std::set m_snap_ids; uint64_t m_current_snap_id = 0; - uint64_t m_next_snap_id = 0; + bool m_ignore_enoent = false; uint64_t m_current_size = 0; diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index b5581eb219cdd..53702ab79b696 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -93,6 +93,7 @@ set(unittest_librbd_srcs mirror/snapshot/test_mock_UnlinkPeerRequest.cc mirror/snapshot/test_mock_Utils.cc mirror/test_mock_DisableRequest.cc + object_map/test_mock_DiffRequest.cc object_map/test_mock_InvalidateRequest.cc object_map/test_mock_LockRequest.cc object_map/test_mock_RefreshRequest.cc diff --git a/src/test/librbd/object_map/test_mock_DiffRequest.cc b/src/test/librbd/object_map/test_mock_DiffRequest.cc new file mode 100644 index 0000000000000..366bbb2786034 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_DiffRequest.cc @@ -0,0 +1,490 @@ +// -*- 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 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 + 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