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

sk0x50 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 47cfe883c23 IGNITE-25859 Implement pessimistic case of create zone on 
unstable topology  (#6430)
47cfe883c23 is described below

commit 47cfe883c230454abd9294607eaac1b9da52ae5d
Author: Mikhail Efremov <[email protected]>
AuthorDate: Mon Aug 25 15:16:32 2025 +0600

    IGNITE-25859 Implement pessimistic case of create zone on unstable topology 
 (#6430)
---
 modules/sql-engine/build.gradle                    |  1 +
 .../internal/sql/api/ItSqlCreateZoneTest.java      | 55 ++++++++++++++++++++++
 .../ddl/ClusterWideStorageProfileValidator.java    | 41 +++++++++++++---
 .../DistributionZoneSqlToCommandConverterTest.java |  3 ++
 4 files changed, 93 insertions(+), 7 deletions(-)

diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index e7ea8ed53ee..0f9d3efad2a 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -124,6 +124,7 @@ dependencies {
     integrationTestImplementation libs.netty.handler
     integrationTestImplementation project(':ignite-api')
     integrationTestImplementation project(':ignite-metastorage')
+    integrationTestImplementation project(':ignite-raft')
     integrationTestImplementation project(':ignite-catalog')
     integrationTestImplementation project(':ignite-transactions')
     integrationTestImplementation project(':ignite-storage-api')
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
index b3cf44ad20e..86151283230 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
@@ -21,13 +21,21 @@ import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIMEM_
 import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME;
 import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_ROCKSDB_PROFILE_NAME;
 import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_TEST_PROFILE_NAME;
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCode;
+import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.List;
 import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.cluster.management.CmgGroupId;
+import org.apache.ignite.raft.jraft.rpc.RpcRequests.AppendEntriesRequest;
 import org.apache.ignite.sql.SqlException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
@@ -71,6 +79,53 @@ class ItSqlCreateZoneTest extends 
ClusterPerTestIntegrationTest {
         assertDoesNotThrow(() -> createZoneQuery(0, EXTRA_PROFILE_NAME));
     }
 
+    @Test
+    void 
testCreateZoneSucceedWithCorrectStorageProfileOnDifferentNodeWithDistributedLogicalTopologyUpdate()
 throws InterruptedException {
+        // Node 0 is CMG leader and Node 1 is a laggy query executor.
+        IgniteImpl node0 = unwrapIgniteImpl(node(0));
+        IgniteImpl node1 = unwrapIgniteImpl(cluster.startNode(1));
+
+        assertTrue(waitForCondition(
+                () -> 
node1.logicalTopologyService().localLogicalTopology().nodes().size() == 2,
+                10_000
+        ));
+
+        // Assert that we can't create the zone without a node with extra 
profile.
+        assertThrowsWithCause(
+                () -> createZoneQuery(1, EXTRA_PROFILE_NAME),
+                SqlException.class,
+                "Some storage profiles don't exist [missedProfileNames=[" + 
EXTRA_PROFILE_NAME + "]]."
+        );
+
+        // Node 1 won't see node 2 joined with extra profile because node 0 is 
CMG leader and all CMG-related RAFT-replicated messages to
+        // node 1 will be dropped after the code below.
+        node0.dropMessages((recipient, msg) -> msg instanceof 
AppendEntriesRequest
+                && ((AppendEntriesRequest) 
msg).groupId().equals(CmgGroupId.INSTANCE.toString())
+                && node1.name().equals(recipient));
+
+        // Then start node 2 with the desired extra profile.
+        cluster.startNode(2, NODE_BOOTSTRAP_CFG_TEMPLATE_WITH_EXTRA_PROFILE);
+
+        // Check that Node 0 and 2 will see all three nodes in local logical 
topologies.
+        assertTrue(waitForCondition(
+                () -> 
unwrapIgniteImpl(node(0)).logicalTopologyService().localLogicalTopology().nodes().size()
 == 3,
+                10_000
+        ));
+
+        assertTrue(waitForCondition(
+                () -> 
unwrapIgniteImpl(node(2)).logicalTopologyService().localLogicalTopology().nodes().size()
 == 3,
+                10_000
+        ));
+
+        // And we expect that node 1 won't see node 2 in its local logical 
topology.
+        assertEquals(2, 
node1.logicalTopologyService().localLogicalTopology().nodes().size());
+
+        // But still we're able to create zone with extra profile on node 2 
because node 1 will try to ask CMG leader (node 0) directly over
+        // the network for its up-to-date leader's local logical topology and 
check this snapshot's storage profiles that should
+        // extra profile because 2nd node was accepted to cluster by node 0 
because it's the only CMG group voting member.
+        assertDoesNotThrow(() -> createZoneQuery(1, EXTRA_PROFILE_NAME));
+    }
+
     @Test
     void testCreateZoneFailedWithoutCorrectStorageProfileInCluster() {
         assertThrowsWithCode(
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
index 367dd230ad7..221615a8801 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.sql.engine.prepare.ddl;
 
-import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.stream.Collectors.toSet;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
+import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
 
 import java.util.Collection;
@@ -49,14 +51,39 @@ public class ClusterWideStorageProfileValidator implements 
StorageProfileValidat
                 localLogicalTopologySnapshot
         );
 
-        if (!missedStorageProfileNames.isEmpty()) {
-            throw new SqlException(STMT_VALIDATION_ERR, format(
-                    "Some storage profiles don't exist 
[missedProfileNames={}].",
-                    missedStorageProfileNames
-            ));
+        if (missedStorageProfileNames.isEmpty()) {
+            return nullCompletedFuture();
         }
 
-        return completedFuture(null);
+        return logicalTopologyService.logicalTopologyOnLeader()
+                    .thenApply(topologySnapshot -> 
findStorageProfileNotPresentedInLogicalTopologySnapshot(
+                            storageProfiles,
+                            topologySnapshot
+                    )).handle((missedProfileNames, e) -> {
+                        if (e != null) {
+                            String msg = format(
+                                    "Storage profiles {} don't exist in local 
topology snapshot with profiles [{}], "
+                                            + "and distributed refresh 
failed.",
+                                    missedStorageProfileNames,
+                                    localLogicalTopologySnapshot
+                                            .nodes()
+                                            .stream()
+                                            .map(LogicalNode::storageProfiles)
+                                            .collect(toSet())
+                            );
+
+                            throw new SqlException(INTERNAL_ERR, msg, e);
+                        }
+
+                        if (!missedProfileNames.isEmpty()) {
+                            throw new SqlException(STMT_VALIDATION_ERR, format(
+                                    "Some storage profiles don't exist 
[missedProfileNames={}].",
+                                    missedProfileNames
+                            ));
+                        }
+
+                        return null;
+                    });
     }
 
     private static Set<String> 
findStorageProfileNotPresentedInLogicalTopologySnapshot(
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
index 96896c4be52..f88b632bb1c 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine.prepare.ddl;
 
+import static java.util.concurrent.CompletableFuture.completedFuture;
 import static 
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
 import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.INFINITE_TIMER_VALUE;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
@@ -113,6 +114,8 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
 
         
when(logicalTopologyService.localLogicalTopology()).thenReturn(defaultLogicalTopologySnapshot);
 
+        
when(logicalTopologyService.logicalTopologyOnLeader()).thenReturn(completedFuture(defaultLogicalTopologySnapshot));
+
         converter = new DdlSqlToCommandConverter(new 
ClusterWideStorageProfileValidator(logicalTopologyService));
 
         assertThat(ZoneOptionEnum.values().length, is(NUMERIC_OPTIONS.size() + 
STRING_OPTIONS.size()));

Reply via email to