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

petrov-mg 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 e18edd87deb IGNITE-28969 Added remote node features validation during 
the TCP Discovery handshake (#13456)
e18edd87deb is described below

commit e18edd87debc8a6deec4a98c94d14f4b598e2277
Author: Mikhail Petrov <[email protected]>
AuthorDate: Mon Aug 10 14:43:20 2026 +0300

    IGNITE-28969 Added remote node features validation during the TCP Discovery 
handshake (#13456)
---
 .../apache/ignite/internal/GridKernalContext.java  |  4 ++
 .../ignite/internal/GridKernalContextImpl.java     |  6 +++
 .../wal/reader/StandaloneGridKernalContext.java    | 10 +++++
 .../feature/IgnitePluginFeatureSet.java            |  4 +-
 .../ignite/spi/discovery/tcp/ClientImpl.java       | 15 +++++++-
 .../ignite/spi/discovery/tcp/ServerImpl.java       | 45 +++++++++++++++++-----
 .../ignite/spi/discovery/tcp/TcpDiscoverySpi.java  | 43 +++++++++++++++++++++
 .../internal/UnsupportedNodeVersionException.java} | 13 +++++--
 .../tcp/messages/TcpDiscoveryHandshakeRequest.java | 16 +++++++-
 .../messages/TcpDiscoveryHandshakeResponse.java    | 16 +++++++-
 .../rollingupgrade/AbstractRollingUpgradeTest.java | 12 ++++--
 .../CoreVersionRollingUpgradeTest.java             | 24 +++++++++++-
 .../PluginVersionRollingUpgradeTest.java           |  4 +-
 ...0.java => TestPluginReleaseFeatures_2_1_0.java} |  7 +++-
 .../feature/TestPluginReleaseFeatures_3_0_0.java   |  3 ++
 15 files changed, 195 insertions(+), 27 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
index 3d92dfa0d56..0dee5ba7674 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
@@ -68,6 +68,7 @@ import 
org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.processors.resource.GridResourceProcessor;
 import org.apache.ignite.internal.processors.rest.IgniteRestProcessor;
 import 
org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import 
org.apache.ignite.internal.processors.schedule.IgniteScheduleProcessorAdapter;
 import org.apache.ignite.internal.processors.security.IgniteSecurity;
 import 
org.apache.ignite.internal.processors.segmentation.GridSegmentationProcessor;
@@ -140,6 +141,9 @@ public interface GridKernalContext extends 
Iterable<GridComponent> {
      */
     public GridKernalGateway gateway();
 
+    /** @return Local node features. */
+    public IgniteNodeFeatureSet localNodeFeatures();
+
     /**
      * Gets grid instance managed by kernal.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
index 8981207abf2..9892823a091 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
@@ -90,6 +90,7 @@ import 
org.apache.ignite.internal.processors.query.QueryEngine;
 import org.apache.ignite.internal.processors.resource.GridResourceProcessor;
 import org.apache.ignite.internal.processors.rest.IgniteRestProcessor;
 import 
org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import 
org.apache.ignite.internal.processors.schedule.IgniteScheduleProcessorAdapter;
 import org.apache.ignite.internal.processors.security.IgniteSecurity;
 import 
org.apache.ignite.internal.processors.segmentation.GridSegmentationProcessor;
@@ -632,6 +633,11 @@ public class GridKernalContextImpl implements 
GridKernalContext, Externalizable
         return gw;
     }
 
+    /** {@inheritDoc} */
+    @Override public IgniteNodeFeatureSet localNodeFeatures() {
+        return rollUpProc.features().localVersionFeatures();
+    }
+
     /** {@inheritDoc} */
     @Override public IgniteEx grid() {
         return grid;
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
index 3b0acf2e65e..44a5bdabcf5 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
@@ -91,6 +91,8 @@ import 
org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.processors.resource.GridResourceProcessor;
 import org.apache.ignite.internal.processors.rest.IgniteRestProcessor;
 import 
org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import 
org.apache.ignite.internal.processors.schedule.IgniteScheduleProcessorAdapter;
 import org.apache.ignite.internal.processors.security.IgniteSecurity;
 import 
org.apache.ignite.internal.processors.security.NoOpIgniteSecurityProcessor;
@@ -177,6 +179,9 @@ public class StandaloneGridKernalContext implements 
GridKernalContext {
     /** Operation context dispacther. */
     private final OperationContextDispatcher opCtxDispatcher = new 
OperationContextDispatcher();
 
+    /** */
+    private final IgniteNodeFeatureSet locNodeFeatures = new 
IgniteNodeFeatureSet(IgniteCoreFeatureSet.local());
+
     /**
      * @param log Logger.
      * @param ft Node file tree.
@@ -317,6 +322,11 @@ public class StandaloneGridKernalContext implements 
GridKernalContext {
         return null;
     }
 
+    /** {@inheritDoc} */
+    @Override public IgniteNodeFeatureSet localNodeFeatures() {
+        return locNodeFeatures;
+    }
+
     /** {@inheritDoc} */
     @Override public IgniteEx grid() {
         final IgniteEx kernal = new IgniteKernal() {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgnitePluginFeatureSet.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgnitePluginFeatureSet.java
index 7c444c9d4fe..0334ef04b1b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgnitePluginFeatureSet.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgnitePluginFeatureSet.java
@@ -50,8 +50,8 @@ public class IgnitePluginFeatureSet extends 
IgniteComponentFeatureSet {
         this.compName = compName;
     }
 
-    /** */
-    public String componentName() {
+    /** {@inheritDoc} */
+    @Override public String componentName() {
         return compName;
     }
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java
index 43d4f1b4867..4d7e1e22f57 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java
@@ -88,6 +88,7 @@ import org.apache.ignite.spi.discovery.DiscoverySpiListener;
 import org.apache.ignite.spi.discovery.tcp.internal.DiscoveryDataPacket;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNodesRing;
+import 
org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException;
 import 
org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;
 import 
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
 import 
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAuthFailedMessage;
@@ -708,7 +709,7 @@ class ClientImpl extends TcpDiscoveryImpl {
 
                 TcpDiscoveryIoSession ses = createSession(sock);
 
-                TcpDiscoveryHandshakeRequest req = new 
TcpDiscoveryHandshakeRequest(locNodeId);
+                TcpDiscoveryHandshakeRequest req = new 
TcpDiscoveryHandshakeRequest(locNodeId, spi.localNodeFeatures());
 
                 req.client(true);
                 req.dcId(locNode.dataCenterId());
@@ -717,6 +718,8 @@ class ClientImpl extends TcpDiscoveryImpl {
 
                 TcpDiscoveryHandshakeResponse res = 
spi.readHandshakeResponse(ses, ackTimeout0);
 
+                spi.validateRemoteFeatures(res.nodeFeatures());
+
                 // Convert the addresses once.
                 Collection<InetSocketAddress> redirectAddrs = 
res.redirectAddresses();
 
@@ -791,6 +794,16 @@ class ClientImpl extends TcpDiscoveryImpl {
 
                 errs.add(e);
 
+                if (e instanceof UnsupportedNodeVersionException 
unsupportedVerEx) {
+                    LT.error(log, e, "Failed to initialize a connection with 
the remote node. The remote node is running" +
+                        " components with an incompatible versions, so the 
nodes cannot agree on serialization protocol" +
+                        " [rmtAddr=" + addr + ", errMsg=" + 
unsupportedVerEx.getMessage() + ']');
+
+                    throw new IgniteSpiException("Failed to initialize a 
connection with the remote node. The remote node" +
+                        " is running components with an incompatible versions, 
so the nodes cannot agree on serialization" +
+                        " protocol [rmtAddr=" + addr + ", errMsg=" + 
unsupportedVerEx.getMessage() + ']', e);
+                }
+
                 if (X.hasCause(e, SSLException.class)) {
                     if (--sslConnectAttempts == 0)
                         throw new IgniteSpiException("Unable to establish 
secure connection. " +
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
index c17346d10e7..9a221431b1d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
@@ -129,6 +129,7 @@ import 
org.apache.ignite.spi.discovery.tcp.internal.FutureTask;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNodesRing;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoverySpiState;
+import 
org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException;
 import 
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage;
 import 
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAuthFailedMessage;
 import 
org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryCheckFailedMessage;
@@ -1502,13 +1503,15 @@ class ServerImpl extends TcpDiscoveryImpl {
 
                 openSock = true;
 
-                TcpDiscoveryHandshakeRequest req = new 
TcpDiscoveryHandshakeRequest(locNodeId);
+                TcpDiscoveryHandshakeRequest req = new 
TcpDiscoveryHandshakeRequest(locNodeId, spi.localNodeFeatures());
 
                 // Handshake.
                 spi.writeMessage(ses, req, 
timeoutHelper.nextTimeoutChunk(spi.getSocketTimeout()));
 
                 TcpDiscoveryHandshakeResponse res = 
spi.readHandshakeResponse(ses, timeoutHelper.nextTimeoutChunk(ackTimeout0));
 
+                spi.validateRemoteFeatures(res.nodeFeatures());
+
                 if (msg instanceof TcpDiscoveryJoinRequestMessage) {
                     boolean ignore = false;
 
@@ -1587,6 +1590,16 @@ class ServerImpl extends TcpDiscoveryImpl {
 
                 errs.add(e);
 
+                if (e instanceof UnsupportedNodeVersionException 
unsupportedVerEx) {
+                    LT.error(log, e, "Failed to initialize a connection with 
the remote node. The remote node is running" +
+                        " components with an incompatible versions, so the 
nodes cannot agree on serialization protocol" +
+                        " [rmtAddr=" + addr + ", errMsg=" + 
unsupportedVerEx.getMessage() + ']');
+
+                    throw new IgniteException("Failed to initialize a 
connection with the remote node. The remote node" +
+                        " is running components with an incompatible versions, 
so the nodes cannot agree on a serialization" +
+                        " protocol [rmtAddr=" + addr + ", errMsg=" + 
unsupportedVerEx.getMessage() + ']', e);
+                }
+
                 if (X.hasCause(e, SSLException.class)) {
                     if (--sslConnectAttempts == 0)
                         throw new IgniteException("Unable to establish secure 
connection. " +
@@ -3402,7 +3415,7 @@ class ServerImpl extends TcpDiscoveryImpl {
                                 openSock = true;
 
                                 // Handshake.
-                                TcpDiscoveryHandshakeRequest hndMsg = new 
TcpDiscoveryHandshakeRequest(locNodeId);
+                                TcpDiscoveryHandshakeRequest hndMsg = new 
TcpDiscoveryHandshakeRequest(locNodeId, spi.localNodeFeatures());
 
                                 if (sndState != null) {
                                     // If want a forced connection, we set the 
change-topology node flag to current node id.
@@ -3431,6 +3444,8 @@ class ServerImpl extends TcpDiscoveryImpl {
                                 if (log.isDebugEnabled())
                                     log.debug("Handshake response: " + res);
 
+                                spi.validateRemoteFeatures(res.nodeFeatures());
+
                                 // We should take previousNodeAlive flag into 
account
                                 // only if we received the response from the 
correct node.
                                 if (res.creatorNodeId().equals(next.id()) && 
res.previousNodeAlive() && sndState != null) {
@@ -6579,8 +6594,11 @@ class ServerImpl extends TcpDiscoveryImpl {
                     U.enhanceThreadName(U.id8(nodeId) + ' ' + 
sock.getInetAddress().getHostAddress()
                         + ":" + sock.getPort() + (req.client() ? " client" : 
""));
 
-                    TcpDiscoveryHandshakeResponse res =
-                        new TcpDiscoveryHandshakeResponse(locNodeId, 
locNode.internalOrder());
+                    TcpDiscoveryHandshakeResponse res = new 
TcpDiscoveryHandshakeResponse(
+                        locNodeId,
+                        locNode.internalOrder(),
+                        spi.localNodeFeatures()
+                    );
 
                     if (req.client()) {
                         if (req.dcId() != null && !Objects.equals(req.dcId(), 
locNode.dataCenterId())) {
@@ -6694,6 +6712,8 @@ class ServerImpl extends TcpDiscoveryImpl {
 
                     spi.writeMessage(ses, res, 
spi.getEffectiveSocketTimeout(srvSock));
 
+                    spi.validateRemoteFeatures(req.nodeFeatures());
+
                     // It can happen if a remote node is stopped and it has a 
loopback address in the list of addresses,
                     // the local node sends a handshake request message on the 
loopback address, so we get here.
                     if (locNodeId.equals(nodeId)) {
@@ -6790,21 +6810,26 @@ class ServerImpl extends TcpDiscoveryImpl {
 
                     onException("Caught exception on handshake [err=" + e + ", 
sock=" + sock + ']', e);
 
-                    if (e.hasCause(SocketTimeoutException.class))
+                    if (e instanceof UnsupportedNodeVersionException 
unsupportedVerEx) {
+                        LT.warn(log, "Failed to initialize a connection with 
the remote node. The remote node is running" +
+                            " components with an incompatible versions, so the 
nodes cannot agree on serialization protocol" +
+                            " [rmtAddr=" + rmtAddr + ", errMsg=" + 
unsupportedVerEx.getMessage() + ']', e);
+                    }
+                    else if (e.hasCause(SocketTimeoutException.class)) {
                         LT.warn(log, "Socket operation timed out on handshake 
" +
                             "(consider increasing 'networkTimeout' 
configuration property) " +
                             "[netTimeout=" + spi.netTimeout + ']');
-
-                    else if (e.hasCause(ClassNotFoundException.class))
+                    }
+                    else if (e.hasCause(ClassNotFoundException.class)) {
                         LT.warn(log, "Failed to read message due to 
ClassNotFoundException " +
                             "(make sure same versions of all classes are 
available on all nodes) " +
                             "[rmtAddr=" + rmtAddr +
                             ", err=" + X.cause(e, 
ClassNotFoundException.class).getMessage() + ']');
-
+                    }
+                    else if (e.hasCause(ObjectStreamException.class) || 
(!sock.isClosed() && !e.hasCause(IOException.class))) {
                         // Always report marshalling problems.
-                    else if (e.hasCause(ObjectStreamException.class) ||
-                        (!sock.isClosed() && !e.hasCause(IOException.class)))
                         LT.error(log, e, "Failed to initialize connection 
[sock=" + sock + ']');
+                    }
 
                     return;
                 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java
index b1cc53cbee5..9c52f9504be 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java
@@ -60,6 +60,8 @@ import 
org.apache.ignite.internal.managers.communication.UnknownMessageException
 import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.processors.metric.MetricRegistryImpl;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.X;
@@ -99,6 +101,7 @@ import 
org.apache.ignite.spi.discovery.DiscoverySpiOrderSupport;
 import org.apache.ignite.spi.discovery.tcp.internal.DiscoveryDataPacket;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryStatistics;
+import 
org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
 import 
org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder;
 import 
org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;
@@ -468,6 +471,9 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter 
implements IgniteDiscovery
     /** For test purposes. */
     private boolean skipAddrsRandomization = false;
 
+    /** */
+    private IgniteNodeFeatureSet locNodeFeatures;
+
     /**
      * Gets current SPI state.
      *
@@ -1189,6 +1195,8 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter 
implements IgniteDiscovery
         // Init local node.
         initAddresses();
 
+        locNodeFeatures = ((IgniteEx)ignite).context().localNodeFeatures();
+
         locNode = new TcpDiscoveryNode(
             ignite.configuration().getNodeId(),
             addrs.get1(),
@@ -1686,6 +1694,41 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter 
implements IgniteDiscovery
         }
     }
 
+    /** */
+    IgniteNodeFeatureSet localNodeFeatures() {
+        return locNodeFeatures;
+    }
+
+    /** */
+    void validateRemoteFeatures(IgniteNodeFeatureSet rmtFeatures) throws 
IgniteCheckedException {
+        if (rmtFeatures == null) {
+            throw new UnsupportedNodeVersionException(
+                "Failed to obtain remote node features. The remote node may be 
running an unsupported Ignite version," +
+                    " which may result in unexpected handshake message 
serialization");
+        }
+
+        for (IgniteComponentFeatureSet rmtCmpFeatures : rmtFeatures.values()) {
+            IgniteComponentFeatureSet locCmpFeatures = 
locNodeFeatures.componentFeatures(rmtCmpFeatures.componentName());
+
+            if (locCmpFeatures == null)
+                continue;
+
+            int c = 
locCmpFeatures.version().compareTo(rmtCmpFeatures.version());
+
+            if (c == 0)
+                continue;
+
+            IgniteComponentFeatureSet src = c > 0 ? rmtCmpFeatures : 
locCmpFeatures;
+            IgniteComponentFeatureSet target = c > 0 ? locCmpFeatures : 
rmtCmpFeatures;
+
+            if (!src.isUpgradableTo(target)) {
+                throw new UnsupportedNodeVersionException("Remote node 
component versions are not supported" +
+                    " [locComponents=" + locNodeFeatures +
+                    ", rmtComponents=" + rmtFeatures + ']');
+            }
+        }
+    }
+
     /**
      * Writes message to the socket.
      *
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/UnsupportedNodeVersionException.java
similarity index 70%
copy from 
modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
copy to 
modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/UnsupportedNodeVersionException.java
index b40cea1590d..ca46b73cc20 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/UnsupportedNodeVersionException.java
@@ -15,10 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.rollingupgrade.feature;
+package org.apache.ignite.spi.discovery.tcp.internal;
+
+import org.apache.ignite.IgniteCheckedException;
 
 /** */
-public class TestPluginReleaseFeatures_3_0_0 {
+public class UnsupportedNodeVersionException extends IgniteCheckedException {
+    /** */
+    private static final long serialVersionUID = 0L;
+
     /** */
-    public static final IgniteFeature VER_3_0_0_ID_2_FEATURE = new 
TestPluginFeature(2);
+    public UnsupportedNodeVersionException(String message) {
+        super(message);
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeRequest.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeRequest.java
index 6ce9f16a7bc..d2a33c6d059 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeRequest.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeRequest.java
@@ -19,6 +19,8 @@ package org.apache.ignite.spi.discovery.tcp.messages;
 
 import java.util.UUID;
 import org.apache.ignite.internal.Order;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.plugin.extensions.communication.MessageFactory;
 import org.jetbrains.annotations.Nullable;
@@ -35,6 +37,10 @@ public class TcpDiscoveryHandshakeRequest extends 
TcpDiscoveryAbstractMessage {
     @Order(1)
     @Nullable String dcId;
 
+    /** */
+    @Order(2)
+    IgniteComponentFeatureSet[] nodeFeatures;
+
     /**
      * Default constructor for {@link MessageFactory}.
      */
@@ -46,9 +52,12 @@ public class TcpDiscoveryHandshakeRequest extends 
TcpDiscoveryAbstractMessage {
      * Constructor.
      *
      * @param creatorNodeId Creator node ID.
+     * @param locNodeFeatures Local node features.
      */
-    public TcpDiscoveryHandshakeRequest(UUID creatorNodeId) {
+    public TcpDiscoveryHandshakeRequest(UUID creatorNodeId, 
IgniteNodeFeatureSet locNodeFeatures) {
         super(creatorNodeId);
+
+        this.nodeFeatures = locNodeFeatures.values();
     }
 
     /**
@@ -79,6 +88,11 @@ public class TcpDiscoveryHandshakeRequest extends 
TcpDiscoveryAbstractMessage {
         this.dcId = dcId;
     }
 
+    /** @return Features supported by the sender node. */
+    public IgniteNodeFeatureSet nodeFeatures() {
+        return nodeFeatures == null ? null : new 
IgniteNodeFeatureSet(nodeFeatures);
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(TcpDiscoveryHandshakeRequest.class, this, "super", 
super.toString(),
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeResponse.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeResponse.java
index b5ca363ae01..d11e0814ef8 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeResponse.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/messages/TcpDiscoveryHandshakeResponse.java
@@ -21,6 +21,8 @@ import java.net.InetSocketAddress;
 import java.util.Collection;
 import java.util.UUID;
 import org.apache.ignite.internal.Order;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet;
+import 
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.plugin.extensions.communication.MessageFactory;
@@ -42,6 +44,10 @@ public class TcpDiscoveryHandshakeResponse extends 
TcpDiscoveryAbstractMessage {
     @Order(2)
     @Nullable Collection<InetSocketAddressMessage> redirectAddrsMsgs;
 
+    /** */
+    @Order(3)
+    IgniteComponentFeatureSet[] nodeFeatures;
+
     /**
      * Default constructor for {@link MessageFactory}.
      */
@@ -54,11 +60,14 @@ public class TcpDiscoveryHandshakeResponse extends 
TcpDiscoveryAbstractMessage {
      *
      * @param creatorNodeId Creator node ID.
      * @param locNodeOrder Local node order.
+     * @param locNodeFeatures Local node features.
      */
-    public TcpDiscoveryHandshakeResponse(UUID creatorNodeId, long 
locNodeOrder) {
+    public TcpDiscoveryHandshakeResponse(UUID creatorNodeId, long 
locNodeOrder, IgniteNodeFeatureSet locNodeFeatures) {
         super(creatorNodeId);
 
         order = locNodeOrder;
+
+        this.nodeFeatures = locNodeFeatures.values();
     }
 
     /**
@@ -104,6 +113,11 @@ public class TcpDiscoveryHandshakeResponse extends 
TcpDiscoveryAbstractMessage {
             : F.viewReadOnly(sockAddrs, addr -> new 
InetSocketAddressMessage(addr.getAddress(), addr.getPort()));
     }
 
+    /** @return Features supported by the sender node. */
+    public IgniteNodeFeatureSet nodeFeatures() {
+        return nodeFeatures == null ? null : new 
IgniteNodeFeatureSet(nodeFeatures);
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(TcpDiscoveryHandshakeResponse.class, this, "super", 
super.toString(),
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java
index 1802069a95a..0bbdb9824a5 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java
@@ -71,6 +71,7 @@ import org.apache.ignite.spi.IgniteNodeValidationResult;
 import org.apache.ignite.spi.IgniteSpiException;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
 import org.apache.ignite.spi.discovery.tcp.TestBlockingTcpDiscoverySpi;
+import 
org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.jspecify.annotations.Nullable;
@@ -148,8 +149,7 @@ public abstract class AbstractRollingUpgradeTest extends 
GridCommonAbstractTest
         "One or more component versions on the joining node differ from the 
corresponding versions active in the cluster";
 
     /** */
-    protected static final String RU_UNAVAILABLE_BETWEEN_VER_ERR = "Ignite 
component Rolling Upgrade is not supported" +
-        " between the component version active in the cluster and the version 
running on the joining node";
+    protected static final String NOT_SUPPORTED_VER_ERR = "Remote node 
component versions are not supported";
 
     /** {@inheritDoc} */
     @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
@@ -346,10 +346,14 @@ public abstract class AbstractRollingUpgradeTest extends 
GridCommonAbstractTest
     protected void checkJoinFailed(int nodeIdx, String ver, boolean 
checkClientNode, String msg) {
         int expClusterSize = clusterNode().cluster().nodes().size();
 
-        GridTestUtils.assertThrowsAnyCause(log, () -> startGrid(nodeIdx, ver), 
IgniteSpiException.class, msg);
+        Class<? extends Throwable> errCls = Objects.equals(msg, 
NOT_SUPPORTED_VER_ERR)
+            ? UnsupportedNodeVersionException.class
+            : IgniteSpiException.class;
+
+        GridTestUtils.assertThrowsAnyCause(log, () -> startGrid(nodeIdx, ver), 
errCls, msg);
 
         if (checkClientNode)
-            GridTestUtils.assertThrowsAnyCause(log, () -> 
startClientGrid(nodeIdx, ver), IgniteSpiException.class, msg);
+            GridTestUtils.assertThrowsAnyCause(log, () -> 
startClientGrid(nodeIdx, ver), errCls, msg);
 
         assertEquals(expClusterSize, clusterNode().cluster().nodes().size());
     }
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/CoreVersionRollingUpgradeTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/CoreVersionRollingUpgradeTest.java
index e3f65fd3565..8483d87647a 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/CoreVersionRollingUpgradeTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/CoreVersionRollingUpgradeTest.java
@@ -311,6 +311,28 @@ public class CoreVersionRollingUpgradeTest extends 
AbstractRollingUpgradeTest {
         finalizeClusterVersion(1, "2.19.3");
     }
 
+    /** */
+    @Test
+    public void testJoinOfNodeWithGreaterUnsupportedVersion() throws Exception 
{
+        startCluster("2.19.0");
+
+        checkJoinFailed(3, "2.21.0", NOT_SUPPORTED_VER_ERR);
+
+        checkJoinSuccess(3, "2.19.0", false);
+        checkJoinSuccess(4, "2.19.0", true);
+    }
+
+    /** */
+    @Test
+    public void testJoinOfNodeWithSmallerUnsupportedVersion() throws Exception 
{
+        startCluster("2.21.0");
+
+        checkJoinFailed(3, "2.19.0", NOT_SUPPORTED_VER_ERR);
+
+        checkJoinSuccess(3, "2.21.0", false);
+        checkJoinSuccess(4, "2.21.0", true);
+    }
+
     /** */
     @Test
     public void testUpgradeBetweenVersionsWithCherryPicks() throws Exception {
@@ -318,7 +340,7 @@ public class CoreVersionRollingUpgradeTest extends 
AbstractRollingUpgradeTest {
 
         ru(1).enableVersionUpgrade();
 
-        checkJoinFailed(3, "2.20.0", RU_UNAVAILABLE_BETWEEN_VER_ERR);
+        checkJoinFailed(3, "2.20.0", NOT_SUPPORTED_VER_ERR);
 
         forAllNodes(nodeIdx -> upgradeNodeVersion(nodeIdx, "2.19.3", 
"2.20.1"));
 
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/PluginVersionRollingUpgradeTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/PluginVersionRollingUpgradeTest.java
index 0f7291a9665..68b404de6f3 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/PluginVersionRollingUpgradeTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/PluginVersionRollingUpgradeTest.java
@@ -131,11 +131,11 @@ public class PluginVersionRollingUpgradeTest extends 
AbstractRollingUpgradeTest
         checkJoinFailed(3, "2.20.0", false, "Some components active in the 
cluster are not configured on the joining server node");
         checkJoinSuccess(3, "2.20.0", true);
 
-        checkJoinFailed(4, "2.20.0 | 3.0.0", RU_UNAVAILABLE_BETWEEN_VER_ERR);
+        checkJoinFailed(4, "2.20.0 | 3.0.0", NOT_SUPPORTED_VER_ERR);
 
         checkJoinSuccess(4, "2.20.0 | 2.0.0", true);
 
-        checkJoinFailed(5, "2.20.0 | 3.0.0", VER_INCOMPATIBLE_ERR);
+        checkJoinFailed(5, "2.20.0 | 2.1.0", VER_INCOMPATIBLE_ERR);
 
         upgradeNodeVersion(0, "2.19.0 | 1.0.0", "2.20.0 | 2.0.0");
         upgradeNodeVersion(1, "2.19.0 | 1.0.0", "2.20.0 | 2.0.0");
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java
similarity index 81%
copy from 
modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
copy to 
modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java
index b40cea1590d..edb4b3b3646 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java
@@ -18,7 +18,10 @@
 package org.apache.ignite.internal.processors.rollingupgrade.feature;
 
 /** */
-public class TestPluginReleaseFeatures_3_0_0 {
+public class TestPluginReleaseFeatures_2_1_0 {
     /** */
-    public static final IgniteFeature VER_3_0_0_ID_2_FEATURE = new 
TestPluginFeature(2);
+    public static final IgniteFeature VER_2_1_0_ID_1_FEATURE = new 
TestPluginFeature(1);
+
+    /** */
+    public static final IgniteFeature VER_2_1_0_ID_2_FEATURE = new 
TestPluginFeature(2);
 }
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
index b40cea1590d..d5c7b4f2e23 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_3_0_0.java
@@ -21,4 +21,7 @@ package 
org.apache.ignite.internal.processors.rollingupgrade.feature;
 public class TestPluginReleaseFeatures_3_0_0 {
     /** */
     public static final IgniteFeature VER_3_0_0_ID_2_FEATURE = new 
TestPluginFeature(2);
+
+    /** */
+    public static final IgniteFeature VER_3_0_0_ID_3_FEATURE = new 
TestPluginFeature(3);
 }

Reply via email to