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();