#include "librbd/asio/Utils.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageRequest.h"
#include "librbd/io/Utils.h"
#include <boost/optional.hpp>
if (r == -ENOENT) {
// the object does not exist -- mark the missing extents
zero_initial_extent(true);
- this->finish(0);
+ list_from_parent();
return;
} else if (r < 0) {
lderr(cct) << "failed to retrieve object snapshot list: " << cpp_strerror(r)
ldout(cct, 20) << "snapshot_delta=" << snapshot_delta << dendl;
+ if (snapshot_delta_empty) {
+ list_from_parent();
+ return;
+ }
+
+ this->finish(0);
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::list_from_parent() {
+ 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();
+ librados::snap_t snap_id_end = *m_snap_ids.rbegin();
+
+ std::unique_lock image_locker{image_ctx->image_lock};
+ if ((snap_id_start > 0) || (image_ctx->parent == nullptr) ||
+ ((m_list_snaps_flags & LIST_SNAPS_FLAG_DISABLE_LIST_FROM_PARENT) != 0)) {
+ image_locker.unlock();
+
+ this->finish(0);
+ return;
+ }
+
+ // calculate reverse mapping onto the parent image
+ Extents parent_image_extents;
+ for (auto [object_off, object_len]: m_object_extents) {
+ Striper::extent_to_file(cct, &image_ctx->layout, this->m_object_no,
+ object_off, object_len, parent_image_extents);
+ }
+
+ uint64_t parent_overlap = 0;
+ uint64_t object_overlap = 0;
+ int r = image_ctx->get_parent_overlap(snap_id_end, &parent_overlap);
+ if (r == 0) {
+ object_overlap = image_ctx->prune_parent_extents(parent_image_extents,
+ parent_overlap);
+ }
+
+ if (object_overlap == 0) {
+ image_locker.unlock();
+
+ this->finish(0);
+ return;
+ }
+
+ auto ctx = create_context_callback<
+ ObjectListSnapsRequest<I>,
+ &ObjectListSnapsRequest<I>::handle_list_from_parent>(this);
+ auto aio_comp = AioCompletion::create_and_start(
+ ctx, librbd::util::get_image_ctx(image_ctx->parent), AIO_TYPE_GENERIC);
+ ldout(cct, 20) << "aio_comp=" << aio_comp<< ", "
+ << "parent_image_extents " << parent_image_extents << dendl;
+
+ ImageListSnapsRequest<I> req(
+ *image_ctx->parent, aio_comp, std::move(parent_image_extents), {0,
+ image_ctx->parent->snap_id}, 0, &m_parent_snapshot_delta, this->m_trace);
+ req.send();
+}
+
+template <typename I>
+void ObjectListSnapsRequest<I>::handle_list_from_parent(int r) {
+ I *image_ctx = this->m_ictx;
+ auto cct = image_ctx->cct;
+
+ ldout(cct, 20) << "r=" << r << ", "
+ << "parent_snapshot_delta=" << m_parent_snapshot_delta
+ << dendl;
+
+ // ignore special-case of fully empty dataset
+ m_parent_snapshot_delta.erase(INITIAL_WRITE_READ_SNAP_IDS);
+ if (m_parent_snapshot_delta.empty()) {
+ this->finish(0);
+ return;
+ }
+
+ // the write/read snapshot id key is not useful for parent images so
+ // map the the special-case INITIAL_WRITE_READ_SNAP_IDS key
+ *m_snapshot_delta = {};
+ auto& intervals = (*m_snapshot_delta)[INITIAL_WRITE_READ_SNAP_IDS];
+ for (auto& [key, image_extents] : m_parent_snapshot_delta) {
+ for (auto image_extent : image_extents) {
+ auto state = image_extent.get_val().state;
+
+ // map image-extents back to this object
+ striper::LightweightObjectExtents object_extents;
+ Striper::file_to_extents(cct, &image_ctx->layout, image_extent.get_off(),
+ image_extent.get_len(), 0, 0, &object_extents);
+ for (auto& object_extent : object_extents) {
+ ceph_assert(object_extent.object_no == this->m_object_no);
+ intervals.insert(
+ object_extent.offset, object_extent.length,
+ {state, object_extent.length});
+ }
+ }
+ }
+
+ ldout(cct, 20) << "snapshot_delta=" << *m_snapshot_delta << dendl;
this->finish(0);
}
neorados::SnapSet m_snap_set;
boost::system::error_code m_ec;
+ SnapshotDelta m_parent_snapshot_delta;
+
void list_snaps();
void handle_list_snaps(int r);
- void zero_initial_extent(bool dne);
+ void list_from_parent();
+ void handle_list_from_parent(int r);
+ void zero_initial_extent(bool dne);
};
} // namespace io
OBJECT_DISPATCH_FLAG_WILL_RETRY_ON_ERROR = 1UL << 1
};
+enum {
+ LIST_SNAPS_FLAG_DISABLE_LIST_FROM_PARENT = 1UL << 0,
+};
+
enum SnapshotExtentState {
SNAPSHOT_EXTENT_STATE_DNE, /* does not exist */
SNAPSHOT_EXTENT_STATE_ZEROED,
#include "test/librados_test_stub/MockTestMemRadosClient.h"
#include "include/rbd/librbd.hpp"
#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageRequest.h"
#include "librbd/io/ObjectRequest.h"
#include "librbd/io/Utils.h"
CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+template <>
+struct ImageListSnapsRequest<librbd::MockTestImageCtx> {
+ static ImageListSnapsRequest* s_instance;
+
+ AioCompletion* aio_comp;
+ Extents image_extents;
+ SnapshotDelta* snapshot_delta;
+
+ ImageListSnapsRequest() {
+ s_instance = this;
+ }
+ ImageListSnapsRequest(
+ librbd::MockImageCtx& image_ctx, AioCompletion* aio_comp,
+ Extents&& image_extents, SnapIds&& snap_ids, int list_snaps_flags,
+ SnapshotDelta* snapshot_delta, const ZTracer::Trace& parent_trace) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->aio_comp = aio_comp;
+ s_instance->image_extents = image_extents;
+ s_instance->snapshot_delta = snapshot_delta;
+ }
+
+
+ MOCK_METHOD0(execute_send, void());
+ void send() {
+ ceph_assert(s_instance != nullptr);
+ s_instance->execute_send();
+ }
+};
+
+ImageListSnapsRequest<librbd::MockTestImageCtx>* ImageListSnapsRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
namespace util {
namespace {
typedef ObjectListSnapsRequest<librbd::MockTestImageCtx> MockObjectListSnapsRequest;
typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageListSnapsRequest<librbd::MockTestImageCtx> MockImageListSnapsRequest;
typedef util::Mock MockUtils;
void expect_object_may_exist(MockTestImageCtx &mock_image_ctx,
return r;
})));
}
+
+ void expect_image_list_snaps(MockImageListSnapsRequest& req,
+ const Extents& image_extents,
+ const SnapshotDelta& image_snapshot_delta,
+ int r) {
+ EXPECT_CALL(req, execute_send())
+ .WillOnce(Invoke(
+ [&req, image_extents, image_snapshot_delta, r]() {
+ ASSERT_EQ(image_extents, req.image_extents);
+ *req.snapshot_delta = image_snapshot_delta;
+
+ auto aio_comp = req.aio_comp;
+ aio_comp->set_request_count(1);
+ aio_comp->add_request();
+ aio_comp->complete_request(r);
+ }));
+ }
};
TEST_F(TestMockIoObjectRequest, Read) {
ASSERT_EQ(-EPERM, ctx.wait());
}
+TEST_F(TestMockIoObjectRequest, ListSnapsParent) {
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ mock_image_ctx.parent = &mock_image_ctx;
+
+ InSequence seq;
+
+ expect_list_snaps(mock_image_ctx, {}, -ENOENT);
+
+ expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0);
+ expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096);
+
+ MockImageListSnapsRequest mock_image_list_snaps_request;
+ SnapshotDelta image_snapshot_delta;
+ image_snapshot_delta[{1,6}].insert(
+ 0, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ expect_image_list_snaps(mock_image_list_snaps_request,
+ {{0, 4096}}, image_snapshot_delta, 0);
+
+ SnapshotDelta snapshot_delta;
+ C_SaferCond ctx;
+ auto req = MockObjectListSnapsRequest::create(
+ &mock_image_ctx, 0,
+ {{440320, 1024}, {2122728, 1024}, {2220032, 2048}, {3072000, 4096}},
+ {0, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ SnapshotDelta expected_snapshot_delta;
+ expected_snapshot_delta[{0,0}].insert(
+ 0, 1024, {SNAPSHOT_EXTENT_STATE_DATA, 1024});
+ ASSERT_EQ(expected_snapshot_delta, snapshot_delta);
+}
+
} // namespace io
} // namespace librbd