This is an automated email from the ASF dual-hosted git repository.
sergeychugunov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new b62b7650aaf IGNITE-26891 Add serdes tests for Services with custom
exceptions (#12555)
b62b7650aaf is described below
commit b62b7650aaf3cd47e9e0acc7463209fec044c782
Author: Maksim Davydov <[email protected]>
AuthorDate: Fri Jan 16 10:51:09 2026 +0300
IGNITE-26891 Add serdes tests for Services with custom exceptions (#12555)
---
.../processors/service/ServiceDeploymentTask.java | 22 +
...dServiceDeploymentExceptionPropagationTest.java | 80 ----
.../GridServiceExceptionPropagationTest.java | 510 +++++++++++++++++++++
.../testsuites/IgniteServiceGridTestSuite.java | 4 +-
4 files changed, 534 insertions(+), 82 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
index 0d86c4d2896..db1c5c9033e 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
@@ -541,6 +541,15 @@ class ServiceDeploymentTask {
}
catch (IgniteCheckedException e) {
log.error("Failed to unmarshal deployment error.", e);
+
+ Exception ex = new IgniteCheckedException(
+ "Failed to unmarshal deployment error, see server logs
for details."
+ );
+
+ if (depErr == null)
+ depErr = ex;
+ else
+ depErr.addSuppressed(ex);
}
}
@@ -685,6 +694,19 @@ class ServiceDeploymentTask {
}
catch (IgniteCheckedException e) {
log.error("Failed to marshal deployment error, err=" + th, e);
+
+ try {
+ Exception ex = new IgniteCheckedException(
+ "Failed to marshal deployment error, see server logs
for details, err=" + th
+ );
+
+ byte[] arr = U.marshal(ctx, ex);
+
+ errorsBytes.add(arr);
+ }
+ catch (IgniteCheckedException ex) {
+ log.error("Failed to attach deployment error information
to deployment result message", ex);
+ }
}
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceDeploymentExceptionPropagationTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceDeploymentExceptionPropagationTest.java
deleted file mode 100644
index 9982667b191..00000000000
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceDeploymentExceptionPropagationTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.service;
-
-import org.apache.ignite.Ignite;
-import org.apache.ignite.internal.IgniteEx;
-import org.apache.ignite.services.Service;
-import org.apache.ignite.services.ServiceContext;
-import org.apache.ignite.services.ServiceDeploymentException;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
-import org.junit.Test;
-
-/** */
-public class GridServiceDeploymentExceptionPropagationTest extends
GridCommonAbstractTest {
- /** */
- @Test
- public void testExceptionPropagation() throws Exception {
- try (IgniteEx srv = startGrid("server")) {
- try (Ignite client = startClientGrid("client",
getConfiguration("client"))) {
- final String srvcName = "my-service";
-
- try {
- client.services().deployClusterSingleton(srvcName, new
ServiceImpl());
-
- fail("Deployment exception has been expected.");
- }
- catch (ServiceDeploymentException ex) {
- String errMsg = ex.getSuppressed()[0].getMessage();
-
- // Check that message contains cause node id
-
assertTrue(errMsg.contains(srv.cluster().localNode().id().toString()));
-
- // Check that message contains service name
- assertTrue(errMsg.contains(srvcName));
-
- Throwable cause = ex.getSuppressed()[0].getCause();
-
- // Check that error's cause contains users message
- assertTrue(cause.getMessage().contains("ServiceImpl init
exception"));
- }
- }
- }
- }
-
- /**
- * Simple service implementation throwing an exception on init.
- * Doesn't even try to do anything useful because what we're testing here
is failure.
- */
- private static class ServiceImpl implements Service {
- /** {@inheritDoc} */
- @Override public void cancel(ServiceContext ctx) {
- // No-op.
- }
-
- /** {@inheritDoc} */
- @Override public void init(ServiceContext ctx) throws Exception {
- throw new RuntimeException("ServiceImpl init exception");
- }
-
- /** {@inheritDoc} */
- @Override public void execute(ServiceContext ctx) throws Exception {
- // No-op.
- }
- }
-}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceExceptionPropagationTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceExceptionPropagationTest.java
new file mode 100644
index 00000000000..96179238ff2
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceExceptionPropagationTest.java
@@ -0,0 +1,510 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.service;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.services.ServiceDeploymentException;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/** */
+public class GridServiceExceptionPropagationTest extends
GridCommonAbstractTest {
+ /** */
+ private static final String BROKEN_EX_MSG = "Exception occurred on
serialization step";
+
+ /** */
+ private static final String BROKEN_EX_WRAPPER_MSG = ", see server logs for
details";
+
+ /** */
+ private static final String EX_MSG = "Exception message";
+
+ /** */
+ private static final String SERVICE_NAME = "my-service";
+
+ /** */
+ private static final ExceptionThrower SERIALIZABLE_EX_THROWER =
ExceptionThrower.serializable();
+
+ /** */
+ private static final ExceptionThrower EXTERNALIZABLE_EX_THROWER =
+ ExceptionThrower.externalizable(false, false);
+
+ /** */
+ private static final ExceptionThrower BROKEN_WRITE_EX_THROWER =
+ ExceptionThrower.externalizable(true, true);
+
+ /** */
+ private static final ExceptionThrower BROKEN_READ_EX_THROWER =
+ ExceptionThrower.externalizable(true, false);
+
+ /** */
+ private boolean isNodeInfoAvailableInExMsg = true;
+
+ /** */
+ @Test
+ public void testServiceCancelThrowsSerializableException() throws
Exception {
+ Service svc = new
ServiceWithException().withCancelException(SERIALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, true);
+ }
+
+ /** */
+ @Test
+ public void testServiceCancelThrowsExternalizableException() throws
Exception {
+ Service svc = new
ServiceWithException().withCancelException(EXTERNALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, true);
+ }
+
+ /** */
+ @Test
+ public void
testServiceCancelThrowsExternalizableExceptionWithBrokenSerialization() throws
Exception {
+ Service svc = new
ServiceWithException().withCancelException(BROKEN_WRITE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, true);
+ }
+
+ /** */
+ @Test
+ public void
testServiceCancelThrowsExternalizableExceptionWithBrokenDeserialization()
throws Exception {
+ Service svc = new
ServiceWithException().withCancelException(BROKEN_READ_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, true);
+ }
+
+ /** */
+ @Test
+ public void testServiceInitThrowsSerializableException() throws Exception {
+ Service svc = new
ServiceWithException().withInitException(SERIALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), true, false);
+ }
+
+ /** */
+ @Test
+ public void testServiceInitThrowsExternalizableException() throws
Exception {
+ Service svc = new
ServiceWithException().withInitException(EXTERNALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), true, false);
+ }
+
+ /** */
+ @Test
+ public void
testServiceInitThrowsExternalizableExceptionWithBrokenSerialization() throws
Exception {
+ Service svc = new
ServiceWithException().withInitException(BROKEN_WRITE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), true, false);
+ }
+
+ /** */
+ @Test
+ public void
testServiceInitThrowsExternalizableExceptionWithBrokenDeserialization() throws
Exception {
+ Service svc = new
ServiceWithException().withInitException(BROKEN_READ_EX_THROWER);
+
+ isNodeInfoAvailableInExMsg = false;
+
+ testExceptionPropagation(getServiceConfiguration(svc), true, false);
+
+ isNodeInfoAvailableInExMsg = true;
+ }
+
+ /** */
+ @Test
+ public void testServiceExecuteThrowsSerializableException() throws
Exception {
+ Service svc = new
ServiceWithException().withExecuteException(SERIALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, false);
+ }
+
+ /** */
+ @Test
+ public void testServiceExecuteThrowsExternalizableException() throws
Exception {
+ Service svc = new
ServiceWithException().withExecuteException(EXTERNALIZABLE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, false);
+ }
+
+ /** */
+ @Test
+ public void
testServiceExecuteThrowsExternalizableExceptionWithBrokenSerialization() throws
Exception {
+ Service svc = new
ServiceWithException().withExecuteException(BROKEN_WRITE_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, false);
+ }
+
+ /** */
+ @Test
+ public void
testServiceExecuteThrowsExternalizableExceptionWithBrokenDeserialization()
throws Exception {
+ Service svc = new
ServiceWithException().withExecuteException(BROKEN_READ_EX_THROWER);
+
+ testExceptionPropagation(getServiceConfiguration(svc), false, false);
+ }
+
+ /** */
+ private void testExceptionPropagation(
+ ServiceConfiguration srvcCfg,
+ boolean shouldThrow,
+ boolean withCancel
+ ) throws Exception {
+ try (IgniteEx srv = startGrid(0); IgniteEx client =
startClientGrid(1)) {
+ try {
+ client.services().deploy(srvcCfg);
+
+ if (withCancel)
+ client.services().cancel(SERVICE_NAME);
+
+ if (shouldThrow)
+ fail("An expected exception has not been thrown.");
+ }
+ catch (ServiceDeploymentException ex) {
+ assertTrue(shouldThrow);
+
+ String errMsg = ex.getSuppressed()[0].getMessage();
+
+ if (isNodeInfoAvailableInExMsg) {
+
assertTrue(errMsg.contains(srv.cluster().localNode().id().toString()));
+ assertTrue(errMsg.contains(SERVICE_NAME));
+ }
+
+ Throwable cause = ex.getSuppressed()[0].getCause();
+
+ if (cause == null)
+ assertTrue(errMsg.contains(BROKEN_EX_WRAPPER_MSG));
+ else
+ assertTrue(cause.getMessage().contains(EX_MSG));
+ }
+ catch (Throwable e) {
+ throw new AssertionError("Unexpected exception has been
thrown.", e);
+ }
+ }
+ }
+
+ /** */
+ private ServiceConfiguration getServiceConfiguration(Service svc) {
+ return new ServiceConfiguration()
+ .setService(svc)
+ .setName(SERVICE_NAME)
+ .setMaxPerNodeCount(1)
+ .setNodeFilter(new ClientNodeFilter());
+ }
+
+ /**
+ *
+ */
+ private static class ClientNodeFilter implements
IgnitePredicate<ClusterNode> {
+ /** {@inheritDoc} */
+ @Override public boolean apply(ClusterNode node) {
+ return !node.isClient();
+ }
+ }
+
+ /**
+ * A simple {@link Service} implementation that intentionally throws
exceptions during
+ * specific {@link Service} lifecycle phases for testing purposes.
+ *
+ * <p>This service can be configured to:
+ * <ul>
+ * <li>Throw an {@link Exception} during either {@link #init()} or
{@link #execute()}.</li>
+ * <li>Throw a {@link RuntimeException} during {@link #cancel()}.</li>
+ * </ul>
+ */
+ private static class ServiceWithException implements Service {
+ /** */
+ private boolean throwOnCancel;
+
+ /** */
+ private boolean throwOnInit;
+
+ /** */
+ private boolean throwOnExec;
+
+ /** */
+ private ExceptionThrower exThrower;
+
+ /** {@inheritDoc} */
+ @Override public void cancel() {
+ if (throwOnCancel)
+ exThrower.throwRuntimeException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init() throws Exception {
+ if (throwOnInit)
+ exThrower.throwException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void execute() throws Exception {
+ if (throwOnExec)
+ exThrower.throwException();
+ }
+
+ /** */
+ public Service withCancelException(ExceptionThrower exThrower) {
+ this.exThrower = exThrower;
+
+ throwOnCancel = true;
+
+ return this;
+ }
+
+ /** */
+ public Service withInitException(ExceptionThrower exThrower) {
+ this.exThrower = exThrower;
+
+ throwOnInit = true;
+
+ return this;
+ }
+
+ /** */
+ public Service withExecuteException(ExceptionThrower exThrower) {
+ this.exThrower = exThrower;
+
+ throwOnExec = true;
+
+ return this;
+ }
+ }
+
+ /** */
+ private static class ExceptionThrower implements Serializable {
+ /** */
+ private final boolean isExternalizable;
+
+ /** */
+ private final boolean isBroken;
+
+ /** */
+ private final boolean isWriteBroken;
+
+ /** */
+ private ExceptionThrower(boolean isExternalizable, boolean isBroken,
boolean isWriteBroken) {
+ this.isExternalizable = isExternalizable;
+ this.isBroken = isBroken;
+ this.isWriteBroken = isWriteBroken;
+ }
+
+ /**
+ * @return Serializable exception configuration
+ */
+ static ExceptionThrower serializable() {
+ return new ExceptionThrower(false, false, false);
+ }
+
+ /**
+ * @return Externalizable exception configuration
+ */
+ static ExceptionThrower externalizable(boolean isBroken, boolean
isWriteBroken) {
+ return new ExceptionThrower(true, isBroken, isWriteBroken);
+ }
+
+ /** */
+ public void throwException() throws Exception {
+ if (!isExternalizable)
+ throw new Exception(EX_MSG);
+
+ if (!isBroken)
+ throw new ExternalizableException(EX_MSG);
+
+ throw new BrokenExternalizableException(EX_MSG, isWriteBroken);
+ }
+
+ /** */
+ public void throwRuntimeException() {
+ if (!isExternalizable)
+ throw new RuntimeException(EX_MSG);
+
+ if (!isBroken)
+ throw new ExternalizableRuntimeException(EX_MSG);
+
+ throw new BrokenExternalizableRuntimeException(EX_MSG,
isWriteBroken);
+ }
+ }
+
+ /** Custom {@link Externalizable} Exception */
+ public static class ExternalizableException extends Exception implements
Externalizable {
+ /** */
+ public ExternalizableException() {
+ // No-op.
+ }
+
+ /** */
+ public ExternalizableException(String msg) {
+ super(msg);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws
IOException {
+ out.writeObject(getMessage());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ String msg = (String)in.readObject();
+
+ try {
+ Field detailMsg =
Throwable.class.getDeclaredField("detailMessage");
+
+ detailMsg.setAccessible(true);
+ detailMsg.set(this, msg);
+ }
+ catch (Exception ignored) {
+ // No-op.
+ }
+ }
+ }
+
+ /** Custom {@link Externalizable} Exception with broken serialization. */
+ public static class BrokenExternalizableException extends
ExternalizableException {
+ /** */
+ private boolean isWriteBroken;
+
+ /** */
+ public BrokenExternalizableException() {
+ // No-op.
+ }
+
+ /** */
+ public BrokenExternalizableException(String msg, boolean
isWriteBroken) {
+ super(msg);
+
+ this.isWriteBroken = isWriteBroken;
+ }
+
+ /** */
+ public boolean isWriteBroken() {
+ return isWriteBroken;
+ }
+
+ /** */
+ public void setWriteBroken(boolean writeBroken) {
+ isWriteBroken = writeBroken;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws
IOException {
+ if (isWriteBroken())
+ throw new RuntimeException(BROKEN_EX_MSG);
+
+ out.writeBoolean(isWriteBroken());
+
+ super.writeExternal(out);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ setWriteBroken(in.readBoolean());
+
+ if (!isWriteBroken())
+ throw new RuntimeException(BROKEN_EX_MSG);
+
+ super.readExternal(in);
+ }
+ }
+
+ /** Custom externalizable {@link RuntimeException} Exception */
+ public static class ExternalizableRuntimeException extends
RuntimeException implements Externalizable {
+ /** */
+ public ExternalizableRuntimeException() {
+ // No-op.
+ }
+
+ /** */
+ public ExternalizableRuntimeException(String msg) {
+ super(msg);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws
IOException {
+ out.writeObject(getMessage());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ String msg = (String)in.readObject();
+
+ try {
+ Field detailMsg =
Throwable.class.getDeclaredField("detailMessage");
+
+ detailMsg.setAccessible(true);
+ detailMsg.set(this, msg);
+ }
+ catch (Exception ignored) {
+ // No-op.
+ }
+ }
+ }
+
+ /** Custom externalizable {@link RuntimeException} Exception with broken
serialization. */
+ public static class BrokenExternalizableRuntimeException extends
ExternalizableRuntimeException {
+ /** */
+ private boolean isWriteBroken;
+
+ /** */
+ public BrokenExternalizableRuntimeException() {
+ // No-op.
+ }
+
+ /** */
+ public BrokenExternalizableRuntimeException(String msg, boolean
isWriteBroken) {
+ super(msg);
+
+ this.isWriteBroken = isWriteBroken;
+ }
+
+ /** */
+ public boolean isWriteBroken() {
+ return isWriteBroken;
+ }
+
+ /** */
+ public void setWriteBroken(boolean writeBroken) {
+ isWriteBroken = writeBroken;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws
IOException {
+ if (isWriteBroken())
+ throw new RuntimeException(BROKEN_EX_MSG);
+
+ out.writeBoolean(isWriteBroken());
+
+ super.writeExternal(out);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ setWriteBroken(in.readBoolean());
+
+ if (!isWriteBroken())
+ throw new RuntimeException(BROKEN_EX_MSG);
+
+ super.readExternal(in);
+ }
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
index d6855825f3d..373182884d9 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
@@ -24,7 +24,7 @@ import
org.apache.ignite.internal.processors.service.GridServiceClusterReadOnlyM
import
org.apache.ignite.internal.processors.service.GridServiceContinuousQueryRedeployTest;
import
org.apache.ignite.internal.processors.service.GridServiceDeployClusterReadOnlyModeTest;
import
org.apache.ignite.internal.processors.service.GridServiceDeploymentCompoundFutureSelfTest;
-import
org.apache.ignite.internal.processors.service.GridServiceDeploymentExceptionPropagationTest;
+import
org.apache.ignite.internal.processors.service.GridServiceExceptionPropagationTest;
import org.apache.ignite.internal.processors.service.GridServiceMetricsTest;
import
org.apache.ignite.internal.processors.service.GridServicePackagePrivateSelfTest;
import
org.apache.ignite.internal.processors.service.GridServiceProcessorBatchDeploySelfTest;
@@ -102,7 +102,7 @@ import org.junit.runners.Suite;
IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.class,
IgniteServiceDeploymentFailureTest.class,
- GridServiceDeploymentExceptionPropagationTest.class,
+ GridServiceExceptionPropagationTest.class,
ServiceDeploymentProcessingOnCoordinatorLeftTest.class,
ServiceDeploymentProcessingOnCoordinatorFailTest.class,
ServiceDeploymentProcessingOnNodesLeftTest.class,