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 4d750a69cec7c..f24bba840b1fb 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 8c495d8082514..f5173872ef0c9 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 acb9ef559aa93..55602bbb8f853 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 extends IgniteDataTransferObject { /** */ private static final long serialVersionUID = 0L; + /** + * Magic header used for Management API argument serialization. + * + *

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.

+ */ + private static final int MAGIC = 0xBAA55F5E; + + /** */ + transient IgniteCoreFeatureSet cmdInitiatorFeatures = IgniteCoreFeatureSet.local(); + /** Node IDs task should be mapped to. */ @Order(0) List nodes; @@ -119,6 +137,41 @@ public boolean isDebug() { 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 1af21e3ecb2e8..5eee5dac16396 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 @@ /** * */ -public class TestManagementVisorOneNodeTask extends VisorOneNodeTask { +public class TestManagementVisorOneNodeTask extends VisorOneNodeTask { /** */ 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 { + private static class VisorValidOneNodeJob extends VisorJob { /** */ private static final long serialVersionUID = 0L; @@ -51,12 +51,12 @@ private static class VisorValidOneNodeJob extends VisorJob { * @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 8e7148f0d4c4d..1802069a95af2 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 @@ protected void checkVersionUpgradeInProgress(String logicalVer, String srcVer, S 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 @@ protected void checkPreviousClusterFeatures(@Nullable String expVer) throws Exce } } + /** */ + 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 0000000000000..b8af58f9f7cda --- /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 arg) throws Exception { + try (IgniteClient cli = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER))) { + cli.compute().execute(TestManagementVisorOneNodeTask.class.getName(), arg); + } + } + + /** */ + private VisorTaskArgument createCommandArgument(int destNodeIdx, String ver) throws Exception { + return withCoreVersion(ver, () -> new VisorTaskArgument<>(nodeId(destNodeIdx), "", false)); + } + + /** */ + private static R withCoreVersion(String ver, Callable 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 a36175a09ebac..a49d63953f13e 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.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 @@ CoreVersionRollingUpgradeTest.class, PluginVersionRollingUpgradeTest.class, + ManagementApiVersionValidationTest.class, GridProductVersionSelfTest.class, GridAffinityAssignmentV2Test.class, GridAffinityAssignmentV2TestNoOptimizations.class,