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 2d9f5213c01 IGNITE-26318 Add a mechanism to differentiate between user 
input errors and system errors (#6549)
2d9f5213c01 is described below

commit 2d9f5213c014f06322f9c9b4e75645c21e39cde1
Author: Aditya Mukhopadhyay <[email protected]>
AuthorDate: Wed Sep 17 11:39:36 2025 +0530

    IGNITE-26318 Add a mechanism to differentiate between user input errors and 
system errors (#6549)
---
 .../cluster/management/ItClusterManagerTest.java   | 25 +++++++++++++++++++++
 ...java => InvalidNodeConfigurationException.java} | 12 +++++-----
 .../management/raft/CmgRaftGroupListener.java      | 11 ++++++---
 .../cluster/management/raft/CmgRaftService.java    | 14 ++++++++++--
 .../management/raft/JoinDeniedException.java       |  6 +++++
 .../cluster/management/raft/ValidationManager.java |  2 +-
 .../cluster/management/raft/ValidationResult.java  | 26 +++++++++++++++++-----
 .../raft/responses/ValidationErrorResponse.java    | 14 +++++++++++-
 .../internal/cluster/management/MockNode.java      |  8 ++++++-
 .../app/ItEnabledColocationHomogeneityTest.java    |  4 ++--
 .../org/apache/ignite/internal/app/IgniteImpl.java | 11 +++++++--
 11 files changed, 111 insertions(+), 22 deletions(-)

diff --git 
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/ItClusterManagerTest.java
 
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/ItClusterManagerTest.java
index 85d1859c15c..a5cea4cc7af 100644
--- 
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/ItClusterManagerTest.java
+++ 
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/ItClusterManagerTest.java
@@ -50,6 +50,7 @@ import java.util.function.Consumer;
 import org.apache.ignite.internal.cluster.management.raft.JoinDeniedException;
 import 
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyImpl;
 import org.apache.ignite.internal.cluster.management.topology.api.LogicalNode;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
 import org.apache.ignite.internal.lang.NodeStoppingException;
 import org.apache.ignite.internal.network.DefaultMessagingService;
 import org.apache.ignite.internal.network.InternalClusterNode;
@@ -767,4 +768,28 @@ public class ItClusterManagerTest extends 
BaseItClusterManagementTest {
                         + ", recipientColocationMode=" + !colocationEnabled + 
"]."
         );
     }
+
+    @Test
+    void testJoinFailsOnDifferentEnabledColocationModesWithinCmgNodes() throws 
Exception {
+        final boolean colocationEnabled = true;
+
+        System.setProperty(COLOCATION_FEATURE_FLAG, 
Boolean.toString(colocationEnabled));
+        startCluster(1);
+
+        String[] cmgNodes = clusterNodeNames();
+        initCluster(cmgNodes, cmgNodes);
+
+        System.setProperty(COLOCATION_FEATURE_FLAG, 
Boolean.toString(!colocationEnabled));
+
+        MockNode secondNode = addNodeToCluster(cluster);
+
+        secondNode.startAndJoin();
+
+        assertThrowsWithCause(
+                () -> secondNode.startFuture().get(),
+                InvalidNodeConfigurationException.class,
+                IgniteStringFormatter.format("Colocation enabled mode does not 
match. Joining node colocation mode is: {},"
+                        + " cluster colocation mode is: {}", 
!colocationEnabled, colocationEnabled)
+        );
+    }
 }
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InvalidNodeConfigurationException.java
similarity index 68%
copy from 
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
copy to 
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InvalidNodeConfigurationException.java
index 51631415a42..4ec8ac3b613 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InvalidNodeConfigurationException.java
@@ -15,15 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cluster.management.raft;
+package org.apache.ignite.internal.cluster.management;
+
+import static 
org.apache.ignite.lang.ErrorGroups.CommonConfiguration.CONFIGURATION_VALIDATION_ERR;
 
 import org.apache.ignite.internal.lang.IgniteInternalException;
 
 /**
- * Exception thrown if a node was unable to pass the validation step.
+ * Exception representing invalid node configuration.
  */
-public class JoinDeniedException extends IgniteInternalException {
-    public JoinDeniedException(String msg) {
-        super(msg);
+public class InvalidNodeConfigurationException extends IgniteInternalException 
{
+    public InvalidNodeConfigurationException(String message) {
+        super(CONFIGURATION_VALIDATION_ERR, message);
     }
 }
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftGroupListener.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftGroupListener.java
index 25303163003..b9b96d93082 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftGroupListener.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftGroupListener.java
@@ -193,7 +193,9 @@ public class CmgRaftGroupListener implements 
RaftGroupListener {
                 } else if (command instanceof JoinRequestCommand) {
                     ValidationResult response = 
validateNode((JoinRequestCommand) command);
 
-                    clo.result(response.isValid() ? null : new 
ValidationErrorResponse(response.errorDescription()));
+                    clo.result(response.isValid()
+                            ? null
+                            : new 
ValidationErrorResponse(response.errorDescription(), 
response.isInvalidNodeConfig()));
                 } else if (command instanceof JoinReadyCommand) {
                     ValidationResult response = 
completeValidation((JoinReadyCommand) command);
 
@@ -202,7 +204,9 @@ public class CmgRaftGroupListener implements 
RaftGroupListener {
                         onLogicalTopologyChanged.accept(clo.term());
                     }
 
-                    clo.result(response.isValid() ? null : new 
ValidationErrorResponse(response.errorDescription()));
+                    clo.result(response.isValid()
+                            ? null
+                            : new 
ValidationErrorResponse(response.errorDescription(), 
response.isInvalidNodeConfig()));
                 } else if (command instanceof NodesLeaveCommand) {
                     removeNodesFromLogicalTopology((NodesLeaveCommand) 
command);
 
@@ -246,7 +250,8 @@ public class CmgRaftGroupListener implements 
RaftGroupListener {
                     command.clusterState()
             );
 
-            return validationResult.isValid() ? state : new 
ValidationErrorResponse(validationResult.errorDescription());
+            return validationResult.isValid() ? state
+                    : new 
ValidationErrorResponse(validationResult.errorDescription(), 
validationResult.isInvalidNodeConfig());
         }
     }
 
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftService.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftService.java
index 8b8e5d28009..b054658c3f8 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftService.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/CmgRaftService.java
@@ -30,6 +30,7 @@ import org.apache.ignite.internal.close.ManuallyCloseable;
 import 
org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.ClusterState;
 import org.apache.ignite.internal.cluster.management.ClusterTag;
+import 
org.apache.ignite.internal.cluster.management.InvalidNodeConfigurationException;
 import org.apache.ignite.internal.cluster.management.MetaStorageInfo;
 import org.apache.ignite.internal.cluster.management.NodeAttributes;
 import 
org.apache.ignite.internal.cluster.management.network.messages.CmgMessagesFactory;
@@ -148,10 +149,19 @@ public class CmgRaftService implements ManuallyCloseable {
         return raftService.run(command, RaftCommandRunner.NO_TIMEOUT)
                 .thenAccept(response -> {
                     if (response instanceof ValidationErrorResponse) {
-                        throw new 
JoinDeniedException(((ValidationErrorResponse) response).reason());
+                        var validationErrorResponse = 
(ValidationErrorResponse) response;
+
+                        if (validationErrorResponse.isInvalidNodeConfig()) {
+                            var invalidNodeConfigurationException = new 
InvalidNodeConfigurationException(validationErrorResponse.reason());
+
+                            // TODO: IGNITE-26433 Use dedicated error code for 
JoinDeniedException
+                            throw new JoinDeniedException("JoinRequest command 
failed", invalidNodeConfigurationException);
+                        } else {
+                            throw new 
JoinDeniedException(validationErrorResponse.reason());
+                        }
                     } else if (response != null) {
                         throw new IgniteInternalException("Unexpected 
response: " + response);
-                    }  else {
+                    } else {
                         LOG.info("JoinRequest command executed successfully");
                     }
                 });
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
index 51631415a42..3a8619a97c8 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/JoinDeniedException.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.cluster.management.raft;
 
+import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
+
 import org.apache.ignite.internal.lang.IgniteInternalException;
 
 /**
@@ -26,4 +28,8 @@ public class JoinDeniedException extends 
IgniteInternalException {
     public JoinDeniedException(String msg) {
         super(msg);
     }
+
+    public JoinDeniedException(String msg, Throwable cause) {
+        super(INTERNAL_ERR, msg, cause);
+    }
 }
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationManager.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationManager.java
index b27293bf741..3b7b125d725 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationManager.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationManager.java
@@ -114,7 +114,7 @@ public class ValidationManager {
                     clusterTag, state.clusterTag()
             ));
         } else if (!isColocationEnabledMatched(isColocationEnabled(node))) {
-            return ValidationResult.errorResult(String.format(
+            return ValidationResult.configErrorResult(String.format(
                     "Colocation enabled mode does not match. Joining node 
colocation mode is: %s, cluster colocation mode is: %s",
                     isColocationEnabled(node),
                     
isColocationEnabled(logicalTopology.getLogicalTopology().nodes().iterator().next())
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationResult.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationResult.java
index 0972d0982fb..77193f99395 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationResult.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/ValidationResult.java
@@ -25,23 +25,32 @@ import org.jetbrains.annotations.Nullable;
 public class ValidationResult {
     @Nullable
     private final String errorDescription;
+    private final boolean invalidNodeConfig;
 
-    private ValidationResult(@Nullable String errorDescription) {
+    private ValidationResult(@Nullable String errorDescription, boolean 
invalidNodeConfig) {
         this.errorDescription = errorDescription;
+        this.invalidNodeConfig = invalidNodeConfig;
     }
 
     /**
      * Creates a successful validation result.
      */
-    public static ValidationResult successfulResult() {
-        return new ValidationResult(null);
+    static ValidationResult successfulResult() {
+        return new ValidationResult(null, false);
+    }
+
+    /**
+     * Creates a failed validation result with a flag denoting whether caused 
by invalid node configuration.
+     */
+    static ValidationResult configErrorResult(String errorDescription) {
+        return new ValidationResult(errorDescription, true);
     }
 
     /**
      * Creates a failed validation result.
      */
-    public static ValidationResult errorResult(String errorDescription) {
-        return new ValidationResult(errorDescription);
+    static ValidationResult errorResult(String errorDescription) {
+        return new ValidationResult(errorDescription, false);
     }
 
     /**
@@ -59,4 +68,11 @@ public class ValidationResult {
 
         return errorDescription;
     }
+
+    /**
+     * Returns flag denoting whether erroneous result is caused by invalid 
node configuration.
+     */
+    boolean isInvalidNodeConfig() {
+        return invalidNodeConfig;
+    }
 }
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/responses/ValidationErrorResponse.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/responses/ValidationErrorResponse.java
index 816e08f4b97..b3d1154230d 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/responses/ValidationErrorResponse.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/raft/responses/ValidationErrorResponse.java
@@ -24,14 +24,17 @@ import java.io.Serializable;
  */
 public class ValidationErrorResponse implements Serializable {
     private final String reason;
+    private final boolean invalidNodeConfig;
 
     /**
      * Creates a new response.
      *
      * @param reason Textual representation of the reason of join rejection.
+     * @param invalidNodeConfig Flag denoting whether erroneous result is 
caused by invalid node configuration.
      */
-    public ValidationErrorResponse(String reason) {
+    public ValidationErrorResponse(String reason, boolean invalidNodeConfig) {
         this.reason = reason;
+        this.invalidNodeConfig = invalidNodeConfig;
     }
 
     /**
@@ -42,4 +45,13 @@ public class ValidationErrorResponse implements Serializable 
{
     public String reason() {
         return reason;
     }
+
+    /**
+     * Flag marking this error as being caused by invalid node configuration.
+     *
+     * @return flag denoting whether erroneous result is caused by invalid 
node configuration.
+     */
+    public boolean isInvalidNodeConfig() {
+        return invalidNodeConfig;
+    }
 }
diff --git 
a/modules/cluster-management/src/testFixtures/java/org/apache/ignite/internal/cluster/management/MockNode.java
 
b/modules/cluster-management/src/testFixtures/java/org/apache/ignite/internal/cluster/management/MockNode.java
index 04b412cf181..30167d227db 100644
--- 
a/modules/cluster-management/src/testFixtures/java/org/apache/ignite/internal/cluster/management/MockNode.java
+++ 
b/modules/cluster-management/src/testFixtures/java/org/apache/ignite/internal/cluster/management/MockNode.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.cluster.management;
 
 import static java.util.Collections.reverse;
+import static 
org.apache.ignite.internal.lang.IgniteSystemProperties.COLOCATION_FEATURE_FLAG;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.apache.ignite.internal.util.IgniteUtils.stopAsync;
@@ -28,6 +29,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
@@ -131,6 +133,10 @@ public class MockNode {
 
         boolean colocationEnabled = IgniteSystemProperties.colocationEnabled();
 
+        var collector = new NodeAttributesCollector(nodeAttributes, 
storageProfilesConfiguration);
+
+        collector.register(() -> Map.of(COLOCATION_FEATURE_FLAG, 
Boolean.toString(colocationEnabled)));
+
         this.clusterManager = new ClusterManagementGroupManager(
                 vaultManager,
                 new SystemDisasterRecoveryStorage(vaultManager),
@@ -144,7 +150,7 @@ public class MockNode {
                 raftManager,
                 clusterStateStorage,
                 new LogicalTopologyImpl(clusterStateStorage, failureManager),
-                new NodeAttributesCollector(nodeAttributes, 
storageProfilesConfiguration),
+                collector,
                 failureManager,
                 clusterIdHolder,
                 cmgRaftConfigurer,
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItEnabledColocationHomogeneityTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItEnabledColocationHomogeneityTest.java
index 48dfd8fa540..af9dcbd8782 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItEnabledColocationHomogeneityTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItEnabledColocationHomogeneityTest.java
@@ -21,7 +21,7 @@ import static 
org.apache.ignite.internal.lang.IgniteSystemProperties.COLOCATION_
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 
 import org.apache.ignite.internal.BaseIgniteRestartTest;
-import org.apache.ignite.internal.cluster.management.raft.JoinDeniedException;
+import 
org.apache.ignite.internal.cluster.management.InvalidNodeConfigurationException;
 import org.apache.ignite.internal.lang.IgniteStringFormatter;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -58,7 +58,7 @@ public class ItEnabledColocationHomogeneityTest extends 
BaseIgniteRestartTest {
         System.setProperty(COLOCATION_FEATURE_FLAG, 
Boolean.toString(!colocationEnabled));
         assertThrowsWithCause(
                 () -> startNode(1),
-                JoinDeniedException.class,
+                InvalidNodeConfigurationException.class,
                 IgniteStringFormatter.format("Colocation enabled mode does not 
match. Joining node colocation mode is: {},"
                         + " cluster colocation mode is: {}", 
!colocationEnabled, colocationEnabled)
         );
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index ce14609c05d..cf511826f16 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -30,6 +30,7 @@ import static 
org.apache.ignite.internal.thread.ThreadOperation.STORAGE_WRITE;
 import static org.apache.ignite.internal.util.CompletableFutures.copyStateTo;
 import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
 import static org.apache.ignite.internal.util.ExceptionUtils.extractCodeFrom;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapRootCause;
 
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
@@ -79,6 +80,7 @@ import 
org.apache.ignite.internal.cluster.management.ClusterInitializer;
 import 
org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.ClusterState;
 import org.apache.ignite.internal.cluster.management.CmgGroupId;
+import 
org.apache.ignite.internal.cluster.management.InvalidNodeConfigurationException;
 import org.apache.ignite.internal.cluster.management.NodeAttributesCollector;
 import 
org.apache.ignite.internal.cluster.management.configuration.NodeAttributesExtensionConfiguration;
 import org.apache.ignite.internal.cluster.management.raft.ClusterStateStorage;
@@ -1699,8 +1701,13 @@ public class IgniteImpl implements Ignite {
 
         var igniteException = new IgniteException(extractCodeFrom(e), errMsg, 
e);
 
-        // We log the exception as soon as possible to minimize the 
probability that it gets lost due to something like an OOM later.
-        LOG.error(errMsg, igniteException);
+        Throwable rootEx = unwrapRootCause(e);
+        if (rootEx instanceof InvalidNodeConfigurationException) {
+            LOG.error("{}. Reason: {}", errMsg,  rootEx.getMessage());
+        } else {
+            // We log the exception as soon as possible to minimize the 
probability that it gets lost due to something like an OOM later.
+            LOG.error(errMsg, igniteException);
+        }
 
         ExecutorService lifecycleExecutor = stopExecutor();
 

Reply via email to