From f3c44b083a484dea43552dbef3e3811875baee8d Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Wed, 13 Jan 2021 15:11:27 -0500 Subject: [PATCH] librbd/migration: compute deltas between QCOW2 snapshots Utilize the COPIED bit within the cluster offset to quickly determine if the corresponding cluster block has been copied. Signed-off-by: Jason Dillaman --- qa/workunits/rbd/cli_migration.sh | 4 ++ src/librbd/migration/QCOWFormat.cc | 70 ++++++++++++++++++++++++++---- src/librbd/migration/QCOWFormat.h | 3 ++ 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/qa/workunits/rbd/cli_migration.sh b/qa/workunits/rbd/cli_migration.sh index 1fafbabe361..63ceeb14e3b 100755 --- a/qa/workunits/rbd/cli_migration.sh +++ b/qa/workunits/rbd/cli_migration.sh @@ -247,10 +247,14 @@ EOF rbd migration execute ${dest_image} + compare_images "${base_image}@snap1" "${dest_image}@snap1" + compare_images "${base_image}@snap2" "${dest_image}@snap2" compare_images "${base_image}" "${dest_image}" rbd migration commit ${dest_image} + compare_images "${base_image}@snap1" "${dest_image}@snap1" + compare_images "${base_image}@snap2" "${dest_image}@snap2" compare_images "${base_image}" "${dest_image}" remove_image "${dest_image}" diff --git a/src/librbd/migration/QCOWFormat.cc b/src/librbd/migration/QCOWFormat.cc index 2c75859b3bd..85393562a14 100644 --- a/src/librbd/migration/QCOWFormat.cc +++ b/src/librbd/migration/QCOWFormat.cc @@ -14,6 +14,7 @@ #include "librbd/migration/SnapshotInterface.h" #include "librbd/migration/SourceSpecBuilder.h" #include "librbd/migration/StreamInterface.h" +#include "librbd/migration/Utils.h" #include #include #include @@ -609,10 +610,12 @@ class QCOWFormat::ListSnapsRequest { public: ListSnapsRequest(QCOWFormat* qcow_format, uint32_t l1_size, const uint64_t* l1_table, io::Extents&& image_extents, - io::SparseExtents* sparse_extents, Context* on_finish) + bool require_copied_bit, io::SparseExtents* sparse_extents, + Context* on_finish) : qcow_format(qcow_format), l1_size(l1_size), l1_table(l1_table), image_extents(std::move(image_extents)), - sparse_extents(sparse_extents), on_finish(on_finish) { + require_copied_bit(require_copied_bit), sparse_extents(sparse_extents), + on_finish(on_finish) { } void send() { @@ -624,6 +627,7 @@ private: uint32_t l1_size; const uint64_t* l1_table; io::Extents image_extents; + bool require_copied_bit; io::SparseExtents* sparse_extents; Context* on_finish; @@ -665,11 +669,22 @@ private: << "cluster_offset=" << cluster_extent.cluster_offset << dendl; + // QCOW2 will set the copied bit when a CoW occurs so we know the cluster + // is dirty for this snapshot. We can't determine dirty state for compressed + // and zeroed clusters so always treat as dirty. + auto cluster_offset = cluster_extent.cluster_offset; + if (require_copied_bit && + ((cluster_extent.cluster_offset & QCOW_OFLAG_COPIED) == 0) && + ((cluster_extent.cluster_offset & QCOW_OFLAG_COMPRESSED) == 0) && + (cluster_extent.cluster_offset != QCOW_OFLAG_ZERO)) { + cluster_offset = 0; + } + if (r == -ENOENT) { r = 0; - } else if (r >= 0 && cluster_extent.cluster_offset != 0) { + } else if (r >= 0 && cluster_offset != 0) { auto state = io::SPARSE_EXTENT_STATE_DATA; - if (cluster_extent.cluster_offset == QCOW_OFLAG_ZERO) { + if (cluster_offset == QCOW_OFLAG_ZERO) { state = io::SPARSE_EXTENT_STATE_ZEROED; } @@ -1312,14 +1327,51 @@ void QCOWFormat::list_snaps(io::Extents&& image_extents, auto cct = m_image_ctx->cct; ldout(cct, 20) << "image_extents=" << image_extents << dendl; - // TODO add QCOW2 snapshot support + on_finish = new LambdaContext([this, snap_ids=std::move(snap_ids), + snapshot_delta, on_finish](int r) mutable { + handle_list_snaps(r, std::move(snap_ids), snapshot_delta, on_finish); + }); + + auto gather_ctx = new C_Gather(cct, on_finish); + + bool require_copied_bit = false; + std::optional previous_size = std::nullopt; + for (auto& [snap_id, snapshot] : m_snapshots) { + auto sparse_extents = &(*snapshot_delta)[{snap_id, snap_id}]; + util::zero_shrunk_snapshot(cct, image_extents, snap_id, snapshot.size, + &previous_size, sparse_extents); + + auto list_snaps_request = new ListSnapsRequest( + this, snapshot.l1_size, snapshot.l1_table, io::Extents{image_extents}, + require_copied_bit, sparse_extents, gather_ctx->new_sub()); + list_snaps_request->send(); + + require_copied_bit = false; + } + + // HEAD revision + auto sparse_extents = &(*snapshot_delta)[{CEPH_NOSNAP, CEPH_NOSNAP}]; + util::zero_shrunk_snapshot(cct, image_extents, CEPH_NOSNAP, m_size, + &previous_size, sparse_extents); - // QCOW does support snapshots so just use cluster existence for delta - auto snapshot = &(*snapshot_delta)[{CEPH_NOSNAP, CEPH_NOSNAP}]; auto list_snaps_request = new ListSnapsRequest( - this, m_l1_size, m_l1_table, io::Extents{image_extents}, snapshot, - on_finish); + this, m_l1_size, m_l1_table, io::Extents{image_extents}, require_copied_bit, + sparse_extents, gather_ctx->new_sub()); list_snaps_request->send(); + + gather_ctx->activate(); +} + +template +void QCOWFormat::handle_list_snaps(int r, io::SnapIds&& snap_ids, + io::SnapshotDelta* snapshot_delta, + Context* on_finish) { + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "r=" << r << ", " + << "snapshot_delta=" << snapshot_delta << dendl; + + util::merge_snapshot_delta(snap_ids, snapshot_delta); + on_finish->complete(r); } } // namespace migration diff --git a/src/librbd/migration/QCOWFormat.h b/src/librbd/migration/QCOWFormat.h index 230ee862639..b40f082d7bb 100644 --- a/src/librbd/migration/QCOWFormat.h +++ b/src/librbd/migration/QCOWFormat.h @@ -177,6 +177,9 @@ private: void handle_read_l1_table(int r, Context* on_finish); void read_backing_file(Context* on_finish); + + void handle_list_snaps(int r, io::SnapIds&& snap_ids, + io::SnapshotDelta* snapshot_delta, Context* on_finish); }; } // namespace migration -- 2.39.5