io/ReadResult.cc
io/RefreshImageDispatch.cc
io/SimpleSchedulerObjectDispatch.cc
+ io/Types.cc
io/Utils.cc
io/WriteBlockImageDispatch.cc
journal/CreateRequest.cc
#include "include/err.h"
#include "include/neorados/RADOS.hpp"
#include "osd/osd_types.h"
-
+#include "librados/snap_set_diff.h"
#include "librbd/AsioEngine.h"
#include "librbd/ExclusiveLock.h"
#include "librbd/ImageCtx.h"
ictx->exclusive_lock->is_lock_owner()));
}
+template <typename S, typename D>
+void convert_snap_set(const S& src_snap_set,
+ D* dst_snap_set) {
+ dst_snap_set->seq = src_snap_set.seq;
+ dst_snap_set->clones.reserve(src_snap_set.clones.size());
+ for (auto& src_clone : src_snap_set.clones) {
+ dst_snap_set->clones.emplace_back();
+ auto& dst_clone = dst_snap_set->clones.back();
+ dst_clone.cloneid = src_clone.cloneid;
+ dst_clone.snaps = src_clone.snaps;
+ dst_clone.overlap = src_clone.overlap;
+ dst_clone.size = src_clone.size;
+ }
+}
+
} // anonymous namespace
template <typename I>
return r;
}
+template <typename I>
+ObjectListSnapsRequest<I>::ObjectListSnapsRequest(
+ I *ictx, uint64_t objectno, Extents&& object_extents, SnapIds&& snap_ids,
+ int list_snaps_flags, const ZTracer::Trace &parent_trace,
+ SnapshotDelta* snapshot_delta, Context *completion)
+ : ObjectRequest<I>(
+ ictx, objectno, ictx->duplicate_data_io_context(), "snap_list",
+ parent_trace, completion),
+ m_object_extents(std::move(object_extents)),
+ m_snap_ids(std::move(snap_ids)), m_list_snaps_flags(list_snaps_flags),
+ m_snapshot_delta(snapshot_delta) {
+ this->m_io_context->read_snap(CEPH_SNAPDIR);
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::send() {
+ I *image_ctx = this->m_ictx;
+ ldout(image_ctx->cct, 20) << dendl;
+
+ if (m_snap_ids.size() < 2) {
+ lderr(image_ctx->cct) << "invalid snap ids: " << m_snap_ids << dendl;
+ this->async_finish(-EINVAL);
+ return;
+ }
+
+ list_snaps();
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::list_snaps() {
+ I *image_ctx = this->m_ictx;
+ ldout(image_ctx->cct, 20) << dendl;
+
+ neorados::ReadOp read_op;
+ read_op.list_snaps(&m_snap_set, &m_ec);
+
+ image_ctx->rados_api.execute(
+ {data_object_name(this->m_ictx, this->m_object_no)},
+ *this->m_io_context, std::move(read_op), nullptr,
+ librbd::asio::util::get_callback_adapter(
+ [this](int r) { handle_list_snaps(r); }), nullptr,
+ (this->m_trace.valid() ? this->m_trace.get_info() : nullptr));
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::handle_list_snaps(int r) {
+ I *image_ctx = this->m_ictx;
+ auto cct = image_ctx->cct;
+
+ if (r >= 0) {
+ r = -m_ec.value();
+ }
+
+ ldout(cct, 20) << "r=" << r << dendl;
+
+ m_snapshot_delta->clear();
+ auto& snapshot_delta = *m_snapshot_delta;
+
+ if (r == -ENOENT) {
+ // the object does not exist -- mark the missing extents
+ zero_initial_extent(true);
+ this->finish(0);
+ return;
+ } else if (r < 0) {
+ lderr(cct) << "failed to retrieve object snapshot list: " << cpp_strerror(r)
+ << dendl;
+ this->finish(r);
+ return;
+ }
+
+ // helper function requires the librados legacy data structure
+ librados::snap_set_t snap_set;
+ convert_snap_set(m_snap_set, &snap_set);
+
+ ceph_assert(!m_snap_ids.empty());
+ librados::snap_t start_snap_id = 0;
+ librados::snap_t first_snap_id = *m_snap_ids.begin();
+ librados::snap_t last_snap_id = *m_snap_ids.rbegin();
+
+ // loop through all expected snapshots and build interval sets for
+ // data and zeroed ranges for each snapshot
+ bool prev_exists = false;
+ uint64_t prev_end_size = 0;
+ bool whiteout_detected = false;
+ for (auto end_snap_id : m_snap_ids) {
+ if (start_snap_id == end_snap_id) {
+ continue;
+ } else if (end_snap_id > last_snap_id) {
+ break;
+ }
+
+ interval_set<uint64_t> diff;
+ uint64_t end_size;
+ bool exists;
+ librados::snap_t clone_end_snap_id;
+ bool read_whole_object;
+ calc_snap_set_diff(cct, snap_set, start_snap_id,
+ end_snap_id, &diff, &end_size, &exists,
+ &clone_end_snap_id, &read_whole_object);
+
+ if (read_whole_object) {
+ ldout(cct, 1) << "need to read full object" << dendl;
+ diff.insert(0, image_ctx->layout.object_size);
+ end_size = image_ctx->layout.object_size;
+ clone_end_snap_id = end_snap_id;
+ } else if (!exists) {
+ end_size = 0;
+ } else if (exists && end_size == 0 && start_snap_id == 0) {
+ ldout(cct, 20) << "whiteout detected" << dendl;
+ whiteout_detected = true;
+ }
+
+ if (exists) {
+ // reads should be issued against the newest (existing) snapshot within
+ // the associated snapshot object clone. writes should be issued
+ // against the oldest snapshot in the snap_map.
+ ceph_assert(clone_end_snap_id >= end_snap_id);
+ if (clone_end_snap_id > last_snap_id) {
+ // do not read past the copy point snapshot
+ clone_end_snap_id = last_snap_id;
+ }
+ }
+
+ ldout(cct, 20) << "start_snap_id=" << start_snap_id << ", "
+ << "end_snap_id=" << end_snap_id << ", "
+ << "clone_end_snap_id=" << clone_end_snap_id << ", "
+ << "diff=" << diff << ", "
+ << "end_size=" << end_size << ", "
+ << "exists=" << exists << dendl;
+ if (end_snap_id <= first_snap_id) {
+ // don't include deltas from the starting snapshots, but we iterate over
+ // it to track its existence and size
+ ldout(cct, 20) << "skipping prior snapshots" << dendl;
+ } else if (exists || prev_exists || !diff.empty()) {
+ // clip diff to size of object (in case it was truncated)
+ if (exists && end_size < prev_end_size) {
+ interval_set<uint64_t> trunc;
+ trunc.insert(end_size, prev_end_size - end_size);
+ trunc.intersection_of(diff);
+ diff.subtract(trunc);
+ ldout(cct, 20) << "clearing truncate diff: " << trunc << dendl;
+ }
+
+ for (auto& object_extent : m_object_extents) {
+ interval_set<uint64_t> object_interval;
+ object_interval.insert(object_extent.first, object_extent.second);
+
+ // clip diff to current object extent
+ interval_set<uint64_t> diff_interval;
+ diff_interval.intersection_of(object_interval, diff);
+
+ interval_set<uint64_t> zero_interval;
+ if (end_size < prev_end_size) {
+ // insert zeroed object extent from truncation
+ auto zero_length = prev_end_size - end_size;
+ zero_interval.insert(end_size, zero_length);
+ }
+
+ if (exists) {
+ ldout(cct, 20) << "object_extent=" << object_extent.first << "~"
+ << object_extent.second << ", "
+ << "data_interval=" << diff_interval << dendl;
+ for (auto& interval : diff_interval) {
+ snapshot_delta[{end_snap_id, clone_end_snap_id}].insert(
+ interval.first, interval.second,
+ SnapshotExtent(SNAPSHOT_EXTENT_STATE_DATA, interval.second));
+ }
+ } else {
+ zero_interval.union_of(diff_interval);
+ }
+
+ zero_interval.intersection_of(object_interval);
+ if (!zero_interval.empty()) {
+ ldout(cct, 20) << "object_extent=" << object_extent.first << "~"
+ << object_extent.second << " "
+ << "zero_interval=" << zero_interval << dendl;
+ for (auto& interval : zero_interval) {
+ snapshot_delta[{end_snap_id, end_snap_id}].insert(
+ interval.first, interval.second,
+ SnapshotExtent(SNAPSHOT_EXTENT_STATE_ZEROED, interval.second));
+ }
+ }
+ }
+ }
+
+ prev_end_size = end_size;
+ prev_exists = exists;
+ start_snap_id = end_snap_id;
+ }
+
+ bool snapshot_delta_empty = snapshot_delta.empty();
+ if (whiteout_detected || snapshot_delta_empty) {
+ zero_initial_extent(false);
+ }
+
+ ldout(cct, 20) << "snapshot_delta=" << snapshot_delta << dendl;
+
+ this->finish(0);
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::zero_initial_extent(bool dne) {
+ I *image_ctx = this->m_ictx;
+ auto cct = image_ctx->cct;
+
+ ceph_assert(!m_snap_ids.empty());
+ librados::snap_t snap_id_start = *m_snap_ids.begin();
+
+ // the object does not exist -- mark the missing extents
+ if (snap_id_start == 0) {
+ for (auto [object_offset, object_length] : m_object_extents) {
+ ldout(cct, 20) << "zeroing initial extent " << object_offset << "~"
+ << object_length << dendl;
+ (*m_snapshot_delta)[INITIAL_WRITE_READ_SNAP_IDS].insert(
+ object_offset, object_length,
+ SnapshotExtent(
+ (dne ? SNAPSHOT_EXTENT_STATE_DNE : SNAPSHOT_EXTENT_STATE_ZEROED),
+ object_length));
+ }
+ }
+}
+
} // namespace io
} // namespace librbd
template class librbd::io::ObjectDiscardRequest<librbd::ImageCtx>;
template class librbd::io::ObjectWriteSameRequest<librbd::ImageCtx>;
template class librbd::io::ObjectCompareAndWriteRequest<librbd::ImageCtx>;
+template class librbd::io::ObjectListSnapsRequest<librbd::ImageCtx>;
#include "include/int_types.h"
#include "include/buffer.h"
+#include "include/neorados/RADOS.hpp"
#include "include/rados/librados.hpp"
#include "common/zipkin_trace.h"
#include "librbd/ObjectMap.h"
int m_op_flags;
};
+template <typename ImageCtxT = ImageCtx>
+class ObjectListSnapsRequest : public ObjectRequest<ImageCtxT> {
+public:
+ static ObjectListSnapsRequest* create(
+ ImageCtxT *ictx, uint64_t objectno, Extents&& object_extents,
+ SnapIds&& snap_ids, int list_snaps_flags,
+ const ZTracer::Trace &parent_trace, SnapshotDelta* snapshot_delta,
+ Context *completion) {
+ return new ObjectListSnapsRequest(ictx, objectno,
+ std::move(object_extents),
+ std::move(snap_ids), list_snaps_flags,
+ parent_trace, snapshot_delta, completion);
+ }
+
+ ObjectListSnapsRequest(
+ ImageCtxT *ictx, uint64_t objectno, Extents&& object_extents,
+ SnapIds&& snap_ids, int list_snaps_flags,
+ const ZTracer::Trace &parent_trace, SnapshotDelta* snapshot_delta,
+ Context *completion);
+
+ void send() override;
+
+ const char *get_op_type() const override {
+ return "snap_list";
+ }
+
+private:
+ Extents m_object_extents;
+ SnapIds m_snap_ids;
+ int m_list_snaps_flags;
+ SnapshotDelta* m_snapshot_delta;
+
+ neorados::SnapSet m_snap_set;
+ boost::system::error_code m_ec;
+
+ void list_snaps();
+ void handle_list_snaps(int r);
+
+ void zero_initial_extent(bool dne);
+
+};
+
} // namespace io
} // namespace librbd
extern template class librbd::io::ObjectDiscardRequest<librbd::ImageCtx>;
extern template class librbd::io::ObjectWriteSameRequest<librbd::ImageCtx>;
extern template class librbd::io::ObjectCompareAndWriteRequest<librbd::ImageCtx>;
+extern template class librbd::io::ObjectListSnapsRequest<librbd::ImageCtx>;
#endif // CEPH_LIBRBD_IO_OBJECT_REQUEST_H
#include "librbd/io/SimpleSchedulerObjectDispatch.h"
#include "include/neorados/RADOS.hpp"
+#include "common/ceph_time.h"
#include "common/Timer.h"
#include "common/errno.h"
#include "librbd/AsioEngine.h"
namespace io {
using namespace boost::accumulators;
+using ceph::operator<<;
using librbd::util::data_object_name;
static const int LATENCY_STATS_WINDOW_SIZE = 10;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/io/Types.h"
+#include <iostream>
+
+namespace librbd {
+namespace io {
+
+const WriteReadSnapIds INITIAL_WRITE_READ_SNAP_IDS{0, 0};
+
+std::ostream& operator<<(std::ostream& os, SnapshotExtentState state) {
+ switch (state) {
+ case SNAPSHOT_EXTENT_STATE_ZEROED:
+ os << "zeroed";
+ break;
+ case SNAPSHOT_EXTENT_STATE_DATA:
+ os << "data";
+ break;
+ default:
+ ceph_abort();
+ break;
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const SnapshotExtent& se) {
+ os << "["
+ << "state=" << se.state << ", "
+ << "length=" << se.length << "]";
+ return os;
+}
+
+} // namespace io
+} // namespace librbd
#define CEPH_LIBRBD_IO_TYPES_H
#include "include/int_types.h"
+#include "include/rados/rados_types.hpp"
+#include "common/interval_map.h"
#include "osdc/StriperTypes.h"
+#include <iosfwd>
#include <map>
#include <vector>
OBJECT_DISPATCH_FLAG_WILL_RETRY_ON_ERROR = 1UL << 1
};
+enum SnapshotExtentState {
+ SNAPSHOT_EXTENT_STATE_DNE, /* does not exist */
+ SNAPSHOT_EXTENT_STATE_ZEROED,
+ SNAPSHOT_EXTENT_STATE_DATA
+};
+
+std::ostream& operator<<(std::ostream& os, SnapshotExtentState state);
+
+struct SnapshotExtent {
+ SnapshotExtentState state;
+ size_t length;
+
+ SnapshotExtent(SnapshotExtentState state, size_t length)
+ : state(state), length(length) {
+ }
+
+ operator SnapshotExtentState() const {
+ return state;
+ }
+
+ bool operator==(const SnapshotExtent& rhs) const {
+ return state == rhs.state && length == rhs.length;
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, const SnapshotExtent& state);
+
+struct SnapshotExtentSplitMerge {
+ SnapshotExtent split(uint64_t offset, uint64_t length,
+ SnapshotExtent &se) const {
+ return SnapshotExtent(se.state, se.length);
+ }
+
+ bool can_merge(const SnapshotExtent& left,
+ const SnapshotExtent& right) const {
+ return left.state == right.state;
+ }
+
+ SnapshotExtent merge(SnapshotExtent&& left, SnapshotExtent&& right) const {
+ SnapshotExtent se(left);
+ se.length += right.length;
+ return se;
+ }
+
+ uint64_t length(const SnapshotExtent& se) const {
+ return se.length;
+ }
+};
+
+typedef std::vector<uint64_t> SnapIds;
+
+typedef std::pair<librados::snap_t, librados::snap_t> WriteReadSnapIds;
+extern const WriteReadSnapIds INITIAL_WRITE_READ_SNAP_IDS;
+
+typedef std::map<WriteReadSnapIds,
+ interval_map<uint64_t,
+ SnapshotExtent,
+ SnapshotExtentSplitMerge>> SnapshotDelta;
+
using striper::LightweightBufferExtents;
using striper::LightweightObjectExtent;
using striper::LightweightObjectExtents;
o->ops.push_back(op);
}
+void ReadOp::list_snaps(SnapSet* snaps, bs::error_code* ec) {
+ auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
+ librados::ObjectOperationTestImpl op =
+ [snaps]
+ (librados::TestIoCtxImpl* io_ctx, const std::string& oid, bufferlist*,
+ uint64_t, const SnapContext&) mutable -> int {
+ librados::snap_set_t snap_set;
+ int r = io_ctx->list_snaps(oid, &snap_set);
+ if (r >= 0 && snaps != nullptr) {
+ *snaps = {};
+ snaps->seq = snap_set.seq;
+ snaps->clones.reserve(snap_set.clones.size());
+ for (auto& clone : snap_set.clones) {
+ neorados::CloneInfo clone_info;
+ clone_info.cloneid = clone.cloneid;
+ clone_info.snaps = clone.snaps;
+ clone_info.overlap = clone.overlap;
+ clone_info.size = clone.size;
+ snaps->clones.push_back(clone_info);
+ }
+ }
+ return r;
+ };
+ if (ec != NULL) {
+ op = std::bind(save_operation_ec,
+ std::bind(op, _1, _2, _3, _4, _5), ec);
+ }
+ o->ops.push_back(op);
+}
+
void WriteOp::create(bool exclusive) {
auto o = *reinterpret_cast<librados::TestObjectOperationImpl**>(&impl);
o->ops.push_back(std::bind(
typedef ObjectDiscardRequest<librbd::MockTestImageCtx> MockObjectDiscardRequest;
typedef ObjectWriteSameRequest<librbd::MockTestImageCtx> MockObjectWriteSameRequest;
typedef ObjectCompareAndWriteRequest<librbd::MockTestImageCtx> MockObjectCompareAndWriteRequest;
+ typedef ObjectListSnapsRequest<librbd::MockTestImageCtx> MockObjectListSnapsRequest;
typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
typedef util::Mock MockUtils;
expect.WillOnce(DoDefault());
}
}
+
+ void expect_list_snaps(MockTestImageCtx &mock_image_ctx,
+ const librados::snap_set_t& snap_set, int r) {
+ auto io_context = *mock_image_ctx.get_data_io_context();
+ io_context.read_snap(CEPH_SNAPDIR);
+ auto& mock_io_ctx = librados::get_mock_io_ctx(mock_image_ctx.rados_api,
+ io_context);
+ EXPECT_CALL(mock_io_ctx, list_snaps(_, _))
+ .WillOnce(WithArg<1>(Invoke(
+ [snap_set, r](librados::snap_set_t* out_snap_set) {
+ *out_snap_set = snap_set;
+ return r;
+ })));
+ }
};
TEST_F(TestMockIoObjectRequest, Read) {
ASSERT_EQ(-EBLOCKLISTED, ctx.wait());
}
+TEST_F(TestMockIoObjectRequest, ListSnaps) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.snaps = {3, 4, 5, 6, 7};
+
+ librados::snap_set_t snap_set;
+ snap_set.seq = 6;
+ librados::clone_info_t clone_info;
+
+ clone_info.cloneid = 3;
+ clone_info.snaps = {3};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {0, 4194304}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = 4;
+ clone_info.snaps = {4};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {278528, 4096}, {442368, 4096}, {1859584, 4096}, {2224128, 4096},
+ {2756608, 4096}, {3227648, 4096}, {3739648, 4096}, {3903488, 4096}};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = 6;
+ clone_info.snaps = {5, 6};
+ clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{
+ {425984, 4096}, {440320, 1024}, {1925120, 4096}, {2125824, 4096},
+ {2215936, 5120}, {3067904, 4096}};
+ clone_info.size = 3072000;
+ snap_set.clones.push_back(clone_info);
+
+ clone_info.cloneid = CEPH_NOSNAP;
+ clone_info.snaps = {};
+ clone_info.overlap = {};
+ clone_info.size = 4194304;
+ snap_set.clones.push_back(clone_info);
+
+ expect_list_snaps(mock_image_ctx, snap_set, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {3, 4, 5, 6, 7, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{5,6}].insert(
+ 440320, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{5,6}].insert(
+ 2122728, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{5,6}].insert(
+ 2220032, 2048, {SNAPSHOT_EXTENT_STATE_DATA, 2048});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 2122728, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 2221056, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ expected_snapshot_delta[{7,CEPH_NOSNAP}].insert(
+ 3072000, 4096, {SNAPSHOT_EXTENT_STATE_DATA, 4096});
+ expected_snapshot_delta[{5,5}].insert(
+ 3072000, 4096, {SNAPSHOT_EXTENT_STATE_ZEROED, 4096});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsDNE) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, -ENOENT);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 440320, 1024, {SNAPSHOT_EXTENT_STATE_DNE, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsEmpty) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 440320, 1024, {SNAPSHOT_EXTENT_STATE_ZEROED, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
+TEST_F(TestMockIoObjectRequest, ListSnapsError) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_list_snaps(mock_image_ctx, {}, -EPERM);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {3, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
} // namespace io
} // namespace librbd