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 e1803947e17 IGNITE-28916 Added support for Management API Client
version validation (#13400)
e1803947e17 is described below
commit e1803947e17f87cbbe6ab725fad2674e0ac77fa1
Author: Mikhail Petrov <[email protected]>
AuthorDate: Mon Jul 27 13:21:34 2026 +0300
IGNITE-28916 Added support for Management API Client version validation
(#13400)
---
.../internal/dto/IgniteDataTransferObject.java | 20 +++-
.../feature/IgniteCoreFeatureSet.java | 2 +-
.../ignite/internal/visor/VisorTaskArgument.java | 53 ++++++++++
.../internal/TestManagementVisorOneNodeTask.java | 10 +-
.../rollingupgrade/AbstractRollingUpgradeTest.java | 13 ++-
.../ManagementApiVersionValidationTest.java | 108 +++++++++++++++++++++
.../ignite/testsuites/IgniteBasicTestSuite.java | 2 +
7 files changed, 192 insertions(+), 16 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObject.java
b/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObject.java
index 4d750a69cec..f24bba840b1 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObject.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObject.java
@@ -53,11 +53,7 @@ public abstract class IgniteDataTransferObject implements
Externalizable {
@Override public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(MAGIC);
- try (IgniteDataTransferObjectOutput dtout = new
IgniteDataTransferObjectOutput(out)) {
- IgniteDataTransferObjectSerializer serializer =
IDTOSerializerFactory.getInstance().serializer(getClass());
-
- serializer.writeExternal(this, dtout);
- }
+ writeIgniteDataTransferObject(out);
}
/** {@inheritDoc} */
@@ -68,6 +64,20 @@ public abstract class IgniteDataTransferObject implements
Externalizable {
throw new IOException("Unexpected IgniteDataTransferObject header
" +
"[actual=" + Integer.toHexString(hdr) + ", expected=" +
Integer.toHexString(MAGIC) + "]");
+ readIgniteDataTransferObject(in);
+ }
+
+ /** */
+ protected void writeIgniteDataTransferObject(ObjectOutput out) throws
IOException {
+ try (IgniteDataTransferObjectOutput dtout = new
IgniteDataTransferObjectOutput(out)) {
+ IgniteDataTransferObjectSerializer serializer =
IDTOSerializerFactory.getInstance().serializer(getClass());
+
+ serializer.writeExternal(this, dtout);
+ }
+ }
+
+ /** */
+ protected void readIgniteDataTransferObject(ObjectInput in) throws
IOException, ClassNotFoundException {
try (IgniteDataTransferObjectInput dtin = new
IgniteDataTransferObjectInput(in)) {
IgniteDataTransferObjectSerializer serializer =
IDTOSerializerFactory.getInstance().serializer(getClass());
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteCoreFeatureSet.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteCoreFeatureSet.java
index 8c495d80825..f5173872ef0 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteCoreFeatureSet.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteCoreFeatureSet.java
@@ -29,7 +29,7 @@ public class IgniteCoreFeatureSet extends
IgniteComponentFeatureSet {
private static final long serialVersionUID = 0L;
/** */
- private static final IgniteCoreFeatureSet INSTANCE = new
IgniteCoreFeatureSet(
+ static IgniteCoreFeatureSet INSTANCE = new IgniteCoreFeatureSet(
IgniteVersionUtils.VER,
IgniteFeatureSet.buildFrom(SupportedFeatureRegistry.class)
);
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskArgument.java
b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskArgument.java
index acb9ef559aa..55602bbb8f8 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskArgument.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskArgument.java
@@ -17,12 +17,17 @@
package org.apache.ignite.internal.visor;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.ignite.internal.Order;
import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import
org.apache.ignite.internal.processors.metastorage.persistence.DistributedMetaStorageVersion;
+import
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet;
import org.apache.ignite.internal.util.typedef.internal.S;
/**
@@ -32,6 +37,19 @@ public class VisorTaskArgument<A> extends
IgniteDataTransferObject {
/** */
private static final long serialVersionUID = 0L;
+ /**
+ * Magic header used for Management API argument serialization.
+ *
+ * <p>This value differs from the magic header defined in the parent class
because some {@link IgniteDataTransferObject}
+ * implementations (for example, {@link DistributedMetaStorageVersion})
are already stored in the PDS, which
+ * requires full backward compatibility. A separate magic header is
required to distinguish Management API arguments
+ * serialized by pre-Rolling Upgrade clients and handle them correctly.</p>
+ */
+ private static final int MAGIC = 0xBAA55F5E;
+
+ /** */
+ transient IgniteCoreFeatureSet cmdInitiatorFeatures =
IgniteCoreFeatureSet.local();
+
/** Node IDs task should be mapped to. */
@Order(0)
List<UUID> nodes;
@@ -119,6 +137,41 @@ public class VisorTaskArgument<A> extends
IgniteDataTransferObject {
return debug;
}
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(MAGIC);
+
+ cmdInitiatorFeatures.writeExternal(out);
+
+ writeIgniteDataTransferObject(out);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ int hdr = in.readInt();
+
+ if ((hdr & MAGIC) != MAGIC) {
+ throw new IOException("Failed to deserialize the Ignite Management
API command argument. Unexpected Ignite" +
+ " DTO message header. The input stream is malformed or was
generated by an incompatible Ignite version" +
+ " [actual=" + Integer.toHexString(hdr) +
+ ", expected=" + Integer.toHexString(MAGIC) + ']'
+ );
+ }
+
+ cmdInitiatorFeatures = new IgniteCoreFeatureSet();
+ cmdInitiatorFeatures.readExternal(in);
+
+ if
(!cmdInitiatorFeatures.isUpgradableTo(IgniteCoreFeatureSet.local())) {
+ throw new IOException("Failed to deserialize the Ignite Management
API command argument. The data was" +
+ " serialized by an incompatible Ignite version" +
+ " [remoteVersion=" + cmdInitiatorFeatures.version() +
+ ", localVersion=" + IgniteCoreFeatureSet.local().version() +
']'
+ );
+ }
+
+ readIgniteDataTransferObject(in);
+ }
+
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(VisorTaskArgument.class, this);
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/TestManagementVisorOneNodeTask.java
b/modules/core/src/test/java/org/apache/ignite/internal/TestManagementVisorOneNodeTask.java
index 1af21e3ecb2..5eee5dac163 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/TestManagementVisorOneNodeTask.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/TestManagementVisorOneNodeTask.java
@@ -26,12 +26,12 @@ import org.jetbrains.annotations.Nullable;
/**
*
*/
-public class TestManagementVisorOneNodeTask extends VisorOneNodeTask<String,
Object> {
+public class TestManagementVisorOneNodeTask extends VisorOneNodeTask<Object,
Object> {
/** */
private static final long serialVersionUID = 0L;
/** {@inheritDoc} */
- @Override protected VisorValidOneNodeJob job(String arg) {
+ @Override protected VisorValidOneNodeJob job(Object arg) {
return new VisorValidOneNodeJob(arg, debug);
}
@@ -43,7 +43,7 @@ public class TestManagementVisorOneNodeTask extends
VisorOneNodeTask<String, Obj
/**
* Valid Management one node visor job.
*/
- private static class VisorValidOneNodeJob extends VisorJob<String, Object>
{
+ private static class VisorValidOneNodeJob extends VisorJob<Object, Object>
{
/** */
private static final long serialVersionUID = 0L;
@@ -51,12 +51,12 @@ public class TestManagementVisorOneNodeTask extends
VisorOneNodeTask<String, Obj
* @param arg Argument.
* @param debug Debug flag.
*/
- protected VisorValidOneNodeJob(String arg, boolean debug) {
+ protected VisorValidOneNodeJob(Object arg, boolean debug) {
super(arg, debug);
}
/** {@inheritDoc} */
- @Override protected Object run(String arg) {
+ @Override protected Object run(Object arg) {
return null;
}
}
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 8e7148f0d4c..1802069a95a 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
@@ -389,11 +389,7 @@ public abstract class AbstractRollingUpgradeTest extends
GridCommonAbstractTest
protected void checkPreviousClusterFeatures(@Nullable String expVer)
throws Exception {
TestVersions expVersions = expVer == null ? null :
TestVersions.parse(expVer);
- IgniteCoreFeatureSet expPrevCoreFeatures = expVersions != null
- ? new IgniteCoreFeatureSet(
- IgniteProductVersion.fromString(expVersions.coreVersion()),
-
IgniteFeatureSet.buildFrom(readDeclaredCoreFeatures(expVersions.coreVersion())))
- : null;
+ IgniteCoreFeatureSet expPrevCoreFeatures = expVersions != null ?
createCoreFeatureSet(expVersions.coreVersion()) : null;
IgnitePluginFeatureSet expPrevPluginFeatures = expVersions != null &&
expVersions.containsPlugin()
? new IgnitePluginFeatureSet(
@@ -417,6 +413,13 @@ public abstract class AbstractRollingUpgradeTest extends
GridCommonAbstractTest
}
}
+ /** */
+ public static IgniteCoreFeatureSet createCoreFeatureSet(String ver) throws
Exception {
+ return new IgniteCoreFeatureSet(
+ IgniteProductVersion.fromString(ver),
+ IgniteFeatureSet.buildFrom(readDeclaredCoreFeatures(ver)));
+ }
+
/** */
protected void checkVersionUpgradeInactive(String expVer) throws Exception
{
checkVersionUpgradeEnabledStatus(false);
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ManagementApiVersionValidationTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ManagementApiVersionValidationTest.java
new file mode 100644
index 00000000000..b8af58f9f7c
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ManagementApiVersionValidationTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.rollingupgrade.feature;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientConnectionException;
+import org.apache.ignite.client.Config;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.TestManagementVisorOneNodeTask;
+import
org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.apache.ignite.testframework.LogListener;
+import org.junit.Test;
+
+/** */
+public class ManagementApiVersionValidationTest extends
AbstractRollingUpgradeTest {
+ /** */
+ public static final LogListener DESERIALIZATION_FAILED_LSNR =
LogListener.builder().andMatches(
+ "Failed to deserialize the Ignite Management API command argument"
+ ).build();
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName, String ver) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName,
ver);
+
+ cfg.setGridLogger(new ListeningTestLogger(log,
DESERIALIZATION_FAILED_LSNR));
+
+ return cfg;
+ }
+
+ /** */
+ @Test
+ public void testVersionValidation() throws Exception {
+ withCoreVersion("2.21.0", () -> {
+ startGrid(0);
+
+ checkCommandArgumentDeserializationFailed(0, "2.21.1");
+ checkCommandArgumentDeserializationFailed(0, "2.19.0");
+
+ executeCommand(createCommandArgument(0, "2.21.0"));
+ executeCommand(createCommandArgument(0, "2.20.0"));
+
+ return null;
+ });
+ }
+
+ /** */
+ private void checkCommandArgumentDeserializationFailed(int destNodeIdx,
String ver) throws Exception {
+ DESERIALIZATION_FAILED_LSNR.reset();
+
+ GridTestUtils.assertThrowsAnyCause(
+ log,
+ () -> {
+ executeCommand(createCommandArgument(destNodeIdx, ver));
+
+ return null;
+ },
+ ClientConnectionException.class,
+ "Channel is closed");
+
+ DESERIALIZATION_FAILED_LSNR.check(getTestTimeout());
+ }
+
+ /** */
+ private void executeCommand(VisorTaskArgument<Object> arg) throws
Exception {
+ try (IgniteClient cli = Ignition.startClient(new
ClientConfiguration().setAddresses(Config.SERVER))) {
+
cli.compute().execute(TestManagementVisorOneNodeTask.class.getName(), arg);
+ }
+ }
+
+ /** */
+ private VisorTaskArgument<Object> createCommandArgument(int destNodeIdx,
String ver) throws Exception {
+ return withCoreVersion(ver, () -> new
VisorTaskArgument<>(nodeId(destNodeIdx), "", false));
+ }
+
+ /** */
+ private static <R> R withCoreVersion(String ver, Callable<R> action)
throws Exception {
+ IgniteCoreFeatureSet prev = IgniteCoreFeatureSet.INSTANCE;
+ IgniteCoreFeatureSet.INSTANCE = createCoreFeatureSet(ver);
+
+ try {
+ return action.call();
+ }
+ finally {
+ IgniteCoreFeatureSet.INSTANCE = prev;
+ }
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index a36175a09eb..a49d63953f1 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -66,6 +66,7 @@ import
org.apache.ignite.internal.processors.odbc.SqlListenerUtilsTest;
import
org.apache.ignite.internal.processors.rollingupgrade.CoreVersionRollingUpgradeTest;
import
org.apache.ignite.internal.processors.rollingupgrade.PluginVersionRollingUpgradeTest;
import
org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSetTest;
+import
org.apache.ignite.internal.processors.rollingupgrade.feature.ManagementApiVersionValidationTest;
import org.apache.ignite.internal.product.GridProductVersionSelfTest;
import org.apache.ignite.internal.util.nio.IgniteExceptionInNioWorkerSelfTest;
import org.apache.ignite.messaging.GridMessagingNoPeerClassLoadingSelfTest;
@@ -108,6 +109,7 @@ import org.junit.runners.Suite;
CoreVersionRollingUpgradeTest.class,
PluginVersionRollingUpgradeTest.class,
+ ManagementApiVersionValidationTest.class,
GridProductVersionSelfTest.class,
GridAffinityAssignmentV2Test.class,
GridAffinityAssignmentV2TestNoOptimizations.class,