]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd-mirror: prevent image deletion if remote image is not primary
authorVinayBhaskar-V <vvarada@redhat.com>
Tue, 13 May 2025 20:25:44 +0000 (01:55 +0530)
committerVinayBhaskar-V <vvarada@redhat.com>
Tue, 29 Jul 2025 13:54:41 +0000 (13:54 +0000)
A resync on a mirrored image may incorrectly results in the local
image being deleted even when the remote image is no longer primary.
This issue can occur under the following conditions:
* if  resync is requested on the secondary before the remote image has
  been fully demoted
* if the demotion of the primary image is not mirrored
  due to the rbd-mirror daemon being offline.

This can be fixed by ensuring that image deletion during a resync is
only allowed when the remote image is confirmed to be primary.

This commit fixes the issue only for snapshot based mirroring mode

Fixes: https://tracker.ceph.com/issues/70948
Signed-off-by: VinayBhaskar-V <vvarada@redhat.com>
(cherry picked from commit e14afbc95a5fb8f5a33e7ea23a035992b966d671)

qa/workunits/rbd/rbd_mirror.sh
src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc
src/tools/rbd_mirror/image_replayer/snapshot/PrepareReplayRequest.cc
src/tools/rbd_mirror/image_replayer/snapshot/Replayer.cc
src/tools/rbd_mirror/image_replayer/snapshot/Replayer.h

index 589ed26d4779d366b8269f0d0eb940e3c9ea46d2..61cc0d6c8f36abf9d85c237bcd76b68c64685ed8 100755 (executable)
@@ -526,6 +526,54 @@ for i in ${image2} ${image4}; do
   remove_image_retry ${CLUSTER2} ${POOL} ${i}
 done
 
+if [ "${RBD_MIRROR_MODE}" = "snapshot" ]; then
+  testlog "TEST: request image resync when remote is not primary"
+  test_resync_image=test_resync_image
+  create_image_and_enable_mirror ${CLUSTER2} ${POOL} ${test_resync_image} ${RBD_MIRROR_MODE}
+  write_image ${CLUSTER2} ${POOL} ${test_resync_image} 100
+  wait_for_image_replay_stopped ${CLUSTER2} ${POOL} ${test_resync_image}
+  wait_for_image_replay_started ${CLUSTER1} ${POOL} ${test_resync_image}
+  wait_for_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${test_resync_image}
+  wait_for_replaying_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image}
+  wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+stopped'
+  write_image ${CLUSTER2} ${POOL} ${test_resync_image} 100
+  demote_image ${CLUSTER2} ${POOL} ${test_resync_image}
+  request_resync_image ${CLUSTER1} ${POOL} ${test_resync_image} test_resync_image_id
+  wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+unknown' 'remote image is not primary'
+  wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image} 'up+unknown' 'remote image is not primary'
+  promote_image ${CLUSTER1} ${POOL} ${test_resync_image}
+  wait_for_image_replay_started ${CLUSTER2} ${POOL} ${test_resync_image}
+  wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image} 'up+stopped'
+  wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+replaying'
+  compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${test_resync_image}
+  remove_image_retry ${CLUSTER1} ${POOL} ${test_resync_image}
+
+  if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+    testlog "TEST: request image resync when remote is not primary and daemon is offline"
+    test_resync_image=test_resync_image
+    create_image_and_enable_mirror ${CLUSTER2} ${POOL} ${test_resync_image} ${RBD_MIRROR_MODE}
+    write_image ${CLUSTER2} ${POOL} ${test_resync_image} 100
+    wait_for_image_replay_stopped ${CLUSTER2} ${POOL} ${test_resync_image}
+    wait_for_image_replay_started ${CLUSTER1} ${POOL} ${test_resync_image}
+    wait_for_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${test_resync_image}
+    wait_for_replaying_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image}
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+stopped'
+    stop_mirrors ${CLUSTER1}
+    write_image ${CLUSTER2} ${POOL} ${test_resync_image} 100
+    demote_image ${CLUSTER2} ${POOL} ${test_resync_image}
+    request_resync_image ${CLUSTER1} ${POOL} ${test_resync_image} test_resync_image_id
+    start_mirrors ${CLUSTER1}
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+unknown' 'remote image is not primary'
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image} 'up+unknown' 'remote image is not primary'
+    promote_image ${CLUSTER1} ${POOL} ${test_resync_image}
+    wait_for_image_replay_started ${CLUSTER2} ${POOL} ${test_resync_image}
+    wait_for_status_in_pool_dir ${CLUSTER1} ${POOL} ${test_resync_image} 'up+stopped'
+    wait_for_status_in_pool_dir ${CLUSTER2} ${POOL} ${test_resync_image} 'up+replaying'
+    compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${test_resync_image}
+    remove_image_retry ${CLUSTER1} ${POOL} ${test_resync_image}
+  fi
+fi
+
 testlog "TEST: disable mirror while daemon is stopped"
 stop_mirrors ${CLUSTER1}
 stop_mirrors ${CLUSTER2}
index 75141e5a7be30409797ce002008b9c017faa17e0..dff9cd4b65cf4360ddd9e0293c51b3d448c3172c 100644 (file)
@@ -676,8 +676,8 @@ public:
     expect_register_update_watcher(mock_remote_image_ctx, update_watch_ctx, 234,
                                    0);
     expect_load_image_meta(mock_image_meta, false, 0);
-    expect_is_refresh_required(mock_local_image_ctx, false);
     expect_is_refresh_required(mock_remote_image_ctx, false);
+    expect_is_refresh_required(mock_local_image_ctx, false);
 
     C_SaferCond init_ctx;
     mock_replayer.init(&init_ctx);
@@ -800,8 +800,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -826,14 +826,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
 
   // sync snap4
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, true);
-  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, {{1, CEPH_NOSNAP}}},
-       0, {}, 0, 0, {}}},
-    }, 0);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
     mock_remote_image_ctx, {
@@ -856,6 +848,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
          "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}}
     }, 0);
+  expect_is_refresh_required(mock_local_image_ctx, true);
+  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, {{1, CEPH_NOSNAP}}},
+       0, {}, 0, 0, {}}},
+    }, 0);
   expect_snapshot_copy(mock_snapshot_copy_request, 1, 4, 11,
                        {{1, 11}, {2, 12}, {4, CEPH_NOSNAP}}, 0);
   expect_get_image_state(mock_get_image_state_request, 4, 0);
@@ -877,20 +877,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
 
   // prune non-primary snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, true);
-  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, {}},
-       0, {}, 0, 0, {}}},
-      {12U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
-       0, {}, 0, 0, {}}},
-      {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
-         4, true, 0, {}},
-       0, {}, 0, 0, {}}},
-    }, 0);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
     mock_remote_image_ctx, {
@@ -909,18 +895,24 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
          "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}}
     }, 0);
-  expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
-
-  // idle
-  expect_load_image_meta(mock_image_meta, false, 0);
   expect_is_refresh_required(mock_local_image_ctx, true);
   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, {}},
+       0, {}, 0, 0, {}}},
+      {12U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+       0, {}, 0, 0, {}}},
       {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
          cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
          4, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
+  expect_prune_non_primary_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, true);
   expect_refresh(
     mock_remote_image_ctx, {
@@ -929,6 +921,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
          "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}}
     }, 0);
+  expect_is_refresh_required(mock_local_image_ctx, true);
+  expect_refresh(
+    mock_local_image_ctx, {
+      {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+         cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+         4, true, 0, {}},
+       0, {}, 0, 0, {}}},
+    }, 0);
 
   // fire init
   C_SaferCond init_ctx;
@@ -990,8 +990,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncInitial) {
 
   // re-sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockGetImageStateRequest mock_get_image_state_request;
   expect_get_image_state(mock_get_image_state_request, 11, 0);
   expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
@@ -1008,6 +1008,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncInitial) {
 
   // 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, {
@@ -1016,7 +1017,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncInitial) {
          1, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1084,8 +1084,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
 
   // re-sync snap2
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockGetImageStateRequest mock_get_image_state_request;
   expect_get_image_state(mock_get_image_state_request, 12, 0);
   expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
@@ -1105,6 +1105,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
 
   // prune non-primary snap1
   expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, true);
+  expect_refresh(
+    mock_remote_image_ctx, {
+      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         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);
   expect_refresh(
     mock_local_image_ctx, {
@@ -1117,18 +1125,11 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, true);
-  expect_refresh(
-    mock_remote_image_ctx, {
-      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
-         "", CEPH_NOSNAP, true, 0, {}},
-       0, {}, 0, 0, {}}},
-    }, 0);
   expect_prune_non_primary_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, {
@@ -1137,7 +1138,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1206,8 +1206,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
 
   // re-sync snap2
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockGetImageStateRequest mock_get_image_state_request;
   expect_get_image_state(mock_get_image_state_request, 12, 0);
   expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
@@ -1227,6 +1227,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, true);
+  expect_refresh(
+    mock_remote_image_ctx, {
+      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         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);
   expect_refresh(
     mock_local_image_ctx, {
@@ -1239,14 +1247,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, true);
-  expect_refresh(
-    mock_remote_image_ctx, {
-      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
-         "", CEPH_NOSNAP, true, 0, {}},
-       0, {}, 0, 0, {}}},
-    }, 0);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1305,8 +1305,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncInitial) {
 
   // re-sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockGetImageStateRequest mock_get_image_state_request;
   expect_get_image_state(mock_get_image_state_request, 11, 0);
   expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
@@ -1322,6 +1322,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncInitial) {
 
   // 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, {
@@ -1330,7 +1331,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncInitial) {
          1, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1398,12 +1398,13 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
 
   // prune non-primary snap2
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   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);
 
   // sync snap2
   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, {
@@ -1412,7 +1413,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
          1, true, 0, {{1, CEPH_NOSNAP}}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11,
                        {{2, CEPH_NOSNAP}}, 0);
@@ -1438,6 +1438,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
 
   // prune non-primary snap1
   expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, true);
+  expect_refresh(
+    mock_remote_image_ctx, {
+      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         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);
   expect_refresh(
     mock_local_image_ctx, {
@@ -1450,18 +1458,11 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, true);
-  expect_refresh(
-    mock_remote_image_ctx, {
-      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
-         "", CEPH_NOSNAP, true, 0, {}},
-       0, {}, 0, 0, {}}},
-    }, 0);
   expect_prune_non_primary_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, {
@@ -1470,7 +1471,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1539,12 +1539,13 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
 
   // prune non-primary snap2
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   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);
 
   // sync snap2
   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, {
@@ -1553,7 +1554,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
          {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11,
                        {{2, CEPH_NOSNAP}}, 0);
@@ -1579,6 +1579,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, true);
+  expect_refresh(
+    mock_remote_image_ctx, {
+      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+         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);
   expect_refresh(
     mock_local_image_ctx, {
@@ -1591,14 +1599,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote)
          2, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, true);
-  expect_refresh(
-    mock_remote_image_ctx, {
-      {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
-         "", CEPH_NOSNAP, true, 0, {}},
-       0, {}, 0, 0, {}}},
-    }, 0);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1652,8 +1652,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -1678,6 +1678,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
 
   // 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, {
@@ -1686,7 +1687,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
          1, true, 0, {}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1741,8 +1741,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, LocalImagePromoted) {
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1787,9 +1787,16 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequested) {
                                    mock_replayer_listener,
                                    mock_image_meta,
                                    &update_watch_ctx));
+  // inject a primary snapshot
+  mock_remote_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, {}},
+      0, {}, 0, 0, {}}}};
 
   // idle
   expect_load_image_meta(mock_image_meta, true, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -1803,6 +1810,94 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequested) {
                                         mock_remote_image_ctx));
 }
 
+TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequestedRemoteNotPrimary) {
+  librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+  librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+  MockThreads mock_threads(m_threads);
+  expect_work_queue_repeatedly(mock_threads);
+
+  MockReplayerListener mock_replayer_listener;
+  expect_notification(mock_threads, mock_replayer_listener);
+
+  InSequence seq;
+
+  MockInstanceWatcher mock_instance_watcher;
+  MockImageMeta mock_image_meta;
+  MockStateBuilder mock_state_builder(mock_local_image_ctx,
+                                      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};
+  m_pool_meta_cache.set_remote_pool_meta(
+    m_remote_io_ctx.get_id(),
+    {"remote mirror uuid", "remote mirror peer uuid"});
+
+  librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+  ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+                                   mock_local_image_ctx,
+                                   mock_remote_image_ctx,
+                                   mock_replayer_listener,
+                                   mock_image_meta,
+                                   &update_watch_ctx));
+
+  // inject a demotion snapshot
+  mock_remote_image_ctx.snap_info = {
+    {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+       cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+       {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+     0, {}, 0, 0, {}}}};
+
+  // resync requested
+  expect_load_image_meta(mock_image_meta, true, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
+
+  // remote is not primary so no resync should occur
+  expect_is_refresh_required(mock_local_image_ctx, false);
+  MockSnapshotCopyRequest mock_snapshot_copy_request;
+  expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0,
+                    {{1, CEPH_NOSNAP}}, 0);
+  MockGetImageStateRequest mock_get_image_state_request;
+  expect_get_image_state(mock_get_image_state_request, 1, 0);
+  MockCreateNonPrimaryRequest mock_create_non_primary_request;
+  expect_create_non_primary_request(mock_create_non_primary_request,
+                                    true, "remote mirror uuid", 1,
+                                    {{1, CEPH_NOSNAP}}, 11, 0);
+  MockImageStateUpdateRequest mock_image_state_update_request;
+  expect_update_mirror_image_state(mock_image_state_update_request, 0);
+  expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+  MockImageCopyRequest mock_image_copy_request;
+  expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+                    {{1, CEPH_NOSNAP}}, 0);
+  MockApplyImageStateRequest mock_apply_state_request;
+  expect_apply_image_state(mock_apply_state_request, 0);
+  expect_mirror_image_snapshot_set_copy_progress(
+    mock_local_image_ctx, 11, true, 0, 0);
+  expect_notify_update(mock_local_image_ctx);
+  expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+  expect_load_image_meta(mock_image_meta, true, 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::MirrorSnapshotNamespace{
+        cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED, {"uuid"},
+        "remote mirror uuid", 1, true, 0, {{1, CEPH_NOSNAP}}},
+       0, {}, 0, 0, {}}},
+    }, 0);
+
+  // wake-up replayer
+  update_watch_ctx->handle_notify();
+
+  ASSERT_EQ(0, wait_for_notification(1));
+  ASSERT_FALSE(mock_replayer.is_replaying());
+
+  ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+                                        mock_local_image_ctx,
+                                        mock_remote_image_ctx));
+}
+
 TEST_F(TestMockImageReplayerSnapshotReplayer, RegisterLocalUpdateWatcherError) {
   librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
   librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
@@ -2039,6 +2134,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshLocalImageError) {
 
   // sync
   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, {}, -EINVAL);
 
@@ -2089,7 +2185,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshRemoteImageError) {
 
   // sync
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(mock_remote_image_ctx, {}, -EINVAL);
 
@@ -2147,8 +2242,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CopySnapshotsError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        -EINVAL);
@@ -2207,8 +2302,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, GetImageStateError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2269,8 +2364,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CreateNonPrimarySnapshotError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2335,8 +2430,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateMirrorImageStateError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2403,8 +2498,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RequestSyncError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2473,8 +2568,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, CopyImageError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2546,8 +2641,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateNonPrimarySnapshotError) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -2632,8 +2727,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkPeerError) {
 
   // sync snap2
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11, {{2, CEPH_NOSNAP}},
                        0);
@@ -2716,8 +2811,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SplitBrain) {
 
   // detect split-brain
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -2784,8 +2879,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteSnapshotMissingSplitBrain) {
 
   // split-brain due to missing snapshot 1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -2858,8 +2953,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
 
   // attach to promoted remote image
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 2, 3, 12,
                        {{2, 12}, {3, CEPH_NOSNAP}}, 0);
@@ -2886,6 +2981,20 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, true);
+  expect_refresh(
+    mock_remote_image_ctx, {
+      {1U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+         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, {}},
+       0, {}, 0, 0, {}}},
+      {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP, true, 0,
+         {}},
+       0, {}, 0, 0, {}}}
+    }, 0);
   expect_is_refresh_required(mock_local_image_ctx, true);
   expect_refresh(
     mock_local_image_ctx, {
@@ -2901,20 +3010,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
          {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, true);
-  expect_refresh(
-    mock_remote_image_ctx, {
-      {1U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
-         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, {}},
-       0, {}, 0, 0, {}}},
-      {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
-         cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP, true, 0,
-         {}},
-       0, {}, 0, 0, {}}}
-    }, 0);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -2977,15 +3072,14 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkRemoteSnapshot) {
 
   // unlink snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockUnlinkPeerRequest mock_unlink_peer_request;
   expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
                      false, 0);
 
   // idle
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, true);
   expect_refresh(
     mock_remote_image_ctx, {
@@ -3000,6 +3094,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkRemoteSnapshot) {
          "", CEPH_NOSNAP, true, 0, {}},
        0, {}, 0, 0, {}}}
     }, 0);
+  expect_is_refresh_required(mock_local_image_ctx, false);
 
   // fire init
   C_SaferCond init_ctx;
@@ -3055,8 +3150,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SkipImageSync) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -3076,6 +3171,7 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SkipImageSync) {
 
   // 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, {
@@ -3084,7 +3180,6 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, SkipImageSync) {
          1, true, 0, {{1, CEPH_NOSNAP}}},
        0, {}, 0, 0, {}}},
     }, 0);
-  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // fire init
   C_SaferCond init_ctx;
@@ -3132,11 +3227,19 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ImageNameUpdated) {
                                    mock_image_meta,
                                    &update_watch_ctx));
 
+  // inject a primary snapshot
+  mock_remote_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, {}},
+      0, {}, 0, 0, {}}}};
+
   // change the name of the image
   mock_local_image_ctx.name = "NEW NAME";
 
   // idle
   expect_load_image_meta(mock_image_meta, true, 0);
+  expect_is_refresh_required(mock_remote_image_ctx, false);
 
   // wake-up replayer
   update_watch_ctx->handle_notify();
@@ -3196,8 +3299,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStatePendingShutdown) {
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
@@ -3283,8 +3386,8 @@ TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStateErrorPendingShutdow
 
   // sync snap1
   expect_load_image_meta(mock_image_meta, false, 0);
-  expect_is_refresh_required(mock_local_image_ctx, false);
   expect_is_refresh_required(mock_remote_image_ctx, false);
+  expect_is_refresh_required(mock_local_image_ctx, false);
   MockSnapshotCopyRequest mock_snapshot_copy_request;
   expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
                        0);
index 575eb8534fa38965b8270a1ac9bca5eb7de29446..8c9a69348509d4700ebe32403ee1ac3c5138e51b 100644 (file)
@@ -58,7 +58,10 @@ void PrepareReplayRequest<I>::handle_load_local_image_meta(int r) {
     return;
   }
 
-  *m_resync_requested = m_state_builder->local_image_meta->resync_requested;
+  if (r >= 0 && m_state_builder->local_image_meta->resync_requested &&
+      m_state_builder->is_remote_primary()) {
+    *m_resync_requested = true;
+  }
   finish(0);
 }
 
index 645c0616f8bce8e3035592dc983a70de0f80f3f0..20fe59ebdf7d5741c87fcb9ecf9c89537294ad15 100644 (file)
@@ -336,6 +336,22 @@ bool Replayer<I>::get_replay_status(std::string* description,
   return true;
 }
 
+template <typename I>
+bool Replayer<I>::is_remote_primary() {
+  auto remote_image_ctx = m_state_builder->remote_image_ctx;
+  std::shared_lock image_locker{remote_image_ctx->image_lock};
+  for (auto snap_info_it = remote_image_ctx->snap_info.rbegin();
+       snap_info_it != remote_image_ctx->snap_info.rend(); ++snap_info_it) {
+    const auto& snap_ns = snap_info_it->second.snap_namespace;
+    auto mirror_ns = std::get_if<
+      cls::rbd::MirrorSnapshotNamespace>(&snap_ns);
+    if (mirror_ns != nullptr) {
+      return mirror_ns->state == cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY;
+    }
+  }
+  return false;
+}
+
 template <typename I>
 void Replayer<I>::load_local_image_meta() {
   dout(10) << dendl;
@@ -380,46 +396,47 @@ void Replayer<I>::handle_load_local_image_meta(int r) {
     return;
   }
 
-  if (r >= 0 && m_state_builder->local_image_meta->resync_requested) {
-    m_resync_requested = true;
-
-    dout(10) << "local image resync requested" << dendl;
-    handle_replay_complete(0, "resync requested");
-    return;
-  }
-
-  refresh_local_image();
+  refresh_remote_image();
 }
 
 template <typename I>
-void Replayer<I>::refresh_local_image() {
-  if (!m_state_builder->local_image_ctx->state->is_refresh_required()) {
-    refresh_remote_image();
+void Replayer<I>::refresh_remote_image() {
+  if (!m_state_builder->remote_image_ctx->state->is_refresh_required()) {
+    refresh_local_image();
     return;
   }
 
   dout(10) << dendl;
   auto ctx = create_context_callback<
-    Replayer<I>, &Replayer<I>::handle_refresh_local_image>(this);
-  m_state_builder->local_image_ctx->state->refresh(ctx);
+    Replayer<I>, &Replayer<I>::handle_refresh_remote_image>(this);
+  m_state_builder->remote_image_ctx->state->refresh(ctx);
 }
 
 template <typename I>
-void Replayer<I>::handle_refresh_local_image(int r) {
+void Replayer<I>::handle_refresh_remote_image(int r) {
   dout(10) << "r=" << r << dendl;
 
   if (r < 0) {
-    derr << "failed to refresh local image: " << cpp_strerror(r) << dendl;
-    handle_replay_complete(r, "failed to refresh local image");
+    derr << "failed to refresh remote image: " << cpp_strerror(r) << dendl;
+    handle_replay_complete(r, "failed to refresh remote image");
     return;
   }
 
-  refresh_remote_image();
+  refresh_local_image();
 }
 
 template <typename I>
-void Replayer<I>::refresh_remote_image() {
-  if (!m_state_builder->remote_image_ctx->state->is_refresh_required()) {
+void Replayer<I>::refresh_local_image() {
+  if (m_state_builder->local_image_meta->resync_requested &&
+      is_remote_primary()) {
+    std::unique_lock locker{m_lock};
+    m_resync_requested = true;
+
+    dout(10) << "local image resync requested" << dendl;
+    handle_replay_complete(&locker, 0, "resync requested");
+    return;
+  }
+  if (!m_state_builder->local_image_ctx->state->is_refresh_required()) {
     std::unique_lock locker{m_lock};
     scan_local_mirror_snapshots(&locker);
     return;
@@ -427,17 +444,17 @@ void Replayer<I>::refresh_remote_image() {
 
   dout(10) << dendl;
   auto ctx = create_context_callback<
-    Replayer<I>, &Replayer<I>::handle_refresh_remote_image>(this);
-  m_state_builder->remote_image_ctx->state->refresh(ctx);
+    Replayer<I>, &Replayer<I>::handle_refresh_local_image>(this);
+  m_state_builder->local_image_ctx->state->refresh(ctx);
 }
 
 template <typename I>
-void Replayer<I>::handle_refresh_remote_image(int r) {
+void Replayer<I>::handle_refresh_local_image(int r) {
   dout(10) << "r=" << r << dendl;
 
   if (r < 0) {
-    derr << "failed to refresh remote image: " << cpp_strerror(r) << dendl;
-    handle_replay_complete(r, "failed to refresh remote image");
+    derr << "failed to refresh local image: " << cpp_strerror(r) << dendl;
+    handle_replay_complete(r, "failed to refresh local image");
     return;
   }
 
index 17d45f6bc802d693f42835bd6a7a5745f02c7076..6a4be2f8973d31d0d3819c090f8dc44f4bebf9a9 100644 (file)
@@ -115,10 +115,10 @@ private:
    * LOAD_LOCAL_IMAGE_META <----------------------------\
    *    |                                               |
    *    v (skip if not needed)                          |
-   * REFRESH_LOCAL_IMAGE                                |
+   * REFRESH_REMOTE_IMAGE                               |
    *    |                                               |
    *    v (skip if not needed)                          |
-   * REFRESH_REMOTE_IMAGE                               |
+   * REFRESH_LOCAL_IMAGE                                |
    *    |                                               |
    *    | (unused non-primary snapshot)                 |
    *    |\--------------> PRUNE_NON_PRIMARY_SNAPSHOT---/|
@@ -257,6 +257,8 @@ private:
 
   PerfCounters *m_perf_counters = nullptr;
 
+  bool is_remote_primary();
+
   void load_local_image_meta();
   void handle_load_local_image_meta(int r);