]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd-mirror: prune obsolete primary mirror snapshots after relocation 68161/head
authorMiki Patel <miki.patel132@gmail.com>
Sun, 19 Apr 2026 06:29:40 +0000 (11:59 +0530)
committerSuper User <root@li-4c4c4544-0051-4710-8053-c6c04f423534.ibm.com>
Wed, 17 Jun 2026 10:47:27 +0000 (16:17 +0530)
Previously, obsolete primary and demoted primary snapshots on the
secondary cluster were not cleaned up immediately after relocation.
Instead, old primary snapshots remained until a subsequent promote
operation triggered their cleanup, while old demoted primary snapshots
persisted until a later demote operation removed them.

Adding changes for proactive cleanup of obsolete primary and demoted
primary snapshots that are no longer required after relocation.

Also adding test coverage to validate the cleanup behavior.

Fixes: https://tracker.ceph.com/issues/76154
Signed-off-by: Miki Patel <miki.patel132@gmail.com>
13 files changed:
qa/workunits/rbd/rbd_mirror.sh
qa/workunits/rbd/rbd_mirror_helpers.sh
src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc
src/test/rbd_mirror/test_mock_ImageReplayer.cc
src/tools/rbd_mirror/ImageReplayer.cc
src/tools/rbd_mirror/Types.h
src/tools/rbd_mirror/image_replayer/StateBuilder.h
src/tools/rbd_mirror/image_replayer/journal/StateBuilder.cc
src/tools/rbd_mirror/image_replayer/journal/StateBuilder.h
src/tools/rbd_mirror/image_replayer/snapshot/Replayer.cc
src/tools/rbd_mirror/image_replayer/snapshot/Replayer.h
src/tools/rbd_mirror/image_replayer/snapshot/StateBuilder.cc
src/tools/rbd_mirror/image_replayer/snapshot/StateBuilder.h

index 7f8afddc61354f1077223f401b234639a569c666..99166f169ba585d0ad29f3c07b914e6f723f4886 100755 (executable)
@@ -274,6 +274,60 @@ wait_for_replaying_status_in_pool_dir ${CLUSTER1} ${POOL} ${image}
 wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'up+stopped'
 compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
 
+if [ "${RBD_MIRROR_MODE}" = "snapshot" ]; then
+  testlog "TEST: failover / failback loop with snapshot presence checks"
+  snap_id_1=""
+  demote_snap_id_1=""
+  snap_id_2=""
+  demote_snap_id_2=""
+  get_newest_complete_mirror_snapshot_id ${CLUSTER2} ${POOL} ${image} snap_id_2
+  for i in `seq 1 5`; do
+    demote_image ${CLUSTER2} ${POOL} ${image}
+    wait_for_image_replay_stopped ${CLUSTER1} ${POOL} ${image}
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${image} 'up+unknown'
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'up+unknown'
+    get_newest_complete_mirror_snapshot_id ${CLUSTER2} ${POOL} ${image} demote_snap_id_2
+    wait_for_non_primary_snap_present ${CLUSTER1} ${POOL} ${image} ${demote_snap_id_2}
+    wait_for_non_primary_snap_not_present ${CLUSTER1} ${POOL} ${image} ${snap_id_2}
+
+    promote_image ${CLUSTER1} ${POOL} ${image}
+    wait_for_image_replay_started ${CLUSTER2} ${POOL} ${image}
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${image} 'up+stopped'
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'up+replaying'
+    get_newest_complete_mirror_snapshot_id ${CLUSTER1} ${POOL} ${image} snap_id_1
+    wait_for_non_primary_snap_present ${CLUSTER2} ${POOL} ${image} ${snap_id_1}
+    wait_for_snap_not_present ${CLUSTER2} ${POOL} ${image} ${snap_id_2}
+    wait_for_snap_not_present ${CLUSTER2} ${POOL} ${image} ${demote_snap_id_2}
+
+    demote_image ${CLUSTER1} ${POOL} ${image}
+    wait_for_non_primary_snap_not_present ${CLUSTER1} ${POOL} ${image} ${demote_snap_id_2}
+    wait_for_image_replay_stopped ${CLUSTER2} ${POOL} ${image}
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${image} 'up+unknown'
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'up+unknown'
+    get_newest_complete_mirror_snapshot_id ${CLUSTER1} ${POOL} ${image} demote_snap_id_1
+    wait_for_non_primary_snap_present ${CLUSTER2} ${POOL} ${image} ${demote_snap_id_1}
+    wait_for_non_primary_snap_not_present ${CLUSTER2} ${POOL} ${image} ${snap_id_1}
+
+    promote_image ${CLUSTER2} ${POOL} ${image}
+    wait_for_image_replay_started ${CLUSTER1} ${POOL} ${image}
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${image} 'up+stopped'
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${image} 'up+replaying'
+    get_newest_complete_mirror_snapshot_id ${CLUSTER2} ${POOL} ${image} snap_id_2
+    wait_for_non_primary_snap_present ${CLUSTER1} ${POOL} ${image} ${snap_id_2}
+    wait_for_snap_not_present ${CLUSTER1} ${POOL} ${image} ${snap_id_1}
+    wait_for_snap_not_present ${CLUSTER1} ${POOL} ${image} ${demote_snap_id_1}
+  done
+  # expected snapshot in non-primary image: non-primary
+  SNAPS=$(get_snaps_json ${CLUSTER1} ${POOL} ${image})
+  jq -e 'length == 1' <<< ${SNAPS}
+  jq -e '.[0].namespace["type"] == "mirror" and .[0].namespace["state"] == "non-primary"' <<< ${SNAPS}
+  # expected snapshots in primary image: non-primary demoted, primary
+  SNAPS=$(get_snaps_json ${CLUSTER2} ${POOL} ${image})
+  jq -e 'length == 2' <<< ${SNAPS}
+  jq -e '.[0].namespace["type"] == "mirror" and .[0].namespace["state"] == "demoted" and (.[0]["name"] | startswith(".mirror.non_primary"))' <<< ${SNAPS}
+  jq -e '.[1].namespace["type"] == "mirror" and .[1].namespace["state"] == "primary"' <<< ${SNAPS}
+fi
+
 testlog "TEST: failover / failback loop"
 for i in `seq 1 20`; do
   demote_image ${CLUSTER2} ${POOL} ${image}
index 324b0877655aefd8ed1df4907f079740c7c195a0..509039716432205b322f8dc0661da8f1c6bde685 100755 (executable)
@@ -826,6 +826,43 @@ wait_for_non_primary_snap_present()
     return 1
 }
 
+wait_for_snap_not_present()
+{
+    local cluster=$1
+    local pool=$2
+    local image=$3
+    local snap_id=$4
+    local snap_count
+    local s
+
+    for s in 0.1 1 2 4 8 8 8 8 8 8 8 8 16 16 32 32; do
+        sleep ${s}
+        snap_count=$(rbd --cluster ${cluster} snap list --all ${pool}/${image} --format xml | \
+                     xmlstarlet sel -t -v "count(//snapshots/snapshot[id='${snap_id}'])")
+        [ "${snap_count}" = "0" ] && return 0
+    done
+
+    return 1
+}
+
+wait_for_non_primary_snap_not_present()
+{
+    local cluster=$1
+    local pool=$2
+    local image=$3
+    local primary_snap_id=$4
+    local snap_count
+    local s
+
+    for s in 0.1 1 2 4 8 8 8 8 8 8 8 8 16 16 32 32; do
+        sleep ${s}
+        snap_count=$(rbd --cluster ${cluster} snap list --all ${pool}/${image} --format xml | \
+            xmlstarlet sel -t -v "count(//snapshots/snapshot/namespace[primary_snap_id='${primary_snap_id}'])")
+        [ "${snap_count}" = "0" ] && return 0
+    done
+    return 1
+}
+
 wait_for_snapshot_sync_complete()
 {
     local local_cluster=$1
index db85304f06b2f514baeb38e88571896cc13427c3..a2dabf2e0ae8b1dcc617975f25b79955cf2596c1 100644 (file)
@@ -495,8 +495,8 @@ public:
       }));
   }
 
-  void expect_prune_non_primary_snapshot(librbd::MockTestImageCtx& mock_image_ctx,
-                                         uint64_t snap_id, int r) {
+  void expect_prune_mirror_snapshot(librbd::MockTestImageCtx& mock_image_ctx,
+                                    uint64_t snap_id, int r) {
     EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
       .WillOnce(Invoke([&mock_image_ctx](uint64_t snap_id) -> librbd::SnapInfo* {
         auto it = mock_image_ctx.snap_info.find(snap_id);
@@ -734,8 +734,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InitShutDown) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -788,8 +789,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -913,7 +915,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
          4, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 11, 0);
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
@@ -966,8 +968,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncInitial) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1051,8 +1054,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1129,7 +1133,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 11, 0);
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
@@ -1172,8 +1176,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1201,7 +1206,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
   mock_local_image_ctx.snap_info = {
     {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
        cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
-       {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+       {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
      0, {}, 0, 0, {}}},
     {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
        cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
@@ -1229,7 +1234,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
                      false, 0);
   expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
 
-  // idle
+  // prune primary demotion snap1
   expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
@@ -1244,8 +1249,21 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
     mock_local_image_ctx, {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
-         {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+         {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+       0, {}, 0, 0, {}}},
+      {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+         2, true, 0, {}},
        0, {}, 0, 0, {}}},
+    }, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 11, 0);
+
+  // idle
+  expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, true);
+  expect_refresh(
+    mock_local_image_ctx, {
       {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
          2, true, 0, {}},
@@ -1281,8 +1299,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncInitial) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1365,8 +1384,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1404,7 +1424,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
   expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_remote_image_ctx, false);
   expect_is_refresh_required(mock_local_image_ctx, false);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 12, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 12, 0);
 
   // sync snap2
   expect_load_image_meta(mock_image_meta, false, 0);
@@ -1462,7 +1482,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 11, 0);
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
@@ -1505,8 +1525,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1534,7 +1555,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
   mock_local_image_ctx.snap_info = {
     {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
        cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
-       {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+       {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
      0, {}, 0, 0, {}}},
     {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
        cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
@@ -1545,7 +1566,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
   expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_remote_image_ctx, false);
   expect_is_refresh_required(mock_local_image_ctx, false);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 12, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 12, 0);
 
   // sync snap2
   expect_load_image_meta(mock_image_meta, false, 0);
@@ -1555,7 +1576,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
     mock_local_image_ctx, {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
-         {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+         {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
@@ -1581,7 +1602,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
                      false, 0);
   expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
 
-  // idle
+  // prune primary demotion snap1
   expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
@@ -1596,13 +1617,26 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
     mock_local_image_ctx, {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
-         {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+         {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}},
       {13U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 11, 0);
+
+  // idle
+  expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, true);
+  expect_refresh(
+    mock_local_image_ctx, {
+      {13U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+         2, true, 0, {}},
+       0, {}, 0, 0, {}}},
+    }, 0);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1633,8 +1667,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1687,8 +1722,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
   expect_refresh(
     mock_local_image_ctx, {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
-         1, true, 0, {}},
+         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+         {"local mirror peer uuid"}, "remote mirror uuid", 1, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
 
@@ -1722,8 +1757,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, LocalImagePromoted) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1740,7 +1776,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, LocalImagePromoted) {
   mock_local_image_ctx.snap_info = {
     {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
        cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY,
-       {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+       {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
      0, {}, 0, 0, {}}}};
 
   // idle
@@ -1778,8 +1814,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequested) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1834,8 +1871,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequestedRemoteNotPrimary) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1890,8 +1928,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequestedRemoteNotPrimary) {
   expect_refresh(
     mock_local_image_ctx, {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
-        cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED, {"uuid"},
-        "remote mirror uuid", 1, true, 0, {{1, CEPH_NOSNAP}}},
+        cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+        {"local mirror peer uuid"}, "remote mirror uuid", 1, true, 0,
+        {{1, CEPH_NOSNAP}}},
        0, {}, 0, 0, {}}},
     }, 0);
 
@@ -1923,8 +1962,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RegisterLocalUpdateWatcherError) {
                                       mock_image_meta);
   MockReplayerListener mock_replayer_listener;
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1956,8 +1996,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RegisterRemoteUpdateWatcherError)
                                       mock_image_meta);
   MockReplayerListener mock_replayer_listener;
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -1995,8 +2036,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnregisterRemoteUpdateWatcherError
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2037,8 +2079,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnregisterLocalUpdateWatcherError)
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2079,8 +2122,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, LoadImageMetaError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2127,8 +2171,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshLocalImageError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2178,8 +2223,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshRemoteImageError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2228,8 +2274,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CopySnapshotsError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2288,8 +2335,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, GetImageStateError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2350,8 +2398,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CreateNonPrimarySnapshotError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2416,8 +2465,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateMirrorImageStateError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2484,8 +2534,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RequestSyncError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2554,8 +2605,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CopyImageError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2627,8 +2679,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateNonPrimarySnapshotError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2704,8 +2757,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkPeerError) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2792,8 +2846,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SplitBrain) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2855,8 +2910,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteSnapshotMissingSplitBrain) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2923,8 +2979,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -2956,8 +3013,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
     {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
      0, {}, 0, 0, {}}},
     {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-       cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {}, "", CEPH_NOSNAP,
-       true, 0, {}},
+       cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+       {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
      0, {}, 0, 0, {}}}};
 
   // attach to promoted remote image
@@ -2988,7 +3045,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
                      false, 0);
   expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
 
-  // idle
+  // prune primary demotion snap2
   expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
@@ -2997,11 +3054,11 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
          0, {}, 0, 0, {}}},
       {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
-         {"remote mirror peer uuid"}, "local mirror uuid", 12U, true, 0, {}},
+         {}, "local mirror uuid", 12U, true, 0, {}},
        0, {}, 0, 0, {}}},
       {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP, true, 0,
-         {}},
+         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+         "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}}
     }, 0);
   expect_is_refresh_required(mock_local_image_ctx, true);
@@ -3010,8 +3067,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
       {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
          0, {}, 0, 0, {}}},
       {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {}, "", CEPH_NOSNAP,
-         true, 0, {}},
+         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+         {"local mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}},
       {13U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {},
@@ -3019,6 +3076,22 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
          {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}},
        0, {}, 0, 0, {}}},
     }, 0);
+  expect_prune_mirror_snapshot(mock_local_image_ctx, 12, 0);
+
+  // idle
+  expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, true);
+  expect_refresh(
+    mock_local_image_ctx, {
+      {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+         0, {}, 0, 0, {}}},
+      {13U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {},
+         "remote mirror uuid", 3, true, 0,
+         {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}},
+       0, {}, 0, 0, {}}},
+    }, 0);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -3065,8 +3138,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkRemoteSnapshot) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -3143,8 +3217,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SkipImageSync) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -3222,8 +3297,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ImageNameUpdated) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
     {"remote mirror uuid", "remote mirror peer uuid"});
@@ -3284,8 +3360,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStatePendingShutdown) {
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   C_SaferCond shutdown_ctx;
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
@@ -3371,8 +3448,9 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStateErrorPendingShutdow
                                       mock_remote_image_ctx,
                                       mock_image_meta);
   MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
-                             "local mirror uuid", &m_pool_meta_cache,
-                             &mock_state_builder, &mock_replayer_listener};
+                             "local mirror uuid", "local mirror peer uuid",
+                             &m_pool_meta_cache, &mock_state_builder,
+                             &mock_replayer_listener};
   C_SaferCond shutdown_ctx;
   m_pool_meta_cache.set_remote_pool_meta(
     m_remote_fsid, m_remote_io_ctx.get_id(),
index 0788ce51bfa3cfbdf5a1b77ab5ba276fe62b5eeb..1bdd0c6b96f51b15e15136cec120d082ad88141a 100644 (file)
@@ -178,9 +178,11 @@ struct StateBuilder<librbd::MockTestImageCtx> {
   }
 
   MOCK_METHOD1(close, void(Context*));
-  MOCK_METHOD5(create_replayer, Replayer*(Threads<librbd::MockTestImageCtx>*,
+  MOCK_METHOD6(create_replayer, Replayer*(Threads<librbd::MockTestImageCtx>*,
                                           InstanceWatcher<librbd::MockTestImageCtx>*,
-                                          const std::string&, PoolMetaCache*,
+                                          const std::string&,
+                                          const std::string&,
+                                          PoolMetaCache*,
                                           ReplayerListener*));
 
   StateBuilder() {
@@ -312,8 +314,8 @@ public:
 
   void expect_create_replayer(MockStateBuilder& mock_state_builder,
                               MockReplayer& mock_replayer) {
-    EXPECT_CALL(mock_state_builder, create_replayer(_, _, _, _, _))
-      .WillOnce(WithArg<4>(
+    EXPECT_CALL(mock_state_builder, create_replayer(_, _, _, _, _, _))
+      .WillOnce(WithArg<5>(
         Invoke([&mock_replayer]
                (image_replayer::ReplayerListener* replayer_listener) {
           mock_replayer.replayer_listener = replayer_listener;
index de4ef76b7c74fe71ff92a88a7bd16044cd44b27f..9aee9a96b4d8b1395397c20688f0f6dbac0079ee 100644 (file)
@@ -431,6 +431,7 @@ void ImageReplayer<I>::start_replay() {
   ceph_assert(m_replayer == nullptr);
   m_replayer = m_state_builder->create_replayer(m_threads, m_instance_watcher,
                                                 m_local_mirror_uuid,
+                                                m_remote_image_peer.uuid,
                                                 m_pool_meta_cache,
                                                 m_replayer_listener);
 
index f5e9c67fde84209d18dbb6d7e2527d6bcb8a945c..ef13f20fa9c6cef4d61d6b70d2a345364ade2ecb 100644 (file)
@@ -116,7 +116,7 @@ struct Peer {
 
 template <typename I>
 std::ostream& operator<<(std::ostream& os, const Peer<I>& peer) {
-  return os << "uuid=" << peer.uuid << ", "
+  return os << "uuid=" << peer.uuid << ", remote_pool_meta="
             << peer.remote_pool_meta;
 }
 
index 0831f72f1301c18fb7c2eed6a846606e26d0e803..79c27cb19b97dd6651c2376fd04d34c8e71c6a06 100644 (file)
@@ -75,6 +75,7 @@ public:
       Threads<ImageCtxT>* threads,
       InstanceWatcher<ImageCtxT>* instance_watcher,
       const std::string& local_mirror_uuid,
+      const std::string& local_mirror_peer_uuid,
       PoolMetaCache* pool_meta_cache,
       ReplayerListener* replayer_listener) = 0;
 
index 358af64bea271be8b8446bc318d8d681ab1f5132..9e346e7adc67c3992ec2dad096b3fd00db4a4ba5 100644 (file)
@@ -106,6 +106,7 @@ image_replayer::Replayer* StateBuilder<I>::create_replayer(
     Threads<I>* threads,
     InstanceWatcher<I>* instance_watcher,
     const std::string& local_mirror_uuid,
+    const std::string& local_mirror_peer_uuid,
     PoolMetaCache* pool_meta_cache,
     ReplayerListener* replayer_listener) {
   return Replayer<I>::create(
index 903cce98bfb41b7c3415d3aa78899e22b43b1ec1..5605b1631fa8b48259dffc7c5ee9203ac3adc14c 100644 (file)
@@ -65,6 +65,7 @@ public:
       Threads<ImageCtxT>* threads,
       InstanceWatcher<ImageCtxT>* instance_watcher,
       const std::string& local_mirror_uuid,
+      const std::string& local_mirror_peer_uuid,
       PoolMetaCache* pool_meta_cache,
       ReplayerListener* replayer_listener) override;
 
index 14a2113bea5eb0b205e107b7d7f581f4ad4770cb..b85edfc105d4d4aa55fb4103e79a321a535ece62 100644 (file)
@@ -116,12 +116,14 @@ Replayer<I>::Replayer(
     Threads<I>* threads,
     InstanceWatcher<I>* instance_watcher,
     const std::string& local_mirror_uuid,
+    const std::string& local_mirror_peer_uuid,
     PoolMetaCache* pool_meta_cache,
     StateBuilder<I>* state_builder,
     ReplayerListener* replayer_listener)
   : m_threads(threads),
     m_instance_watcher(instance_watcher),
     m_local_mirror_uuid(local_mirror_uuid),
+    m_local_mirror_peer_uuid(local_mirror_peer_uuid),
     m_pool_meta_cache(pool_meta_cache),
     m_state_builder(state_builder),
     m_replayer_listener(replayer_listener),
@@ -171,7 +173,9 @@ void Replayer<I>::init(Context* on_finish) {
   }
 
   m_remote_mirror_peer_uuid = remote_pool_meta.mirror_peer_uuid;
-  dout(10) << "remote_mirror_peer_uuid=" << m_remote_mirror_peer_uuid << dendl;
+  dout(10) << "local_mirror_peer_uuid=" << m_local_mirror_peer_uuid
+           << ", remote_mirror_peer_uuid=" << m_remote_mirror_peer_uuid
+           << dendl;
 
   {
     auto local_image_ctx = m_state_builder->local_image_ctx;
@@ -539,6 +543,18 @@ void Replayer<I>::scan_local_mirror_snapshots(
       }
     } else if (mirror_ns->is_primary()) {
       if (mirror_ns->complete) {
+        const auto& peer_uuids = mirror_ns->mirror_peer_uuids;
+        if (peer_uuids.empty() ||
+            (mirror_ns->state == cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED &&
+             peer_uuids.size() == 1 &&
+             peer_uuids.count(m_local_mirror_peer_uuid) == 1)) {
+          // After relocation, the primary snapshots on the new secondary become
+          // obsolete snapshots. They can be pruned if they are already unlinked.
+          // Additionally, in this case, a demoted primary snapshot that is linked
+          // to only a single remote (current primary) peer is safe to prune,
+          // because no other peer depends on it waiting for sync.
+          prune_snap_ids.insert(local_snap_id);
+        }
         m_local_snap_id_start = local_snap_id;
         ceph_assert(m_local_snap_id_end == CEPH_NOSNAP);
       } else {
@@ -564,8 +580,8 @@ void Replayer<I>::scan_local_mirror_snapshots(
     locker->unlock();
 
     auto prune_snap_id = *prune_snap_ids.begin();
-    dout(5) << "pruning unused non-primary snapshot " << prune_snap_id << dendl;
-    prune_non_primary_snapshot(prune_snap_id);
+    dout(5) << "pruning unused mirror snapshot " << prune_snap_id << dendl;
+    prune_mirror_snapshot(prune_snap_id);
     return;
   }
 
@@ -798,7 +814,7 @@ void Replayer<I>::scan_remote_mirror_snapshots(
 }
 
 template <typename I>
-void Replayer<I>::prune_non_primary_snapshot(uint64_t snap_id) {
+void Replayer<I>::prune_mirror_snapshot(uint64_t snap_id) {
   dout(10) << "snap_id=" << snap_id << dendl;
 
   auto local_image_ctx = m_state_builder->local_image_ctx;
@@ -825,18 +841,18 @@ void Replayer<I>::prune_non_primary_snapshot(uint64_t snap_id) {
   }
 
   auto ctx = create_context_callback<
-    Replayer<I>, &Replayer<I>::handle_prune_non_primary_snapshot>(this);
+    Replayer<I>, &Replayer<I>::handle_prune_mirror_snapshot>(this);
   local_image_ctx->operations->snap_remove(snap_namespace, snap_name, ctx);
 }
 
 template <typename I>
-void Replayer<I>::handle_prune_non_primary_snapshot(int r) {
+void Replayer<I>::handle_prune_mirror_snapshot(int r) {
   dout(10) << "r=" << r << dendl;
 
   if (r < 0 && r != -ENOENT) {
-    derr << "failed to prune non-primary snapshot: " << cpp_strerror(r)
+    derr << "failed to prune mirror snapshot: " << cpp_strerror(r)
          << dendl;
-    handle_replay_complete(r, "failed to prune non-primary snapshot");
+    handle_replay_complete(r, "failed to prune mirror snapshot");
     return;
   }
 
index 399e636a9197284888c63f03030754feb571a626..6f111b26d7608c97fbed8b28f69bb08bc613d33c 100644 (file)
@@ -47,17 +47,20 @@ public:
       Threads<ImageCtxT>* threads,
       InstanceWatcher<ImageCtxT>* instance_watcher,
       const std::string& local_mirror_uuid,
+      const std::string& local_mirror_peer_uuid,
       PoolMetaCache* pool_meta_cache,
       StateBuilder<ImageCtxT>* state_builder,
       ReplayerListener* replayer_listener) {
     return new Replayer(threads, instance_watcher, local_mirror_uuid,
-                        pool_meta_cache, state_builder, replayer_listener);
+                        local_mirror_peer_uuid, pool_meta_cache, state_builder,
+                        replayer_listener);
   }
 
   Replayer(
       Threads<ImageCtxT>* threads,
       InstanceWatcher<ImageCtxT>* instance_watcher,
       const std::string& local_mirror_uuid,
+      const std::string& local_mirror_peer_uuid,
       PoolMetaCache* pool_meta_cache,
       StateBuilder<ImageCtxT>* state_builder,
       ReplayerListener* replayer_listener);
@@ -202,6 +205,7 @@ private:
   Threads<ImageCtxT>* m_threads;
   InstanceWatcher<ImageCtxT>* m_instance_watcher;
   std::string m_local_mirror_uuid;
+  std::string m_local_mirror_peer_uuid;
   PoolMetaCache* m_pool_meta_cache;
   StateBuilder<ImageCtxT>* m_state_builder;
   ReplayerListener* m_replayer_listener;
@@ -271,8 +275,8 @@ private:
   void scan_local_mirror_snapshots(std::unique_lock<ceph::mutex>* locker);
   void scan_remote_mirror_snapshots(std::unique_lock<ceph::mutex>* locker);
 
-  void prune_non_primary_snapshot(uint64_t snap_id);
-  void handle_prune_non_primary_snapshot(int r);
+  void prune_mirror_snapshot(uint64_t snap_id);
+  void handle_prune_mirror_snapshot(int r);
 
   void copy_snapshots();
   void handle_copy_snapshots(int r);
index b3c58be95f6cc4fbe683ca2a6e35cff1327f4a4e..d678e6ea87cc1d18a6e66555e2bba219226ac795 100644 (file)
@@ -105,11 +105,12 @@ image_replayer::Replayer* StateBuilder<I>::create_replayer(
     Threads<I>* threads,
     InstanceWatcher<I>* instance_watcher,
     const std::string& local_mirror_uuid,
+    const std::string& local_mirror_peer_uuid,
     PoolMetaCache* pool_meta_cache,
     ReplayerListener* replayer_listener) {
   return Replayer<I>::create(
-    threads, instance_watcher, local_mirror_uuid, pool_meta_cache, this,
-    replayer_listener);
+    threads, instance_watcher, local_mirror_uuid, local_mirror_peer_uuid,
+    pool_meta_cache, this, replayer_listener);
 }
 
 } // namespace snapshot
index baa8dcedc7aeb7505ff616e9845c90ffdc505c95..c26690740144f088e97498b10cbd91a73364692c 100644 (file)
@@ -70,6 +70,7 @@ public:
       Threads<ImageCtxT>* threads,
       InstanceWatcher<ImageCtxT>* instance_watcher,
       const std::string& local_mirror_uuid,
+      const std::string& local_mirror_peer_uuid,
       PoolMetaCache* pool_meta_cache,
       ReplayerListener* replayer_listener) override;