This is an automated email from the ASF dual-hosted git repository. tkalkirill 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 745f0456c6a IGNITE-26110 Add unit tests for serialization compatibility of all Raft commands (#6744) 745f0456c6a is described below commit 745f0456c6afb5fce32ca10e16807b03535d3425 Author: Kirill Tkalenko <tkalkir...@yandex.ru> AuthorDate: Fri Oct 10 14:54:04 2025 +0300 IGNITE-26110 Add unit tests for serialization compatibility of all Raft commands (#6744) --- modules/replicator/build.gradle | 1 + .../ReplicatorCommandsCompatibilityTest.java | 126 +++++++++++++++++++++ modules/transactions/build.gradle | 1 + .../tx/message/TxCommandsCompatibilityTest.java | 100 ++++++++++++++++ 4 files changed, 228 insertions(+) diff --git a/modules/replicator/build.gradle b/modules/replicator/build.gradle index c54ad928f5c..1ab45a03a14 100644 --- a/modules/replicator/build.gradle +++ b/modules/replicator/build.gradle @@ -57,6 +57,7 @@ dependencies { integrationTestImplementation testFixtures(project(':ignite-metrics')) integrationTestImplementation testFixtures(project(':ignite-raft')) + testImplementation project(':ignite-network') testImplementation testFixtures(project(':ignite-core')) testImplementation testFixtures(project(':ignite-placement-driver-api')) testImplementation testFixtures(project(':ignite-failure-handler')) diff --git a/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java b/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java new file mode 100644 index 00000000000..290180b1fef --- /dev/null +++ b/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java @@ -0,0 +1,126 @@ +/* + * 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.replicator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Base64; +import java.util.List; +import java.util.UUID; +import org.apache.ignite.internal.hlc.HybridTimestamp; +import org.apache.ignite.internal.network.MessageSerializationRegistryImpl; +import org.apache.ignite.internal.network.serialization.MessageSerializationRegistry; +import org.apache.ignite.internal.raft.Command; +import org.apache.ignite.internal.raft.Marshaller; +import org.apache.ignite.internal.raft.util.ThreadLocalOptimizedMarshaller; +import org.apache.ignite.internal.replicator.command.SafeTimeSyncCommand; +import org.apache.ignite.internal.replicator.message.PrimaryReplicaChangeCommand; +import org.apache.ignite.internal.replicator.message.ReplicaMessagesFactory; +import org.apache.ignite.internal.replicator.message.ReplicaMessagesSerializationRegistryInitializer; +import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Compatibility testing for serialization/deserialization of replicator raft commands. It is verified that deserialization of commands that + * were created on earlier versions of the product will be error-free. + * + * <p>For MAC users with aarch64 architecture, you will need to add {@code || "aarch64".equals(arch)} to the + * {@code GridUnsafe#unaligned()} for the tests to pass. For more details, see + * <a href="https://lists.apache.org/thread/67coyvm8mo7106mkndt24yqwtbvb7590">discussion</a>.</p> + * + * <p>To serialize commands, use {@link #serializeAll()} and insert the result into the appropriate tests.</p> + */ +public class ReplicatorCommandsCompatibilityTest extends BaseIgniteAbstractTest { + private final MessageSerializationRegistry registry = new MessageSerializationRegistryImpl(); + + private final Marshaller marshaller = new ThreadLocalOptimizedMarshaller(registry); + + private final ReplicaMessagesFactory factory = new ReplicaMessagesFactory(); + + @BeforeEach + void setUp() { + new ReplicaMessagesSerializationRegistryInitializer().registerFactories(registry); + } + + @Test + void testSafeTimeSyncCommand() { + SafeTimeSyncCommand command = decodeCommand("CSlH"); + + assertEquals(initiatorTime(), command.initiatorTime()); + } + + @Test + void testPrimaryReplicaChangeCommand() { + PrimaryReplicaChangeCommand command = decodeCommand("CSorAAAAAAAAAAAqAAAAAAAAAEUGbm9kZTE="); + + assertEquals(42, command.leaseStartTime()); + assertEquals(uuid(), command.primaryReplicaNodeId()); + assertEquals("node1", command.primaryReplicaNodeName()); + } + + private static HybridTimestamp initiatorTime() { + return HybridTimestamp.hybridTimestamp(70); + } + + private static UUID uuid() { + return new UUID(42, 69); + } + + private <T extends Command> T deserializeCommand(byte[] bytes) { + return marshaller.unmarshall(bytes); + } + + private <T extends Command> T decodeCommand(String base64) { + return deserializeCommand(Base64.getDecoder().decode(base64)); + } + + @SuppressWarnings("unused") + private void serializeAll() { + List<Command> commands = List.of( + createSafeTimeSyncCommand(), + createPrimaryReplicaChangeCommand() + ); + + for (Command c : commands) { + log.info(">>>>> Serialized command: [c={}, base64='{}']", c.getClass().getSimpleName(), encodeCommand(c)); + } + } + + private PrimaryReplicaChangeCommand createPrimaryReplicaChangeCommand() { + return factory.primaryReplicaChangeCommand() + .leaseStartTime(42) + .primaryReplicaNodeId(uuid()) + .primaryReplicaNodeName("node1") + .build(); + } + + private SafeTimeSyncCommand createSafeTimeSyncCommand() { + return factory.safeTimeSyncCommand() + .initiatorTime(initiatorTime()) + .build(); + } + + private byte[] serializeCommand(Command c) { + return marshaller.marshall(c); + } + + private String encodeCommand(Command c) { + return Base64.getEncoder().encodeToString(serializeCommand(c)); + } +} diff --git a/modules/transactions/build.gradle b/modules/transactions/build.gradle index 76189930ad0..416d2640ff0 100644 --- a/modules/transactions/build.gradle +++ b/modules/transactions/build.gradle @@ -51,6 +51,7 @@ dependencies { implementation libs.fastutil.core testImplementation project(':ignite-core') + testImplementation project(':ignite-raft') testImplementation testFixtures(project(':ignite-core')) testImplementation testFixtures(project(':ignite-configuration')) testImplementation testFixtures(project(':ignite-configuration-system')) diff --git a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java new file mode 100644 index 00000000000..f5902a7655f --- /dev/null +++ b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java @@ -0,0 +1,100 @@ +/* + * 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.tx.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Base64; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.apache.ignite.internal.network.MessageSerializationRegistryImpl; +import org.apache.ignite.internal.network.serialization.MessageSerializationRegistry; +import org.apache.ignite.internal.raft.Command; +import org.apache.ignite.internal.raft.Marshaller; +import org.apache.ignite.internal.raft.util.ThreadLocalOptimizedMarshaller; +import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Compatibility testing for serialization/deserialization of tx raft commands. It is verified that deserialization of commands that were + * created on earlier versions of the product will be error-free. + * + * <p>For MAC users with aarch64 architecture, you will need to add {@code || "aarch64".equals(arch)} to the + * {@code GridUnsafe#unaligned()} for the tests to pass. For more details, see + * <a href="https://lists.apache.org/thread/67coyvm8mo7106mkndt24yqwtbvb7590">discussion</a>.</p> + * + * <p>To serialize commands, use {@link #serializeAll()} and insert the result into the appropriate tests.</p> + */ +public class TxCommandsCompatibilityTest extends BaseIgniteAbstractTest { + private final MessageSerializationRegistry registry = new MessageSerializationRegistryImpl(); + + private final Marshaller marshaller = new ThreadLocalOptimizedMarshaller(registry); + + private final TxMessagesFactory factory = new TxMessagesFactory(); + + @BeforeEach + void setUp() { + new TxMessagesSerializationRegistryInitializer().registerFactories(registry); + } + + @Test + void testVacuumTxStatesCommand() { + VacuumTxStatesCommand command = decodeCommand("Bg4CAAAAAAAAAAAqAAAAAAAAAEU="); + + assertEquals(Set.of(uuid()), command.txIds()); + } + + private static UUID uuid() { + return new UUID(42, 69); + } + + private <T extends Command> T deserializeCommand(byte[] bytes) { + return marshaller.unmarshall(bytes); + } + + private <T extends Command> T decodeCommand(String base64) { + return deserializeCommand(Base64.getDecoder().decode(base64)); + } + + @SuppressWarnings("unused") + private void serializeAll() { + List<Command> commands = List.of( + createVacuumTxStatesCommand() + ); + + for (Command c : commands) { + log.info(">>>>> Serialized command: [c={}, base64='{}']", c.getClass().getSimpleName(), encodeCommand(c)); + } + } + + private VacuumTxStatesCommand createVacuumTxStatesCommand() { + return factory.vacuumTxStatesCommand() + .txIds(Set.of(uuid())) + .build(); + } + + private byte[] serializeCommand(Command c) { + return marshaller.marshall(c); + } + + private String encodeCommand(Command c) { + return Base64.getEncoder().encodeToString(serializeCommand(c)); + } +}