This is an automated email from the ASF dual-hosted git repository.

rpuch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new c40c10ab33 IGNITE-23768 Prohibit migration from repaired cluster to 
old one (#4785)
c40c10ab33 is described below

commit c40c10ab33dac4e74eb6c8d8afcaab81b993ad05
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Mon Nov 25 21:41:17 2024 +0400

    IGNITE-23768 Prohibit migration from repaired cluster to old one (#4785)
---
 .../system/SystemDisasterRecoveryManagerImpl.java  | 36 +++++++++++++++++++++-
 .../SystemDisasterRecoveryManagerImplTest.java     | 35 +++++++++++++++++++++
 2 files changed, 70 insertions(+), 1 deletion(-)

diff --git 
a/modules/system-disaster-recovery/src/main/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImpl.java
 
b/modules/system-disaster-recovery/src/main/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImpl.java
index a4f9fcf78c..5b85cfe011 100644
--- 
a/modules/system-disaster-recovery/src/main/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImpl.java
+++ 
b/modules/system-disaster-recovery/src/main/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImpl.java
@@ -432,8 +432,21 @@ public class SystemDisasterRecoveryManagerImpl implements 
SystemDisasterRecovery
 
     @Override
     public CompletableFuture<Void> migrate(ClusterState targetClusterState) {
+        try {
+            return doMigrate(targetClusterState);
+        } catch (MigrateException e) {
+            return failedFuture(e);
+        }
+    }
+
+    private CompletableFuture<Void> doMigrate(ClusterState targetClusterState) 
{
         if (targetClusterState.formerClusterIds() == null) {
-            return failedFuture(new MigrateException("Migration can only 
happen using cluster state from a node that saw a cluster reset"));
+            throw new MigrateException("Migration can only happen using 
cluster state from a node that saw a cluster reset");
+        }
+
+        ClusterState clusterState = ensureClusterStateIsPresent();
+        if (isDescendantOrSame(clusterState, targetClusterState)) {
+            throw new MigrateException("Migration can only happen from old 
cluster to new one, not the other way around");
         }
 
         Collection<ClusterNode> nodesInTopology = topologyService.allMembers();
@@ -452,6 +465,27 @@ public class SystemDisasterRecoveryManagerImpl implements 
SystemDisasterRecovery
                 }, restartExecutor);
     }
 
+    private static boolean isDescendantOrSame(ClusterState 
potencialDescendant, ClusterState potentialAncestor) {
+        List<UUID> descendantLineage = 
extractClusterIdsLineage(potencialDescendant);
+        List<UUID> ancestorLineage = 
extractClusterIdsLineage(potentialAncestor);
+
+        return descendantLineage.size() >= ancestorLineage.size()
+                && descendantLineage.subList(0, 
ancestorLineage.size()).equals(ancestorLineage);
+    }
+
+    private static List<UUID> extractClusterIdsLineage(ClusterState state) {
+        List<UUID> lineage = new ArrayList<>();
+
+        List<UUID> formerClusterIds = state.formerClusterIds();
+        if (formerClusterIds != null) {
+            lineage.addAll(formerClusterIds);
+        }
+
+        lineage.add(state.clusterTag().clusterId());
+
+        return lineage;
+    }
+
     private ResetClusterMessage 
buildResetClusterMessageForMigrate(ClusterState clusterState) {
         List<UUID> formerClusterIds = clusterState.formerClusterIds();
         assert formerClusterIds != null : "formerClusterIds is null, but it 
must never be here as it's from a node that saw a CMG reset; "
diff --git 
a/modules/system-disaster-recovery/src/test/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImplTest.java
 
b/modules/system-disaster-recovery/src/test/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImplTest.java
index 9f0abc84cf..daf2a8bd1c 100644
--- 
a/modules/system-disaster-recovery/src/test/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImplTest.java
+++ 
b/modules/system-disaster-recovery/src/test/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryManagerImplTest.java
@@ -22,6 +22,7 @@ import static 
java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.CompletableFuture.failedFuture;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toSet;
+import static 
org.apache.ignite.internal.cluster.management.ClusterTag.clusterTag;
 import static 
org.apache.ignite.internal.cluster.management.ClusterTag.randomClusterTag;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static 
org.apache.ignite.internal.testframework.asserts.CompletableFutureAssert.assertWillThrow;
@@ -589,6 +590,8 @@ class SystemDisasterRecoveryManagerImplTest extends 
BaseIgniteAbstractTest {
 
     @Test
     void migrateSendsMessagesToAllNodes() {
+        putClusterState();
+
         ClusterState newState = newClusterState();
 
         ArgumentCaptor<ResetClusterMessage> messageCaptor = 
ArgumentCaptor.forClass(ResetClusterMessage.class);
@@ -639,6 +642,8 @@ class SystemDisasterRecoveryManagerImplTest extends 
BaseIgniteAbstractTest {
 
     @Test
     void migrateInitiatesRestart() {
+        putClusterState();
+
         ClusterState newState = newClusterState();
 
         when(topologyService.allMembers()).thenReturn(List.of(thisNode, node2, 
node3));
@@ -650,6 +655,36 @@ class SystemDisasterRecoveryManagerImplTest extends 
BaseIgniteAbstractTest {
         verify(restarter).initiateRestart();
     }
 
+    @Test
+    void migrationInWrongDirectionIsProhibited() {
+        UUID clusterId1 = randomUUID();
+        UUID clusterId2 = randomUUID();
+        UUID clusterId3 = randomUUID();
+
+        ClusterState oldClusterState = cmgMessagesFactory.clusterState()
+                .cmgNodes(Set.of(thisNodeName))
+                .metaStorageNodes(Set.of(thisNodeName))
+                .version(IgniteProductVersion.CURRENT_VERSION.toString())
+                .clusterTag(clusterTag(cmgMessagesFactory, CLUSTER_NAME, 
clusterId2))
+                .initialClusterConfiguration(INITIAL_CONFIGURATION)
+                .formerClusterIds(List.of(clusterId1))
+                .build();
+
+        ClusterState newClusterState = cmgMessagesFactory.clusterState()
+                .cmgNodes(Set.of(thisNodeName))
+                .metaStorageNodes(Set.of(thisNodeName))
+                .version(IgniteProductVersion.CURRENT_VERSION.toString())
+                .clusterTag(clusterTag(cmgMessagesFactory, CLUSTER_NAME, 
clusterId3))
+                .initialClusterConfiguration(INITIAL_CONFIGURATION)
+                .formerClusterIds(List.of(clusterId1, clusterId2))
+                .build();
+
+        manager.saveClusterState(newClusterState);
+
+        MigrateException ex = 
assertWillThrow(manager.migrate(oldClusterState), MigrateException.class);
+        assertThat(ex.getMessage(), is("Migration can only happen from old 
cluster to new one, not the other way around"));
+    }
+
     @ParameterizedTest
     @ValueSource(ints = {0, -1})
     void resetClusterWithMgRequiresPositiveMgReplicationFactor(int 
metastorageReplicationFactor) {

Reply via email to