From 449c072af225eb3f309e839f5b5a21a3af9fbef6 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Mon, 24 Aug 2026 11:14:30 +0300 Subject: [PATCH 1/6] IGNITE-29006 Refactored TcpDiscoveryMessageSerializer to handle serialization only --- .../ignite/spi/discovery/tcp/ServerImpl.java | 119 ++++++++---------- .../discovery/tcp/TcpDiscoveryIoSession.java | 79 ++++++------ .../tcp/TcpDiscoveryMessageSerializer.java | 68 ---------- .../tcp/internal/ClientMessageHolder.java | 59 +++++++++ .../TcpDiscoveryMessageSerializer.java | 96 ++++++++++++++ 5 files changed, 243 insertions(+), 178 deletions(-) delete mode 100644 modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java create mode 100644 modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java 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 5974e61780743..392ec9c190d54 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 @@ -99,7 +99,6 @@ import org.apache.ignite.internal.util.typedef.C1; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.P1; -import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.S; @@ -122,8 +121,10 @@ import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.DiscoverySpiListener; import org.apache.ignite.spi.discovery.IgniteDiscoveryThread; +import org.apache.ignite.spi.discovery.tcp.internal.ClientMessageHolder; import org.apache.ignite.spi.discovery.tcp.internal.DiscoveryDataPacket; import org.apache.ignite.spi.discovery.tcp.internal.FutureTask; +import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryMessageSerializer; 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; @@ -2859,7 +2860,7 @@ protected class RingMessageWorker extends MessageWorker> { + private class ClientMessageWorker extends MessageWorker { /** Node ID. */ private final UUID clientNodeId; - // The code responsible for sending and receiving messages to and from client nodes represents a special case in ServerImpl, - // as it is split into two separate components. - // One part, ClientMessageWorker, handles only message sending to clients and does not process responses. - // The other part, which reads messages from clients, is implemented in SocketReader. - // Due to this separation, we don't require a full TcpDiscoveryIoSession here - // and can instead extract just the message-writing functionality. - // At the same time, we aim to keep both reading and writing logic encapsulated within TcpDiscoveryIoSession. - // As a result, we need to copy some code from TcpDiscoveryIoSession into the new class, TcpDiscoveryMessageSerializer. - /** */ - private final TcpDiscoveryMessageSerializer clientMsgSer; - /** Session shared with the socket reader serving the same client connection. */ private final TcpDiscoveryIoSession ses; @@ -7518,8 +7507,6 @@ private ClientMessageWorker(TcpDiscoveryIoSession ses, UUID clientNodeId, Ignite this.ses = ses; this.clientNodeId = clientNodeId; - clientMsgSer = new TcpDiscoveryMessageSerializer(ctx); - lastMetricsUpdateMsgTimeNanos = System.nanoTime(); } @@ -7546,24 +7533,19 @@ void metrics(ClusterMetrics metrics) { this.metrics = metrics; } - /** - * @param msg Message. - */ + /** @param msg Discovery Message. */ void addMessage(TcpDiscoveryAbstractMessage msg) { - addMessage(msg, null); + addMessage(new ClientMessageHolder(msg)); } - /** - * @param msg Message. - * @param msgBytes Optional message bytes. - */ - void addMessage(TcpDiscoveryAbstractMessage msg, @Nullable byte[] msgBytes) { - T2 t = new T2<>(msg, msgBytes); + /** @param msgHolder Holder of a Discovery Message to send to the client. */ + void addMessage(ClientMessageHolder msgHolder) { + TcpDiscoveryAbstractMessage msg = msgHolder.message(); if (msg.highPriority()) - queue.addFirst(t); + queue.addFirst(msgHolder); else - queue.add(t); + queue.add(msgHolder); DebugLogger log = messageLogger(msg); @@ -7572,10 +7554,10 @@ void addMessage(TcpDiscoveryAbstractMessage msg, @Nullable byte[] msgBytes) { } /** {@inheritDoc} */ - @Override protected void processMessage(T2 msgT) { + @Override protected void processMessage(ClientMessageHolder msgHolder) { boolean success = false; - TcpDiscoveryAbstractMessage msg = msgT.get1(); + TcpDiscoveryAbstractMessage msg = msgHolder.message(); try { assert msg.verified() : msg; @@ -7601,8 +7583,9 @@ else if (msgLog.isDebugEnabled()) { + getLocalNodeId() + ", rmtNodeId=" + clientNodeId + ", msg=" + msg + ']'); } - writeToSocket(msgT, spi.failureDetectionTimeoutEnabled() ? spi.clientFailureDetectionTimeout() : - spi.getSocketTimeout()); + long timeout = spi.failureDetectionTimeoutEnabled() ? spi.clientFailureDetectionTimeout() : spi.getSocketTimeout(); + + writeMessage(msgHolder, timeout); } } else { @@ -7613,7 +7596,7 @@ else if (msgLog.isDebugEnabled()) { assert topologyInitialized(msg) : msg; - writeToSocket(msgT, spi.getEffectiveSocketTimeout(false)); + writeMessage(msgHolder, spi.getEffectiveSocketTimeout(false)); } boolean clientFailed = msg instanceof TcpDiscoveryNodeFailedMessage && @@ -7643,14 +7626,16 @@ else if (msgLog.isDebugEnabled()) { } /** - * @param msgT Message tuple. + * @param msgHolder Message holder. * @param timeout Timeout. */ - private void writeToSocket(T2 msgT, long timeout) - throws IgniteCheckedException, IOException { - byte[] msgBytes = msgT.get2() == null ? clientMsgSer.serializeMessage(msgT.get1()) : msgT.get2(); + private void writeMessage(ClientMessageHolder msgHolder, long timeout) throws IgniteCheckedException, IOException { + byte[] msgBytes = msgHolder.messageBytes(); - spi.write(ses, msgBytes, timeout); + if (msgBytes != null) + spi.write(ses, msgBytes, timeout); + else + spi.writeMessage(ses, msgHolder.message(), timeout); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 0251712fc95b7..5fe8ec2bab3f7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -29,6 +29,7 @@ import java.net.SocketException; import java.nio.ByteBuffer; import java.security.cert.Certificate; +import java.util.concurrent.locks.ReentrantLock; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import org.apache.ignite.IgniteCheckedException; @@ -36,7 +37,6 @@ import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.direct.DirectMessageReader; -import org.apache.ignite.internal.direct.DirectMessageWriter; import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.util.CommonUtils; @@ -47,6 +47,7 @@ import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; +import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryMessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,8 +67,8 @@ public class TcpDiscoveryIoSession implements AutoCloseable { /** Default size of buffer used for buffering socket in/out. */ private static final int DFLT_SOCK_BUFFER_SIZE = 8192; - /** Size for an intermediate buffer for serializing discovery messages. */ - private static final int MSG_BUFFER_SIZE = 100; + /** Size of the intermediate buffer a message is deserialized through. */ + private static final int READ_BUFFER_SIZE = 100; /** */ private final GridKernalContext ctx; @@ -82,11 +83,14 @@ public class TcpDiscoveryIoSession implements AutoCloseable { private final Socket sock; /** */ - private final DirectMessageWriter msgWriter; + private final TcpDiscoveryMessageSerializer msgSer; /** */ private final DirectMessageReader msgReader; + /** */ + private final ByteBuffer readBuf; + /** Buffered socket output stream. */ private final OutputStream out; @@ -94,10 +98,7 @@ public class TcpDiscoveryIoSession implements AutoCloseable { private final CompositeInputStream in; /** */ - private final ByteBuffer readBuf; - - /** */ - private final ByteBuffer writeBuf; + private final ReentrantLock sesWriteLock = new ReentrantLock(); /** * Creates a new discovery I/O session bound to the given socket. @@ -112,12 +113,11 @@ public class TcpDiscoveryIoSession implements AutoCloseable { this.msgFactory = ctx.messageFactory(); this.log = ctx.log(getClass()); - readBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); - writeBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); - - msgWriter = new DirectMessageWriter(msgFactory); + readBuf = ByteBuffer.allocate(READ_BUFFER_SIZE); msgReader = new DirectMessageReader(msgFactory, null); + msgSer = new TcpDiscoveryMessageSerializer(ctx); + try { int sendBufSize = sock.getSendBufferSize() > 0 ? sock.getSendBufferSize() : DFLT_SOCK_BUFFER_SIZE; int rcvBufSize = sock.getReceiveBufferSize() > 0 ? sock.getReceiveBufferSize() : DFLT_SOCK_BUFFER_SIZE; @@ -137,8 +137,10 @@ public class TcpDiscoveryIoSession implements AutoCloseable { * @throws IgniteCheckedException If serialization fails. */ void writeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException, IOException { + sesWriteLock.lock(); + try { - serializeMessage((Message)msg, out); + msgSer.writeTo(msg, out); out.flush(); } @@ -153,6 +155,9 @@ void writeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException throw new IgniteCheckedException(e); } + finally { + sesWriteLock.unlock(); + } } /** @@ -262,32 +267,6 @@ public Socket socket() { return sock; } - /** - * Serializes a discovery message into given output stream. - * - * @param m Discovery message to serialize. - * @param out Output stream to write serialized message. - * @throws IOException If serialization fails. - */ - void serializeMessage(Message m, OutputStream out) throws IOException, IgniteCheckedException { - DiscoveryMarshalling.marshal(m, ctx, null); - - msgWriter.reset(); - msgWriter.setBuffer(writeBuf); - - boolean finished; - - do { - // Should be cleared before first operation. - writeBuf.clear(); - - finished = MessageSerialization.writeTo(msgFactory, m, msgWriter); - - out.write(writeBuf.array(), 0, writeBuf.position()); - } - while (!finished); - } - /** * Writes raw data to the underlying socket output stream. * @@ -295,9 +274,16 @@ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteChe * @throws IOException If failed. */ void write(byte[] data) throws IOException { - out.write(data); + sesWriteLock.lock(); + + try { + out.write(data); - out.flush(); + out.flush(); + } + finally { + sesWriteLock.unlock(); + } } /** @@ -307,9 +293,16 @@ void write(byte[] data) throws IOException { * @throws IOException If failed. */ void write(int b) throws IOException { - out.write(b); + sesWriteLock.lock(); + + try { + out.write(b); - out.flush(); + out.flush(); + } + finally { + sesWriteLock.unlock(); + } } /** diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java deleted file mode 100644 index ec7cdc569f0c7..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java +++ /dev/null @@ -1,68 +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.spi.discovery.tcp; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageSerializer; -import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; - -/** - * Class is responsible for serializing discovery messages using RU-ready {@link MessageSerializer} mechanism. - *

- * It is used in a special case: when server wants to send discovery messages to clients, it may not have a {@link TcpDiscoveryIoSession} - * to serialize the messages. - * This class enables server to serialize discovery messages anyway, duplicating serialization code from {@link TcpDiscoveryIoSession}. - */ -class TcpDiscoveryMessageSerializer extends TcpDiscoveryIoSession { - /** - * @param ctx Kernal context. - */ - public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { - super(ctx, new Socket() { - @Override public OutputStream getOutputStream() throws IOException { - return null; - } - - @Override public InputStream getInputStream() throws IOException { - return null; - } - }); - } - - /** - * Serializes a discovery message into a byte array. - * - * @param msg Discovery message to serialize. - * @return Serialized byte array containing the message data. - * @throws IgniteCheckedException If serialization fails. - * @throws IOException If serialization fails. - */ - byte[] serializeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException, IOException { - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - serializeMessage((Message)msg, out); - - return out.toByteArray(); - } - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java new file mode 100644 index 0000000000000..8e2f50152b57f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java @@ -0,0 +1,59 @@ +/* + * 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.spi.discovery.tcp.internal; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; +import org.jetbrains.annotations.Nullable; + +/** */ +public class ClientMessageHolder { + /** */ + private final TcpDiscoveryAbstractMessage msg; + + /** */ + private byte[] msgBytes; + + /** */ + public ClientMessageHolder(TcpDiscoveryAbstractMessage msg) { + assert msg != null; + + this.msg = msg; + } + + /** */ + public TcpDiscoveryAbstractMessage message() { + return msg; + } + + /** */ + public synchronized byte @Nullable [] messageBytes() { + return msgBytes; + } + + /** */ + public synchronized void serialize(TcpDiscoveryMessageSerializer ser) throws IgniteCheckedException { + if (msgBytes == null) + msgBytes = ser.serialize(msg); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return msg.toString(); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java new file mode 100644 index 0000000000000..45aa1d9b76743 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java @@ -0,0 +1,96 @@ +/* + * 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.spi.discovery.tcp.internal; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.direct.DirectMessageWriter; +import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; +import org.apache.ignite.internal.util.io.GridByteArrayOutputStream; +import org.apache.ignite.internal.util.nio.MessageSerialization; +import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; + +/** */ +public class TcpDiscoveryMessageSerializer { + /** Size of the intermediate buffer a message is serialized through. */ + private static final int BUFFER_SIZE = 100; + + /** */ + private final GridKernalContext ctx; + + /** */ + private final DirectMessageWriter writer; + + /** */ + private final ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE); + + /** @param ctx Kernal context. */ + public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { + this.ctx = ctx; + + writer = new DirectMessageWriter(ctx.messageFactory()); + } + + /** + * Serializes a discovery message into given output stream. + * + * @param msg Discovery message to serialize. + * @param out Output stream to write serialized message. + * @throws IgniteCheckedException If serialization fails. + * @throws IOException If serialization fails. + */ + public void writeTo(TcpDiscoveryAbstractMessage msg, OutputStream out) throws IgniteCheckedException, IOException { + DiscoveryMarshalling.marshal(msg, ctx, null); + + writer.reset(); + writer.setBuffer(buf); + + boolean finished; + + do { + // Should be cleared before first operation. + buf.clear(); + + finished = MessageSerialization.writeTo(ctx.messageFactory(), msg, writer); + + out.write(buf.array(), 0, buf.position()); + } + while (!finished); + } + + /** + * Serializes a discovery message into a byte array. + * + * @param msg Discovery message to serialize. + * @return Serialized byte array containing the message data. + * @throws IgniteCheckedException If serialization fails. + */ + public byte[] serialize(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException { + try (GridByteArrayOutputStream out = new GridByteArrayOutputStream()) { + writeTo(msg, out); + + return out.toByteArray(); + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to serialize a discovery message: " + msg, e); + } + } +} From 48cbd1122de3db9e1f27267f1226726f9006a209 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Fri, 4 Sep 2026 17:44:25 +0300 Subject: [PATCH 2/6] IGNITE-29006 Fixed lost isEmpty check for client workers. --- .../java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java | 3 +++ 1 file changed, 3 insertions(+) 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 392ec9c190d54..d722f9ff51381 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 @@ -3236,6 +3236,9 @@ private void sendMessageToClients(TcpDiscoveryAbstractMessage msg) { if (spi.ensured(msg)) msgHist.add(msg); + if (clientMsgWorkers.isEmpty()) + return; + ClientMessageHolder sharedMsgHolder = new ClientMessageHolder(msg); for (ClientMessageWorker worker : clientMsgWorkers.values()) { From 18274086c9d0c3c962019206ca18a578e35c0c34 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Sun, 6 Sep 2026 17:00:04 +0300 Subject: [PATCH 3/6] IGNITE-29906 Replaced reentrant lock with synchronized. --- .../discovery/tcp/TcpDiscoveryIoSession.java | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 5fe8ec2bab3f7..fcf4964b268dc 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -29,7 +29,6 @@ import java.net.SocketException; import java.nio.ByteBuffer; import java.security.cert.Certificate; -import java.util.concurrent.locks.ReentrantLock; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import org.apache.ignite.IgniteCheckedException; @@ -97,9 +96,6 @@ public class TcpDiscoveryIoSession implements AutoCloseable { /** Buffered socket input stream. */ private final CompositeInputStream in; - /** */ - private final ReentrantLock sesWriteLock = new ReentrantLock(); - /** * Creates a new discovery I/O session bound to the given socket. * @@ -136,9 +132,7 @@ public class TcpDiscoveryIoSession implements AutoCloseable { * @param msg Message to send to the remote node. * @throws IgniteCheckedException If serialization fails. */ - void writeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException, IOException { - sesWriteLock.lock(); - + synchronized void writeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException, IOException { try { msgSer.writeTo(msg, out); @@ -155,9 +149,6 @@ void writeMessage(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException throw new IgniteCheckedException(e); } - finally { - sesWriteLock.unlock(); - } } /** @@ -273,17 +264,10 @@ public Socket socket() { * @param data Raw data to write. * @throws IOException If failed. */ - void write(byte[] data) throws IOException { - sesWriteLock.lock(); + synchronized void write(byte[] data) throws IOException { + out.write(data); - try { - out.write(data); - - out.flush(); - } - finally { - sesWriteLock.unlock(); - } + out.flush(); } /** @@ -292,17 +276,10 @@ void write(byte[] data) throws IOException { * @param b Integer response. * @throws IOException If failed. */ - void write(int b) throws IOException { - sesWriteLock.lock(); - - try { - out.write(b); + synchronized void write(int b) throws IOException { + out.write(b); - out.flush(); - } - finally { - sesWriteLock.unlock(); - } + out.flush(); } /** From 69ba88a45c303e660d975d0d327548d7aed9e6b2 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Fri, 28 Aug 2026 10:25:27 +0300 Subject: [PATCH 4/6] IGNITE-28851 --- .../JmhDirectMessageReaderBenchmark.java | 5 +- .../ignite/internal/FeatureRegistry.java | 41 ++ .../ignite/internal/MessageProcessor.java | 105 ++++ .../internal/MessageSerializerGenerator.java | 104 +++- .../org/apache/ignite/internal/Order.java | 27 +- .../internal/MessageSerializationContext.java | 88 +++ .../rollingupgrade/feature/IgniteFeature.java | 0 .../internal/direct/DirectMessageReader.java | 31 +- .../internal/direct/DirectMessageWriter.java | 37 +- .../IgniteMessageSerializationContext.java | 233 ++++++++ .../direct/stream/DirectByteBufferStream.java | 81 +-- .../CompressedMessageSerializer.java | 5 +- .../discovery/GridDiscoveryManager.java | 24 + .../feature/IgniteComponentFeatureSet.java | 5 + .../tcp/internal/ClusterStateProvider.java | 5 + .../tcp/internal/GridNioServerWrapper.java | 9 +- .../internal/InboundConnectionHandler.java | 20 + .../tcp/internal/TcpHandshakeExecutor.java | 7 +- .../ignite/spi/discovery/tcp/ClientImpl.java | 8 +- .../ignite/spi/discovery/tcp/ServerImpl.java | 15 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 21 +- .../spi/discovery/tcp/TcpDiscoverySpi.java | 33 -- .../tcp/internal/ClientMessageHolder.java | 15 +- .../TcpDiscoveryMessageSerializer.java | 18 +- .../codegen/MessageProcessorTest.java | 40 ++ .../direct/DirectMarshallingMessagesTest.java | 5 +- .../AbstractMessageSerializationTest.java | 22 +- .../communication/CompressedMessageTest.java | 15 +- ...dIoManagerOrderedUnmarshalFailureTest.java | 5 +- .../cache/CacheMetricsCacheSizeTest.java | 6 +- ...acheContinuousQueryImmutableEntryTest.java | 5 +- .../QueryEntityMessageSerializationTest.java | 5 +- .../TestPluginReleaseFeatures_2_0_0.java | 3 + .../TestPluginReleaseFeatures_2_1_0.java | 5 +- ...ollingUpgradeMessageSerializationTest.java | 510 ++++++++++++++++++ .../message/TestCoreMessage.java | 107 ++++ .../message/TestDefaultRegistryMessage.java | 104 ++++ .../rollingupgrade/message/TestMessage.java | 57 ++ .../message/TestPluginMessage.java | 107 ++++ ...ConfigurationMessageSerializationTest.java | 5 +- ...stributedProcessResultMarshallingTest.java | 9 +- .../IgniteExceptionInNioWorkerSelfTest.java | 5 +- .../communication/MessageMarshalOnceTest.java | 9 +- ...tyBasicPermissionSetSerializationTest.java | 5 +- .../tcp/TestDelayMessageSerializer.java | 5 +- .../DiscoveryUnmarshalVulnerabilityTest.java | 12 +- .../discovery/tcp/TestTcpDiscoverySpi.java | 6 +- .../ignite/testframework/GridTestNode.java | 33 +- .../junits/common/GridCommonAbstractTest.java | 4 +- .../testsuites/IgniteBasicTestSuite.java | 2 + .../codegen/ChildMessageSerializer.java | 5 +- .../CorrectEmptyMessageSerializer.java | 5 +- ...stomMapperEnumFieldsMessageSerializer.java | 9 +- ...aultMapperEnumFieldsMessageSerializer.java | 13 +- .../TestCollectionsMessageSerializer.java | 109 ++-- .../codegen/TestEnumSetMessageSerializer.java | 17 +- .../codegen/TestFeatureConflictMessage.java | 27 + .../codegen/TestFeatureRegistry.java | 30 ++ .../codegen/TestInvalidFeatureMessage.java | 28 + .../codegen/TestInvalidFeatureRegistry.java | 24 + .../codegen/TestMapMessageSerializer.java | 109 ++-- .../TestMarshallableMessageSerializer.java | 5 +- ...stMarshalledArrayMapMessageSerializer.java | 21 +- ...MarshalledCollectionMessageSerializer.java | 9 +- .../TestMarshalledMapMessageSerializer.java | 13 +- .../TestMarshalledMessageSerializer.java | 5 +- ...estMarshalledObjectsMessageSerializer.java | 9 +- .../codegen/TestMessageSerializer.java | 21 +- .../TestRollingUpgradeAwareMessage.java | 45 ++ ...tRollingUpgradeAwareMessageSerializer.java | 127 +++++ .../codegen/TestUnknownFeatureMessage.java | 27 + .../internal/util/nio/GridDirectParser.java | 3 +- .../internal/util/nio/GridNioServer.java | 18 +- .../util/nio/GridNioSessionMetaKey.java | 5 +- .../util/nio/MessageSerialization.java | 32 +- .../communication/MessageReader.java | 23 +- .../communication/MessageSerializer.java | 8 +- .../communication/MessageWriter.java | 23 +- .../zk/internal/DiscoveryMessageParser.java | 6 +- 79 files changed, 2399 insertions(+), 405 deletions(-) create mode 100644 modules/codegen/src/main/java/org/apache/ignite/internal/FeatureRegistry.java create mode 100644 modules/commons/src/main/java/org/apache/ignite/internal/MessageSerializationContext.java rename modules/{core => commons}/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteFeature.java (100%) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestCoreMessage.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestDefaultRegistryMessage.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestMessage.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestPluginMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestFeatureConflictMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestFeatureRegistry.java create mode 100644 modules/core/src/test/resources/codegen/TestInvalidFeatureMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestInvalidFeatureRegistry.java create mode 100644 modules/core/src/test/resources/codegen/TestRollingUpgradeAwareMessage.java create mode 100644 modules/core/src/test/resources/codegen/TestRollingUpgradeAwareMessageSerializer.java create mode 100644 modules/core/src/test/resources/codegen/TestUnknownFeatureMessage.java diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/direct/JmhDirectMessageReaderBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/direct/JmhDirectMessageReaderBenchmark.java index 7c93e2be90b60..6735e9514b407 100644 --- a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/direct/JmhDirectMessageReaderBenchmark.java +++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/direct/JmhDirectMessageReaderBenchmark.java @@ -44,6 +44,7 @@ import org.openjdk.jmh.annotations.Warmup; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.openjdk.jmh.annotations.Mode.Throughput; /** Benchmarks the {@link DirectMessageReader} compressed-field hot path. */ @@ -96,7 +97,7 @@ public void setup() { writer.setBuffer(buf); - boolean finished = writer.writeMessage(msg, true); + boolean finished = writer.writeMessage(msg, true, IGNORED); if (!finished) throw new IllegalStateException("Message does not fit into the buffer."); @@ -111,7 +112,7 @@ public Message compressedMessage() { reader.setBuffer(buf); - Message msg = reader.readMessage(true); + Message msg = reader.readMessage(true, IGNORED); reader.reset(); diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/FeatureRegistry.java b/modules/codegen/src/main/java/org/apache/ignite/internal/FeatureRegistry.java new file mode 100644 index 0000000000000..144d6e87324b2 --- /dev/null +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/FeatureRegistry.java @@ -0,0 +1,41 @@ +/* + * 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; + +/** + * Links the annotated class to the specified {@link IgniteFeature} registry. The registry + * is used to resolve fully qualified names of features that introduced or deprecated fields + * (see {@link Order#introducedBy()} and {@link Order#deprecatedBy()}). + * + *

If this annotation is absent, the Ignite Core Feature Registry is used.

+ * + * @see Order + * @see IgniteFeature + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface FeatureRegistry { + /** @return Class of the feature registry. */ + Class value(); +} diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index 8b4ead738ee22..3a075ca0c010e 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -41,6 +41,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; @@ -48,9 +49,11 @@ import org.apache.ignite.internal.systemview.SystemViewRowAttributeWalkerProcessor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteBiTuple; +import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.MessageSerializerGenerator.DLFT_ENUM_MAPPER_CLS; import static org.apache.ignite.internal.MessageSerializerGenerator.enumType; +import static org.apache.ignite.internal.MessageSerializerGenerator.qualifiedClassName; /** * Annotation processor that generates serialization and deserialization code for classes implementing the {@code Message} interface. @@ -96,6 +99,13 @@ public class MessageProcessor extends AbstractProcessor { /** Checked exception declared by the generated methods. */ static final String IGNITE_CHECKED_EXCEPTION_CLS = "org.apache.ignite.IgniteCheckedException"; + /** Feature registry a message resolves its guards against unless it declares one with {@link FeatureRegistry}. */ + static final String DFLT_FEATURE_REG_CLS = + "org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry"; + + /** */ + static final String IGNITE_FEATURE_CLS = "org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature"; + /** */ public static final String GRID_H2_NULL = "org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2Null"; @@ -425,4 +435,99 @@ TypeMirror type(String clazz) { TypeElement typeElement = elementUtils.getTypeElement(clazz); return typeElement != null ? typeElement.asType() : null; } + + /** */ + @Nullable public static FieldFeatureGuard buildFieldFeatureGuard(ProcessingEnvironment env, VariableElement field) { + Order ann = field.getAnnotation(Order.class); + + String introducingFeature = ann.introducedBy(); + String deprecatingFeature = ann.deprecatedBy(); + + if (introducingFeature.isEmpty() && deprecatingFeature.isEmpty()) + return null; + + if (introducingFeature.equals(deprecatingFeature)) { + printError(env, field, "Elements introducedBy and deprecatedBy of the @Order annotation must not reference the same feature."); + + return null; + } + + String regCls = resolveFeatureRegistry(field.getEnclosingElement()); + + String regName = regCls.substring(regCls.lastIndexOf('.') + 1); + + List conditions = new ArrayList<>(); + + if (!introducingFeature.isEmpty()) { + validateFeature(env, field, introducingFeature, regCls); + + conditions.add("ctx.includeFieldIntroducedBy(" + regName + '.' + introducingFeature + ")"); + } + + if (!deprecatingFeature.isEmpty()) { + validateFeature(env, field, deprecatingFeature, regCls); + + conditions.add("ctx.includeFieldDeprecatedBy(" + regName + '.' + deprecatingFeature + ")"); + } + + return new FieldFeatureGuard(regCls, String.join(" && ", conditions)); + } + + /** */ + private static void validateFeature(ProcessingEnvironment env, VariableElement field, String featureName, String regCls) { + TypeElement regElem = env.getElementUtils().getTypeElement(regCls); + + if (regElem == null) { + printError(env, field, "Cannot resolve the feature registry class [reg=" + regCls + ']'); + + return; + } + + for (Element featureElem : regElem.getEnclosedElements()) { + if (featureElem.getKind() != ElementKind.FIELD || !featureElem.getSimpleName().contentEquals(featureName)) + continue; + + Set mods = featureElem.getModifiers(); + + if (!mods.contains(Modifier.PUBLIC) || !mods.contains(Modifier.STATIC) || !mods.contains(Modifier.FINAL)) + printError(env, field, "Feature constant must be public static final [reg=" + regCls + ", feature=" + featureName + ']'); + else if (!isIgniteFeature(env, featureElem)) + printError(env, field, "Feature constant must be of type IgniteFeature [reg=" + regCls + ", feature=" + featureName + ']'); + + return; + } + + printError(env, field, + "Failed to resolve feature in the registry by its name [reg=" + regCls + ", feature=" + featureName + ']'); + } + + /** */ + private static boolean isIgniteFeature(ProcessingEnvironment env, Element featureElem) { + TypeElement igniteFeatureType = env.getElementUtils().getTypeElement(IGNITE_FEATURE_CLS); + + return igniteFeatureType != null && env.getTypeUtils().isAssignable(featureElem.asType(), igniteFeatureType.asType()); + } + + /** */ + private static void printError(ProcessingEnvironment env, Element el, String msg) { + env.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, el); + } + + /** */ + private static String resolveFeatureRegistry(Element cls) { + FeatureRegistry ann = cls.getAnnotation(FeatureRegistry.class); + + if (ann == null) + return DFLT_FEATURE_REG_CLS; + + try { + return ann.value().getName(); + } + catch (MirroredTypeException e) { + return qualifiedClassName(e.getTypeMirror()); + } + } + + /** */ + public record FieldFeatureGuard(String registry, String expression) { } } diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java index 81680f1b1088c..c3b483c017381 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java @@ -41,6 +41,7 @@ import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.apache.ignite.internal.MessageProcessor.FieldFeatureGuard; import org.apache.ignite.internal.systemview.SystemViewRowAttributeWalkerProcessor; import org.apache.ignite.internal.util.typedef.F; import org.jetbrains.annotations.Nullable; @@ -50,6 +51,7 @@ import static org.apache.ignite.internal.MessageProcessor.GRID_H2_NULL; import static org.apache.ignite.internal.MessageProcessor.KEY_CACHE_OBJECT_CLS; import static org.apache.ignite.internal.MessageProcessor.MESSAGE_INTERFACE; +import static org.apache.ignite.internal.MessageProcessor.buildFieldFeatureGuard; /** Generates {@code *Serializer} classes for {@code Message} types. */ public class MessageSerializerGenerator extends MessageCompanionGenerator { @@ -68,6 +70,9 @@ public class MessageSerializerGenerator extends MessageCompanionGenerator { /** */ private static final String MESSAGE_READER_CLS = "org.apache.ignite.plugin.extensions.communication.MessageReader"; + /** */ + private static final String MESSAGE_SER_CTX_CLS = "org.apache.ignite.internal.MessageSerializationContext"; + /** */ private static final String ENUM_MAPPER_CLS = "org.apache.ignite.plugin.extensions.communication.mappers.EnumMapper"; @@ -145,6 +150,7 @@ public class MessageSerializerGenerator extends MessageCompanionGenerator { imports.add(MESSAGE_SERIALIZER_CLS); imports.add(MESSAGE_WRITER_CLS); imports.add(MESSAGE_READER_CLS); + imports.add(MESSAGE_SER_CTX_CLS); writeClassHeader(writer, "MessageSerializer", serClsName); @@ -195,8 +201,10 @@ private void generateMethods(List fields) throws Exception { private void generateMethod(List code, List fields, boolean write) throws Exception { code.add(indentedLine(METHOD_JAVADOC)); - code.add(indentedLine("@Override public final boolean %s(" + simpleNameWithGeneric(type) + " msg, %s) {", - write ? "writeTo" : "readFrom", write ? "MessageWriter writer" : "MessageReader reader")); + code.add(indentedLine( + "@Override public final boolean %s(" + simpleNameWithGeneric(type) + " msg, %s, MessageSerializationContext ctx) {", + write ? "writeTo" : "readFrom", + write ? "MessageWriter writer" : "MessageReader reader")); indent++; @@ -261,7 +269,7 @@ private void processField(VariableElement field, int opt, boolean write) throws throw new UnsupportedOperationException("You should use ErrorMessage for serialization of throwables."); if (write) - writeField(opt, callExpr(field, true)); + writeField(field, opt, callExpr(field, true)); else readField(field, opt, callExpr(field, false)); } @@ -301,13 +309,29 @@ private String callExpr(VariableElement field, boolean write) throws Exception { * @param opt Case option. * @param writeExpr Writer call expression. */ - private void writeField(int opt, String writeExpr) { + private void writeField(VariableElement field, int opt, String writeExpr) { write.add(indentedLine("case %d:", opt)); indent++; + FieldFeatureGuard guard = buildFieldFeatureGuard(env, field); + + if (guard != null) { + imports.add(guard.registry()); + + write.add(indentedLine("if (%s) {", guard.expression())); + + indent++; + } + returnFalseIf(write, "!" + writeExpr); + if (guard != null) { + indent--; + + write.add(indentedLine("}")); + } + write.add(EMPTY); write.add(indentedLine("writer.incrementState();")); write.add(EMPTY); @@ -335,11 +359,27 @@ private void readField(VariableElement field, int opt, String readExpr) { indent++; + FieldFeatureGuard guard = buildFieldFeatureGuard(env, field); + + if (guard != null) { + imports.add(guard.registry()); + + read.add(indentedLine("if (%s) {", guard.expression())); + + indent++; + } + read.add(indentedLine("%s = %s;", fieldRef(field), readExpr)); read.add(EMPTY); returnFalseIf(read, "!reader.isLastRead()"); + if (guard != null) { + indent--; + + read.add(indentedLine("}")); + } + read.add(EMPTY); read.add(indentedLine("reader.incrementState();")); read.add(EMPTY); @@ -360,13 +400,13 @@ private FieldCall fieldCall(VariableElement field) throws Exception { checkTypeForCompress(type); if (type.getKind().isPrimitive()) - return new FieldCall(capitalizeOnlyFirst(type.getKind().name()), null, false); + return FieldCall.scalar(capitalizeOnlyFirst(type.getKind().name())); if (type.getKind() == TypeKind.ARRAY) { TypeMirror compType = ((ArrayType)type).getComponentType(); if (compType.getKind().isPrimitive()) - return new FieldCall(capitalizeOnlyFirst(compType.getKind().name()) + "Array", null, false); + return FieldCall.scalar(capitalizeOnlyFirst(compType.getKind().name()) + "Array"); if (compType.getKind() == TypeKind.DECLARED) { Element compElem = ((DeclaredType)compType).asElement(); @@ -375,52 +415,52 @@ private FieldCall fieldCall(VariableElement field) throws Exception { imports.add(((QualifiedNameable)compElem).getQualifiedName().toString()); } - return new FieldCall("ObjectArray", messageCollectionItemTypes(field, type), false); + return FieldCall.collection("ObjectArray", messageCollectionItemTypes(field, type), false); } if (type.getKind() == TypeKind.DECLARED) { if (sameType(type, String.class)) - return new FieldCall("String", null, false); + return FieldCall.scalar("String"); if (sameType(type, BitSet.class)) - return new FieldCall("BitSet", null, false); + return FieldCall.scalar("BitSet"); if (sameType(type, UUID.class)) - return new FieldCall("Uuid", null, false); + return FieldCall.scalar("Uuid"); if (sameType(type, IGNITE_UUID_CLS)) - return new FieldCall("IgniteUuid", null, false); + return FieldCall.scalar("IgniteUuid"); if (sameType(type, AFFINITY_TOPOLOGY_VERSION_CLS)) - return new FieldCall("AffinityTopologyVersion", null, false); + return FieldCall.scalar("AffinityTopologyVersion"); if (assignableFrom(erasedType(type), type(Map.class.getName()))) - return new FieldCall("Map", messageCollectionItemTypes(field, type), compress); + return FieldCall.collection("Map", messageCollectionItemTypes(field, type), compress); if (assignableFrom(type, type(KEY_CACHE_OBJECT_CLS))) - return new FieldCall("KeyCacheObject", null, false); + return FieldCall.scalar("KeyCacheObject"); if (assignableFrom(type, type(CACHE_OBJECT_CLS))) - return new FieldCall("CacheObject", null, false); + return FieldCall.scalar("CacheObject"); if (assignableFrom(type, type(GRID_LONG_LIST_CLS))) - return new FieldCall("GridLongList", null, false); + return FieldCall.scalar("GridLongList"); if (assignableFrom(type, type(IGNITE_PRODUCT_VERSION_CLS))) - return new FieldCall("IgniteProductVersion", null, false); + return FieldCall.scalar("IgniteProductVersion"); if (assignableFrom(type, type(GRID_CACHE_VERSION_CLS))) - return new FieldCall("GridCacheVersion", null, false); + return FieldCall.scalar("GridCacheVersion"); if (assignableFrom(type, type(MESSAGE_INTERFACE))) { if (sameType(type, COMPRESSED_MESSAGE_CLASS)) throw new IllegalArgumentException(COMPRESSED_MSG_ERROR); - return new FieldCall("Message", null, compress); + return FieldCall.message(compress); } if (assignableFrom(erasedType(type), type(Collection.class.getName()))) - return new FieldCall("Collection", messageCollectionItemTypes(field, type), false); + return FieldCall.collection("Collection", messageCollectionItemTypes(field, type), false); throw new IllegalArgumentException("Unsupported declared type: " + type); } @@ -756,10 +796,14 @@ private static final class FieldCall { private final boolean compress; /** */ - private FieldCall(String mtd, @Nullable String collDesc, boolean compress) { + private final boolean isSerCtxRequired; + + /** */ + private FieldCall(String mtd, @Nullable String collDesc, boolean compress, boolean isSerCtxRequired) { this.mtd = mtd; this.collDesc = collDesc; this.compress = compress; + this.isSerCtxRequired = isSerCtxRequired; } /** @return Full call expression; {@code valArg}, when given, is passed as the first argument (write side). */ @@ -775,8 +819,26 @@ private String expr(String mtdPrefix, @Nullable String valArg) { if (compress) args.add("true"); + if (isSerCtxRequired) + args.add("ctx"); + return mtdPrefix + mtd + "(" + String.join(", ", args) + ")"; } + + /** */ + private static FieldCall scalar(String mtd) { + return new FieldCall(mtd, null, false, false); + } + + /** */ + private static FieldCall collection(String mtd, String collDesc, boolean compress) { + return new FieldCall(mtd, collDesc, compress, true); + } + + /** */ + private static FieldCall message(boolean compress) { + return new FieldCall("Message", null, compress, true); + } } /** */ diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/Order.java b/modules/codegen/src/main/java/org/apache/ignite/internal/Order.java index 0e4562537c435..3eecc4cbca38c 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/Order.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/Order.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; /** * The annotation specifies the position of a field in the serialized and deserialized byte sequence of a {@code Message} class. @@ -28,14 +29,38 @@ * The {@code value} indicates the index of the field in the serialization order. * Fields annotated with {@code @Order} are processed in ascending order of their index. *

By default, it is assumed that getters and setters are named as the annotated fields, - * e.g. field 'val' should have getters and satters with name 'val' (according Ignite's to code-style). + * e.g. field 'val' should have getters and setters with name 'val' (according Ignite's to code-style). *

This annotation must be used on non-static fields, and access to those fields * should be performed strictly through corresponding getter and setter methods * following the naming convention: {@code fieldName()} for getter and {@code fieldName(Type)} for setter. + * + * @see FeatureRegistry */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface Order { /** @return Order of the field. */ int value(); + + /** + * {@link IgniteFeature} that introduced the field marked with the current annotation. + * + *

An annotated field is included in message serialization only when doing so does not break backward compatibility + * during a Rolling Upgrade.

+ * + * @return Name of the Ignite feature that introduced this field, or an empty string if the field is not guarded. + */ + String introducedBy() default ""; + + /** + * {@link IgniteFeature} that deprecated the field marked with the current annotation. + * + *

Deprecation means that the field is planned for removal in a future release.

+ * + *

An annotated field is excluded from message serialization only when doing so does not break backward compatibility + * during a Rolling Upgrade.

+ * + * @return Name of the Ignite feature that deprecated this field, or an empty string if the field is not guarded. + */ + String deprecatedBy() default ""; } diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/MessageSerializationContext.java b/modules/commons/src/main/java/org/apache/ignite/internal/MessageSerializationContext.java new file mode 100644 index 0000000000000..7db50eac2b28e --- /dev/null +++ b/modules/commons/src/main/java/org/apache/ignite/internal/MessageSerializationContext.java @@ -0,0 +1,88 @@ +/* + * 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; + +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; + +/** Represents the context that determines how message fields are serialized and deserialized when transmitted between nodes. */ +public interface MessageSerializationContext { + /** + * @param feature Feature that deprecated the field. + * @return {@code true} if the message field should be included during message serialization or deserialization. + */ + boolean includeFieldDeprecatedBy(IgniteFeature feature); + + /** + * @param feature Feature that introduced the field. + * @return {@code true} if the message field should be included during message serialization or deserialization. + */ + boolean includeFieldIntroducedBy(IgniteFeature feature); + + /** + * {@link MessageSerializationContext} implementation that instructs the serialization framework to always + * serialize the actual message state: all newly introduced fields are included, and all deprecated fields are + * excluded. + */ + MessageSerializationContext IGNORED = new MessageSerializationContext() { + /** {@inheritDoc} */ + @Override public boolean includeFieldDeprecatedBy(IgniteFeature feature) { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean includeFieldIntroducedBy(IgniteFeature feature) { + return true; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "MessageSerializationContext [IGNORED]"; + } + }; + + /** + * Stub {@link MessageSerializationContext} implementation used when the serialization context has not yet been determined. + * + *

The serialization context is unavailable between connection establishment and serialization protocol negotiation. + * Messages sent during this period cannot rely on the {@link IgniteFeature} mechanism to adjust the message serialization + * in an RU-compatible way.

+ */ + MessageSerializationContext UNNEGOTIATED = new MessageSerializationContext() { + /** {@inheritDoc} */ + @Override public boolean includeFieldDeprecatedBy(IgniteFeature feature) { + throw buildError(feature); + } + + /** {@inheritDoc} */ + @Override public boolean includeFieldIntroducedBy(IgniteFeature feature) { + throw buildError(feature); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "MessageSerializationContext [UNNEGOTIATED]"; + } + + /** */ + private IllegalStateException buildError(IgniteFeature feature) { + return new IllegalStateException( + "A feature-guarded field was serialized before the peer's features were negotiated [feature=" + feature + ']' + ); + } + }; +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteFeature.java b/modules/commons/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteFeature.java similarity index 100% rename from modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteFeature.java rename to modules/commons/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteFeature.java diff --git a/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageReader.java b/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageReader.java index 9ec9b68cc189e..b070629a38ba3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageReader.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageReader.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.Function; import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.direct.state.DirectMessageState; import org.apache.ignite.internal.direct.state.DirectMessageStateItem; import org.apache.ignite.internal.direct.stream.DirectByteBufferStream; @@ -344,7 +345,7 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Nullable @Override public T readMessage(boolean compress) { + @Nullable @Override public T readMessage(boolean compress, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; T msg; @@ -352,10 +353,11 @@ public ByteBuffer getBuffer() { if (compress) msg = readCompressedMessageAndDeserialize( stream, - r -> r.state.item().stream.readMessage(r) + r -> r.state.item().stream.readMessage(r, ctx), + ctx ); else { - msg = stream.readMessage(this); + msg = stream.readMessage(this, ctx); lastRead = stream.lastFinished(); } @@ -397,10 +399,10 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Override public T[] readObjectArray(MessageArrayType type) { + @Override public T[] readObjectArray(MessageArrayType type, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; - T[] msg = stream.readObjectArray(type, this); + T[] msg = stream.readObjectArray(type, this, ctx); lastRead = stream.lastFinished(); @@ -408,10 +410,10 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Override public > C readCollection(MessageCollectionType type) { + @Override public > C readCollection(MessageCollectionType type, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; - C col = stream.readCollection(type, this); + C col = stream.readCollection(type, this, ctx); lastRead = stream.lastFinished(); @@ -419,7 +421,7 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Override public > M readMap(MessageMapType type, boolean compress) { + @Override public > M readMap(MessageMapType type, boolean compress, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; M map; @@ -427,10 +429,11 @@ public ByteBuffer getBuffer() { if (compress) map = readCompressedMessageAndDeserialize( stream, - r -> r.state.item().stream.readMap(type, r) + r -> r.state.item().stream.readMap(type, r, ctx), + ctx ); else { - map = stream.readMap(type, this); + map = stream.readMap(type, this, ctx); lastRead = stream.lastFinished(); } @@ -509,8 +512,12 @@ public ByteBuffer getBuffer() { } /** @return Deserialized object. */ - private T readCompressedMessageAndDeserialize(DirectByteBufferStream stream, Function fun) { - Message msg = stream.readMessage(this); + private T readCompressedMessageAndDeserialize( + DirectByteBufferStream stream, + Function fun, + MessageSerializationContext ctx + ) { + Message msg = stream.readMessage(this, ctx); lastRead = stream.lastFinished(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageWriter.java b/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageWriter.java index 319aae7b7e947..f09632cfbac4e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageWriter.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/direct/DirectMessageWriter.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.UUID; import java.util.function.Consumer; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.direct.state.DirectMessageState; import org.apache.ignite.internal.direct.state.DirectMessageStateItem; import org.apache.ignite.internal.direct.stream.DirectByteBufferStream; @@ -334,17 +335,18 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Override public boolean writeMessage(@Nullable Message msg, boolean compress) { + @Override public boolean writeMessage(@Nullable Message msg, boolean compress, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; if (compress) writeCompressedMessage( - w -> w.state.item().stream.writeMessage(msg, w), + w -> w.state.item().stream.writeMessage(msg, w, ctx), msg == null, - stream + stream, + ctx ); else - stream.writeMessage(msg, this); + stream.writeMessage(msg, this, ctx); return stream.lastFinished(); } @@ -377,35 +379,36 @@ public ByteBuffer getBuffer() { } /** {@inheritDoc} */ - @Override public boolean writeObjectArray(T[] arr, MessageArrayType type) { + @Override public boolean writeObjectArray(T[] arr, MessageArrayType type, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; - stream.writeObjectArray(arr, type, this); + stream.writeObjectArray(arr, type, this, ctx); return stream.lastFinished(); } /** {@inheritDoc} */ - @Override public boolean writeCollection(Collection col, MessageCollectionType type) { + @Override public boolean writeCollection(Collection col, MessageCollectionType type, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; - stream.writeCollection(col, type, this); + stream.writeCollection(col, type, this, ctx); return stream.lastFinished(); } /** {@inheritDoc} */ - @Override public boolean writeMap(Map map, MessageMapType type, boolean compress) { + @Override public boolean writeMap(Map map, MessageMapType type, boolean compress, MessageSerializationContext ctx) { DirectByteBufferStream stream = curStream; if (compress) writeCompressedMessage( - w -> w.state.item().stream.writeMap(map, type, w), + w -> w.state.item().stream.writeMap(map, type, w, ctx), map == null, - stream + stream, + ctx ); else - stream.writeMap(map, type, this); + stream.writeMap(map, type, this, ctx); return stream.lastFinished(); } @@ -485,8 +488,14 @@ public ByteBuffer getBuffer() { * @param consumer Consumer. * @param isNull {@code True} if message is null. * @param stream Byte buffer stream. + * @param ctx Serialization context. */ - private void writeCompressedMessage(Consumer consumer, boolean isNull, DirectByteBufferStream stream) { + private void writeCompressedMessage( + Consumer consumer, + boolean isNull, + DirectByteBufferStream stream, + MessageSerializationContext ctx + ) { if (isNull) { stream.writeShort(Short.MIN_VALUE); @@ -536,7 +545,7 @@ private void writeCompressedMessage(Consumer consumer, bool stream.serializeFinished(true); } - stream.writeMessage(stream.compressedMessage(), this); + stream.writeMessage(stream.compressedMessage(), this, ctx); if (stream.lastFinished()) { stream.compressedMessage(null); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java b/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java new file mode 100644 index 0000000000000..ddfe4a3693c48 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java @@ -0,0 +1,233 @@ +/* + * 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.direct; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.apache.ignite.Ignite; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.MessageSerializationContext; +import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException; +import org.jetbrains.annotations.Nullable; + +/** */ +public class IgniteMessageSerializationContext implements MessageSerializationContext { + /** */ + @GridToStringInclude + private final Map ctxByComponent; + + /** */ + private IgniteMessageSerializationContext(Map ctxByComponent) { + this.ctxByComponent = ctxByComponent; + } + + /** {@inheritDoc} */ + @Override public boolean includeFieldIntroducedBy(IgniteFeature feature) { + return componentContext(feature).includeFieldIntroducedBy(feature.id()); + } + + /** {@inheritDoc} */ + @Override public boolean includeFieldDeprecatedBy(IgniteFeature feature) { + return componentContext(feature).includeFieldDeprecatedBy(feature.id()); + } + + /** */ + private ComponentMessageSerializationContext componentContext(IgniteFeature feature) { + ComponentMessageSerializationContext cmpCtx = ctxByComponent.get(feature.componentName()); + + if (cmpCtx == null) { + throw new IllegalStateException( + "A field is guarded by a feature of an undeclared component" + + " [feature=" + feature + + ", component=" + feature.componentName() + + ", declaredComponents=" + ctxByComponent.keySet() + ']' + ); + } + + return cmpCtx; + } + + /** */ + public static IgniteMessageSerializationContext buildForPeers( + Ignite loc, + ClusterNode rmt + ) throws UnsupportedNodeVersionException, ClusterTopologyCheckedException { + GridKernalContext ctx = ((IgniteEx)loc).context(); + + return buildForPeers(ctx.localNodeFeatures(), ctx.discovery().resolveNodeFeatures(rmt)); + } + + /** */ + public static IgniteMessageSerializationContext buildForPeers( + IgniteNodeFeatureSet loc, + IgniteNodeFeatureSet rmt + ) throws UnsupportedNodeVersionException { + assert loc != null; + + if (rmt == null) { + throw new UnsupportedNodeVersionException("Failed to build the message serialization context for the remote node." + + " The remote node's feature set is unavailable."); + } + + Set components = new HashSet<>(loc.components()); + + components.addAll(rmt.components()); + + Map ctxByComponent = new HashMap<>(); + + for (String cmp : components) { + ComponentMessageSerializationContext ctx = resolveComponentSerializationContext( + cmp, + loc.componentFeatures(cmp), + rmt.componentFeatures(cmp) + ); + + ctxByComponent.put(cmp, ctx); + } + + return new IgniteMessageSerializationContext(ctxByComponent); + } + + /** */ + private static ComponentMessageSerializationContext resolveComponentSerializationContext( + String cmpName, + @Nullable IgniteComponentFeatureSet locCmpFeatures, + @Nullable IgniteComponentFeatureSet rmtCmpFeatures + ) throws UnsupportedNodeVersionException { + assert locCmpFeatures != null || rmtCmpFeatures != null; + + // One of the sides has no component configured. This may happen when one side uses an RU-unaware plugin version + // while the other uses an RU-aware version. In this case, all newly introduced fields are skipped, while all + // deprecated fields are included. + if (locCmpFeatures == null || rmtCmpFeatures == null) + return new ComponentMessageSerializationContext(null, null); + + int c = locCmpFeatures.version().compareTo(rmtCmpFeatures.version()); + + if (c == 0) { + assert locCmpFeatures.features().equals(rmtCmpFeatures.features()); + + // Both newly introduced and deprecated fields are included. During an RU, a node builds messages according + // to both the old logical version (while RU is in progress, deprecated fields are used and newly introduced + // fields are not) and the new logical version (after RU is finished, newly introduced fields are used and + // deprecated fields are not). + return new ComponentMessageSerializationContext(null, rmtCmpFeatures.features()); + } + else { + IgniteComponentFeatureSet src = c < 0 ? locCmpFeatures : rmtCmpFeatures; + IgniteComponentFeatureSet target = c < 0 ? rmtCmpFeatures : locCmpFeatures; + + if (!src.isUpgradableTo(target)) { + throw new UnsupportedNodeVersionException("Remote node component versions are not supported" + + " [component=" + cmpName + + ", locComponent=" + locCmpFeatures + + ", rmtComponent=" + rmtCmpFeatures + ']'); + } + + // The old version dictates the serialization rules. + return new ComponentMessageSerializationContext(src.features(), src.features()); + } + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(IgniteMessageSerializationContext.class, this); + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + return Objects.equals(ctxByComponent, ((IgniteMessageSerializationContext)o).ctxByComponent); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hashCode(ctxByComponent); + } + + /** */ + private static final class ComponentMessageSerializationContext { + /** */ + @GridToStringInclude + @Nullable private final IgniteFeatureSet excludedDeprecatedFields; + + /** */ + @GridToStringInclude + @Nullable private final IgniteFeatureSet includedIntroducedFields; + + /** */ + private ComponentMessageSerializationContext( + @Nullable IgniteFeatureSet excludedDeprecatedFields, + @Nullable IgniteFeatureSet includedIntroducedFields + ) { + this.excludedDeprecatedFields = excludedDeprecatedFields; + this.includedIntroducedFields = includedIntroducedFields; + } + + /** */ + boolean includeFieldIntroducedBy(int featureId) { + return includedIntroducedFields != null && includedIntroducedFields.contains(featureId); + } + + /** */ + boolean includeFieldDeprecatedBy(int featureId) { + return excludedDeprecatedFields == null || !excludedDeprecatedFields.contains(featureId); + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + ComponentMessageSerializationContext other = (ComponentMessageSerializationContext)o; + + return Objects.equals(excludedDeprecatedFields, other.excludedDeprecatedFields) + && Objects.equals(includedIntroducedFields, other.includedIntroducedFields); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hash(includedIntroducedFields, excludedDeprecatedFields); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(ComponentMessageSerializationContext.class, this); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java b/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java index d2176dfb6f617..cdabbaa7c5699 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java @@ -34,6 +34,7 @@ import java.util.function.Supplier; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.binary.StringWriter; import org.apache.ignite.internal.managers.communication.CompressedMessage; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; @@ -932,11 +933,12 @@ public void writeGridLongList(@Nullable GridLongList val) { /** * @param msg Message. * @param writer Writer. + * @param ctx Serialization context. */ - public void writeMessage(Message msg, MessageWriter writer) { + public void writeMessage(Message msg, MessageWriter writer, MessageSerializationContext ctx) { if (msg != null) { if (buf.hasRemaining()) - nestedWrite(writer, () -> MessageSerialization.writeTo(msgFactory, msg, writer)); + nestedWrite(writer, () -> MessageSerialization.writeTo(msgFactory, msg, writer, ctx)); else lastFinished = false; } @@ -948,8 +950,9 @@ public void writeMessage(Message msg, MessageWriter writer) { * @param arr Array. * @param type Type. * @param writer Writer. + * @param ctx Serialization context. */ - public void writeObjectArray(T[] arr, MessageArrayType type, MessageWriter writer) { + public void writeObjectArray(T[] arr, MessageArrayType type, MessageWriter writer, MessageSerializationContext ctx) { if (arr != null) { int len = arr.length; @@ -966,7 +969,7 @@ public void writeObjectArray(T[] arr, MessageArrayType type, MessageWriter w if (arrCur == NULL) arrCur = arr[arrPos++]; - write(type.valueType(), arrCur, writer); + write(type.valueType(), arrCur, writer, ctx); if (!lastFinished) return; @@ -984,11 +987,12 @@ public void writeObjectArray(T[] arr, MessageArrayType type, MessageWriter w * @param col Collection. * @param type Type. * @param writer Writer. + * @param ctx Serialization context. */ - public void writeCollection(Collection col, MessageCollectionType type, MessageWriter writer) { + public void writeCollection(Collection col, MessageCollectionType type, MessageWriter writer, MessageSerializationContext ctx) { if (col != null) { if (col instanceof List && col instanceof RandomAccess) - writeRandomAccessList((List)col, type, writer); + writeRandomAccessList((List)col, type, writer, ctx); else { if (it == null) { writeInt(col.size()); @@ -1003,7 +1007,7 @@ public void writeCollection(Collection col, MessageCollectionType type, M if (cur == NULL) cur = it.next(); - write(type.valueType(), cur, writer); + write(type.valueType(), cur, writer, ctx); if (!lastFinished) return; @@ -1022,8 +1026,14 @@ public void writeCollection(Collection col, MessageCollectionType type, M * @param list List. * @param type Type. * @param writer Writer. - */ - private void writeRandomAccessList(List list, MessageCollectionType type, MessageWriter writer) { + * @param ctx Serialization context. + */ + private void writeRandomAccessList( + List list, + MessageCollectionType type, + MessageWriter writer, + MessageSerializationContext ctx + ) { assert list instanceof RandomAccess; int size = list.size(); @@ -1041,7 +1051,7 @@ private void writeRandomAccessList(List list, MessageCollectionType type, if (arrCur == NULL) arrCur = list.get(arrPos++); - write(type.valueType(), arrCur, writer); + write(type.valueType(), arrCur, writer, ctx); if (!lastFinished) return; @@ -1056,8 +1066,9 @@ private void writeRandomAccessList(List list, MessageCollectionType type, * @param map Map. * @param type Type. * @param writer Writer. + * @param ctx Serialization context. */ - public void writeMap(Map map, MessageMapType type, MessageWriter writer) { + public void writeMap(Map map, MessageMapType type, MessageWriter writer, MessageSerializationContext ctx) { if (map != null) { if (mapIt == null) { writeInt(map.size()); @@ -1077,7 +1088,7 @@ public void writeMap(Map map, MessageMapType type, MessageWriter wr e = (Map.Entry)mapCur; if (!keyDone) { - write(type.keyType(), e.getKey(), writer); + write(type.keyType(), e.getKey(), writer, ctx); if (!lastFinished) return; @@ -1085,7 +1096,7 @@ public void writeMap(Map map, MessageMapType type, MessageWriter wr keyDone = true; } - write(type.valueType(), e.getValue(), writer); + write(type.valueType(), e.getValue(), writer, ctx); if (!lastFinished) return; @@ -1561,9 +1572,10 @@ public GridLongList readGridLongList() { /** * @param reader Reader. + * @param ctx Serialization context. * @return Message. */ - public T readMessage(MessageReader reader) { + public T readMessage(MessageReader reader, MessageSerializationContext ctx) { if (!msgTypeDone) { if (buf.remaining() < Message.DIRECT_TYPE_SIZE) { lastFinished = false; @@ -1582,7 +1594,7 @@ public T readMessage(MessageReader reader) { try { reader.beforeNestedRead(); - lastFinished = MessageSerialization.readFrom(msgFactory, msg, reader); + lastFinished = MessageSerialization.readFrom(msgFactory, msg, reader, ctx); } finally { reader.afterNestedRead(lastFinished); @@ -1606,9 +1618,10 @@ public T readMessage(MessageReader reader) { /** * @param type Item type. * @param reader Reader. + * @param ctx Serialization context. * @return Array. */ - public T[] readObjectArray(MessageArrayType type, MessageReader reader) { + public T[] readObjectArray(MessageArrayType type, MessageReader reader, MessageSerializationContext ctx) { if (readSize == -1) { int size = readInt(); @@ -1623,7 +1636,7 @@ public T[] readObjectArray(MessageArrayType type, MessageReader reader) { objArr = type.clazz() != null ? (Object[])Array.newInstance(type.clazz(), readSize) : new Object[readSize]; for (int i = readItems; i < readSize; i++) { - Object item = read(type.valueType(), reader); + Object item = read(type.valueType(), reader, ctx); if (!lastFinished) return null; @@ -1650,9 +1663,10 @@ public T[] readObjectArray(MessageArrayType type, MessageReader reader) { * * @param type Item type. * @param reader Reader. + * @param ctx Serialization context. * @return {@link ArrayList}, {@link HashSet} or {@link EnumSet}. */ - public > C readCollection(MessageCollectionType type, MessageReader reader) { + public > C readCollection(MessageCollectionType type, MessageReader reader, MessageSerializationContext ctx) { if (readSize == -1) { int size = readInt(); @@ -1667,7 +1681,7 @@ public > C readCollection(MessageCollectionType type, Me col = newCollection(type); for (int i = readItems; i < readSize; i++) { - Object item = read(type.valueType(), reader); + Object item = read(type.valueType(), reader, ctx); if (!lastFinished) return null; @@ -1702,9 +1716,10 @@ private Collection newCollection(MessageCollectionType type) { /** * @param type Value type. * @param reader Reader. + * @param ctx Serialization context. * @return Map. */ - public > M readMap(MessageMapType type, MessageReader reader) { + public > M readMap(MessageMapType type, MessageReader reader, MessageSerializationContext ctx) { if (readSize == -1) { int size = readInt(); @@ -1720,7 +1735,7 @@ private Collection newCollection(MessageCollectionType type) { for (int i = readItems; i < readSize; i++) { if (!keyDone) { - Object key = read(type.keyType(), reader); + Object key = read(type.keyType(), reader, ctx); if (!lastFinished) return null; @@ -1729,7 +1744,7 @@ private Collection newCollection(MessageCollectionType type) { keyDone = true; } - Object val = read(type.valueType(), reader); + Object val = read(type.valueType(), reader, ctx); if (!lastFinished) return null; @@ -2009,8 +2024,9 @@ T readArrayLE(ArrayCreator creator, int typeSize, int lenShift, long off) * @param type Type. * @param val Value. * @param writer Writer. + * @param ctx Serialization context. */ - protected void write(MessageType type, Object val, MessageWriter writer) { + protected void write(MessageType type, Object val, MessageWriter writer, MessageSerializationContext ctx) { switch (type.type()) { case BYTE: writeByte((Byte)val); @@ -2138,17 +2154,17 @@ protected void write(MessageType type, Object val, MessageWriter writer) break; case MAP: - nestedWrite(writer, () -> writer.writeMap((Map)val, (MessageMapType)type)); + nestedWrite(writer, () -> writer.writeMap((Map)val, (MessageMapType)type, ctx)); break; case COLLECTION: - nestedWrite(writer, () -> writer.writeCollection((Collection)val, (MessageCollectionType)type)); + nestedWrite(writer, () -> writer.writeCollection((Collection)val, (MessageCollectionType)type, ctx)); break; case ARRAY: - nestedWrite(writer, () -> writer.writeObjectArray((V[])val, (MessageArrayType)type)); + nestedWrite(writer, () -> writer.writeObjectArray((V[])val, (MessageArrayType)type, ctx)); break; @@ -2158,7 +2174,7 @@ protected void write(MessageType type, Object val, MessageWriter writer) break; case MSG: - writeMessage((Message)val, writer); + writeMessage((Message)val, writer, ctx); break; @@ -2182,9 +2198,10 @@ private void nestedWrite(MessageWriter writer, BooleanSupplier s) { /** * @param type Type. * @param reader Reader. + * @param ctx Serialization context. * @return Value. */ - protected Object read(MessageType type, MessageReader reader) { + protected Object read(MessageType type, MessageReader reader, MessageSerializationContext ctx) { switch (type.type()) { case BYTE: return readByte(); @@ -2262,19 +2279,19 @@ protected Object read(MessageType type, MessageReader reader) { return readGridLongList(); case MAP: - return nestedRead(reader, () -> reader.readMap((MessageMapType)type)); + return nestedRead(reader, () -> reader.readMap((MessageMapType)type, ctx)); case COLLECTION: - return nestedRead(reader, () -> reader.readCollection((MessageCollectionType)type)); + return nestedRead(reader, () -> reader.readCollection((MessageCollectionType)type, ctx)); case ARRAY: - return nestedRead(reader, () -> reader.readObjectArray((MessageArrayType)type)); + return nestedRead(reader, () -> reader.readObjectArray((MessageArrayType)type, ctx)); case ENUM: return ((MessageEnumType)type).decode(readByte()); case MSG: - return readMessage(reader); + return readMessage(reader, ctx); default: throw new IllegalArgumentException("Unknown type: " + type); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/CompressedMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/CompressedMessageSerializer.java index 7661a11421972..4f96b37f88e63 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/CompressedMessageSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/CompressedMessageSerializer.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.plugin.extensions.communication.MessageReader; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.plugin.extensions.communication.MessageWriter; @@ -28,7 +29,7 @@ /** Message serializer for compressed message. */ public class CompressedMessageSerializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(CompressedMessage msg, MessageWriter writer) { + @Override public boolean writeTo(CompressedMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -74,7 +75,7 @@ public class CompressedMessageSerializer implements MessageSerializer T readMessage() throws IgniteCheckedException, IOException { readBuf.limit(read); - finished = MessageSerialization.readFrom(msgFactory, msg, msgReader); + finished = MessageSerialization.readFrom(msgFactory, msg, msgReader, serCtx); // Server Discovery only sends next message to next Server upon receiving a receipt for the previous one. // This behaviour guarantees that we never read a next message from the buffer right after the end of 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 7b8490ded8385..c2e1f64295681 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 @@ -58,8 +58,6 @@ import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi; 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; @@ -98,7 +96,6 @@ 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; @@ -1680,36 +1677,6 @@ Socket createSocket() throws IOException { } } - /** */ - 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 = locNode.features().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=" + locNode.features() + - ", rmtComponents=" + rmtFeatures + ']'); - } - } - } - /** * Writes raw data to the session socket. * diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java index 8e2f50152b57f..e663ab47795fc 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/ClientMessageHolder.java @@ -17,7 +17,10 @@ package org.apache.ignite.spi.discovery.tcp.internal; +import java.util.HashMap; +import java.util.Map; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.jetbrains.annotations.Nullable; @@ -27,7 +30,7 @@ public class ClientMessageHolder { private final TcpDiscoveryAbstractMessage msg; /** */ - private byte[] msgBytes; + private final Map bytesByCtx = new HashMap<>(1); /** */ public ClientMessageHolder(TcpDiscoveryAbstractMessage msg) { @@ -42,14 +45,14 @@ public TcpDiscoveryAbstractMessage message() { } /** */ - public synchronized byte @Nullable [] messageBytes() { - return msgBytes; + public synchronized byte @Nullable [] messageBytes(MessageSerializationContext ctx) { + return bytesByCtx.get(ctx); } /** */ - public synchronized void serialize(TcpDiscoveryMessageSerializer ser) throws IgniteCheckedException { - if (msgBytes == null) - msgBytes = ser.serialize(msg); + public synchronized void serialize(TcpDiscoveryMessageSerializer ser, MessageSerializationContext ctx) throws IgniteCheckedException { + if (!bytesByCtx.containsKey(ctx)) + bytesByCtx.put(ctx, ser.serialize(msg, ctx)); } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java index 45aa1d9b76743..9102876206bd4 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryMessageSerializer.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.direct.DirectMessageWriter; import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; import org.apache.ignite.internal.util.io.GridByteArrayOutputStream; @@ -54,10 +55,15 @@ public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { * * @param msg Discovery message to serialize. * @param out Output stream to write serialized message. + * @param serCtx Serialization context the recipient agreed on. * @throws IgniteCheckedException If serialization fails. * @throws IOException If serialization fails. */ - public void writeTo(TcpDiscoveryAbstractMessage msg, OutputStream out) throws IgniteCheckedException, IOException { + public void writeTo( + TcpDiscoveryAbstractMessage msg, + OutputStream out, + MessageSerializationContext serCtx + ) throws IgniteCheckedException, IOException { DiscoveryMarshalling.marshal(msg, ctx, null); writer.reset(); @@ -69,7 +75,7 @@ public void writeTo(TcpDiscoveryAbstractMessage msg, OutputStream out) throws Ig // Should be cleared before first operation. buf.clear(); - finished = MessageSerialization.writeTo(ctx.messageFactory(), msg, writer); + finished = MessageSerialization.writeTo(ctx.messageFactory(), msg, writer, serCtx); out.write(buf.array(), 0, buf.position()); } @@ -80,12 +86,16 @@ public void writeTo(TcpDiscoveryAbstractMessage msg, OutputStream out) throws Ig * Serializes a discovery message into a byte array. * * @param msg Discovery message to serialize. + * @param serCtx Serialization context the recipient agreed on. * @return Serialized byte array containing the message data. * @throws IgniteCheckedException If serialization fails. */ - public byte[] serialize(TcpDiscoveryAbstractMessage msg) throws IgniteCheckedException { + public byte[] serialize( + TcpDiscoveryAbstractMessage msg, + MessageSerializationContext serCtx + ) throws IgniteCheckedException { try (GridByteArrayOutputStream out = new GridByteArrayOutputStream()) { - writeTo(msg, out); + writeTo(msg, out, serCtx); return out.toByteArray(); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java index 1105f85cf3fd9..1f8a288af4d3f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java @@ -93,6 +93,46 @@ public void testProcessorGeneratesSerializer() { .hasSourceEquivalentTo(javaFile("TestMessageMarshaller.java")); } + /** */ + @Test + public void testRollingUpgradeAwareMessage() { + Compilation compilation = compile("TestFeatureRegistry.java", "TestRollingUpgradeAwareMessage.java"); + + assertThat(compilation).succeeded(); + + assertThat(compilation) + .generatedSourceFile("org.apache.ignite.internal.TestRollingUpgradeAwareMessageSerializer") + .hasSourceEquivalentTo(javaFile("TestRollingUpgradeAwareMessageSerializer.java")); + } + + /** */ + @Test + public void testUnknownFeatureConstantRejected() { + Compilation compilation = compile("TestUnknownFeatureMessage.java"); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining("Failed to resolve feature in the registry by its name [reg="); + assertThat(compilation).hadErrorContaining(", feature=NO_SUCH_FEATURE]"); + } + + /** */ + @Test + public void testSameFeatureInBothGuardsRejected() { + Compilation compilation = compile("TestFeatureConflictMessage.java"); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining("must not reference the same feature"); + } + + /** */ + @Test + public void testFeatureConstantOfWrongTypeRejected() { + Compilation compilation = compile("TestInvalidFeatureRegistry.java", "TestInvalidFeatureMessage.java"); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining("must be of type IgniteFeature [reg="); + } + /** */ @Test public void testCollectionsMessage() { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/direct/DirectMarshallingMessagesTest.java b/modules/core/src/test/java/org/apache/ignite/internal/direct/DirectMarshallingMessagesTest.java index e9874e03055fc..a3e66c91819bd 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/direct/DirectMarshallingMessagesTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/direct/DirectMarshallingMessagesTest.java @@ -31,6 +31,7 @@ import org.apache.ignite.transactions.TransactionIsolation; import org.junit.Test; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.apache.ignite.transactions.TransactionIsolation.READ_COMMITTED; import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; import static org.apache.ignite.transactions.TransactionIsolation.SERIALIZABLE; @@ -139,7 +140,7 @@ private T doMarshalUnmarshalChunked(T srcMsg) { writer.setBuffer(chunk); - fullyWritten = writer.writeMessage(srcMsg, false); + fullyWritten = writer.writeMessage(srcMsg, false, IGNORED); chunk.flip(); @@ -168,7 +169,7 @@ private T doMarshalUnmarshalChunked(T srcMsg) { reader.setBuffer(chunk); - resMsg = reader.readMessage(false); + resMsg = reader.readMessage(false, IGNORED); pos += chunk.position(); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java index bfa3c8e314204..48e861cc6c98a 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheObject; @@ -47,6 +48,7 @@ import org.junit.Test; import static java.lang.Integer.MAX_VALUE; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.apache.ignite.plugin.extensions.communication.CollectionImplementationType.HASH_SET; import static org.junit.Assert.assertEquals; @@ -106,13 +108,13 @@ private void checkSerializationAndDeserializationConsistency( initializeMessage(msg); - while (!MessageSerialization.writeTo(msgFactory, msg, writer)) { + while (!MessageSerialization.writeTo(msgFactory, msg, writer, IGNORED)) { // No-op. } msg = msgFactory.create(msgType); - while (!MessageSerialization.readFrom(msgFactory, msg, reader)) { + while (!MessageSerialization.readFrom(msgFactory, msg, reader, IGNORED)) { // No-op. } @@ -295,22 +297,22 @@ private boolean writeField(Class type) { } /** {@inheritDoc} */ - @Override public boolean writeMessage(Message val, boolean compress) { + @Override public boolean writeMessage(Message val, boolean compress, MessageSerializationContext ctx) { return writeField(Message.class); } /** {@inheritDoc} */ - @Override public boolean writeObjectArray(T[] arr, MessageArrayType type) { + @Override public boolean writeObjectArray(T[] arr, MessageArrayType type, MessageSerializationContext ctx) { return writeField(Object[].class); } /** {@inheritDoc} */ - @Override public boolean writeCollection(Collection col, MessageCollectionType type) { + @Override public boolean writeCollection(Collection col, MessageCollectionType type, MessageSerializationContext ctx) { return writeField(type.collectionImplementationType() == HASH_SET ? Set.class : Collection.class); } /** {@inheritDoc} */ - @Override public boolean writeMap(Map map, MessageMapType type, boolean compress) { + @Override public boolean writeMap(Map map, MessageMapType type, boolean compress, MessageSerializationContext ctx) { return writeField(type.linked() ? LinkedHashMap.class : HashMap.class); } @@ -537,7 +539,7 @@ private void readField(Class type) { } /** {@inheritDoc} */ - @Override public T readMessage(boolean compress) { + @Override public T readMessage(boolean compress, MessageSerializationContext ctx) { readField(Message.class); return null; @@ -565,21 +567,21 @@ private void readField(Class type) { } /** {@inheritDoc} */ - @Override public T[] readObjectArray(MessageArrayType type) { + @Override public T[] readObjectArray(MessageArrayType type, MessageSerializationContext ctx) { readField(Object[].class); return null; } /** {@inheritDoc} */ - @Override public > C readCollection(MessageCollectionType type) { + @Override public > C readCollection(MessageCollectionType type, MessageSerializationContext ctx) { readField(type.collectionImplementationType() == HASH_SET ? Set.class : Collection.class); return null; } /** {@inheritDoc} */ - @Override public > M readMap(MessageMapType type, boolean compress) { + @Override public > M readMap(MessageMapType type, boolean compress, MessageSerializationContext ctx) { readField(type.linked() ? LinkedHashMap.class : HashMap.class); return null; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/CompressedMessageTest.java b/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/CompressedMessageTest.java index 046dbbb6f55b0..28c5b23e347be 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/CompressedMessageTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/CompressedMessageTest.java @@ -42,6 +42,7 @@ import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -70,7 +71,7 @@ public void testWriteReadHugeMessage() { ByteBuffer msgBuf = ByteBuffer.allocate(40_960); while (!finished) { - finished = writer.writeMessage(fullMsg, true); + finished = writer.writeMessage(fullMsg, true, IGNORED); if (checkChunkCnt) { DirectMessageState state = U.field(writer, "state"); @@ -104,7 +105,7 @@ public void testWriteReadHugeMessage() { reader.setBuffer(msgBuf); - Message readMsg = reader.readMessage(true); + Message readMsg = reader.readMessage(true, IGNORED); assertTrue(readMsg instanceof GridDhtPartitionsFullMessage); @@ -133,7 +134,7 @@ public void testReadFailsOnNullChunk() { reader.setBuffer(buf); GridTestUtils.assertThrows(null, - () -> MessageSerialization.readFrom(MSG_FACTORY, new CompressedMessage(), reader), + () -> MessageSerialization.readFrom(MSG_FACTORY, new CompressedMessage(), reader, IGNORED), IgniteException.class, "unexpected null chunk"); } @@ -156,7 +157,7 @@ public void testReadFailsOnNegativeDataSize() { reader.setBuffer(buf); GridTestUtils.assertThrows(null, - () -> MessageSerialization.readFrom(MSG_FACTORY, new CompressedMessage(), reader), + () -> MessageSerialization.readFrom(MSG_FACTORY, new CompressedMessage(), reader, IGNORED), IgniteException.class, "Invalid compressed message data size"); } @@ -217,7 +218,7 @@ public void testReadFailsOnTruncatedPayload() { writer.setBuffer(tmpBuf); - assertTrue(writer.writeMessage(fullMessage(), false)); + assertTrue(writer.writeMessage(fullMessage(), false, IGNORED)); tmpBuf.flip(); @@ -231,7 +232,7 @@ public void testReadFailsOnTruncatedPayload() { wireWriter.setBuffer(wire); - assertTrue(wireWriter.writeMessage(compressedMsg, false)); + assertTrue(wireWriter.writeMessage(compressedMsg, false, IGNORED)); wire.flip(); @@ -239,7 +240,7 @@ public void testReadFailsOnTruncatedPayload() { reader.setBuffer(wire); - GridTestUtils.assertThrows(null, () -> reader.readMessage(true), IgniteException.class, "ended unexpectedly"); + GridTestUtils.assertThrows(null, () -> reader.readMessage(true, IGNORED), IgniteException.class, "ended unexpectedly"); } /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/GridIoManagerOrderedUnmarshalFailureTest.java b/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/GridIoManagerOrderedUnmarshalFailureTest.java index 4b51337eac099..2a5ce91b96302 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/GridIoManagerOrderedUnmarshalFailureTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/managers/communication/GridIoManagerOrderedUnmarshalFailureTest.java @@ -28,6 +28,7 @@ import org.apache.ignite.internal.CoreMessagesProvider; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.processors.cache.CacheObjectContext; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.Marshaller; @@ -186,7 +187,7 @@ private static class FailingUnmarshalMessage implements Message { /** Writes the two fields behind the header. */ private static class Serializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(FailingUnmarshalMessage msg, MessageWriter writer) { + @Override public boolean writeTo(FailingUnmarshalMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -212,7 +213,7 @@ private static class Serializer implements MessageSerializer @@ -121,7 +123,7 @@ public void testCacheSize() throws Exception { // 2kb should be enough for an empty message even if it is a relatively large metrics message. msgWritter.setBuffer(ByteBuffer.allocate(2048)); - assertTrue(MessageSerialization.writeTo(msgFactory, msg, msgWritter)); + assertTrue(MessageSerialization.writeTo(msgFactory, msg, msgWritter, IGNORED)); assertTrue(msgWritter.getBuffer().hasRemaining()); @@ -133,7 +135,7 @@ public void testCacheSize() throws Exception { TcpDiscoveryMetricsUpdateMessage msg2 = new TcpDiscoveryMetricsUpdateMessage(); - assertTrue(MessageSerialization.readFrom(msgFactory, msg2, msgReader)); + assertTrue(MessageSerialization.readFrom(msgFactory, msg2, msgReader, IGNORED)); Map cacheMetrics2 = msg2.serversFullMetricsMessages().values().iterator().next() .cachesMetricsMessages(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/continuous/IgniteCacheContinuousQueryImmutableEntryTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/continuous/IgniteCacheContinuousQueryImmutableEntryTest.java index 45a5fb3381fd5..4f6851c31a6e6 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/continuous/IgniteCacheContinuousQueryImmutableEntryTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/continuous/IgniteCacheContinuousQueryImmutableEntryTest.java @@ -45,6 +45,7 @@ import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; /** * @@ -204,7 +205,7 @@ private CacheContinuousQueryEntry roundTrip(CacheContinuousQueryEntry e) throws // Skip write class header. writer.onHeaderWritten(); - MessageSerialization.writeTo(msgFactory, e, writer); + MessageSerialization.writeTo(msgFactory, e, writer, IGNORED); CacheContinuousQueryEntry res = new CacheContinuousQueryEntry(); @@ -212,7 +213,7 @@ private CacheContinuousQueryEntry roundTrip(CacheContinuousQueryEntry e) throws reader.setBuffer(ByteBuffer.wrap(buf.array())); - MessageSerialization.readFrom(msgFactory, res, reader); + MessageSerialization.readFrom(msgFactory, res, reader, IGNORED); return res; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/query/schema/message/QueryEntityMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/query/schema/message/QueryEntityMessageSerializationTest.java index f7fea391425dd..60d05ca3feddf 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/query/schema/message/QueryEntityMessageSerializationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/query/schema/message/QueryEntityMessageSerializationTest.java @@ -48,6 +48,7 @@ import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.apache.ignite.internal.util.CommonUtils.makeMessageType; /** Test for serialization round-trip of {@link QueryEntityMessage} and {@link QueryEntityExMessage}. */ @@ -173,7 +174,7 @@ private T writeAndReadBack(T msg, long expReadsWritesCnt) th DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); - assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer)); + assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer, IGNORED)); assertEquals("Writes" + ERROR_SUFFIX, expReadsWritesCnt, writer.state()); @@ -184,7 +185,7 @@ private T writeAndReadBack(T msg, long expReadsWritesCnt) th T res = (T)msgFactory.create(makeMessageType(buf.get(), buf.get())); - assertTrue(MessageSerialization.readFrom(msgFactory, res, reader)); + assertTrue(MessageSerialization.readFrom(msgFactory, res, reader, IGNORED)); assertEquals("Reads" + ERROR_SUFFIX, expReadsWritesCnt, reader.state()); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_0_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_0_0.java index d661f5cff66bc..066d0403917df 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_0_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_0_0.java @@ -19,6 +19,9 @@ /** */ public class TestPluginReleaseFeatures_2_0_0 { + /** */ + public static final IgniteFeature VER_1_0_0_ID_0_FEATURE = TestPluginReleaseFeatures_1_0_0.VER_1_0_0_ID_0_FEATURE; + /** */ public static final IgniteFeature VER_2_0_0_ID_1_FEATURE = new TestPluginFeature(1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java index edb4b3b3646ac..385f7ded6a900 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestPluginReleaseFeatures_2_1_0.java @@ -20,7 +20,10 @@ /** */ public class TestPluginReleaseFeatures_2_1_0 { /** */ - public static final IgniteFeature VER_2_1_0_ID_1_FEATURE = new TestPluginFeature(1); + public static final IgniteFeature VER_1_0_0_ID_0_FEATURE = TestPluginReleaseFeatures_2_0_0.VER_1_0_0_ID_0_FEATURE; + + /** */ + public static final IgniteFeature VER_2_0_0_ID_1_FEATURE = TestPluginReleaseFeatures_2_0_0.VER_2_0_0_ID_1_FEATURE; /** */ 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/message/RollingUpgradeMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java new file mode 100644 index 0000000000000..8ba97aa82c1c7 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.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.rollingupgrade.message; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import org.apache.ignite.Ignite; +import org.apache.ignite.Ignition; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.managers.communication.GridIoPolicy; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.spi.MessagesPluginProvider; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.A; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.B; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.C; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.D; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.E; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.F; + +/** */ +public class RollingUpgradeMessageSerializationTest extends AbstractRollingUpgradeTest { + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setPluginProviders(org.apache.ignite.internal.util.typedef.F.concat( + cfg.getPluginProviders(), + new MessagesPluginProvider( + TestCoreMessage.class, + TestPluginMessage.class, + TestDefaultRegistryMessage.class)) + ); + + return cfg; + } + + /** */ + @Test + public void testSameOldVersion() throws Exception { + checkMutualCoreMessageSend("2.19.0", "2.19.0", A, B, C, null, null, null); + } + + /** */ + @Test + public void testMixedPair() throws Exception { + checkMutualCoreMessageSend("2.19.0", "2.20.0", A, B, C, null, null, null); + } + + /** */ + @Test + public void testSameNewVersion() throws Exception { + checkMutualCoreMessageSend("2.20.0", "2.20.0", A, B, C, D, E, null); + } + + /** */ + @Test + public void testWindowOpenSameVersion() throws Exception { + checkMutualCoreMessageSend("2.19.2", "2.19.2", A, B, C, D, null, null); + } + + /** */ + @Test + public void testWindowOpenMixedPair() throws Exception { + checkMutualCoreMessageSend("2.19.2", "2.20.0", A, B, C, D, null, null); + } + + /** */ + @Test + public void testWindowClosed() throws Exception { + checkMutualCoreMessageSend("2.20.0", "2.20.1", A, null, C, null, E, null); + } + + /** */ + @Test + public void testDiscoveryNewerClient() throws Exception { + IgniteEx srv = startGrid(0, "2.19.0"); + + ru(srv).enableVersionUpgrade(); + + startClientGrid(1, "2.20.0"); + + checkCoreMessageBroadcast(srv, A, B, C, null, null, null); + } + + /** */ + @Test + public void testDiscoveryClientOriginated() throws Exception { + IgniteEx srv = startGrid(0, "2.19.0"); + + ru(srv).enableVersionUpgrade(); + + IgniteEx cli1 = startClientGrid(1, "2.20.0"); + + startClientGrid(2, "2.19.0"); + + checkCoreMessageBroadcast(cli1, A, B, C, null, null, null); + } + + /** */ + @Test + public void testDiscoveryClientsOnDifferentVersions() throws Exception { + startGrid(0, "2.19.0"); + startGrid(1, "2.19.0"); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.20.0"); + upgradeNodeVersion(1, "2.20.0"); + + IgniteEx newVerCli = startClientGrid(2, "2.20.0"); + IgniteEx oldVerCli = startClientGrid(3, "2.19.0"); + + Map receivedMsgs = sendOverDiscovery(grid(1), TestCoreMessage.build()); + + assertFields(A, B, C, D, E, null, receivedMsgs.get(newVerCli.name())); + assertFields(A, B, C, null, null, null, receivedMsgs.get(oldVerCli.name())); + } + + /** */ + @Test + public void testCommunicationWithClient() throws Exception { + IgniteEx srv = startGrid(0, "2.19.0"); + + ru(srv).enableVersionUpgrade(); + + IgniteEx client = startClientGrid(1, "2.20.0"); + + checkMutualCoreMessageSend(srv, client, A, B, C, null, null, null); + } + + /** */ + @Test + public void testDefaultRegistryMixedPair() throws Exception { + startServerNodes("2.19.0", "2.20.0"); + + checkMutualMessageSend(grid(0), grid(1), TestDefaultRegistryMessage::build, A, null, C, D, E, F); + } + + /** */ + @Test + public void testDiscoveryUniformRing() throws Exception { + startGrid(0, "2.20.0"); + startGrid(1, "2.20.0"); + startGrid(2, "2.20.0"); + + checkCoreMessageBroadcast(grid(1), A, B, C, D, E, null); + } + + /** */ + @Test + public void testDiscoveryMixedRing() throws Exception { + startGrid(0, "2.19.0"); + + ru(grid(0)).enableVersionUpgrade(); + + startGrid(1, "2.20.0"); + startGrid(2, "2.20.0"); + + checkCoreMessageBroadcast(grid(1), A, B, C, null, null, null); + } + + /** */ + @Test + public void testCommunicationUpgradeOpensWindow() throws Exception { + startGrid(0, "2.19.0"); + startGrid(1, "2.19.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, null, null, null); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.19.2"); + upgradeNodeVersion(1, "2.19.2"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, null, null); + } + + /** */ + @Test + public void testCommunicationUpgradeAgreesNewFeature() throws Exception { + startGrid(0, "2.19.2"); + startGrid(1, "2.19.2"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, null, null); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.19.2", "2.20.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, null, null); + + upgradeNodeVersion(1, "2.19.2", "2.20.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, E, null); + } + + /** */ + @Test + public void testPluginDiffersCoreMatches() throws Exception { + startServerNodes("2.20.0 | 1.0.0", "2.20.0 | 2.0.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, E, null); + + checkMutualMessageSend(grid(0), grid(1), TestPluginMessage::build, A, B, C, D, null, null); + } + + /** */ + @Test + public void testPluginSameVersion() throws Exception { + startServerNodes("2.20.0 | 2.0.0", "2.20.0 | 2.0.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, E, null); + + checkMutualMessageSend(grid(0), grid(1), TestPluginMessage::build, A, B, C, D, E, null); + } + + /** */ + @Test + public void testPluginMissingOnClient() throws Exception { + IgniteEx srv = startGrid(0, "2.20.0 | 2.0.0"); + + ru(srv).enableVersionUpgrade(); + + IgniteEx cli = startClientGrid(1, "2.20.0"); + + checkMutualMessageSend(srv, cli, TestPluginMessage::build, A, B, C, null, null, null); + + checkMutualCoreMessageSend(srv, cli, A, B, C, D, E, null); + } + + /** */ + @Test + public void testWholeUpgradeProcess() throws Exception { + startGrid(0, "2.19.0"); + startGrid(1, "2.19.0"); + startClientGrid(2, "2.19.0"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, null, null, null); + + ru(1).enableVersionUpgrade(); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, null, null, null); + + upgradeNodeVersion(0, "2.19.0", "2.19.2"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, null, null, null); + + upgradeNodeVersion(1, "2.19.0", "2.19.2"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, null, null); + checkMutualCoreMessageSend(grid(0), grid(2), A, B, C, null, null, null); + checkMutualCoreMessageSend(grid(1), grid(2), A, B, C, null, null, null); + + upgradeNodeVersion(2, "2.19.0", "2.19.2"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, null, null); + + finalizeClusterVersion(0, "2.19.2"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, null, null); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.19.2", "2.20.0"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, null, null); + + upgradeNodeVersion(1, "2.19.2", "2.20.0"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, E, null); + checkMutualCoreMessageSend(grid(0), grid(2), A, B, C, D, null, null); + checkMutualCoreMessageSend(grid(1), grid(2), A, B, C, D, null, null); + + upgradeNodeVersion(2, "2.19.2", "2.20.0"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, E, null); + + finalizeClusterVersion(0, "2.20.0"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, E, null); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.20.0", "2.20.1"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, null, C, null, E, null); + checkMutualCoreMessageSend(grid(0), grid(2), A, null, C, null, E, null); + checkMutualCoreMessageSend(grid(1), grid(2), A, B, C, D, E, null); + + upgradeNodeVersion(1, "2.20.0", "2.20.1"); + + checkMutualCoreMessageSend(grid(0), grid(1), A, B, C, D, E, F); + checkMutualCoreMessageSend(grid(0), grid(2), A, null, C, null, E, null); + checkMutualCoreMessageSend(grid(1), grid(2), A, null, C, null, E, null); + + upgradeNodeVersion(2, "2.20.0", "2.20.1"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, E, F); + + finalizeClusterVersion(0, "2.20.1"); + + checkMessagesTransmissionBetweenAllNodes(A, B, C, D, E, F); + } + + /** */ + private void checkMessagesTransmissionBetweenAllNodes( + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + List clusterNodes = Ignition.allGrids(); + + for (int i = 0; i < clusterNodes.size(); i++) { + for (int j = i + 1; j < clusterNodes.size(); j++) { + checkMutualCoreMessageSend( + (IgniteEx)clusterNodes.get(i), (IgniteEx)clusterNodes.get(j), expA, expB, expC, expD, expE, expF); + } + } + } + + /** */ + private void checkMutualCoreMessageSend( + String firstVer, + String secondVer, + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + startServerNodes(firstVer, secondVer); + + checkMutualCoreMessageSend(grid(0), grid(1), expA, expB, expC, expD, expE, expF); + } + + /** */ + private void checkMutualCoreMessageSend( + IgniteEx first, + IgniteEx second, + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + checkMutualMessageSend(first, second, TestCoreMessage::build, expA, expB, expC, expD, expE, expF); + } + + /** */ + private void checkCoreMessageBroadcast( + IgniteEx from, + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + Collection receivedMsgs = sendOverDiscovery(from, TestCoreMessage.build()).values(); + + for (TestCoreMessage msg : receivedMsgs) + assertFields(expA, expB, expC, expD, expE, expF, msg); + } + + /** */ + private void checkMutualMessageSend( + IgniteEx first, + IgniteEx second, + Supplier msgFactory, + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + checkReceivedMessageFields(first, second, msgFactory, expA, expB, expC, expD, expE, expF); + checkReceivedMessageFields(second, first, msgFactory, expA, expB, expC, expD, expE, expF); + } + + /** */ + private void checkReceivedMessageFields( + IgniteEx from, + IgniteEx to, + Supplier msgFactory, + String expA, + String expB, + String expC, + String expD, + String expE, + String expF + ) throws Exception { + assertFields(expA, expB, expC, expD, expE, expF, send(from, to, msgFactory.get())); + + assertFields(expA, expB, expC, expD, expE, expF, sendOverDiscovery(from, msgFactory.get()).get(to.name())); + } + + /** */ + private T send(IgniteEx from, IgniteEx to, T msg) throws Exception { + AtomicReference got = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + String topic = msg.getClass().getName(); + + to.context().io().addMessageListener(topic, (nodeId, rcvd, plc) -> { + got.set((T)rcvd); + + latch.countDown(); + }); + + ClusterNode rcvNode = from.context().discovery().node(to.localNode().id()); + + from.context().io().sendToCustomTopic(rcvNode, topic, msg, GridIoPolicy.PUBLIC_POOL); + + assertTrue(latch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + return got.get(); + } + + /** */ + private Map sendOverDiscovery( + IgniteEx from, + T msg + ) throws Exception { + List clusterNodes = Ignition.allGrids(); + + Map receivedMsgs = new ConcurrentHashMap<>(); + + CountDownLatch latch = new CountDownLatch(clusterNodes.size()); + + for (Ignite rcv : clusterNodes) { + String name = rcv.name(); + + ((IgniteEx)rcv).context().discovery().setCustomEventListener((Class)msg.getClass(), + (v, n, m) -> { + receivedMsgs.put(name, m); + + latch.countDown(); + }); + } + + from.context().discovery().sendCustomEvent(msg); + + assertTrue(latch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + receivedMsgs.remove(from.name()); + + return receivedMsgs; + } + + /** */ + private void startServerNodes(String firstVer, String secondVer) throws Exception { + IgniteEx first = startGrid(0, firstVer); + + if (!firstVer.equals(secondVer)) + ru(first).enableVersionUpgrade(); + + startGrid(1, secondVer); + } + + /** */ + private static void assertFields( + String expA, + String expB, + String expC, + String expD, + String expE, + String expF, + TestMessage msg + ) { + assertEquals(expA, msg.fldA()); + assertEquals(expB, msg.fldB()); + assertEquals(expC, msg.fldC()); + assertEquals(expD, msg.fldD()); + assertEquals(expE, msg.fldE()); + assertEquals(expF, msg.fldF()); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestCoreMessage.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestCoreMessage.java new file mode 100644 index 0000000000000..843c928ad1ca0 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestCoreMessage.java @@ -0,0 +1,107 @@ +/* + * 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.message; + +import org.apache.ignite.internal.FeatureRegistry; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_20_1; +import org.apache.ignite.lang.IgniteUuid; +import org.jetbrains.annotations.Nullable; + +/** */ +@FeatureRegistry(TestIgniteReleaseFeatures_2_20_1.class) +public class TestCoreMessage extends DiscoveryCustomMessage implements TestMessage { + /** */ + @Order(0) + String fldA; + + /** */ + @Order(value = 1, deprecatedBy = "VER_2_20_0_ID_3_FEATURE") + String fldB; + + /** */ + @Order(2) + String fldC; + + /** */ + @Order(value = 3, introducedBy = "VER_2_19_2_ID_1_FEATURE", deprecatedBy = "VER_2_20_0_ID_3_FEATURE") + String fldD; + + /** */ + @Order(value = 4, introducedBy = "VER_2_20_0_ID_3_FEATURE") + String fldE; + + /** */ + @Order(value = 5, introducedBy = "VER_2_20_1_ID_6_FEATURE") + String fldF; + + /** */ + public TestCoreMessage() { + super(IgniteUuid.randomUuid()); + } + + /** {@inheritDoc} */ + @Nullable @Override public DiscoveryCustomMessage ackMessage() { + return null; + } + + /** */ + public static TestCoreMessage build() { + TestCoreMessage msg = new TestCoreMessage(); + + msg.fldA = A; + msg.fldB = B; + msg.fldC = C; + msg.fldD = D; + msg.fldE = E; + msg.fldF = F; + + return msg; + } + + /** {@inheritDoc} */ + @Override public String fldA() { + return fldA; + } + + /** {@inheritDoc} */ + @Override public String fldB() { + return fldB; + } + + /** {@inheritDoc} */ + @Override public String fldC() { + return fldC; + } + + /** {@inheritDoc} */ + @Override public String fldD() { + return fldD; + } + + /** {@inheritDoc} */ + @Override public String fldE() { + return fldE; + } + + /** {@inheritDoc} */ + @Override public String fldF() { + return fldF; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestDefaultRegistryMessage.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestDefaultRegistryMessage.java new file mode 100644 index 0000000000000..02e6173990b11 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestDefaultRegistryMessage.java @@ -0,0 +1,104 @@ +/* + * 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.message; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.lang.IgniteUuid; +import org.jetbrains.annotations.Nullable; + +/** */ +public class TestDefaultRegistryMessage extends DiscoveryCustomMessage implements TestMessage { + /** */ + @Order(0) + String fldA; + + /** */ + @Order(value = 1, deprecatedBy = "ROLLING_UPGRADE_FEATURE") + String fldB; + + /** */ + @Order(2) + String fldC; + + /** */ + @Order(3) + String fldD; + + /** */ + @Order(4) + String fldE; + + /** */ + @Order(value = 5, introducedBy = "ROLLING_UPGRADE_FEATURE") + String fldF; + + /** */ + public TestDefaultRegistryMessage() { + super(IgniteUuid.randomUuid()); + } + + /** {@inheritDoc} */ + @Nullable @Override public DiscoveryCustomMessage ackMessage() { + return null; + } + + /** */ + public static TestDefaultRegistryMessage build() { + TestDefaultRegistryMessage msg = new TestDefaultRegistryMessage(); + + msg.fldA = A; + msg.fldB = B; + msg.fldC = C; + msg.fldD = D; + msg.fldE = E; + msg.fldF = F; + + return msg; + } + + /** {@inheritDoc} */ + @Override public String fldA() { + return fldA; + } + + /** {@inheritDoc} */ + @Override public String fldB() { + return fldB; + } + + /** {@inheritDoc} */ + @Override public String fldC() { + return fldC; + } + + /** {@inheritDoc} */ + @Override public String fldD() { + return fldD; + } + + /** {@inheritDoc} */ + @Override public String fldE() { + return fldE; + } + + /** {@inheritDoc} */ + @Override public String fldF() { + return fldF; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestMessage.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestMessage.java new file mode 100644 index 0000000000000..15c639bf6fdaa --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestMessage.java @@ -0,0 +1,57 @@ +/* + * 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.message; + +/** */ +public interface TestMessage { + /** */ + public static final String A = "A"; + + /** */ + public static final String B = "B"; + + /** */ + public static final String C = "C"; + + /** */ + public static final String D = "D"; + + /** */ + public static final String E = "E"; + + /** */ + public static final String F = "F"; + + /** */ + public String fldA(); + + /** */ + public String fldB(); + + /** */ + public String fldC(); + + /** */ + public String fldD(); + + /** */ + public String fldE(); + + /** */ + public String fldF(); +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestPluginMessage.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestPluginMessage.java new file mode 100644 index 0000000000000..4bdf8ebe80ac3 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/TestPluginMessage.java @@ -0,0 +1,107 @@ +/* + * 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.message; + +import org.apache.ignite.internal.FeatureRegistry; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestPluginReleaseFeatures_2_1_0; +import org.apache.ignite.lang.IgniteUuid; +import org.jetbrains.annotations.Nullable; + +/** */ +@FeatureRegistry(TestPluginReleaseFeatures_2_1_0.class) +public class TestPluginMessage extends DiscoveryCustomMessage implements TestMessage { + /** */ + @Order(0) + String fldA; + + /** */ + @Order(value = 1, deprecatedBy = "VER_2_0_0_ID_1_FEATURE") + String fldB; + + /** */ + @Order(2) + String fldC; + + /** */ + @Order(value = 3, introducedBy = "VER_1_0_0_ID_0_FEATURE", deprecatedBy = "VER_2_0_0_ID_1_FEATURE") + String fldD; + + /** */ + @Order(value = 4, introducedBy = "VER_2_0_0_ID_1_FEATURE") + String fldE; + + /** */ + @Order(value = 5, introducedBy = "VER_2_1_0_ID_2_FEATURE") + String fldF; + + /** */ + public TestPluginMessage() { + super(IgniteUuid.randomUuid()); + } + + /** {@inheritDoc} */ + @Nullable @Override public DiscoveryCustomMessage ackMessage() { + return null; + } + + /** */ + public static TestPluginMessage build() { + TestPluginMessage msg = new TestPluginMessage(); + + msg.fldA = A; + msg.fldB = B; + msg.fldC = C; + msg.fldD = D; + msg.fldE = E; + msg.fldF = F; + + return msg; + } + + /** {@inheritDoc} */ + @Override public String fldA() { + return fldA; + } + + /** {@inheritDoc} */ + @Override public String fldB() { + return fldB; + } + + /** {@inheritDoc} */ + @Override public String fldC() { + return fldC; + } + + /** {@inheritDoc} */ + @Override public String fldD() { + return fldD; + } + + /** {@inheritDoc} */ + @Override public String fldE() { + return fldE; + } + + /** {@inheritDoc} */ + @Override public String fldF() { + return fldF; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/LazyServiceConfigurationMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/LazyServiceConfigurationMessageSerializationTest.java index 1536b53e9926d..a978a506abb89 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/LazyServiceConfigurationMessageSerializationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/LazyServiceConfigurationMessageSerializationTest.java @@ -37,6 +37,7 @@ import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; import static org.apache.ignite.internal.util.CommonUtils.makeMessageType; import static org.junit.Assert.assertArrayEquals; @@ -126,7 +127,7 @@ private T writeAndReadBack(T msg, long expReadsWritesCnt) th DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); - assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer)); + assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer, IGNORED)); assertEquals("Writes" + ERROR_SUFFIX, expReadsWritesCnt, writer.state()); buf.flip(); @@ -136,7 +137,7 @@ private T writeAndReadBack(T msg, long expReadsWritesCnt) th T res = (T)msgFactory.create(makeMessageType(buf.get(), buf.get())); - assertTrue(MessageSerialization.readFrom(msgFactory, res, reader)); + assertTrue(MessageSerialization.readFrom(msgFactory, res, reader, IGNORED)); assertEquals("Reads" + ERROR_SUFFIX, expReadsWritesCnt, reader.state()); DiscoveryMarshalling.unmarshal(res, kctx); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/distributed/DistributedProcessResultMarshallingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/distributed/DistributedProcessResultMarshallingTest.java index 0286297db11e4..8a8f0521739c4 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/distributed/DistributedProcessResultMarshallingTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/distributed/DistributedProcessResultMarshallingTest.java @@ -29,6 +29,7 @@ import org.apache.ignite.internal.CoreMessagesProvider; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestRecordingCommunicationSpi; import org.apache.ignite.internal.managers.communication.CommunicationMarshalling; import org.apache.ignite.internal.managers.communication.GridIoMessage; @@ -239,7 +240,7 @@ public PayloadMessage() { /** */ private static class PayloadSerializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(PayloadMessage msg, MessageWriter writer) { + @Override public boolean writeTo(PayloadMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -255,7 +256,7 @@ private static class PayloadSerializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(MarshalOnceCheckMessage msg, MessageWriter writer) { + @Override public boolean writeTo(MarshalOnceCheckMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -169,7 +170,7 @@ private static class Serializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(RetryCheckMessage msg, MessageWriter writer) { + @Override public boolean writeTo(RetryCheckMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -221,7 +222,7 @@ private static class RetrySerializer implements MessageSerializer T writeAndReadBack(T msg) throws IgniteCheckedExcept DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); - assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer)); + assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer, IGNORED)); buf.flip(); @@ -126,7 +127,7 @@ private T writeAndReadBack(T msg) throws IgniteCheckedExcept T res = (T)msgFactory.create(makeMessageType(buf.get(), buf.get())); - assertTrue(MessageSerialization.readFrom(msgFactory, res, reader)); + assertTrue(MessageSerialization.readFrom(msgFactory, res, reader, IGNORED)); return res; } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/communication/tcp/TestDelayMessageSerializer.java b/modules/core/src/test/java/org/apache/ignite/spi/communication/tcp/TestDelayMessageSerializer.java index ce905b9806f70..1d61f44f57fa7 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/communication/tcp/TestDelayMessageSerializer.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/communication/tcp/TestDelayMessageSerializer.java @@ -18,6 +18,7 @@ package org.apache.ignite.spi.communication.tcp; import org.apache.ignite.internal.IgniteInterruptedCheckedException; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.plugin.extensions.communication.MessageReader; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; @@ -26,7 +27,7 @@ /** Serializer for {@link TestDelayMessage} that injects an optional write delay for testing. */ public class TestDelayMessageSerializer implements MessageSerializer { /** {@inheritDoc} */ - @Override public boolean writeTo(TestDelayMessage msg, MessageWriter writer) { + @Override public boolean writeTo(TestDelayMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -47,7 +48,7 @@ public class TestDelayMessageSerializer implements MessageSerializer serde; /** {@inheritDoc} */ - @Override public boolean writeTo(ExploitMessage msg, MessageWriter writer) { + @Override public boolean writeTo(ExploitMessage msg, MessageWriter writer, MessageSerializationContext ctx) { initIfNecessary(); - return serde.writeTo(msg, writer); + return serde.writeTo(msg, writer, ctx); } /** {@inheritDoc} */ - @Override public boolean readFrom(ExploitMessage msg, MessageReader reader) { + @Override public boolean readFrom(ExploitMessage msg, MessageReader reader, MessageSerializationContext ctx) { initIfNecessary(); - return serde.readFrom(msg, reader); + return serde.readFrom(msg, reader, ctx); } /** {@inheritDoc} */ diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java index feedf6a4c6ed5..99eae878e2df1 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java @@ -125,7 +125,11 @@ public void discoveryHook(DiscoveryHook discoHook) { }; try (dataSock) { - return new TcpDiscoveryIoSession(ctx, dataSock).readMessage(); + TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(ctx, dataSock); + + ses.applyMessageSerializationContext(ctx.localNodeFeatures()); + + return ses.readMessage(); } catch (Exception e) { throw new IgniteException("Failed to decode a message", e); diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestNode.java b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestNode.java index cd76954a1a274..e90cc86847564 100644 --- a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestNode.java +++ b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestNode.java @@ -17,15 +17,19 @@ package org.apache.ignite.testframework; +import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.cache.CacheMetrics; import org.apache.ignite.cluster.ClusterMetrics; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.IgniteNodeAttributes; +import org.apache.ignite.internal.managers.discovery.IgniteClusterNode; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter; import org.apache.ignite.lang.IgniteProductVersion; @@ -34,7 +38,7 @@ /** * Test node. */ -public class GridTestNode extends GridMetadataAwareAdapter implements ClusterNode { +public class GridTestNode extends GridMetadataAwareAdapter implements IgniteClusterNode { /** */ private static final IgniteProductVersion VERSION = fromString("99.99.99"); @@ -103,13 +107,6 @@ public GridTestNode(UUID id, ClusterMetrics metrics) { return id; } - /** - * @param consistentId Consistent ID. - */ - public void consistentId(Object consistentId) { - this.consistentId = consistentId; - } - /** {@inheritDoc} */ @Override public Object consistentId() { return consistentId; @@ -213,6 +210,16 @@ public void order(long order) { return VERSION; } + /** {@inheritDoc} */ + @Override public IgniteNodeFeatureSet features() { + return IgniteNodeFeatureSet.LOCAL_CORE_FEATURES; + } + + /** {@inheritDoc} */ + @Override public void setConsistentId(Serializable consistentId) { + this.consistentId = consistentId; + } + /** * Sets node metrics. * @@ -222,6 +229,16 @@ public void setMetrics(ClusterMetrics metrics) { this.metrics = metrics; } + /** {@inheritDoc} */ + @Override public Map cacheMetrics() { + return Collections.emptyMap(); + } + + /** {@inheritDoc} */ + @Override public void setCacheMetrics(Map cacheMetrics) { + // No-op. + } + /** {@inheritDoc} */ @Override public boolean isLocal() { return false; diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java index 55e53c7d44cb1..4767e5fdc587d 100755 --- a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java +++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java @@ -1414,7 +1414,7 @@ protected final List movingKeysAfterJoin(Ignite ign, String cacheName, if (nodeInitializer != null) nodeInitializer.apply(fakeNode); - fakeNode.consistentId(joiningNodeConsistentId == null ? getTestIgniteInstanceName(nodes.size()) : + fakeNode.setConsistentId(joiningNodeConsistentId == null ? getTestIgniteInstanceName(nodes.size()) : joiningNodeConsistentId); nodes.add(fakeNode); @@ -1465,7 +1465,7 @@ protected List evictingPartitionsAfterJoin(Ignite ign, IgniteCache { /** */ - @Override public final boolean writeTo(ChildMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(ChildMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -68,7 +69,7 @@ public final class ChildMessageSerializer implements MessageSerializer { /** */ - @Override public final boolean writeTo(CorrectEmptyMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(CorrectEmptyMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -44,7 +45,7 @@ public final class CorrectEmptyMessageSerializer implements MessageSerializer(TransactionIsolation.class, transactionIsolationMapper::encode, transactionIsolationMapper::decode), CollectionImplementationType.ARRAY_LIST), CollectionImplementationType.ARRAY_LIST); /** */ - @Override public final boolean writeTo(CustomMapperEnumFieldsMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(CustomMapperEnumFieldsMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -57,7 +58,7 @@ public final class CustomMapperEnumFieldsMessageSerializer implements MessageSer writer.incrementState(); case 1: - if (!writer.writeCollection(msg.isolations, isolationsCollDesc)) + if (!writer.writeCollection(msg.isolations, isolationsCollDesc, ctx)) return false; writer.incrementState(); @@ -67,7 +68,7 @@ public final class CustomMapperEnumFieldsMessageSerializer implements MessageSer } /** */ - @Override public final boolean readFrom(CustomMapperEnumFieldsMessage msg, MessageReader reader) { + @Override public final boolean readFrom(CustomMapperEnumFieldsMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: msg.txMode = transactionIsolationMapper.decode(reader.readByte()); @@ -78,7 +79,7 @@ public final class CustomMapperEnumFieldsMessageSerializer implements MessageSer reader.incrementState(); case 1: - msg.isolations = reader.readCollection(isolationsCollDesc); + msg.isolations = reader.readCollection(isolationsCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java index dba15be284691..9e707da48c770 100644 --- a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal; import org.apache.ignite.internal.DefaultMapperEnumFieldsMessage; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.processors.cache.GridCacheOperation; import org.apache.ignite.internal.processors.cache.verify.PartitionHashRecord.PartitionState; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; @@ -50,7 +51,7 @@ public final class DefaultMapperEnumFieldsMessageSerializer implements MessageSe private static final MessageMapType isolationStringMapCollDesc = new MessageMapType(new MessageCollectionType(new MessageEnumType<>(TransactionIsolation.class, DefaultEnumMapper.INSTANCE::encode, b -> DefaultEnumMapper.INSTANCE.decode(transactionIsolationVals, b)), CollectionImplementationType.ARRAY_LIST), new MessageItemType(MessageCollectionItemType.STRING), false); /** */ - @Override public final boolean writeTo(DefaultMapperEnumFieldsMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(DefaultMapperEnumFieldsMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -72,13 +73,13 @@ public final class DefaultMapperEnumFieldsMessageSerializer implements MessageSe writer.incrementState(); case 2: - if (!writer.writeMap(msg.isolationStringMap, isolationStringMapCollDesc)) + if (!writer.writeMap(msg.isolationStringMap, isolationStringMapCollDesc, ctx)) return false; writer.incrementState(); case 3: - if (!writer.writeCollection(msg.partStates, partStatesCollDesc)) + if (!writer.writeCollection(msg.partStates, partStatesCollDesc, ctx)) return false; writer.incrementState(); @@ -88,7 +89,7 @@ public final class DefaultMapperEnumFieldsMessageSerializer implements MessageSe } /** */ - @Override public final boolean readFrom(DefaultMapperEnumFieldsMessage msg, MessageReader reader) { + @Override public final boolean readFrom(DefaultMapperEnumFieldsMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: msg.publicEnum = DefaultEnumMapper.INSTANCE.decode(transactionIsolationVals, reader.readByte()); @@ -107,7 +108,7 @@ public final class DefaultMapperEnumFieldsMessageSerializer implements MessageSe reader.incrementState(); case 2: - msg.isolationStringMap = reader.readMap(isolationStringMapCollDesc); + msg.isolationStringMap = reader.readMap(isolationStringMapCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -115,7 +116,7 @@ public final class DefaultMapperEnumFieldsMessageSerializer implements MessageSe reader.incrementState(); case 3: - msg.partStates = reader.readCollection(partStatesCollDesc); + msg.partStates = reader.readCollection(partStatesCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestCollectionsMessageSerializer.java b/modules/core/src/test/resources/codegen/TestCollectionsMessageSerializer.java index bd79c20a48c26..d1814946151ce 100644 --- a/modules/core/src/test/resources/codegen/TestCollectionsMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestCollectionsMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestCollectionsMessage; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType; @@ -86,7 +87,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer private static final MessageCollectionType uuidListCollDesc = new MessageCollectionType(new MessageItemType(MessageCollectionItemType.UUID), CollectionImplementationType.ARRAY_LIST); /** */ - @Override public final boolean writeTo(TestCollectionsMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestCollectionsMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -96,157 +97,157 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer switch (writer.state()) { case 0: - if (!writer.writeCollection(msg.booleanArrayList, booleanArrayListCollDesc)) + if (!writer.writeCollection(msg.booleanArrayList, booleanArrayListCollDesc, ctx)) return false; writer.incrementState(); case 1: - if (!writer.writeCollection(msg.byteArrayList, byteArrayListCollDesc)) + if (!writer.writeCollection(msg.byteArrayList, byteArrayListCollDesc, ctx)) return false; writer.incrementState(); case 2: - if (!writer.writeCollection(msg.shortArrayList, shortArrayListCollDesc)) + if (!writer.writeCollection(msg.shortArrayList, shortArrayListCollDesc, ctx)) return false; writer.incrementState(); case 3: - if (!writer.writeCollection(msg.intArrayList, intArrayListCollDesc)) + if (!writer.writeCollection(msg.intArrayList, intArrayListCollDesc, ctx)) return false; writer.incrementState(); case 4: - if (!writer.writeCollection(msg.longArrayList, longArrayListCollDesc)) + if (!writer.writeCollection(msg.longArrayList, longArrayListCollDesc, ctx)) return false; writer.incrementState(); case 5: - if (!writer.writeCollection(msg.charArrayList, charArrayListCollDesc)) + if (!writer.writeCollection(msg.charArrayList, charArrayListCollDesc, ctx)) return false; writer.incrementState(); case 6: - if (!writer.writeCollection(msg.floatArrayList, floatArrayListCollDesc)) + if (!writer.writeCollection(msg.floatArrayList, floatArrayListCollDesc, ctx)) return false; writer.incrementState(); case 7: - if (!writer.writeCollection(msg.doubleArrayList, doubleArrayListCollDesc)) + if (!writer.writeCollection(msg.doubleArrayList, doubleArrayListCollDesc, ctx)) return false; writer.incrementState(); case 8: - if (!writer.writeCollection(msg.stringList, stringListCollDesc)) + if (!writer.writeCollection(msg.stringList, stringListCollDesc, ctx)) return false; writer.incrementState(); case 9: - if (!writer.writeCollection(msg.uuidList, uuidListCollDesc)) + if (!writer.writeCollection(msg.uuidList, uuidListCollDesc, ctx)) return false; writer.incrementState(); case 10: - if (!writer.writeCollection(msg.bitSetList, bitSetListCollDesc)) + if (!writer.writeCollection(msg.bitSetList, bitSetListCollDesc, ctx)) return false; writer.incrementState(); case 11: - if (!writer.writeCollection(msg.igniteUuidList, igniteUuidListCollDesc)) + if (!writer.writeCollection(msg.igniteUuidList, igniteUuidListCollDesc, ctx)) return false; writer.incrementState(); case 12: - if (!writer.writeCollection(msg.affTopVersionList, affTopVersionListCollDesc)) + if (!writer.writeCollection(msg.affTopVersionList, affTopVersionListCollDesc, ctx)) return false; writer.incrementState(); case 13: - if (!writer.writeCollection(msg.boxedBooleanList, boxedBooleanListCollDesc)) + if (!writer.writeCollection(msg.boxedBooleanList, boxedBooleanListCollDesc, ctx)) return false; writer.incrementState(); case 14: - if (!writer.writeCollection(msg.boxedByteList, boxedByteListCollDesc)) + if (!writer.writeCollection(msg.boxedByteList, boxedByteListCollDesc, ctx)) return false; writer.incrementState(); case 15: - if (!writer.writeCollection(msg.boxedShortList, boxedShortListCollDesc)) + if (!writer.writeCollection(msg.boxedShortList, boxedShortListCollDesc, ctx)) return false; writer.incrementState(); case 16: - if (!writer.writeCollection(msg.boxedIntList, boxedIntListCollDesc)) + if (!writer.writeCollection(msg.boxedIntList, boxedIntListCollDesc, ctx)) return false; writer.incrementState(); case 17: - if (!writer.writeCollection(msg.boxedLongList, boxedLongListCollDesc)) + if (!writer.writeCollection(msg.boxedLongList, boxedLongListCollDesc, ctx)) return false; writer.incrementState(); case 18: - if (!writer.writeCollection(msg.boxedCharList, boxedCharListCollDesc)) + if (!writer.writeCollection(msg.boxedCharList, boxedCharListCollDesc, ctx)) return false; writer.incrementState(); case 19: - if (!writer.writeCollection(msg.boxedFloatList, boxedFloatListCollDesc)) + if (!writer.writeCollection(msg.boxedFloatList, boxedFloatListCollDesc, ctx)) return false; writer.incrementState(); case 20: - if (!writer.writeCollection(msg.boxedDoubleList, boxedDoubleListCollDesc)) + if (!writer.writeCollection(msg.boxedDoubleList, boxedDoubleListCollDesc, ctx)) return false; writer.incrementState(); case 21: - if (!writer.writeCollection(msg.messageList, messageListCollDesc)) + if (!writer.writeCollection(msg.messageList, messageListCollDesc, ctx)) return false; writer.incrementState(); case 22: - if (!writer.writeCollection(msg.gridLongListList, gridLongListListCollDesc)) + if (!writer.writeCollection(msg.gridLongListList, gridLongListListCollDesc, ctx)) return false; writer.incrementState(); case 23: - if (!writer.writeCollection(msg.boxedIntegerSet, boxedIntegerSetCollDesc)) + if (!writer.writeCollection(msg.boxedIntegerSet, boxedIntegerSetCollDesc, ctx)) return false; writer.incrementState(); case 24: - if (!writer.writeCollection(msg.bitSetSet, bitSetSetCollDesc)) + if (!writer.writeCollection(msg.bitSetSet, bitSetSetCollDesc, ctx)) return false; writer.incrementState(); case 25: - if (!writer.writeCollection(msg.cacheObjectSet, cacheObjectSetCollDesc)) + if (!writer.writeCollection(msg.cacheObjectSet, cacheObjectSetCollDesc, ctx)) return false; writer.incrementState(); @@ -256,10 +257,10 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer } /** */ - @Override public final boolean readFrom(TestCollectionsMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestCollectionsMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: - msg.booleanArrayList = reader.readCollection(booleanArrayListCollDesc); + msg.booleanArrayList = reader.readCollection(booleanArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -267,7 +268,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 1: - msg.byteArrayList = reader.readCollection(byteArrayListCollDesc); + msg.byteArrayList = reader.readCollection(byteArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -275,7 +276,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 2: - msg.shortArrayList = reader.readCollection(shortArrayListCollDesc); + msg.shortArrayList = reader.readCollection(shortArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -283,7 +284,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 3: - msg.intArrayList = reader.readCollection(intArrayListCollDesc); + msg.intArrayList = reader.readCollection(intArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -291,7 +292,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 4: - msg.longArrayList = reader.readCollection(longArrayListCollDesc); + msg.longArrayList = reader.readCollection(longArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -299,7 +300,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 5: - msg.charArrayList = reader.readCollection(charArrayListCollDesc); + msg.charArrayList = reader.readCollection(charArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -307,7 +308,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 6: - msg.floatArrayList = reader.readCollection(floatArrayListCollDesc); + msg.floatArrayList = reader.readCollection(floatArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -315,7 +316,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 7: - msg.doubleArrayList = reader.readCollection(doubleArrayListCollDesc); + msg.doubleArrayList = reader.readCollection(doubleArrayListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -323,7 +324,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 8: - msg.stringList = reader.readCollection(stringListCollDesc); + msg.stringList = reader.readCollection(stringListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -331,7 +332,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 9: - msg.uuidList = reader.readCollection(uuidListCollDesc); + msg.uuidList = reader.readCollection(uuidListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -339,7 +340,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 10: - msg.bitSetList = reader.readCollection(bitSetListCollDesc); + msg.bitSetList = reader.readCollection(bitSetListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -347,7 +348,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 11: - msg.igniteUuidList = reader.readCollection(igniteUuidListCollDesc); + msg.igniteUuidList = reader.readCollection(igniteUuidListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -355,7 +356,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 12: - msg.affTopVersionList = reader.readCollection(affTopVersionListCollDesc); + msg.affTopVersionList = reader.readCollection(affTopVersionListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -363,7 +364,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 13: - msg.boxedBooleanList = reader.readCollection(boxedBooleanListCollDesc); + msg.boxedBooleanList = reader.readCollection(boxedBooleanListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -371,7 +372,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 14: - msg.boxedByteList = reader.readCollection(boxedByteListCollDesc); + msg.boxedByteList = reader.readCollection(boxedByteListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -379,7 +380,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 15: - msg.boxedShortList = reader.readCollection(boxedShortListCollDesc); + msg.boxedShortList = reader.readCollection(boxedShortListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -387,7 +388,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 16: - msg.boxedIntList = reader.readCollection(boxedIntListCollDesc); + msg.boxedIntList = reader.readCollection(boxedIntListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -395,7 +396,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 17: - msg.boxedLongList = reader.readCollection(boxedLongListCollDesc); + msg.boxedLongList = reader.readCollection(boxedLongListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -403,7 +404,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 18: - msg.boxedCharList = reader.readCollection(boxedCharListCollDesc); + msg.boxedCharList = reader.readCollection(boxedCharListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -411,7 +412,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 19: - msg.boxedFloatList = reader.readCollection(boxedFloatListCollDesc); + msg.boxedFloatList = reader.readCollection(boxedFloatListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -419,7 +420,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 20: - msg.boxedDoubleList = reader.readCollection(boxedDoubleListCollDesc); + msg.boxedDoubleList = reader.readCollection(boxedDoubleListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -427,7 +428,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 21: - msg.messageList = reader.readCollection(messageListCollDesc); + msg.messageList = reader.readCollection(messageListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -435,7 +436,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 22: - msg.gridLongListList = reader.readCollection(gridLongListListCollDesc); + msg.gridLongListList = reader.readCollection(gridLongListListCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -443,7 +444,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 23: - msg.boxedIntegerSet = reader.readCollection(boxedIntegerSetCollDesc); + msg.boxedIntegerSet = reader.readCollection(boxedIntegerSetCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -451,7 +452,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 24: - msg.bitSetSet = reader.readCollection(bitSetSetCollDesc); + msg.bitSetSet = reader.readCollection(bitSetSetCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -459,7 +460,7 @@ public final class TestCollectionsMessageSerializer implements MessageSerializer reader.incrementState(); case 25: - msg.cacheObjectSet = reader.readCollection(cacheObjectSetCollDesc); + msg.cacheObjectSet = reader.readCollection(cacheObjectSetCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestEnumSetMessageSerializer.java b/modules/core/src/test/resources/codegen/TestEnumSetMessageSerializer.java index fb0afa111fa6f..d5cc64a6abde1 100644 --- a/modules/core/src/test/resources/codegen/TestEnumSetMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestEnumSetMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestEnumSetMessage; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType; @@ -46,7 +47,7 @@ public final class TestEnumSetMessageSerializer implements MessageSerializer(TransactionIsolation.class, DefaultEnumMapper.INSTANCE::encode, b -> DefaultEnumMapper.INSTANCE.decode(transactionIsolationVals, b)), CollectionImplementationType.ENUM_SET), false); /** */ - @Override public final boolean writeTo(TestEnumSetMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestEnumSetMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -56,19 +57,19 @@ public final class TestEnumSetMessageSerializer implements MessageSerializer { /** */ - @Override public final boolean writeTo(TestMarshallableMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshallableMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -61,7 +62,7 @@ public final class TestMarshallableMessageSerializer implements MessageSerialize } /** */ - @Override public final boolean readFrom(TestMarshallableMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshallableMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: msg.iv = reader.readInt(); diff --git a/modules/core/src/test/resources/codegen/TestMarshalledArrayMapMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMarshalledArrayMapMessageSerializer.java index 4f3a7c0e51fc9..52f8785c633e4 100644 --- a/modules/core/src/test/resources/codegen/TestMarshalledArrayMapMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMarshalledArrayMapMessageSerializer.java @@ -19,6 +19,7 @@ import java.util.List; import org.apache.ignite.internal.GridTopicMessage; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMarshalledArrayMapMessage; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; import org.apache.ignite.plugin.extensions.communication.MessageArrayType; @@ -45,7 +46,7 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer private static final MessageArrayType mapValsCollDesc = new MessageArrayType(new MessageCollectionType(new MessageItemType(MessageCollectionItemType.MSG), CollectionImplementationType.ARRAY_LIST), List.class); /** */ - @Override public final boolean writeTo(TestMarshalledArrayMapMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshalledArrayMapMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -55,25 +56,25 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer switch (writer.state()) { case 0: - if (!writer.writeObjectArray(msg.mapKeys, mapKeysCollDesc)) + if (!writer.writeObjectArray(msg.mapKeys, mapKeysCollDesc, ctx)) return false; writer.incrementState(); case 1: - if (!writer.writeObjectArray(msg.mapVals, mapValsCollDesc)) + if (!writer.writeObjectArray(msg.mapVals, mapValsCollDesc, ctx)) return false; writer.incrementState(); case 2: - if (!writer.writeObjectArray(msg.fixedMapKeys, fixedMapKeysCollDesc)) + if (!writer.writeObjectArray(msg.fixedMapKeys, fixedMapKeysCollDesc, ctx)) return false; writer.incrementState(); case 3: - if (!writer.writeObjectArray(msg.fixedMapVals, fixedMapValsCollDesc)) + if (!writer.writeObjectArray(msg.fixedMapVals, fixedMapValsCollDesc, ctx)) return false; writer.incrementState(); @@ -83,10 +84,10 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer } /** */ - @Override public final boolean readFrom(TestMarshalledArrayMapMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshalledArrayMapMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: - msg.mapKeys = reader.readObjectArray(mapKeysCollDesc); + msg.mapKeys = reader.readObjectArray(mapKeysCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -94,7 +95,7 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer reader.incrementState(); case 1: - msg.mapVals = reader.readObjectArray(mapValsCollDesc); + msg.mapVals = reader.readObjectArray(mapValsCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -102,7 +103,7 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer reader.incrementState(); case 2: - msg.fixedMapKeys = reader.readObjectArray(fixedMapKeysCollDesc); + msg.fixedMapKeys = reader.readObjectArray(fixedMapKeysCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -110,7 +111,7 @@ public final class TestMarshalledArrayMapMessageSerializer implements MessageSer reader.incrementState(); case 3: - msg.fixedMapVals = reader.readObjectArray(fixedMapValsCollDesc); + msg.fixedMapVals = reader.readObjectArray(fixedMapValsCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestMarshalledCollectionMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMarshalledCollectionMessageSerializer.java index 43f7bf2f2e996..7b831f8ec04e8 100644 --- a/modules/core/src/test/resources/codegen/TestMarshalledCollectionMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMarshalledCollectionMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMarshalledCollectionMessage; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.plugin.extensions.communication.MessageArrayType; @@ -36,7 +37,7 @@ public final class TestMarshalledCollectionMessageSerializer implements MessageS private static final MessageArrayType keysArrCollDesc = new MessageArrayType(new MessageItemType(MessageCollectionItemType.GRID_CACHE_VERSION), GridCacheVersion.class); /** */ - @Override public final boolean writeTo(TestMarshalledCollectionMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshalledCollectionMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -46,7 +47,7 @@ public final class TestMarshalledCollectionMessageSerializer implements MessageS switch (writer.state()) { case 0: - if (!writer.writeObjectArray(msg.keysArr, keysArrCollDesc)) + if (!writer.writeObjectArray(msg.keysArr, keysArrCollDesc, ctx)) return false; writer.incrementState(); @@ -56,10 +57,10 @@ public final class TestMarshalledCollectionMessageSerializer implements MessageS } /** */ - @Override public final boolean readFrom(TestMarshalledCollectionMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshalledCollectionMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: - msg.keysArr = reader.readObjectArray(keysArrCollDesc); + msg.keysArr = reader.readObjectArray(keysArrCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestMarshalledMapMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMarshalledMapMessageSerializer.java index dd0fcb2253364..e3aca2bf684f3 100644 --- a/modules/core/src/test/resources/codegen/TestMarshalledMapMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMarshalledMapMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMarshalledMapMessage; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType; @@ -38,7 +39,7 @@ public final class TestMarshalledMapMessageSerializer implements MessageSerializ private static final MessageCollectionType mapValsCollDesc = new MessageCollectionType(new MessageItemType(MessageCollectionItemType.GRID_CACHE_VERSION), CollectionImplementationType.ARRAY_LIST); /** */ - @Override public final boolean writeTo(TestMarshalledMapMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshalledMapMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -48,13 +49,13 @@ public final class TestMarshalledMapMessageSerializer implements MessageSerializ switch (writer.state()) { case 0: - if (!writer.writeCollection(msg.mapKeys, mapKeysCollDesc)) + if (!writer.writeCollection(msg.mapKeys, mapKeysCollDesc, ctx)) return false; writer.incrementState(); case 1: - if (!writer.writeCollection(msg.mapVals, mapValsCollDesc)) + if (!writer.writeCollection(msg.mapVals, mapValsCollDesc, ctx)) return false; writer.incrementState(); @@ -64,10 +65,10 @@ public final class TestMarshalledMapMessageSerializer implements MessageSerializ } /** */ - @Override public final boolean readFrom(TestMarshalledMapMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshalledMapMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: - msg.mapKeys = reader.readCollection(mapKeysCollDesc); + msg.mapKeys = reader.readCollection(mapKeysCollDesc, ctx); if (!reader.isLastRead()) return false; @@ -75,7 +76,7 @@ public final class TestMarshalledMapMessageSerializer implements MessageSerializ reader.incrementState(); case 1: - msg.mapVals = reader.readCollection(mapValsCollDesc); + msg.mapVals = reader.readCollection(mapValsCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestMarshalledMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMarshalledMessageSerializer.java index fa81057b60403..b924687a0d06f 100644 --- a/modules/core/src/test/resources/codegen/TestMarshalledMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMarshalledMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMarshalledMessage; import org.apache.ignite.plugin.extensions.communication.MessageReader; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; @@ -29,7 +30,7 @@ */ public final class TestMarshalledMessageSerializer implements MessageSerializer { /** */ - @Override public final boolean writeTo(TestMarshalledMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshalledMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -49,7 +50,7 @@ public final class TestMarshalledMessageSerializer implements MessageSerializer< } /** */ - @Override public final boolean readFrom(TestMarshalledMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshalledMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: msg.dataBytes = reader.readByteArray(); diff --git a/modules/core/src/test/resources/codegen/TestMarshalledObjectsMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMarshalledObjectsMessageSerializer.java index b68578b06a615..68d62d8879123 100644 --- a/modules/core/src/test/resources/codegen/TestMarshalledObjectsMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMarshalledObjectsMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMarshalledObjectsMessage; import org.apache.ignite.plugin.extensions.communication.CollectionImplementationType; import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType; @@ -36,7 +37,7 @@ public final class TestMarshalledObjectsMessageSerializer implements MessageSeri private static final MessageCollectionType dataBytesCollDesc = new MessageCollectionType(new MessageItemType(MessageCollectionItemType.BYTE_ARR), CollectionImplementationType.ARRAY_LIST); /** */ - @Override public final boolean writeTo(TestMarshalledObjectsMessage msg, MessageWriter writer) { + @Override public final boolean writeTo(TestMarshalledObjectsMessage msg, MessageWriter writer, MessageSerializationContext ctx) { if (!writer.isHeaderWritten()) { if (!writer.writeHeader(msg.directType())) return false; @@ -46,7 +47,7 @@ public final class TestMarshalledObjectsMessageSerializer implements MessageSeri switch (writer.state()) { case 0: - if (!writer.writeCollection(msg.dataBytes, dataBytesCollDesc)) + if (!writer.writeCollection(msg.dataBytes, dataBytesCollDesc, ctx)) return false; writer.incrementState(); @@ -56,10 +57,10 @@ public final class TestMarshalledObjectsMessageSerializer implements MessageSeri } /** */ - @Override public final boolean readFrom(TestMarshalledObjectsMessage msg, MessageReader reader) { + @Override public final boolean readFrom(TestMarshalledObjectsMessage msg, MessageReader reader, MessageSerializationContext ctx) { switch (reader.state()) { case 0: - msg.dataBytes = reader.readCollection(dataBytesCollDesc); + msg.dataBytes = reader.readCollection(dataBytesCollDesc, ctx); if (!reader.isLastRead()) return false; diff --git a/modules/core/src/test/resources/codegen/TestMessageSerializer.java b/modules/core/src/test/resources/codegen/TestMessageSerializer.java index 7aa0a054471aa..1811c09f59037 100644 --- a/modules/core/src/test/resources/codegen/TestMessageSerializer.java +++ b/modules/core/src/test/resources/codegen/TestMessageSerializer.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.TestMessage; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.plugin.extensions.communication.MessageArrayType; @@ -40,7 +41,7 @@ public final class TestMessageSerializer implements MessageSerializer { + /** */ + @Override public final boolean writeTo(TestRollingUpgradeAwareMessage msg, MessageWriter writer, MessageSerializationContext ctx) { + if (!writer.isHeaderWritten()) { + if (!writer.writeHeader(msg.directType())) + return false; + + writer.onHeaderWritten(); + } + + switch (writer.state()) { + case 0: + if (!writer.writeInt(msg.plain)) + return false; + + writer.incrementState(); + + case 1: + if (ctx.includeFieldDeprecatedBy(TestFeatureRegistry.FIRST_FEATURE)) { + if (!writer.writeString(msg.oldFld)) + return false; + } + + writer.incrementState(); + + case 2: + if (ctx.includeFieldIntroducedBy(TestFeatureRegistry.FIRST_FEATURE)) { + if (!writer.writeString(msg.newFld)) + return false; + } + + writer.incrementState(); + + case 3: + if (ctx.includeFieldIntroducedBy(TestFeatureRegistry.FIRST_FEATURE) && ctx.includeFieldDeprecatedBy(TestFeatureRegistry.SECOND_FEATURE)) { + if (!writer.writeLong(msg.windowed)) + return false; + } + + writer.incrementState(); + + } + + return true; + } + + /** */ + @Override public final boolean readFrom(TestRollingUpgradeAwareMessage msg, MessageReader reader, MessageSerializationContext ctx) { + switch (reader.state()) { + case 0: + msg.plain = reader.readInt(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 1: + if (ctx.includeFieldDeprecatedBy(TestFeatureRegistry.FIRST_FEATURE)) { + msg.oldFld = reader.readString(); + + if (!reader.isLastRead()) + return false; + } + + reader.incrementState(); + + case 2: + if (ctx.includeFieldIntroducedBy(TestFeatureRegistry.FIRST_FEATURE)) { + msg.newFld = reader.readString(); + + if (!reader.isLastRead()) + return false; + } + + reader.incrementState(); + + case 3: + if (ctx.includeFieldIntroducedBy(TestFeatureRegistry.FIRST_FEATURE) && ctx.includeFieldDeprecatedBy(TestFeatureRegistry.SECOND_FEATURE)) { + msg.windowed = reader.readLong(); + + if (!reader.isLastRead()) + return false; + } + + reader.incrementState(); + + } + + return true; + } + + /** {@inheritDoc} */ + @Override public final TestRollingUpgradeAwareMessage createMessage() { + return new TestRollingUpgradeAwareMessage(); + } +} diff --git a/modules/core/src/test/resources/codegen/TestUnknownFeatureMessage.java b/modules/core/src/test/resources/codegen/TestUnknownFeatureMessage.java new file mode 100644 index 0000000000000..7e1db62bb29e7 --- /dev/null +++ b/modules/core/src/test/resources/codegen/TestUnknownFeatureMessage.java @@ -0,0 +1,27 @@ +/* + * 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; + +import org.apache.ignite.plugin.extensions.communication.Message; + +/** */ +public class TestUnknownFeatureMessage implements Message { + /** */ + @Order(value = 0, introducedBy = "NO_SUCH_FEATURE") + int fld; +} diff --git a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridDirectParser.java b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridDirectParser.java index f30fbc5cfc0ea..43f89a6ab11c8 100644 --- a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridDirectParser.java +++ b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridDirectParser.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.util.CommonUtils.makeMessageType; +import static org.apache.ignite.internal.util.nio.MessageSerialization.resolveSerializationContext; /** * Parser for direct messages. @@ -86,7 +87,7 @@ public GridDirectParser(IgniteLogger log, MessageFactory msgFactory, GridNioMess if (msg != null && buf.hasRemaining()) { reader.setBuffer(buf); - finished = MessageSerialization.readFrom(msgFactory, msg, reader); + finished = MessageSerialization.readFrom(msgFactory, msg, reader, resolveSerializationContext(ses)); } if (finished) { diff --git a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioServer.java b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioServer.java index a067277648d35..40badc6bcf4f6 100644 --- a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioServer.java +++ b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioServer.java @@ -85,6 +85,7 @@ import static org.apache.ignite.failure.FailureType.SYSTEM_WORKER_TERMINATION; import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.MSG_WRITER; import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.NIO_OPERATION; +import static org.apache.ignite.internal.util.nio.MessageSerialization.resolveSerializationContext; /** * TCP NIO server. Due to asynchronous nature of connections processing @@ -1506,7 +1507,7 @@ private void processWriteSsl(SelectionKey key) throws IOException { List pendingRequests = new ArrayList<>(2); if (req != null) - finished = writeToBuffer(writer, buf, req, pendingRequests); + finished = writeToBuffer(ses, writer, buf, req, pendingRequests); // Fill up as many messages as possible to write buffer. while (finished) { @@ -1518,7 +1519,7 @@ private void processWriteSsl(SelectionKey key) throws IOException { if (req == null) break; - finished = writeToBuffer(writer, buf, req, pendingRequests); + finished = writeToBuffer(ses, writer, buf, req, pendingRequests); } int sesBufLimit = buf.limit(); @@ -1588,6 +1589,7 @@ private void processWriteSsl(SelectionKey key) throws IOException { } /** + * @param ses Session the message is written to. * @param writer Customizer of writing. * @param buf Buffer to write. * @param req Source of data. @@ -1595,6 +1597,7 @@ private void processWriteSsl(SelectionKey key) throws IOException { * @return {@code true} if message successfully written to buffer and {@code false} otherwise. */ private boolean writeToBuffer( + GridSelectorNioSessionImpl ses, MessageWriter writer, ByteBuffer buf, SessionWriteRequest req, @@ -1614,7 +1617,7 @@ private boolean writeToBuffer( else { writer.setBuffer(buf); - finished = MessageSerialization.writeTo(messageFactory(), msg, writer); + finished = MessageSerialization.writeTo(messageFactory(), msg, writer, resolveSerializationContext(ses)); } if (finished) { @@ -1781,14 +1784,13 @@ private void processWrite0(SelectionKey key) throws IOException { } /** - * @param writer Customizer of writing. + * @param ses Session the message is written to. * @param buf Buffer to write. * @param req Source of data. - * @param ses Session for notification about writting. + * @param writer Customizer of writing. * @return {@code true} if message successfully written to buffer and {@code false} otherwise. */ - private boolean writeToBuffer(GridSelectorNioSessionImpl ses, ByteBuffer buf, SessionWriteRequest req, - MessageWriter writer) { + private boolean writeToBuffer(GridSelectorNioSessionImpl ses, ByteBuffer buf, SessionWriteRequest req, MessageWriter writer) { Message msg; boolean finished; msg = (Message)req.message(); @@ -1803,7 +1805,7 @@ private boolean writeToBuffer(GridSelectorNioSessionImpl ses, ByteBuffer buf, Se else { writer.setBuffer(buf); - finished = MessageSerialization.writeTo(msgFactory, msg, writer); + finished = MessageSerialization.writeTo(msgFactory, msg, writer, resolveSerializationContext(ses)); } if (finished) { diff --git a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionMetaKey.java b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionMetaKey.java index fbdcc6d5d34f9..9a5836d14cc4c 100644 --- a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionMetaKey.java +++ b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionMetaKey.java @@ -42,7 +42,10 @@ public enum GridNioSessionMetaKey { MARSHALLER_ID, /** Message writer. */ - MSG_WRITER; + MSG_WRITER, + + /** Message serialization context. */ + MSG_SER_CTX; /** Maximum count of NIO session keys in system. */ public static final int MAX_KEYS_CNT = 64; diff --git a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/MessageSerialization.java b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/MessageSerialization.java index b436edb764d49..5f888f042ad3a 100644 --- a/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/MessageSerialization.java +++ b/modules/nio/src/main/java/org/apache/ignite/internal/util/nio/MessageSerialization.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.util.nio; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.plugin.extensions.communication.MessageReader; @@ -40,11 +41,17 @@ private MessageSerialization() { * @param factory Message factory. * @param msg Message instance. * @param writer Writer. + * @param ctx Serialization context. * @param Message type. * @return Whether message was fully written. */ - public static boolean writeTo(MessageFactory factory, M msg, MessageWriter writer) { - return resolve(factory, msg).writeTo(msg, writer); + public static boolean writeTo( + MessageFactory factory, + M msg, + MessageWriter writer, + MessageSerializationContext ctx + ) { + return resolveMessageserializer(factory, msg).writeTo(msg, writer, ctx); } /** @@ -53,16 +60,31 @@ public static boolean writeTo(MessageFactory factory, M msg, * @param factory Message factory. * @param msg Message instance. * @param reader Reader. + * @param ctx Serialization context. * @param Message type. * @return Whether message was fully read. */ - public static boolean readFrom(MessageFactory factory, M msg, MessageReader reader) { - return resolve(factory, msg).readFrom(msg, reader); + public static boolean readFrom( + MessageFactory factory, + M msg, + MessageReader reader, + MessageSerializationContext ctx + ) { + return resolveMessageserializer(factory, msg).readFrom(msg, reader, ctx); + } + + /** */ + public static MessageSerializationContext resolveSerializationContext(GridNioSession ses) { + MessageSerializationContext ctx = ses.meta(GridNioSessionMetaKey.MSG_SER_CTX.ordinal()); + + assert ctx != null : "Session has no serialization context: " + ses; + + return ctx; } /** @return the serializer registered for {@code msg}'s direct type. */ @SuppressWarnings("unchecked") - private static MessageSerializer resolve(MessageFactory factory, M msg) { + private static MessageSerializer resolveMessageserializer(MessageFactory factory, M msg) { return (MessageSerializer)factory.serializer(msg.directType()); } } diff --git a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageReader.java b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageReader.java index 796acc40a191d..faf50669796dd 100644 --- a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageReader.java +++ b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageReader.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.Map; import java.util.UUID; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheObject; import org.apache.ignite.internal.processors.cache.KeyCacheObject; @@ -196,21 +197,23 @@ public default void setBuffer(ByteBuffer buf) { /** * Reads nested message. * + * @param ctx Serialization context. * @param Type of the message. * @return Message. */ - public default T readMessage() { - return readMessage(false); + public default T readMessage(MessageSerializationContext ctx) { + return readMessage(false, ctx); } /** * Reads nested message. * * @param compress Whether message should be decompressed. + * @param ctx Serialization context. * @param Type of the message. * @return Message. */ - public T readMessage(boolean compress); + public T readMessage(boolean compress, MessageSerializationContext ctx); /** * Reads {@link CacheObject}. @@ -237,29 +240,32 @@ public default T readMessage() { * Reads array of objects. * * @param type Array component type. + * @param ctx Serialization context. * @param Type of the read object. * @return Array of objects. */ - public T[] readObjectArray(MessageArrayType type); + public T[] readObjectArray(MessageArrayType type, MessageSerializationContext ctx); /** * Reads any collection. * * @param type Collection item type. + * @param ctx Serialization context. * @param Type of the read collection. * @return Collection. */ - public > C readCollection(MessageCollectionType type); + public > C readCollection(MessageCollectionType type, MessageSerializationContext ctx); /** * Reads map. * * @param type Map type. + * @param ctx Serialization context. * @param Type of the read map. * @return Map. */ - public default > M readMap(MessageMapType type) { - return readMap(type, false); + public default > M readMap(MessageMapType type, MessageSerializationContext ctx) { + return readMap(type, false, ctx); } /** @@ -267,10 +273,11 @@ public default T readMessage() { * * @param type Map type. * @param compress Whether map should be compressed. + * @param ctx Serialization context. * @param Type of the read map. * @return Map. */ - public > M readMap(MessageMapType type, boolean compress); + public > M readMap(MessageMapType type, boolean compress, MessageSerializationContext ctx); /** @return Ignite product version. */ IgniteProductVersion readIgniteProductVersion(); diff --git a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageSerializer.java b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageSerializer.java index 86ce0d170a09f..ab66f296ed6b0 100644 --- a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageSerializer.java +++ b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageSerializer.java @@ -17,6 +17,8 @@ package org.apache.ignite.plugin.extensions.communication; +import org.apache.ignite.internal.MessageSerializationContext; + /** * Interface for message serialization logic. Resolve-and-dispatch entry points that look the serializer up from the * message factory live in {@code MessageSerialization}. @@ -27,18 +29,20 @@ public interface MessageSerializer { * * @param msg Message instance. * @param writer Writer. + * @param ctx Serialization context. * @return Whether message was fully written. */ - public boolean writeTo(M msg, MessageWriter writer); + public boolean writeTo(M msg, MessageWriter writer, MessageSerializationContext ctx); /** * Reads this message from provided byte buffer. * * @param msg Message instance. * @param reader Reader. + * @param ctx Serialization context. * @return Whether message was fully read. */ - public boolean readFrom(M msg, MessageReader reader); + public boolean readFrom(M msg, MessageReader reader, MessageSerializationContext ctx); /** * @return New instance of message. diff --git a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageWriter.java b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageWriter.java index 019bab8fa9db9..4f281cec3a86a 100644 --- a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageWriter.java +++ b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageWriter.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.Map; import java.util.UUID; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheObject; import org.apache.ignite.internal.processors.cache.KeyCacheObject; @@ -246,10 +247,11 @@ public default void setBuffer(ByteBuffer buf) { * Writes nested message. * * @param val Message. + * @param ctx Serialization context. * @return Whether value was fully written. */ - public default boolean writeMessage(Message val) { - return writeMessage(val, false); + public default boolean writeMessage(Message val, MessageSerializationContext ctx) { + return writeMessage(val, false, ctx); } /** @@ -257,9 +259,10 @@ public default boolean writeMessage(Message val) { * * @param val Message. * @param compress Whether message should be compressed. + * @param ctx Serialization context. * @return Whether value was fully written. */ - public boolean writeMessage(Message val, boolean compress); + public boolean writeMessage(Message val, boolean compress, MessageSerializationContext ctx); /** * Writes {@link CacheObject}. @@ -290,32 +293,35 @@ public default boolean writeMessage(Message val) { * * @param arr Array of objects. * @param type Array component type. + * @param ctx Serialization context. * @param Type of the objects that array contains. * @return Whether array was fully written. */ - public boolean writeObjectArray(T[] arr, MessageArrayType type); + public boolean writeObjectArray(T[] arr, MessageArrayType type, MessageSerializationContext ctx); /** * Writes collection with its elements order. * * @param col Collection. * @param type Collection item type. + * @param ctx Serialization context. * @param Type of the objects that collection contains. * @return Whether value was fully written. */ - public boolean writeCollection(Collection col, MessageCollectionType type); + public boolean writeCollection(Collection col, MessageCollectionType type, MessageSerializationContext ctx); /** * Writes map. * * @param map Map. * @param type Map type. + * @param ctx Serialization context. * @param Initial key types of the map to write. * @param Initial value types of the map to write. * @return Whether value was fully written. */ - public default boolean writeMap(Map map, MessageMapType type) { - return writeMap(map, type, false); + public default boolean writeMap(Map map, MessageMapType type, MessageSerializationContext ctx) { + return writeMap(map, type, false, ctx); } /** @@ -324,11 +330,12 @@ public default boolean writeMap(Map map, MessageMapType type) { * @param map Map. * @param type Map type. * @param compress Whether map should be compressed. + * @param ctx Serialization context. * @param Initial key types of the map to write. * @param Initial value types of the map to write. * @return Whether value was fully written. */ - public boolean writeMap(Map map, MessageMapType type, boolean compress); + public boolean writeMap(Map map, MessageMapType type, boolean compress, MessageSerializationContext ctx); /** * Writes ignite product version. diff --git a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java index aeb7c1be8851b..451fe3d66efb9 100644 --- a/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java +++ b/modules/zookeeper/src/main/java/org/apache/ignite/spi/discovery/zk/internal/DiscoveryMessageParser.java @@ -37,6 +37,8 @@ import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.IgniteSpiException; +import static org.apache.ignite.internal.MessageSerializationContext.IGNORED; + /** * Class is responsible for serializing discovery messages using RU-ready {@link MessageSerializer} mechanism. */ @@ -105,7 +107,7 @@ private void serializeMessage(Message m, OutputStream out) throws IOException { do { msgBuf.clear(); - finished = MessageSerialization.writeTo(msgFactory, m, msgWriter); + finished = MessageSerialization.writeTo(msgFactory, m, msgWriter, IGNORED); out.write(msgBuf.array(), 0, msgBuf.position()); } @@ -131,7 +133,7 @@ private T deserializeMessage(InputStream in) throws IOExcept msgBuf.rewind(); } - finished = MessageSerialization.readFrom(msgFactory, msg, msgReader); + finished = MessageSerialization.readFrom(msgFactory, msg, msgReader, IGNORED); assert read != -1 || finished : "Stream closed before message was fully read."; From fd65ccc1b47d62a748c5bb255b2fe6b407e8c333 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Wed, 9 Sep 2026 21:48:10 +0300 Subject: [PATCH 5/6] IGNITE-29029 --- .../ignite/internal/MessageProcessor.java | 6 +- .../feature/SupportedFeatureRegistry.java | 3 + .../security/IgniteSecurityProcessor.java | 2 +- .../security/SecurityContextWrapper.java | 4 +- .../DistributedAttributeIdRegistry.java | 29 --- .../context/DistributedAttributeKey.java | 69 ++++++ .../DistributedAttributeKeyRegistry.java | 63 ++++++ .../context/OperationContextDispatcher.java | 69 ++++-- .../OperationContextSnapshotMessage.java | 56 +---- ...ationContextSnapshotMessageSerializer.java | 197 ++++++++++++++++++ .../AbstractMessageSerializationTest.java | 5 + .../AbstractRollingUpgradeTest.java | 4 +- .../AbstractRollingUpgradeMessageTest.java | 125 +++++++++++ ...ollingUpgradeDistributedAttributeTest.java | 193 +++++++++++++++++ ...ollingUpgradeMessageSerializationTest.java | 95 ++------- .../AbstractDistributedAttributeTest.java | 75 +++++++ ...rationContextAttributePropagationTest.java | 26 ++- .../testsuites/IgniteBasicTestSuite.java | 2 + 18 files changed, 827 insertions(+), 196 deletions(-) delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeIdRegistry.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKey.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessageSerializer.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/AbstractRollingUpgradeMessageTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/thread/context/AbstractDistributedAttributeTest.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java index 3a075ca0c010e..4f30a67f1bf1d 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java @@ -115,10 +115,14 @@ public class MessageProcessor extends AbstractProcessor { /** */ public static final Set NO_PUBLIC_CTOR_MSGS = Set.of(GRID_H2_NULL, ZK_NO_SERVERS_MESSAGE); - /** Messages with no fields. A serializer generation intentionally skipped. */ + /** */ + static final String OP_CTX_SNAPSHOT_MESSAGE_CLASS = "org.apache.ignite.internal.thread.context.OperationContextSnapshotMessage"; + + /** Messages with no fields, or with a hand-written serializer. A serializer generation intentionally skipped. */ static final String[] SKIP_MESSAGES = { "org.apache.ignite.internal.processors.odbc.ClientMessage", COMPRESSED_MESSAGE_CLASS, + OP_CTX_SNAPSHOT_MESSAGE_CLASS, "org.apache.ignite.loadtests.communication.GridTestMessage", "org.apache.ignite.spi.communication.tcp.TestDelayMessage" }; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java index 7b3e55b85d3c4..8c53c9f66653c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java @@ -93,4 +93,7 @@ public class SupportedFeatureRegistry { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature RU_AWARE_DISTRIBUTED_ATTRIBUTE_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java index b7586b7577a10..3e450ffd239c9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/IgniteSecurityProcessor.java @@ -56,7 +56,7 @@ import static org.apache.ignite.internal.processors.security.SecurityUtils.MSG_SEC_PROC_CLS_IS_INVALID; import static org.apache.ignite.internal.processors.security.SecurityUtils.hasSecurityManager; import static org.apache.ignite.internal.processors.security.SecurityUtils.nodeSecurityContext; -import static org.apache.ignite.internal.thread.context.DistributedAttributeIdRegistry.SECURITY; +import static org.apache.ignite.internal.thread.context.DistributedAttributeKeyRegistry.SECURITY; import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_USER_ACCESS; import static org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityContextWrapper.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityContextWrapper.java index a1feb8488a3cc..cbe5f1a1283f9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityContextWrapper.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/security/SecurityContextWrapper.java @@ -19,7 +19,7 @@ import java.util.UUID; import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.thread.context.DistributedAttributeIdRegistry; +import org.apache.ignite.internal.thread.context.DistributedAttributeKeyRegistry; import org.apache.ignite.internal.thread.context.OperationContextDispatcher; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.security.SecuritySubject; @@ -28,7 +28,7 @@ * {@link SecurityContext} attribute value holder and message for {@link SecuritySubject}'s id. * * @see OperationContextDispatcher#createSnapshot() - * @see DistributedAttributeIdRegistry#SECURITY + * @see DistributedAttributeKeyRegistry#SECURITY */ public class SecurityContextWrapper implements Message { /** A value of {@link SecuritySubject#id()} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeIdRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeIdRegistry.java deleted file mode 100644 index 7b4d284227207..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeIdRegistry.java +++ /dev/null @@ -1,29 +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.thread.context; - -import org.apache.ignite.internal.processors.security.SecurityContext; - -/** - * Declares reserved distributed IDs used to consistently identify {@link OperationContext} attributes across - * all nodes in the cluster. - */ -public class DistributedAttributeIdRegistry { - /** ID Reserved for {@link SecurityContext} propagation. */ - public static final byte SECURITY = 0; -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKey.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKey.java new file mode 100644 index 0000000000000..0e9243f4f99af --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKey.java @@ -0,0 +1,69 @@ +/* + * 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.thread.context; + +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; +import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.MAX_ATTRS_CNT; + +/** + * Represents key that is used to consistently identify {@link OperationContext} attributes across + * all nodes in the cluster. + * + * @see DistributedAttributeKeyRegistry + */ +public final class DistributedAttributeKey { + /** */ + @GridToStringInclude + private final byte id; + + /** */ + @GridToStringInclude + @Nullable private final IgniteFeature introducedBy; + + /** */ + DistributedAttributeKey(int id) { + this(id, null); + } + + /** */ + DistributedAttributeKey(int id, @Nullable IgniteFeature introducedBy) { + assert 0 <= id && id < MAX_ATTRS_CNT : "Invalid distributed attribute id [id=" + id + ']'; + + this.id = (byte)id; + this.introducedBy = introducedBy; + } + + /** @return Cluster-wide id of the attribute. */ + public byte id() { + return id; + } + + /** @return Feature that introduced the attribute, or {@code null} if every peer reads it. */ + public @Nullable IgniteFeature introducedBy() { + return introducedBy; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(DistributedAttributeKey.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java new file mode 100644 index 0000000000000..8f7862a4332a3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java @@ -0,0 +1,63 @@ +/* + * 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.thread.context; + +import java.lang.reflect.Field; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.processors.security.SecurityContext; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.MAX_ATTRS_CNT; + +/** + * Declares every distributed {@link OperationContext} attribute ID this release knows, along with the feature that + * introduced each of them. + * + *

Distributed Attribute key is used to consistently identify {@link OperationContext} attributes across + * all nodes in the cluster.

+ * + * @see DistributedAttributeKey + * @see OperationContextDispatcher + */ +public class DistributedAttributeKeyRegistry { + /** Attribute reserved for {@link SecurityContext} propagation. */ + public static final DistributedAttributeKey SECURITY = new DistributedAttributeKey(0); + + /** Package private so that tests can declare keys that are not constants of this registry. */ + static final DistributedAttributeKey[] VALS = new DistributedAttributeKey[MAX_ATTRS_CNT]; + + static { + try { + for (Field field : DistributedAttributeKeyRegistry.class.getFields()) { + DistributedAttributeKey key = (DistributedAttributeKey)field.get(null); + + assert VALS[key.id()] == null : "Duplicated distributed attribute id [id=" + key.id() + ']'; + + VALS[key.id()] = key; + } + } + catch (IllegalAccessException e) { + throw new IgniteException("Failed to read Distributed Attribute Key Registry", e); + } + } + + /** */ + public static @Nullable DistributedAttributeKey get(int id) { + return id >= 0 && id < VALS.length ? VALS[id] : null; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextDispatcher.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextDispatcher.java index 85b476a7e5185..c994ca8312a95 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextDispatcher.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextDispatcher.java @@ -18,7 +18,6 @@ import java.util.Arrays; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.plugin.extensions.communication.Message; import org.jetbrains.annotations.Nullable; @@ -32,8 +31,9 @@ *

The implementation relies on a mapping between a distributed identifier and an * {@link OperationContextAttribute} instance that is consistent across all cluster nodes.

* - *

To enable propagation of an {@link OperationContextAttribute} value across cluster nodes, the - * attribute must be registered with the {@link #registerDistributedAttribute(int, OperationContextAttribute)} method. + *

To enable propagation of an {@link OperationContextAttribute} value across cluster nodes, the attribute must be + * registered with the {@link #registerDistributedAttribute(DistributedAttributeKey, OperationContextAttribute)} + * method. * *

Note, that the maximum number of distributed attributes to register is currently limited to * {@link #MAX_ATTRS_CNT} for implementation reasons.

@@ -52,21 +52,25 @@ public class OperationContextDispatcher { private boolean regFinished; /** - * Registers an attribute of {@link OperationContext} with the specified distributed ID. + * Registers an attribute of {@link OperationContext} with the specified distributed attribute key. * - *

The distributed ID is used to consistently identify the attribute across all nodes in the cluster. - * It must be unique, and its value must be in the range [{@code 0} : {@code Byte.SIZE}).

+ *

The key consistently identifies the attribute across all nodes in the cluster and must be unique.

* *

Registered attribute value is automatically captured and propagated between cluster nodes * during the messages transmission.

* - * @see DistributedAttributeIdRegistry + * @see DistributedAttributeKeyRegistry */ - public synchronized void registerDistributedAttribute(int id, OperationContextAttribute attr) { + public synchronized void registerDistributedAttribute( + DistributedAttributeKey key, + OperationContextAttribute attr + ) { if (regFinished) throw new IgniteException("Initialization of distributed operation context attributes has already finished."); - assert 0 <= id && id < MAX_ATTRS_CNT : "Invalid distributed attributed id [id=" + id + ']'; + assert DistributedAttributeKeyRegistry.get(key.id()) == key; + + byte id = key.id(); OperationContextAttribute[] locRegisteredAttrs = registeredAttrs; @@ -93,7 +97,10 @@ public synchronized void registerDistributedAttribute(int id if (locRegisteredAttrs.length == 0) return null; - OperationContextSnapshotMessage.Builder snpBuilder = OperationContextSnapshotMessage.Builder.create(); + Message[] attrs = new Message[locRegisteredAttrs.length]; + + byte idBitmap = 0; + int cnt = 0; for (int id = 0; id < locRegisteredAttrs.length; id++) { OperationContextAttribute attr = locRegisteredAttrs[id]; @@ -103,11 +110,18 @@ public synchronized void registerDistributedAttribute(int id Message curVal = OperationContext.get(attr); - if (curVal != attr.initialValue()) - snpBuilder.add(id, curVal); + if (curVal == attr.initialValue()) + continue; + + attrs[cnt++] = curVal; + + idBitmap = set(idBitmap, id); } - return snpBuilder.isEmpty() ? null : snpBuilder.build(); + if (idBitmap == 0) + return null; + + return new OperationContextSnapshotMessage(idBitmap, cnt == attrs.length ? attrs : Arrays.copyOf(attrs, cnt)); } /** Restores {@link OperationContextAttribute} values received from a remote node. */ @@ -117,21 +131,17 @@ public Scope restoreSnapshot(@Nullable OperationContextSnapshotMessage snp) { OperationContextAttribute[] locRegisteredAttrs = registeredAttrs; - assert snp.idBitmap != 0; - assert !F.isEmpty(snp.attrs); - assert snp.attrs.length <= MAX_ATTRS_CNT; - OperationContext.Restorer ctxRestorer = OperationContext.Restorer.create(); - for (byte valIdx = 0, attrId = 0; valIdx < snp.attrs.length; ++valIdx) { - Message attrVal = snp.attrs[valIdx]; + for (byte attrId = 0, valIdx = 0; attrId < MAX_ATTRS_CNT && valIdx < snp.attrs.length; ++attrId) { + if (!contains(snp.idBitmap, attrId)) + continue; - while ((snp.idBitmap & (1 << attrId)) == 0) - ++attrId; + Message attrVal = snp.attrs[valIdx++]; assert attrId < locRegisteredAttrs.length; - OperationContextAttribute attr = (OperationContextAttribute)locRegisteredAttrs[attrId++]; + OperationContextAttribute attr = (OperationContextAttribute)locRegisteredAttrs[attrId]; assert attr != null; @@ -145,4 +155,19 @@ public Scope restoreSnapshot(@Nullable OperationContextSnapshotMessage snp) { public synchronized void finishRegistration() { regFinished = true; } + + /** @return Number of distributed attributes the bitmap of their ids names. */ + static int attributesCount(byte idBitmap) { + return Integer.bitCount(idBitmap & 0xFF); + } + + /** @return Whether the bitmap names the distributed attribute with the specified id. */ + static boolean contains(byte idBitmap, int attrId) { + return (idBitmap & (1 << attrId)) != 0; + } + + /** @return The bitmap with the distributed attribute with the specified id added. */ + static byte set(byte idBitmap, int attrId) { + return (byte)(idBitmap | (1 << attrId)); + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessage.java index 08fa263efa61d..7d4e539c5476b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessage.java @@ -17,13 +17,9 @@ package org.apache.ignite.internal.thread.context; -import java.util.ArrayList; -import java.util.List; -import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.plugin.extensions.communication.Message; -import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.MAX_ATTRS_CNT; - /** * Message for {@link OperationContext} distributed attributes. * @@ -31,11 +27,9 @@ */ public class OperationContextSnapshotMessage implements Message { /** Values of operation context attributes. */ - @Order(0) Message[] attrs; /** Bitmap of effective attributes ids. */ - @Order(1) byte idBitmap; /** Empty constructor for serialization purposes. */ @@ -44,50 +38,12 @@ public OperationContextSnapshotMessage() { } /** */ - private OperationContextSnapshotMessage(byte idBitmap, Message[] attrs) { + OperationContextSnapshotMessage(byte idBitmap, Message[] attrs) { + assert idBitmap != 0; + assert !F.isEmpty(attrs); + assert attrs.length == OperationContextDispatcher.attributesCount(idBitmap); + this.attrs = attrs; this.idBitmap = idBitmap; } - - /** */ - public static class Builder { - /** */ - private byte bitmap = 0; - - /** */ - private List vals; - - /** */ - private Builder() { - // No-op. - } - - /** */ - public void add(int attrId, Message attrVal) { - if (vals == null) - vals = new ArrayList<>(MAX_ATTRS_CNT / 2); - - byte mask = (byte)(1 << attrId); - - assert (bitmap & mask) == 0; - - vals.add(attrVal); - bitmap |= mask; - } - - /** */ - public boolean isEmpty() { - return bitmap == 0; - } - - /** */ - OperationContextSnapshotMessage build() { - return new OperationContextSnapshotMessage(bitmap, vals.toArray(Message[]::new)); - } - - /** */ - public static OperationContextSnapshotMessage.Builder create() { - return new Builder(); - } - } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessageSerializer.java new file mode 100644 index 0000000000000..5f70eaddafba2 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/OperationContextSnapshotMessageSerializer.java @@ -0,0 +1,197 @@ +/* + * 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.thread.context; + +import org.apache.ignite.internal.MessageSerializationContext; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageArrayType; +import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType; +import org.apache.ignite.plugin.extensions.communication.MessageItemType; +import org.apache.ignite.plugin.extensions.communication.MessageReader; +import org.apache.ignite.plugin.extensions.communication.MessageSerializer; +import org.apache.ignite.plugin.extensions.communication.MessageWriter; + +import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.RU_AWARE_DISTRIBUTED_ATTRIBUTE_FEATURE; +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.MAX_ATTRS_CNT; +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.attributesCount; +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.contains; +import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.set; + +/** */ +public final class OperationContextSnapshotMessageSerializer implements MessageSerializer { + /** */ + private static final MessageArrayType ATTRS_TYPE = new MessageArrayType( + new MessageItemType(MessageCollectionItemType.MSG), + Message.class + ); + + /** {@inheritDoc} */ + @Override public boolean writeTo(OperationContextSnapshotMessage msg, MessageWriter writer, MessageSerializationContext ctx) { + if (!writer.isHeaderWritten()) { + if (!writer.writeHeader(msg.directType())) + return false; + + writer.onHeaderWritten(); + } + + byte idBitmap = filterIds(msg.idBitmap, ctx); + + Message[] attrs = idBitmap == msg.idBitmap ? msg.attrs : filterValues(msg, idBitmap); + + return isCompactSerializationSupported(ctx) + ? writeCompact(idBitmap, attrs, writer, ctx) + : writeLegacy(idBitmap, attrs, writer, ctx); + } + + /** {@inheritDoc} */ + @Override public boolean readFrom(OperationContextSnapshotMessage msg, MessageReader reader, MessageSerializationContext ctx) { + return isCompactSerializationSupported(ctx) ? readCompact(msg, reader, ctx) : readLegacy(msg, reader, ctx); + } + + /** {@inheritDoc} */ + @Override public OperationContextSnapshotMessage createMessage() { + return new OperationContextSnapshotMessage(); + } + + /** */ + private static boolean writeCompact(byte idBitmap, Message[] attrs, MessageWriter writer, MessageSerializationContext ctx) { + if (writer.state() == 0) { + if (!writer.writeByte(idBitmap)) + return false; + + writer.incrementState(); + } + + for (int valIdx = writer.state() - 1; valIdx < attrs.length; valIdx = writer.state() - 1) { + if (!writer.writeMessage(attrs[valIdx], ctx)) + return false; + + writer.incrementState(); + } + + return true; + } + + /** */ + private static boolean readCompact(OperationContextSnapshotMessage msg, MessageReader reader, MessageSerializationContext ctx) { + if (reader.state() == 0) { + msg.idBitmap = reader.readByte(); + + if (!reader.isLastRead()) + return false; + + msg.attrs = new Message[attributesCount(msg.idBitmap)]; + + reader.incrementState(); + } + + for (int valIdx = reader.state() - 1; valIdx < msg.attrs.length; valIdx = reader.state() - 1) { + msg.attrs[valIdx] = reader.readMessage(ctx); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + } + + return true; + } + + /** */ + private static boolean writeLegacy(byte idBitmap, Message[] attrs, MessageWriter writer, MessageSerializationContext ctx) { + switch (writer.state()) { + case 0: + if (!writer.writeObjectArray(attrs, ATTRS_TYPE, ctx)) + return false; + + writer.incrementState(); + + case 1: + if (!writer.writeByte(idBitmap)) + return false; + + writer.incrementState(); + } + + return true; + } + + /** */ + private static boolean readLegacy(OperationContextSnapshotMessage msg, MessageReader reader, MessageSerializationContext ctx) { + switch (reader.state()) { + case 0: + msg.attrs = reader.readObjectArray(ATTRS_TYPE, ctx); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + + case 1: + msg.idBitmap = reader.readByte(); + + if (!reader.isLastRead()) + return false; + + reader.incrementState(); + } + + return true; + } + + /** */ + private static byte filterIds(byte idBitmap, MessageSerializationContext ctx) { + byte res = 0; + + for (int attrId = 0; attrId < MAX_ATTRS_CNT; attrId++) { + if (!contains(idBitmap, attrId)) + continue; + + DistributedAttributeKey key = DistributedAttributeKeyRegistry.get(attrId); + + assert key != null; + + if (key.introducedBy() == null || ctx.includeFieldIntroducedBy(key.introducedBy())) + res = set(res, attrId); + } + + return res; + } + + /** */ + private static Message[] filterValues(OperationContextSnapshotMessage msg, byte includedIds) { + Message[] res = new Message[attributesCount(includedIds)]; + + for (int attrId = 0, valIdx = 0, resIdx = 0; attrId < MAX_ATTRS_CNT && resIdx < res.length; attrId++) { + if (!contains(msg.idBitmap, attrId)) + continue; + + if (contains(includedIds, attrId)) + res[resIdx++] = msg.attrs[valIdx]; + + valIdx++; + } + + return res; + } + + /** */ + private static boolean isCompactSerializationSupported(MessageSerializationContext ctx) { + return ctx.includeFieldIntroducedBy(RU_AWARE_DISTRIBUTED_ATTRIBUTE_FEATURE); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java index 48e861cc6c98a..2472a1f440db9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/managers/AbstractMessageSerializationTest.java @@ -31,6 +31,7 @@ import org.apache.ignite.internal.processors.cache.CacheObject; import org.apache.ignite.internal.processors.cache.KeyCacheObject; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; +import org.apache.ignite.internal.thread.context.OperationContextSnapshotMessage; import org.apache.ignite.internal.util.GridLongList; import org.apache.ignite.internal.util.nio.MessageSerialization; import org.apache.ignite.lang.IgniteProductVersion; @@ -106,6 +107,10 @@ private void checkSerializationAndDeserializationConsistency( Message msg = msgFactory.create(msgType); + // OperationContextSnapshotMessage uses custom serialization that is incompatible with test reader and writer implementation. + if (msg instanceof OperationContextSnapshotMessage) + return; + initializeMessage(msg); while (!MessageSerialization.writeTo(msgFactory, msg, writer, IGNORED)) { 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 0bbdb9824a57d..ef1e198be1257 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 @@ -58,6 +58,7 @@ import org.apache.ignite.internal.processors.rollingupgrade.feature.TestPluginComponentFeatureSetProvider; import org.apache.ignite.internal.processors.rollingupgrade.feature.TestPluginFeature; import org.apache.ignite.internal.processors.rollingupgrade.feature.TestPluginReleaseFeatures_1_0_0; +import org.apache.ignite.internal.thread.context.AbstractDistributedAttributeTest; import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.lang.ConsumerX; import org.apache.ignite.internal.util.typedef.F; @@ -73,7 +74,6 @@ 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; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -132,7 +132,7 @@ * * */ -public abstract class AbstractRollingUpgradeTest extends GridCommonAbstractTest { +public abstract class AbstractRollingUpgradeTest extends AbstractDistributedAttributeTest { /** */ protected static final String TEST_DEFAULT_VER = "2.19.0"; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/AbstractRollingUpgradeMessageTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/AbstractRollingUpgradeMessageTest.java new file mode 100644 index 0000000000000..da30b7662ad62 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/AbstractRollingUpgradeMessageTest.java @@ -0,0 +1,125 @@ +/* + * 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.message; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.ignite.Ignite; +import org.apache.ignite.Ignition; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.OperationContextAttribute; +import org.apache.ignite.internal.thread.context.OperationContextSnapshot; +import org.apache.ignite.internal.thread.context.Scope; +import org.apache.ignite.plugin.extensions.communication.Message; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.ignite.internal.managers.communication.GridIoPolicy.PUBLIC_POOL; + +/** */ +public abstract class AbstractRollingUpgradeMessageTest extends AbstractRollingUpgradeTest { + /** */ + protected void startServerNodes(String... vers) throws Exception { + IgniteEx first = startGrid(0, vers[0]); + + if (Arrays.stream(vers).distinct().count() > 1) + ru(first).enableVersionUpgrade(); + + for (int idx = 1; idx < vers.length; idx++) + startGrid(idx, vers[idx]); + } + + /** */ + protected Received send(IgniteEx from, IgniteEx to, T msg) throws Exception { + AtomicReference> got = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + String topic = msg.getClass().getName(); + + to.context().io().addMessageListener(topic, (nodeId, rcvd, plc) -> { + got.set(new Received<>((T)rcvd)); + + latch.countDown(); + }); + + ClusterNode rcvNode = from.context().discovery().node(to.localNode().id()); + + from.context().io().sendToCustomTopic(rcvNode, topic, msg, PUBLIC_POOL); + + assertTrue(latch.await(getTestTimeout(), MILLISECONDS)); + + return got.get(); + } + + /** */ + protected Map> sendOverDiscovery(IgniteEx from, T msg) throws Exception { + List clusterNodes = Ignition.allGrids(); + + Map> receivedMsgs = new ConcurrentHashMap<>(); + + CountDownLatch latch = new CountDownLatch(clusterNodes.size()); + + for (Ignite rcv : clusterNodes) { + String name = rcv.name(); + + ((IgniteEx)rcv).context().discovery().setCustomEventListener((Class)msg.getClass(), (v, n, m) -> { + receivedMsgs.put(name, new Received<>(m)); + + latch.countDown(); + }); + } + + from.context().discovery().sendCustomEvent(msg); + + assertTrue(latch.await(getTestTimeout(), MILLISECONDS)); + + receivedMsgs.remove(from.name()); + + return receivedMsgs; + } + + /** */ + protected static class Received { + /** */ + final T msg; + + /** */ + private final OperationContextSnapshot opCtx; + + /** */ + private Received(T msg) { + this.msg = msg; + + opCtx = OperationContext.createSnapshot(); + } + + /** */ + V attribute(OperationContextAttribute attr) { + try (Scope ignored = OperationContext.restoreSnapshot(opCtx)) { + return OperationContext.get(attr); + } + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java new file mode 100644 index 0000000000000..0d00ee7fa128e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java @@ -0,0 +1,193 @@ +/* + * 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.message; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; +import org.apache.ignite.internal.processors.authentication.User; +import org.apache.ignite.internal.processors.cache.persistence.wal.WALPointer; +import org.apache.ignite.internal.thread.context.DistributedAttributeKey; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.OperationContextAttribute; +import org.apache.ignite.internal.thread.context.Scope; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.spi.MessagesPluginProvider; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_20_0.VER_2_20_0_ID_3_FEATURE; +import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.PTR_VAL; +import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.USR_VAL; +import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.VER_2_19_PTR_ATTR; +import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.VER_2_20_USR_ATTR; + +/** */ +public class RollingUpgradeDistributedAttributeTest extends AbstractRollingUpgradeMessageTest { + /** */ + private static final DistributedAttributeKey VER_2_19_ATTR_KEY = createTestKey(0); + + /** */ + private static final DistributedAttributeKey VER_2_20_ATTR_KEY = createTestKey(7, VER_2_20_0_ID_3_FEATURE); + + /** {@inheritDoc} */ + @Override protected Collection distributedAttributeKeys() { + return Arrays.asList(VER_2_19_ATTR_KEY, VER_2_20_ATTR_KEY); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setPluginProviders(F.concat( + cfg.getPluginProviders(), + new MessagesPluginProvider(TestCoreMessage.class), + new TestIgniteComponent())); + + return cfg; + } + + /** */ + @Test + public void testNewAttributeIsCutForOldPeer() throws Exception { + startServerNodes("2.19.0", "2.20.0"); + + checkMutualSend(grid(0), grid(1), PTR_VAL, null); + } + + /** */ + @Test + public void testNewAttributeIsCutForPeerReadingCompactLayout() throws Exception { + startServerNodes("2.19.2", "2.20.0"); + + checkMutualSend(grid(0), grid(1), PTR_VAL, null); + } + + /** */ + @Test + public void testBothAttributesReachPeerOfSameNewRelease() throws Exception { + startServerNodes("2.20.0", "2.20.0"); + + checkMutualSend(grid(0), grid(1), PTR_VAL, USR_VAL); + } + + /** */ + @Test + public void testNewAttributeIsCutAroundRingWithOldCoordinator() throws Exception { + startServerNodes("2.19.0", "2.20.0", "2.20.0"); + + Map> rcvd = sendOverDiscovery(grid(1), TestCoreMessage.build()); + + assertAttributes(PTR_VAL, null, rcvd.get(grid(0).name())); + assertAttributes(PTR_VAL, null, rcvd.get(grid(2).name())); + + assertAttributes(PTR_VAL, USR_VAL, send(grid(1), grid(2), TestCoreMessage.build())); + } + + /** */ + @Test + public void testNewAttributeReachesOnlyClientOfNewRelease() throws Exception { + startGrid(0, "2.19.0"); + startGrid(1, "2.19.0"); + + ru(1).enableVersionUpgrade(); + + upgradeNodeVersion(0, "2.20.0"); + upgradeNodeVersion(1, "2.20.0"); + + IgniteEx newVerCli = startClientGrid(2, "2.20.0"); + IgniteEx oldVerCli = startClientGrid(3, "2.19.0"); + + Map> rcvd = sendOverDiscovery(grid(1), TestCoreMessage.build()); + + assertAttributes(PTR_VAL, USR_VAL, rcvd.get(newVerCli.name())); + assertAttributes(PTR_VAL, null, rcvd.get(oldVerCli.name())); + + assertAttributes(PTR_VAL, USR_VAL, send(grid(1), newVerCli, TestCoreMessage.build())); + assertAttributes(PTR_VAL, null, send(grid(1), oldVerCli, TestCoreMessage.build())); + } + + /** */ + private void checkMutualSend(IgniteEx first, IgniteEx second, WALPointer expPtr, @Nullable User expUsr) throws Exception { + assertAttributes(expPtr, expUsr, send(first, second, TestCoreMessage.build())); + assertAttributes(expPtr, expUsr, send(second, first, TestCoreMessage.build())); + + assertAttributes(expPtr, expUsr, sendOverDiscovery(first, TestCoreMessage.build()).get(second.name())); + assertAttributes(expPtr, expUsr, sendOverDiscovery(second, TestCoreMessage.build()).get(first.name())); + } + + /** {@inheritDoc} */ + @Override protected Map> sendOverDiscovery( + IgniteEx from, + T msg + ) throws Exception { + try (Scope ignored = OperationContext.set(VER_2_19_PTR_ATTR, PTR_VAL, VER_2_20_USR_ATTR, USR_VAL)) { + return super.sendOverDiscovery(from, msg); + } + } + + /** {@inheritDoc} */ + @Override protected Received send(IgniteEx from, IgniteEx to, T msg) throws Exception { + try (Scope ignored = OperationContext.set(VER_2_19_PTR_ATTR, PTR_VAL, VER_2_20_USR_ATTR, USR_VAL)) { + return super.send(from, to, msg); + } + } + + /** */ + private static void assertAttributes(WALPointer expBase, @Nullable User expNew, Received rcvd) { + assertEquals(expBase, rcvd.attribute(VER_2_19_PTR_ATTR)); + assertEquals(expNew, rcvd.attribute(VER_2_20_USR_ATTR)); + } + + /** */ + static class TestIgniteComponent extends AbstractTestPluginProvider { + /** */ + public static final OperationContextAttribute VER_2_19_PTR_ATTR = OperationContextAttribute.newInstance(); + + /** */ + public static final OperationContextAttribute VER_2_20_USR_ATTR = OperationContextAttribute.newInstance(); + + /** */ + public static final WALPointer PTR_VAL = new WALPointer(1, 1, 1); + + /** */ + public static final User USR_VAL = User.create("1", "1"); + + /** {@inheritDoc} */ + @Override public String name() { + return "TestIgniteComponent"; + } + + /** {@inheritDoc} */ + @Override public void start(PluginContext ctx) { + GridKernalContext kctx = ((IgniteEx)ctx.grid()).context(); + + kctx.operationContextDispatcher().registerDistributedAttribute(VER_2_19_ATTR_KEY, VER_2_19_PTR_ATTR); + + if (kctx.localNodeFeatures().contains(VER_2_20_0_ID_3_FEATURE)) + kctx.operationContextDispatcher().registerDistributedAttribute(VER_2_20_ATTR_KEY, VER_2_20_USR_ATTR); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java index 8ba97aa82c1c7..d42fb160864db 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeMessageSerializationTest.java @@ -20,20 +20,12 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.apache.ignite.Ignite; import org.apache.ignite.Ignition; -import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.managers.communication.GridIoPolicy; import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; -import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; -import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.spi.MessagesPluginProvider; import org.junit.Test; @@ -45,7 +37,7 @@ import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.F; /** */ -public class RollingUpgradeMessageSerializationTest extends AbstractRollingUpgradeTest { +public class RollingUpgradeMessageSerializationTest extends AbstractRollingUpgradeMessageTest { /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); @@ -137,10 +129,10 @@ public void testDiscoveryClientsOnDifferentVersions() throws Exception { IgniteEx newVerCli = startClientGrid(2, "2.20.0"); IgniteEx oldVerCli = startClientGrid(3, "2.19.0"); - Map receivedMsgs = sendOverDiscovery(grid(1), TestCoreMessage.build()); + Map> receivedMsgs = sendOverDiscovery(grid(1), TestCoreMessage.build()); - assertFields(A, B, C, D, E, null, receivedMsgs.get(newVerCli.name())); - assertFields(A, B, C, null, null, null, receivedMsgs.get(oldVerCli.name())); + assertReceived(A, B, C, D, E, null, receivedMsgs.get(newVerCli.name())); + assertReceived(A, B, C, null, null, null, receivedMsgs.get(oldVerCli.name())); } /** */ @@ -388,10 +380,10 @@ private void checkCoreMessageBroadcast( String expE, String expF ) throws Exception { - Collection receivedMsgs = sendOverDiscovery(from, TestCoreMessage.build()).values(); + Collection> receivedMsgs = sendOverDiscovery(from, TestCoreMessage.build()).values(); - for (TestCoreMessage msg : receivedMsgs) - assertFields(expA, expB, expC, expD, expE, expF, msg); + for (Received rcvd : receivedMsgs) + assertReceived(expA, expB, expC, expD, expE, expF, rcvd); } /** */ @@ -422,84 +414,23 @@ private void checkReceivedMessa String expE, String expF ) throws Exception { - assertFields(expA, expB, expC, expD, expE, expF, send(from, to, msgFactory.get())); + assertReceived(expA, expB, expC, expD, expE, expF, send(from, to, msgFactory.get())); - assertFields(expA, expB, expC, expD, expE, expF, sendOverDiscovery(from, msgFactory.get()).get(to.name())); + assertReceived(expA, expB, expC, expD, expE, expF, sendOverDiscovery(from, msgFactory.get()).get(to.name())); } /** */ - private T send(IgniteEx from, IgniteEx to, T msg) throws Exception { - AtomicReference got = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - - String topic = msg.getClass().getName(); - - to.context().io().addMessageListener(topic, (nodeId, rcvd, plc) -> { - got.set((T)rcvd); - - latch.countDown(); - }); - - ClusterNode rcvNode = from.context().discovery().node(to.localNode().id()); - - from.context().io().sendToCustomTopic(rcvNode, topic, msg, GridIoPolicy.PUBLIC_POOL); - - assertTrue(latch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - - return got.get(); - } - - /** */ - private Map sendOverDiscovery( - IgniteEx from, - T msg - ) throws Exception { - List clusterNodes = Ignition.allGrids(); - - Map receivedMsgs = new ConcurrentHashMap<>(); - - CountDownLatch latch = new CountDownLatch(clusterNodes.size()); - - for (Ignite rcv : clusterNodes) { - String name = rcv.name(); - - ((IgniteEx)rcv).context().discovery().setCustomEventListener((Class)msg.getClass(), - (v, n, m) -> { - receivedMsgs.put(name, m); - - latch.countDown(); - }); - } - - from.context().discovery().sendCustomEvent(msg); - - assertTrue(latch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - - receivedMsgs.remove(from.name()); - - return receivedMsgs; - } - - /** */ - private void startServerNodes(String firstVer, String secondVer) throws Exception { - IgniteEx first = startGrid(0, firstVer); - - if (!firstVer.equals(secondVer)) - ru(first).enableVersionUpgrade(); - - startGrid(1, secondVer); - } - - /** */ - private static void assertFields( + private static void assertReceived( String expA, String expB, String expC, String expD, String expE, String expF, - TestMessage msg + Received rcvd ) { + TestMessage msg = rcvd.msg; + assertEquals(expA, msg.fldA()); assertEquals(expB, msg.fldB()); assertEquals(expC, msg.fldC()); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/thread/context/AbstractDistributedAttributeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/thread/context/AbstractDistributedAttributeTest.java new file mode 100644 index 0000000000000..33c8a3ca37fec --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/thread/context/AbstractDistributedAttributeTest.java @@ -0,0 +1,75 @@ +/* + * 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.thread.context; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.internal.thread.context.DistributedAttributeKeyRegistry.VALS; + +/** */ +public abstract class AbstractDistributedAttributeTest extends GridCommonAbstractTest { + /** */ + private DistributedAttributeKey[] originalAttrKeys; + + /** */ + protected Collection distributedAttributeKeys() { + return Collections.emptyList(); + } + + /** */ + protected static DistributedAttributeKey createTestKey(int id) { + return new DistributedAttributeKey(id); + } + + /** */ + protected static DistributedAttributeKey createTestKey(int id, IgniteFeature introducedBy) { + return new DistributedAttributeKey(id, introducedBy); + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + Collection testKeys = distributedAttributeKeys(); + + if (!testKeys.isEmpty()) { + originalAttrKeys = Arrays.copyOf(VALS, VALS.length); + + for (DistributedAttributeKey key : testKeys) { + VALS[key.id()] = key; + } + } + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + stopAllGrids(); + + if (originalAttrKeys != null) { + System.arraycopy(originalAttrKeys, 0, VALS, 0, originalAttrKeys.length); + + originalAttrKeys = null; + } + + super.afterTestsStopped(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/thread/context/OperationContextAttributePropagationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/thread/context/OperationContextAttributePropagationTest.java index ea4978a711ee1..5e1e38f0e8508 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/thread/context/OperationContextAttributePropagationTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/thread/context/OperationContextAttributePropagationTest.java @@ -18,6 +18,8 @@ package org.apache.ignite.internal.thread.context; import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -45,7 +47,6 @@ import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.ListeningTestLogger; import org.apache.ignite.testframework.LogListener; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.Nullable; import org.junit.Test; @@ -67,15 +68,26 @@ import static org.junit.Assume.assumeFalse; /** */ -public class OperationContextAttributePropagationTest extends GridCommonAbstractTest { +public class OperationContextAttributePropagationTest extends AbstractDistributedAttributeTest { /** */ public static final LogListener MSG_DELAYED_LSNR = LogListener.builder().andMatches( "Delay custom message processing, there are joining nodes" ).build(); + /** */ + public static final DistributedAttributeKey PTR_KEY = createTestKey(0); + + /** */ + public static final DistributedAttributeKey USR_KEY = createTestKey(MAX_ATTRS_CNT - 1); + /** */ private volatile Consumer discoMsgLsnr; + /** {@inheritDoc} */ + @Override protected Collection distributedAttributeKeys() { + return Arrays.asList(PTR_KEY, USR_KEY); + } + /** {@inheritDoc} */ @Override protected void beforeTest() throws Exception { super.beforeTest(); @@ -223,7 +235,7 @@ private void prepareCluster() throws Exception { assertThrows( null, - () -> grid(0).context().operationContextDispatcher().registerDistributedAttribute(1, null), + () -> grid(0).context().operationContextDispatcher().registerDistributedAttribute(PTR_KEY, null), IgniteException.class, "Initialization of distributed operation context attributes has already finished" ); @@ -348,7 +360,7 @@ private void checkOperationContextCommunicationTransmission( } } - /** Prevents {@link ClusterNode#isLocal()} to be negative. */ + /** */ private ClusterNode node(Ignite from, Ignite to) { return from.cluster().node(((IgniteEx)to).localNode().id()); } @@ -404,13 +416,13 @@ static class TestIgniteComponent extends AbstractTestPluginProvider { @Override public void start(PluginContext ctx) { kctx = ((IgniteEx)ctx.grid()).context(); - kctx.operationContextDispatcher().registerDistributedAttribute(MAX_ATTRS_CNT - 1, USR_ATTR); - kctx.operationContextDispatcher().registerDistributedAttribute(0, PTR_ATTR); + kctx.operationContextDispatcher().registerDistributedAttribute(USR_KEY, USR_ATTR); + kctx.operationContextDispatcher().registerDistributedAttribute(PTR_KEY, PTR_ATTR); assertThrowsAnyCause( log, () -> { - kctx.operationContextDispatcher().registerDistributedAttribute(MAX_ATTRS_CNT - 1, PTR_ATTR); + kctx.operationContextDispatcher().registerDistributedAttribute(USR_KEY, PTR_ATTR); return null; }, IgniteException.class, 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 e8a54f1c0d41f..d9c760645bdd3 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 @@ -67,6 +67,7 @@ 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.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest; import org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeMessageSerializationTest; import org.apache.ignite.internal.product.GridProductVersionSelfTest; import org.apache.ignite.internal.util.ErrorMessageSelfTest; @@ -114,6 +115,7 @@ CoreVersionRollingUpgradeTest.class, PluginVersionRollingUpgradeTest.class, RollingUpgradeMessageSerializationTest.class, + RollingUpgradeDistributedAttributeTest.class, ManagementApiVersionValidationTest.class, GridProductVersionSelfTest.class, GridAffinityAssignmentV2Test.class, From 0a06ba96c7f2878440d36de56522b0eb71509945 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Mon, 31 Aug 2026 13:56:41 +0300 Subject: [PATCH 6/6] IGNITE-29030 --- .../idto/IDTOSerializerGenerator.java | 39 ++- .../util/OfflineTestCommandArgSerializer.java | 13 +- .../IgniteMessageSerializationContext.java | 16 + .../dto/IgniteDataTransferObject.java | 8 +- .../IgniteDataTransferObjectSerializer.java | 7 +- .../cache/CacheMetricsSnapshotSerializer.java | 9 +- .../processors/job/GridJobWorker.java | 79 +++-- ...butedMetaStorageHistoryItemSerializer.java | 13 +- .../RollingUpgradeProcessor.java | 8 + .../feature/IgniteNodeFeatureSet.java | 6 +- .../feature/SupportedFeatureRegistry.java | 3 + .../processors/task/GridTaskProcessor.java | 141 +++++---- .../processors/task/GridTaskWorker.java | 88 ++++++ .../DistributedAttributeKeyRegistry.java | 4 + .../ignite/internal/util/IgniteUtils.java | 5 +- .../ignite/internal/visor/VisorJob.java | 19 ++ .../internal/visor/VisorMultiNodeTask.java | 12 +- .../internal/visor/VisorTaskArgument.java | 45 ++- .../internal/visor/VisorTaskResult.java | 32 +- .../ignite/internal/TestCommandArgument.java | 47 +++ .../TestCommandArgumentSerializer.java | 54 ++++ .../ignite/internal/TestCommandResponse.java | 68 ++++ .../TestCommandResponseSerializer.java | 65 ++++ .../ignite/internal/TestCommandTask.java | 94 ++++++ .../localtask/SimpleTaskSerializer.java | 9 +- .../AbstractRollingUpgradeTest.java | 24 +- ...stractRollingUpgradeManagementApiTest.java | 65 ++++ ...teTaskOperationContextPropagationTest.java | 295 ++++++++++++++++++ .../ManagementApiVersionValidationTest.java | 53 +--- .../RollingUpgradeManagementApiTest.java | 131 ++++++++ ...ollingUpgradeDistributedAttributeTest.java | 26 ++ .../ignite/marshaller/HolderSerializer.java | 9 +- .../testsuites/IgniteBasicTestSuite.java | 4 + .../idto/TestIgniteDataTransferObject.java | 9 +- ...estIgniteDataTransferObjectSerializer.java | 18 +- .../TestCommandCommandArgSerializer.java | 9 +- 36 files changed, 1347 insertions(+), 180 deletions(-) create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgument.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgumentSerializer.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponse.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponseSerializer.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/TestCommandTask.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/AbstractRollingUpgradeManagementApiTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ComputeTaskOperationContextPropagationTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/RollingUpgradeManagementApiTest.java diff --git a/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java index 87ef9dac8a190..80cf62c153791 100644 --- a/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java +++ b/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java @@ -34,7 +34,6 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.processing.FilerException; import javax.annotation.processing.ProcessingEnvironment; @@ -52,6 +51,7 @@ import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.apache.ignite.internal.MessageProcessor.FieldFeatureGuard; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteBiTuple; @@ -61,6 +61,7 @@ import static org.apache.ignite.internal.MessageCompanionGenerator.TAB; import static org.apache.ignite.internal.MessageCompanionGenerator.identicalFileIsAlreadyGenerated; import static org.apache.ignite.internal.MessageCompanionGenerator.writeLicense; +import static org.apache.ignite.internal.MessageProcessor.buildFieldFeatureGuard; import static org.apache.ignite.internal.MessageSerializerGenerator.enumType; import static org.apache.ignite.internal.MessageSerializerGenerator.qualifiedClassName; import static org.apache.ignite.internal.idto.IgniteDataTransferObjectProcessor.DTO_CLASS; @@ -253,6 +254,7 @@ private String generateSerializerCode() throws IOException { imports.add(ObjectInput.class.getName()); imports.add(IOException.class.getName()); imports.add("org.apache.ignite.internal.util.typedef.internal.U"); + imports.add("org.apache.ignite.internal.MessageSerializationContext"); if (type.getNestingKind() != NestingKind.TOP_LEVEL) imports.add(type.getQualifiedName().toString()); @@ -312,7 +314,10 @@ private List generateWrite(Collection flds) { List code = new ArrayList<>(); code.add("/** {@inheritDoc} */"); - code.add("@Override public void writeExternal(" + typeWithGeneric(type.asType()) + " obj, ObjectOutput out) throws IOException {"); + code.add("@Override public void writeExternal(" + + typeWithGeneric(type.asType()) + " obj," + + " ObjectOutput out," + + " MessageSerializationContext ctx) throws IOException {"); fieldsSerdes(flds).forEach(line -> code.add(TAB + line)); @@ -328,8 +333,10 @@ private List generateRead(Collection flds) { List code = new ArrayList<>(); code.add("/** {@inheritDoc} */"); - code.add("@Override public void readExternal(" + typeWithGeneric(type.asType()) + " obj, ObjectInput in) " + - "throws IOException, ClassNotFoundException {"); + code.add("@Override public void readExternal(" + + typeWithGeneric(type.asType()) + " obj," + + " ObjectInput in," + + " MessageSerializationContext ctx) throws IOException, ClassNotFoundException {"); fieldsSerdes(flds).forEach(line -> code.add(TAB + line)); @@ -343,9 +350,27 @@ private List generateRead(Collection flds) { * @return Lines to serdes fields. */ private List fieldsSerdes(Collection flds) { - return flds.stream() - .flatMap(fld -> variableCode(fld.asType(), "obj." + fld.getSimpleName().toString())) - .collect(Collectors.toList()); + List res = new ArrayList<>(); + + for (VariableElement fld : flds) { + List lines = variableCode(fld.asType(), "obj." + fld.getSimpleName()).toList(); + + FieldFeatureGuard guard = buildFieldFeatureGuard(env, fld); + + if (guard == null) + res.addAll(lines); + else { + imports.add(guard.registry()); + + res.add("if (" + guard.expression() + ") {"); + + lines.forEach(line -> res.add(TAB + line)); + + res.add("}"); + } + } + + return res; } /** diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/OfflineTestCommandArgSerializer.java b/modules/control-utility/src/test/java/org/apache/ignite/util/OfflineTestCommandArgSerializer.java index b59278d3db4f8..03af45f17ccf7 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/OfflineTestCommandArgSerializer.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/OfflineTestCommandArgSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; import org.apache.ignite.internal.util.typedef.internal.U; @@ -36,12 +37,20 @@ */ public class OfflineTestCommandArgSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(TestOfflineTestCommandArg obj, ObjectOutput out) throws IOException { + @Override public void writeExternal( + TestOfflineTestCommandArg obj, + ObjectOutput out, + MessageSerializationContext ctx + ) throws IOException { U.writeString(out, obj.input); } /** {@inheritDoc} */ - @Override public void readExternal(TestOfflineTestCommandArg obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + TestOfflineTestCommandArg obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { obj.input = U.readString(in); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java b/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java index ddfe4a3693c48..5b27320ce27b6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/direct/IgniteMessageSerializationContext.java @@ -115,6 +115,22 @@ public static IgniteMessageSerializationContext buildForPeers( return new IgniteMessageSerializationContext(ctxByComponent); } + /** */ + public static MessageSerializationContext buildForInitiator(@Nullable IgniteNodeFeatureSet initiatorFeatures) { + if (initiatorFeatures == null) + return UNNEGOTIATED; + + Map ctxByComponent = new HashMap<>(); + + for (IgniteComponentFeatureSet cmpFeatures : initiatorFeatures.values()) { + ctxByComponent.put( + cmpFeatures.componentName(), + new ComponentMessageSerializationContext(cmpFeatures.features(), cmpFeatures.features())); + } + + return new IgniteMessageSerializationContext(ctxByComponent); + } + /** */ private static ComponentMessageSerializationContext resolveComponentSerializationContext( String cmpName, 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 f24bba840b1fb..eca7a7327a871 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 @@ -25,8 +25,12 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.codegen.idto.IDTOSerializerFactory; +import org.apache.ignite.internal.thread.context.OperationContext; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.direct.IgniteMessageSerializationContext.buildForInitiator; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; + /** * Base class for data transfer objects. */ @@ -72,7 +76,7 @@ protected void writeIgniteDataTransferObject(ObjectOutput out) throws IOExceptio try (IgniteDataTransferObjectOutput dtout = new IgniteDataTransferObjectOutput(out)) { IgniteDataTransferObjectSerializer serializer = IDTOSerializerFactory.getInstance().serializer(getClass()); - serializer.writeExternal(this, dtout); + serializer.writeExternal(this, dtout, buildForInitiator(OperationContext.get(OP_FEATURES_ATTR))); } } @@ -81,7 +85,7 @@ protected void readIgniteDataTransferObject(ObjectInput in) throws IOException, try (IgniteDataTransferObjectInput dtin = new IgniteDataTransferObjectInput(in)) { IgniteDataTransferObjectSerializer serializer = IDTOSerializerFactory.getInstance().serializer(getClass()); - serializer.readExternal(this, dtin); + serializer.readExternal(this, dtin, buildForInitiator(OperationContext.get(OP_FEATURES_ATTR))); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObjectSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObjectSerializer.java index 7a83d06510d46..4b3e6dde9ef9f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObjectSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/dto/IgniteDataTransferObjectSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; /** * @param Type of specific IgniteDataTransferObject this serializer works with. @@ -29,17 +30,19 @@ public interface IgniteDataTransferObjectSerializer { * * @param instance Instance of IgniteDataTransferObject to serialize. * @param out Output stream to write object to. + * @param ctx Serialization context. * @throws IOException If write operation failed. */ - void writeExternal(T instance, ObjectOutput out) throws IOException; + void writeExternal(T instance, ObjectOutput out, MessageSerializationContext ctx) throws IOException; /** * * @param instance Instance of an IgniteDataTransferObject to read data to. * @param in Input stream to read object from. + * @param ctx Serialization context. * @return * @throws IOException If read operation failed. * @throws ClassNotFoundException If class not found. */ - void readExternal(T instance, ObjectInput in) throws IOException, ClassNotFoundException; + void readExternal(T instance, ObjectInput in, MessageSerializationContext ctx) throws IOException, ClassNotFoundException; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheMetricsSnapshotSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheMetricsSnapshotSerializer.java index 678bce01e71e1..d8fea5af1e3bb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheMetricsSnapshotSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheMetricsSnapshotSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; import org.apache.ignite.internal.util.typedef.internal.U; @@ -34,7 +35,7 @@ */ public class CacheMetricsSnapshotSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(CacheMetricsSnapshot obj, ObjectOutput out) throws IOException { + @Override public void writeExternal(CacheMetricsSnapshot obj, ObjectOutput out, MessageSerializationContext ctx) throws IOException { out.writeLong(obj.m.cacheGets()); out.writeLong(obj.m.cachePuts()); out.writeLong(obj.m.cacheHits()); @@ -114,7 +115,11 @@ public class CacheMetricsSnapshotSerializer implements IgniteDataTransferObjectS } /** {@inheritDoc} */ - @Override public void readExternal(CacheMetricsSnapshot obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + CacheMetricsSnapshot obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { CacheMetricsMessage m = new CacheMetricsMessage(); m.cacheGets(in.readLong()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java index 5c58d42811e29..93f26eb16c575 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java @@ -56,6 +56,8 @@ import org.apache.ignite.internal.processors.service.GridServiceNotFoundException; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.processors.timeout.GridTimeoutObject; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.OperationContextSnapshot; import org.apache.ignite.internal.thread.context.Scope; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; @@ -170,8 +172,8 @@ public class GridJobWorker extends GridWorker implements GridTimeoutObject { /** Request topology version. */ private final String execName; - /** Security context. */ - private final SecurityContext secCtx; + /** Operation context the job was received under. */ + private final OperationContextSnapshot opCtxSnp; /** Job status. */ private volatile ComputeJobStatusEnum status = QUEUED; @@ -247,7 +249,7 @@ public class GridJobWorker extends GridWorker implements GridTimeoutObject { jobTopic = TOPIC_JOB.topic(ses.getJobId(), locNodeId); taskTopic = TOPIC_TASK.topic(ses.getJobId(), locNodeId); - secCtx = ctx.security().securityContext(); + opCtxSnp = OperationContext.createSnapshot(); } /** @@ -491,28 +493,32 @@ boolean initialize(GridDeployment dep, Class taskCls) { /** {@inheritDoc} */ @Override protected void body() { - assert job != null; + try (Scope ignored = OperationContext.restoreSnapshot(opCtxSnp)) { + assert job != null; - startTime = U.currentTimeMillis(); + startTime = U.currentTimeMillis(); - isStarted = true; + isStarted = true; - status = RUNNING; + status = RUNNING; - // Event notification. - evtLsnr.onJobStarted(this); + // Event notification. + evtLsnr.onJobStarted(this); - if (!internal && ctx.event().isRecordable(EVT_JOB_STARTED)) - recordEvent(EVT_JOB_STARTED, /*no message for success*/null); + if (!internal && ctx.event().isRecordable(EVT_JOB_STARTED)) + recordEvent(EVT_JOB_STARTED, /*no message for success*/null); - execute0(true); + execute0(true); + } } /** * Executes the job. */ public void execute() { - execute0(false); + try (Scope ignored = OperationContext.restoreSnapshot(opCtxSnp)) { + execute0(false); + } } /** @@ -525,7 +531,7 @@ private void execute0(boolean skipNtf) { SqlFieldsQuery.setThreadedQueryInitiatorId("task:" + ses.getTaskName() + ":" + getJobId()); - try (Scope ignored = ctx.security().withContext(secCtx)) { + try { if (partsReservation != null) { try { if (!partsReservation.reserve()) { @@ -728,32 +734,34 @@ else if (isNodeStopping && X.hasCause(e, InterruptedException.class, IgniteInter * @param sys System flag. */ public void cancel(boolean sys) { - if (log.isDebugEnabled()) - log.debug("Cancelling job: " + ses); + try (Scope ignored = OperationContext.restoreSnapshot(opCtxSnp)) { + if (log.isDebugEnabled()) + log.debug("Cancelling job: " + ses); - boolean firstCancel = isCancelled.compareAndSet(false, true); + boolean firstCancel = isCancelled.compareAndSet(false, true); - isCancelledBySystem = sys; + isCancelledBySystem = sys; - status = CANCELLED; + status = CANCELLED; - final ComputeJob job0 = job; + final ComputeJob job0 = job; - try (Scope ignored = ctx.security().withContext(secCtx)) { - U.wrapThreadLoader(dep.classLoader(), job0::cancel); - } - catch (Throwable e) { // Catch throwable to protect against bad user code. - U.error(log, "Failed to cancel job due to undeclared user exception [jobId=" + ses.getJobId() + - ", ses=" + ses + ']', e); + try { + U.wrapThreadLoader(dep.classLoader(), job0::cancel); + } + catch (Throwable e) { // Catch throwable to protect against bad user code. + U.error(log, "Failed to cancel job due to undeclared user exception [jobId=" + ses.getJobId() + + ", ses=" + ses + ']', e); - if (e instanceof Error) - throw e; - } - finally { - onCancel(firstCancel); + if (e instanceof Error) + throw e; + } + finally { + onCancel(firstCancel); - if (!internal && ctx.event().isRecordable(EVT_JOB_CANCELLED)) - recordEvent(EVT_JOB_CANCELLED, "Job was cancelled: " + job0); + if (!internal && ctx.event().isRecordable(EVT_JOB_CANCELLED)) + recordEvent(EVT_JOB_CANCELLED, "Job was cancelled: " + job0); + } } } @@ -782,6 +790,9 @@ private void recordEvent(int evtType, @Nullable String msg) { evt.taskSessionId(ses.getId()); evt.type(evtType); evt.taskNode(taskNode); + + SecurityContext secCtx = ses.session().initiatorSecurityContext(); + evt.taskSubjectId(secCtx != null ? secCtx.subject().id() : null); ctx.event().record(evt); @@ -1029,7 +1040,7 @@ private void logError(String msg, @Nullable Throwable e) { boolean onMasterNodeLeft() { if (job instanceof ComputeJobMasterLeaveAware) { if (masterLeaveGuard.compareAndSet(false, true)) { - try { + try (Scope ignored = OperationContext.restoreSnapshot(opCtxSnp)) { ((ComputeJobMasterLeaveAware)job).onMasterNodeLeft(ses.session()); if (log.isDebugEnabled()) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageHistoryItemSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageHistoryItemSerializer.java index 1383f056167d5..1069feb952d1d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageHistoryItemSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageHistoryItemSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; import org.apache.ignite.internal.util.typedef.internal.U; @@ -38,7 +39,11 @@ */ public class DistributedMetaStorageHistoryItemSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(DistributedMetaStorageHistoryItem obj, ObjectOutput out) throws IOException { + @Override public void writeExternal( + DistributedMetaStorageHistoryItem obj, + ObjectOutput out, + MessageSerializationContext ctx + ) throws IOException { out.writeInt(obj.keys.length); for (int i = 0; i < obj.keys.length; i++) { @@ -48,7 +53,11 @@ public class DistributedMetaStorageHistoryItemSerializer implements IgniteDataTr } /** {@inheritDoc} */ - @Override public void readExternal(DistributedMetaStorageHistoryItem obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + DistributedMetaStorageHistoryItem obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { int len = in.readInt(); obj.keys = new String[len]; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/RollingUpgradeProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/RollingUpgradeProcessor.java index 690b1f29521ac..580f58a7134c8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/RollingUpgradeProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/RollingUpgradeProcessor.java @@ -41,6 +41,7 @@ import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureManager; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.thread.context.OperationContextAttribute; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.InitMessage; import org.apache.ignite.internal.util.future.GridFinishedFuture; @@ -58,6 +59,8 @@ import static org.apache.ignite.events.EventType.EVT_NODE_LEFT; import static org.apache.ignite.events.EventType.EVT_NODE_VALIDATION_FAILED; import static org.apache.ignite.internal.GridComponent.DiscoveryDataExchangeType.ROLLING_UPGRADE_PROC; +import static org.apache.ignite.internal.thread.context.DistributedAttributeKeyRegistry.ROLLING_UPGRADE; +import static org.apache.ignite.internal.thread.context.OperationContextAttribute.newInstance; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_ABORT_VERSION_FINALIZATION; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_COMPLETE_VERSION_FINALIZATION; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_ENABLE; @@ -66,6 +69,9 @@ /** */ public class RollingUpgradeProcessor extends GridProcessorAdapter implements DiscoveryNodeValidationProcessor { + /** */ + public static final OperationContextAttribute OP_FEATURES_ATTR = newInstance(); + /** */ private final IgniteFeatureManager featureMgr; @@ -180,6 +186,8 @@ IgniteComponentUpgradeState state(String cmpName) { /** {@inheritDoc} */ @Override public void start() throws IgniteCheckedException { + ctx.operationContextDispatcher().registerDistributedAttribute(ROLLING_UPGRADE, OP_FEATURES_ATTR); + ctx.event().addLocalEventListener( evt -> { synchronized (topGuard) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteNodeFeatureSet.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteNodeFeatureSet.java index d00439e415602..188184261e188 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteNodeFeatureSet.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/IgniteNodeFeatureSet.java @@ -42,9 +42,7 @@ public class IgniteNodeFeatureSet implements Message, Externalizable { private static final long serialVersionUID = 0L; /** */ - public static final IgniteNodeFeatureSet LOCAL_CORE_FEATURES = new IgniteNodeFeatureSet(new IgniteComponentFeatureSet[] { - IgniteCoreFeatureSet.local() - }); + public static final IgniteNodeFeatureSet LOCAL_CORE_FEATURES = new IgniteNodeFeatureSet(IgniteCoreFeatureSet.local()); /** */ @Order(0) @@ -59,7 +57,7 @@ public IgniteNodeFeatureSet() { } /** */ - public IgniteNodeFeatureSet(IgniteComponentFeatureSet[] features) { + public IgniteNodeFeatureSet(IgniteComponentFeatureSet... features) { assert features != null; this.features = features; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java index 8c53c9f66653c..7a3363c66b284 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java @@ -96,4 +96,7 @@ public class SupportedFeatureRegistry { /** */ public static final IgniteFeature RU_AWARE_DISTRIBUTED_ATTRIBUTE_FEATURE = new IgniteCoreFeature(1); + + /** */ + public static final IgniteFeature OP_FEATURES_PROPAGATION_FEATURE = new IgniteCoreFeature(2); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java index 4386f8313e3c3..0a7ab5b9c0ffe 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java @@ -78,6 +78,8 @@ import org.apache.ignite.internal.processors.task.monitor.ComputeTaskStatus; import org.apache.ignite.internal.processors.task.monitor.ComputeTaskStatusSnapshot; import org.apache.ignite.internal.systemview.ComputeTaskViewWalker; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.Scope; import org.apache.ignite.internal.util.GridConcurrentFactory; import org.apache.ignite.internal.util.GridSpinReadWriteLock; import org.apache.ignite.internal.util.lang.GridPeerDeployAware; @@ -108,6 +110,7 @@ import static org.apache.ignite.internal.managers.communication.GridIoPolicy.SYSTEM_POOL; import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isPersistenceEnabled; import static org.apache.ignite.internal.processors.metric.GridMetricManager.SYS_METRICS; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; import static org.apache.ignite.internal.processors.security.SecurityUtils.securitySubjectId; import static org.apache.ignite.internal.processors.task.TaskExecutionOptions.options; import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.nodeIds; @@ -717,64 +720,9 @@ else if (task != null) { if (dep == null || !dep.acquire()) handleException(new IgniteDeploymentCheckedException("Task not deployed: " + ses.getTaskName()), fut); else { - GridTaskWorker taskWorker = new GridTaskWorker<>( - ctx, - arg, - ses, - fut, - taskCls, - task, - dep, - new TaskEventListener(), - opts, - securitySubjectId(ctx)); - - GridTaskWorker taskWorker0 = tasks.putIfAbsent(sesId, taskWorker); - - assert taskWorker0 == null : "Session ID is not unique: " + sesId; - - if (ctx.event().isRecordable(EVT_MANAGEMENT_TASK_STARTED) && dep.visorManagementTask(task, taskCls)) { - VisorTaskArgument visorTaskArg = (VisorTaskArgument)arg; - - Event evt = new ManagementTaskEvent( - ctx.discovery().localNode(), - visorTaskArg != null && visorTaskArg.getArgument() != null - ? visorTaskArg.getArgument().toString() : "[]", - EVT_MANAGEMENT_TASK_STARTED, - ses.getId(), - taskName, - taskCls == null ? null : taskCls.getName(), - false, - securitySubjectId(ctx), - visorTaskArg - ); - - ctx.event().record(evt); + try (Scope ignored = withTaskInitiatorFeatures(arg)) { + startTaskWorker(taskName, taskCls, task, sesId, arg, opts, ses, dep, fut); } - - if (!ctx.clientDisconnected()) { - if (dep.annotation(taskCls, ComputeTaskMapAsync.class) != null) { - try { - // Start task execution in another thread. - if (opts.isSystemTask()) - ctx.pools().getSystemExecutorService().execute(taskWorker); - else - ctx.pools().getExecutorService().execute(taskWorker); - } - catch (RejectedExecutionException e) { - tasks.remove(sesId); - - release(dep); - - handleException(new ComputeExecutionRejectedException("Failed to execute task " + - "due to thread pool execution rejection: " + taskName, e), fut); - } - } - else - taskWorker.run(); - } - else - taskWorker.finishTask(null, disconnectedError(null)); } } else { @@ -787,6 +735,85 @@ else if (task != null) { return fut; } + /** */ + private void startTaskWorker( + @Nullable String taskName, + @Nullable Class taskCls, + @Nullable ComputeTask task, + IgniteUuid sesId, + @Nullable T arg, + TaskExecutionOptions opts, + GridTaskSessionImpl ses, + GridDeployment dep, + ComputeTaskInternalFuture fut + ) { + GridTaskWorker taskWorker = new GridTaskWorker<>( + ctx, + arg, + ses, + fut, + taskCls, + task, + dep, + new TaskEventListener(), + opts, + securitySubjectId(ctx)); + + GridTaskWorker taskWorker0 = tasks.putIfAbsent(sesId, taskWorker); + + assert taskWorker0 == null : "Session ID is not unique: " + sesId; + + if (ctx.event().isRecordable(EVT_MANAGEMENT_TASK_STARTED) && dep.visorManagementTask(task, taskCls)) { + VisorTaskArgument visorTaskArg = (VisorTaskArgument)arg; + + Event evt = new ManagementTaskEvent( + ctx.discovery().localNode(), + visorTaskArg != null && visorTaskArg.getArgument() != null + ? visorTaskArg.getArgument().toString() : "[]", + EVT_MANAGEMENT_TASK_STARTED, + ses.getId(), + taskName, + taskCls == null ? null : taskCls.getName(), + false, + securitySubjectId(ctx), + visorTaskArg + ); + + ctx.event().record(evt); + } + + if (!ctx.clientDisconnected()) { + if (dep.annotation(taskCls, ComputeTaskMapAsync.class) != null) { + try { + // Start task execution in another thread. + if (opts.isSystemTask()) + ctx.pools().getSystemExecutorService().execute(taskWorker); + else + ctx.pools().getExecutorService().execute(taskWorker); + } + catch (RejectedExecutionException e) { + tasks.remove(sesId); + + release(dep); + + handleException(new ComputeExecutionRejectedException("Failed to execute task " + + "due to thread pool execution rejection: " + taskName, e), fut); + } + } + else + taskWorker.run(); + } + else + taskWorker.finishTask(null, disconnectedError(null)); + } + + /** */ + private static Scope withTaskInitiatorFeatures(@Nullable Object arg) { + return arg instanceof VisorTaskArgument + ? OperationContext.set(OP_FEATURES_ATTR, ((VisorTaskArgument)arg).initiatorFeatures()) + : Scope.NOOP_SCOPE; + } + /** * @param sesId Task's session id. * @return A {@link ComputeTaskInternalFuture} instance or {@code null} if no such task found. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java index 12f5dfd8d10e2..767589bfaa24d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskWorker.java @@ -73,9 +73,14 @@ import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.closure.AffinityTask; import org.apache.ignite.internal.processors.job.ComputeJobStatusEnum; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; import org.apache.ignite.internal.processors.security.PublicAccessJob; import org.apache.ignite.internal.processors.service.GridServiceNotFoundException; import org.apache.ignite.internal.processors.timeout.GridTimeoutObject; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.OperationContextSnapshot; +import org.apache.ignite.internal.thread.context.Scope; import org.apache.ignite.internal.util.lang.GridPlainRunnable; import org.apache.ignite.internal.util.typedef.CO; import org.apache.ignite.internal.util.typedef.F; @@ -90,6 +95,7 @@ import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.plugin.security.SecurityException; import org.apache.ignite.resources.TaskContinuousMapperResource; +import org.apache.ignite.spi.discovery.tcp.internal.UnsupportedNodeVersionException; import org.jetbrains.annotations.Nullable; import static java.util.Collections.emptyList; @@ -112,6 +118,7 @@ import static org.apache.ignite.internal.processors.job.ComputeJobStatusEnum.CANCELLED; import static org.apache.ignite.internal.processors.job.ComputeJobStatusEnum.FAILED; import static org.apache.ignite.internal.processors.job.ComputeJobStatusEnum.FINISHED; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; import static org.apache.ignite.internal.processors.security.SecurityUtils.authorizeAll; import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.node2id; import static org.apache.ignite.plugin.security.SecurityPermission.TASK_CANCEL; @@ -159,6 +166,9 @@ private enum State { /** */ private final GridTaskSessionImpl ses; + /** Operation context the task was started under. */ + private final OperationContextSnapshot opCtxSnp; + /** */ private final ComputeTaskInternalFuture fut; @@ -320,6 +330,8 @@ private enum State { this.opts = opts; this.subjId = subjId; + opCtxSnp = OperationContext.createSnapshot(); + log = U.logger(ctx, logRef, this); boolean noResCacheAnnotation = dep.annotation(taskCls, ComputeTaskNoResultCache.class) != null; @@ -586,6 +598,8 @@ private void processMappedJobs(Map jobs) thro if (node == null) throw new IgniteCheckedException("Node can not be null [mappedJob=" + mappedJob + ", ses=" + ses + ']'); + validateArgumentIsSupportedByDestinationNode(node); + authorizeSystemTaskJob(job); IgniteUuid jobId = IgniteUuid.fromUuid(ctx.localNodeId()); @@ -673,6 +687,64 @@ private void processMappedJobs(Map jobs) thro processDelayedResponses(); } + /** */ + private void validateArgumentIsSupportedByDestinationNode(ClusterNode destNode) throws IgniteCheckedException { + IgniteNodeFeatureSet initiatorFeatures = OperationContext.get(OP_FEATURES_ATTR); + + if (initiatorFeatures == null) + return; + + IgniteNodeFeatureSet destNodeFeatures; + + try { + destNodeFeatures = ctx.discovery().resolveNodeFeatures(destNode); + } + catch (ClusterTopologyCheckedException e) { + throw new IgniteCheckedException("Failed to resolve remote node features [nodeId=" + destNode.id() + ']', e); + } + + if (destNodeFeatures == null) { + throw new UnsupportedNodeVersionException( + "Failed to validate remote node features. The remote node's feature set is unavailable [nodeId=" + destNode.id() + ']'); + } + + for (IgniteComponentFeatureSet initiatorCmpFeatures : initiatorFeatures.values()) { + IgniteComponentFeatureSet destCmpFeatures = destNodeFeatures.componentFeatures(initiatorCmpFeatures.componentName()); + + if (destCmpFeatures == null) { + throw new IgniteCheckedException( + "The Ignite Management API command cannot be executed on a remote node because the remote node lacks" + + " a component of the command initiator" + + " [component=" + initiatorCmpFeatures.componentName() + + ", nodeId=" + destNode.id() + + ", nodeVer=" + destNode.version() + ']' + ); + } + + if (destCmpFeatures.version().compareTo(initiatorCmpFeatures.version()) < 0) { + throw new IgniteCheckedException( + "The Ignite Management API command cannot be executed on a remote node because the command initiator's" + + " Ignite version is not yet supported. Retry the operation after the Rolling Upgrade has completed" + + " [component=" + initiatorCmpFeatures.componentName() + + ", initiatorVer=" + initiatorCmpFeatures.version() + + ", nodeId=" + destNode.id() + + ", nodeVer=" + destNode.version() + ']' + ); + } + + if (!initiatorCmpFeatures.isUpgradableTo(destCmpFeatures)) { + throw new IgniteCheckedException( + "The Ignite Management API command cannot be executed on a remote node because the command initiator's" + + " Ignite version is not supported. Update binary version of the Ignite Management API" + + " [component=" + initiatorCmpFeatures.componentName() + + ", initiatorVer=" + initiatorCmpFeatures.version() + + ", nodeId=" + destNode.id() + + ", nodeVer=" + destNode.version() + ']' + ); + } + } + } + /** * @return Topology for this task. * @throws IgniteCheckedException Thrown in case of any error. @@ -714,6 +786,13 @@ private void processDelayedResponses() { * @param msg Job execution response. */ void onResponse(GridJobExecuteResponse msg) { + try (Scope ignored = OperationContext.restoreSnapshot(opCtxSnp)) { + onResponse0(msg); + } + } + + /** */ + private void onResponse0(GridJobExecuteResponse msg) { assert msg != null; if (fut.isDone()) { @@ -1344,6 +1423,15 @@ private void sendRequest(ComputeJobResult res) { ClusterNode node = res.getNode(); + try { + validateArgumentIsSupportedByDestinationNode(node); + } + catch (IgniteCheckedException e) { + finishTask(null, e); + + return; + } + try { ClusterNode curNode = ctx.discovery().node(node.id()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java index 8f7862a4332a3..db87cf76e6155 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/thread/context/DistributedAttributeKeyRegistry.java @@ -22,6 +22,7 @@ import org.apache.ignite.internal.processors.security.SecurityContext; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.OP_FEATURES_PROPAGATION_FEATURE; import static org.apache.ignite.internal.thread.context.OperationContextDispatcher.MAX_ATTRS_CNT; /** @@ -38,6 +39,9 @@ public class DistributedAttributeKeyRegistry { /** Attribute reserved for {@link SecurityContext} propagation. */ public static final DistributedAttributeKey SECURITY = new DistributedAttributeKey(0); + /** Attribute reserved for propagating RU features that determine how an operation should be processed. */ + public static final DistributedAttributeKey ROLLING_UPGRADE = new DistributedAttributeKey(1, OP_FEATURES_PROPAGATION_FEATURE); + /** Package private so that tests can declare keys that are not constants of this registry. */ static final DistributedAttributeKey[] VALS = new DistributedAttributeKey[MAX_ATTRS_CNT]; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 5b87447a84813..af45b1233696a 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -163,6 +163,7 @@ import org.apache.ignite.internal.IgniteDeploymentCheckedException; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.binary.BinaryContext; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.binary.BinaryMetadataHandler; @@ -7681,12 +7682,12 @@ public static void prepareAffinityField(BinaryObjectBuilder builder, CacheObject /** */ public static final IgniteDataTransferObjectSerializer EMPTY_DTO_SERIALIZER = new IgniteDataTransferObjectSerializer() { /** {@inheritDoc} */ - @Override public void writeExternal(Object instance, ObjectOutput out) { + @Override public void writeExternal(Object instance, ObjectOutput out, MessageSerializationContext ctx) { throw new IllegalStateException("Can't find serializer for: " + instance.getClass()); } /** {@inheritDoc} */ - @Override public void readExternal(Object instance, ObjectInput in) { + @Override public void readExternal(Object instance, ObjectInput in, MessageSerializationContext ctx) { throw new IllegalStateException("Can't find serializer for: " + instance.getClass()); } }; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorJob.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorJob.java index 5057cab859a06..33546b64771d6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorJob.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorJob.java @@ -20,12 +20,16 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeature; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; import org.apache.ignite.internal.processors.security.PublicAccessJob; +import org.apache.ignite.internal.thread.context.OperationContext; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.plugin.security.SecurityPermissionSet; import org.apache.ignite.resources.IgniteInstanceResource; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.logFinish; import static org.apache.ignite.internal.visor.util.VisorTaskUtils.logStart; import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_OPS; @@ -87,6 +91,21 @@ protected VisorJob(@Nullable A arg, boolean debug) { */ protected abstract R run(@Nullable A arg) throws IgniteException; + /** */ + protected @Nullable IgniteNodeFeatureSet initiatorFeatures() { + return OperationContext.get(OP_FEATURES_ATTR); + } + + /** */ + protected boolean isSupportedByInitiator(IgniteFeature feature) { + IgniteNodeFeatureSet features = initiatorFeatures(); + + if (features == null) + throw new IgniteException("The feature set of the Management API command initiator is unavailable"); + + return features.contains(feature); + } + /** {@inheritDoc} */ @Override public SecurityPermissionSet requiredPermissions() { return systemPermissions(ADMIN_OPS); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorMultiNodeTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorMultiNodeTask.java index 0c7b159969eeb..b53d5cd72acdf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorMultiNodeTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorMultiNodeTask.java @@ -28,6 +28,7 @@ import org.apache.ignite.compute.ComputeJobResultPolicy; import org.apache.ignite.compute.ComputeTask; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.resources.IgniteInstanceResource; import org.jetbrains.annotations.NotNull; @@ -55,6 +56,9 @@ public abstract class VisorMultiNodeTask implements ComputeTask implements ComputeTask job(A arg); /** {@inheritDoc} */ - @NotNull @Override public Map map(List subgrid, VisorTaskArgument arg) { + @NotNull @Override public final Map map(List subgrid, VisorTaskArgument arg) { assert arg != null; start = U.currentTimeMillis(); @@ -74,6 +78,8 @@ public abstract class VisorMultiNodeTask implements ComputeTask map0(List subgrid, /** {@inheritDoc} */ @Nullable @Override public final VisorTaskResult reduce(List results) { try { - return new VisorTaskResult<>(reduce0(results), null); + return new VisorTaskResult<>(reduce0(results), null, cmdInitiatorFeatures); } catch (Exception e) { - return new VisorTaskResult<>(null, e); + return new VisorTaskResult<>(null, e, cmdInitiatorFeatures); } finally { if (debug) 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 55602bbb8f853..2f4ddd8c1fc43 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 @@ -24,12 +24,20 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import org.apache.ignite.internal.IgnitionEx; 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.IgniteComponentFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeature; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.Scope; import org.apache.ignite.internal.util.typedef.internal.S; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; + /** * Visor tasks argument. */ @@ -48,7 +56,7 @@ public class VisorTaskArgument extends IgniteDataTransferObject { private static final int MAGIC = 0xBAA55F5E; /** */ - transient IgniteCoreFeatureSet cmdInitiatorFeatures = IgniteCoreFeatureSet.local(); + transient IgniteNodeFeatureSet cmdInitiatorFeatures = new IgniteNodeFeatureSet(IgniteCoreFeatureSet.local()); /** Node IDs task should be mapped to. */ @Order(0) @@ -116,6 +124,11 @@ public VisorTaskArgument(UUID node, boolean debug) { this(node, null, debug); } + /** */ + public IgniteNodeFeatureSet initiatorFeatures() { + return cmdInitiatorFeatures; + } + /** * @return Node IDs task should be mapped to. */ @@ -141,9 +154,11 @@ public boolean isDebug() { @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(MAGIC); - cmdInitiatorFeatures.writeExternal(out); + cmdInitiatorFeatures.componentFeatures(IgniteCoreFeature.COMPONENT_NAME).writeExternal(out); - writeIgniteDataTransferObject(out); + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, cmdInitiatorFeatures)) { + writeIgniteDataTransferObject(out); + } } /** {@inheritDoc} */ @@ -158,18 +173,34 @@ public boolean isDebug() { ); } - cmdInitiatorFeatures = new IgniteCoreFeatureSet(); + IgniteCoreFeatureSet cmdInitiatorFeatures = new IgniteCoreFeatureSet(); cmdInitiatorFeatures.readExternal(in); - if (!cmdInitiatorFeatures.isUpgradableTo(IgniteCoreFeatureSet.local())) { + IgniteComponentFeatureSet locFeatures = localFeatures(); + + if (!cmdInitiatorFeatures.isUpgradableTo(locFeatures)) { 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() + ']' + ", localVersion=" + locFeatures.version() + ']' ); } - readIgniteDataTransferObject(in); + this.cmdInitiatorFeatures = new IgniteNodeFeatureSet(cmdInitiatorFeatures); + + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, this.cmdInitiatorFeatures)) { + readIgniteDataTransferObject(in); + } + } + + /** */ + private static IgniteComponentFeatureSet localFeatures() { + try { + return IgnitionEx.localIgnite().context().localNodeFeatures().componentFeatures(IgniteCoreFeature.COMPONENT_NAME); + } + catch (IllegalArgumentException ignored) { + return IgniteCoreFeatureSet.local(); + } } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskResult.java index 58442f507e889..228b9e1aebfb9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/VisorTaskResult.java @@ -17,10 +17,19 @@ package org.apache.ignite.internal.visor; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.thread.context.Scope; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; + /** * Management task result. */ @@ -28,6 +37,9 @@ public class VisorTaskResult extends IgniteDataTransferObject { /** Serial version UID. */ private static final long serialVersionUID = 0L; + /** */ + private transient IgniteNodeFeatureSet cmdInitiatorFeatures; + /** Task result. */ @Order(0) @Nullable R res; @@ -44,10 +56,28 @@ public VisorTaskResult() { /** * @param res Task result. * @param err Error. + * @param cmdInitiatorFeatures Feature set of the command initiator. */ - public VisorTaskResult(@Nullable R res, @Nullable Exception err) { + public VisorTaskResult(@Nullable R res, @Nullable Exception err, IgniteNodeFeatureSet cmdInitiatorFeatures) { + assert cmdInitiatorFeatures != null; + this.res = res; this.err = err; + this.cmdInitiatorFeatures = cmdInitiatorFeatures; + } + + /** {@inheritDoc} */ + @Override protected void writeIgniteDataTransferObject(ObjectOutput out) throws IOException { + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, cmdInitiatorFeatures)) { + super.writeIgniteDataTransferObject(out); + } + } + + /** {@inheritDoc} */ + @Override protected void readIgniteDataTransferObject(ObjectInput in) throws IOException, ClassNotFoundException { + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, new IgniteNodeFeatureSet(IgniteCoreFeatureSet.local()))) { + super.readIgniteDataTransferObject(in); + } } /** diff --git a/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgument.java b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgument.java new file mode 100644 index 0000000000000..ab7b5cb8bc002 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgument.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_21_0; + +/** */ +@FeatureRegistry(TestIgniteReleaseFeatures_2_21_0.class) +public class TestCommandArgument extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + @Order(value = 0, deprecatedBy = "VER_2_21_0_ID_5_FEATURE") + public String fldA; + + /** */ + @Order(value = 1, introducedBy = "VER_2_21_0_ID_5_FEATURE") + public String fldB; + + /** */ + public TestCommandArgument() { + // No-op. + } + + /** */ + public TestCommandArgument(String fldA, String fldB) { + this.fldA = fldA; + this.fldB = fldB; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgumentSerializer.java b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgumentSerializer.java new file mode 100644 index 0000000000000..2e1d9ba6d5521 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandArgumentSerializer.java @@ -0,0 +1,54 @@ +/* + * 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; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_21_0; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** */ +public class TestCommandArgumentSerializer implements IgniteDataTransferObjectSerializer { + /** {@inheritDoc} */ + @Override public void writeExternal( + TestCommandArgument obj, + ObjectOutput out, + MessageSerializationContext ctx + ) throws IOException { + if (ctx.includeFieldDeprecatedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + U.writeString(out, obj.fldA); + + if (ctx.includeFieldIntroducedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + U.writeString(out, obj.fldB); + } + + /** {@inheritDoc} */ + @Override public void readExternal( + TestCommandArgument obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { + if (ctx.includeFieldDeprecatedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + obj.fldA = U.readString(in); + + if (ctx.includeFieldIntroducedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + obj.fldB = U.readString(in); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponse.java b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponse.java new file mode 100644 index 0000000000000..4af615a7d0102 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponse.java @@ -0,0 +1,68 @@ +/* + * 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; + +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_21_0; +import org.jetbrains.annotations.Nullable; + +/** */ +@FeatureRegistry(TestIgniteReleaseFeatures_2_21_0.class) +public class TestCommandResponse extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + @Order(0) + public @Nullable IgniteNodeFeatureSet jobFeatures; + + /** */ + @Order(1) + public @Nullable IgniteNodeFeatureSet taskFeatures; + + /** */ + @Order(2) + public String fldA; + + /** */ + @Order(3) + public String fldB; + + /** */ + @Order(value = 4, deprecatedBy = "VER_2_21_0_ID_5_FEATURE") + public String fldC; + + /** */ + @Order(value = 5, introducedBy = "VER_2_21_0_ID_5_FEATURE") + public String fldD; + + /** */ + public TestCommandResponse() { + // No-op. + } + + /** */ + public TestCommandResponse(@Nullable IgniteNodeFeatureSet jobFeatures, TestCommandArgument arg, String fldC, String fldD) { + this.jobFeatures = jobFeatures; + this.fldA = arg.fldA; + this.fldB = arg.fldB; + this.fldC = fldC; + this.fldD = fldD; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponseSerializer.java b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponseSerializer.java new file mode 100644 index 0000000000000..2c94b8ccd6a5d --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandResponseSerializer.java @@ -0,0 +1,65 @@ +/* + * 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; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_21_0; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** */ +public class TestCommandResponseSerializer implements IgniteDataTransferObjectSerializer { + /** {@inheritDoc} */ + @Override public void writeExternal( + TestCommandResponse obj, + ObjectOutput out, + MessageSerializationContext ctx + ) throws IOException { + out.writeObject(obj.jobFeatures); + out.writeObject(obj.taskFeatures); + U.writeString(out, obj.fldA); + U.writeString(out, obj.fldB); + + if (ctx.includeFieldDeprecatedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + U.writeString(out, obj.fldC); + + if (ctx.includeFieldIntroducedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + U.writeString(out, obj.fldD); + } + + /** {@inheritDoc} */ + @Override public void readExternal( + TestCommandResponse obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { + obj.jobFeatures = (IgniteNodeFeatureSet)in.readObject(); + obj.taskFeatures = (IgniteNodeFeatureSet)in.readObject(); + obj.fldA = U.readString(in); + obj.fldB = U.readString(in); + + if (ctx.includeFieldDeprecatedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + obj.fldC = U.readString(in); + + if (ctx.includeFieldIntroducedBy(TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_5_FEATURE)) + obj.fldD = U.readString(in); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/TestCommandTask.java b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandTask.java new file mode 100644 index 0000000000000..95795a9e2adc3 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/TestCommandTask.java @@ -0,0 +1,94 @@ +/* + * 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; + +import java.util.List; +import java.util.Map; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.compute.ComputeJob; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.compute.ComputeJobResultPolicy; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorOneNodeTask; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.C; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.D; +import static org.junit.Assert.assertEquals; + +/** */ +public class TestCommandTask extends VisorOneNodeTask { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + private transient IgniteNodeFeatureSet cmdInitiatorFeatures; + + /** {@inheritDoc} */ + @Override protected Map map0( + List subgrid, + VisorTaskArgument arg + ) { + cmdInitiatorFeatures = arg.initiatorFeatures(); + + assertEquals(cmdInitiatorFeatures, OperationContext.get(OP_FEATURES_ATTR)); + + return super.map0(subgrid, arg); + } + + /** {@inheritDoc} */ + @Override public ComputeJobResultPolicy result(ComputeJobResult res, List rcvd) { + assertEquals(cmdInitiatorFeatures, OperationContext.get(OP_FEATURES_ATTR)); + + return super.result(res, rcvd); + } + + /** {@inheritDoc} */ + @Override protected CommandJob job(TestCommandArgument arg) { + return new CommandJob(arg, debug); + } + + /** {@inheritDoc} */ + @Nullable @Override protected TestCommandResponse reduce0(List results) { + TestCommandResponse res = super.reduce0(results); + + res.taskFeatures = OperationContext.get(OP_FEATURES_ATTR); + + return res; + } + + /** */ + public static class CommandJob extends VisorJob { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + protected CommandJob(TestCommandArgument arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected TestCommandResponse run(TestCommandArgument arg) { + return new TestCommandResponse(initiatorFeatures(), arg, C, D); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/localtask/SimpleTaskSerializer.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/localtask/SimpleTaskSerializer.java index f74df1740ace1..7cc9e4155ca2e 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/localtask/SimpleTaskSerializer.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/localtask/SimpleTaskSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; import org.apache.ignite.internal.util.typedef.internal.U; @@ -35,12 +36,16 @@ */ public class SimpleTaskSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(SimpleTask obj, ObjectOutput out) throws IOException { + @Override public void writeExternal(SimpleTask obj, ObjectOutput out, MessageSerializationContext ctx) throws IOException { U.writeLongString(out, obj.name); } /** {@inheritDoc} */ - @Override public void readExternal(SimpleTask obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + SimpleTask obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { obj.name = U.readLongString(in); } } 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 ef1e198be1257..b14c52e51a473 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 @@ -396,10 +396,7 @@ protected void checkPreviousClusterFeatures(@Nullable String expVer) throws Exce IgniteCoreFeatureSet expPrevCoreFeatures = expVersions != null ? createCoreFeatureSet(expVersions.coreVersion()) : null; IgnitePluginFeatureSet expPrevPluginFeatures = expVersions != null && expVersions.containsPlugin() - ? new IgnitePluginFeatureSet( - TestPluginFeature.COMPONENT_NAME, - IgniteProductVersion.fromString(expVersions.pluginVersion()), - IgniteFeatureSet.buildFrom(readDeclaredPluginFeatures(expVersions.pluginVersion()))) + ? createPluginFeatureSet(expVersions.pluginVersion()) : null; for (Ignite ignite : Ignition.allGrids()) { @@ -424,6 +421,25 @@ public static IgniteCoreFeatureSet createCoreFeatureSet(String ver) throws Excep IgniteFeatureSet.buildFrom(readDeclaredCoreFeatures(ver))); } + /** */ + public static IgnitePluginFeatureSet createPluginFeatureSet(String ver) throws Exception { + return new IgnitePluginFeatureSet( + TestPluginFeature.COMPONENT_NAME, + IgniteProductVersion.fromString(ver), + IgniteFeatureSet.buildFrom(readDeclaredPluginFeatures(ver))); + } + + /** */ + public static IgniteNodeFeatureSet createNodeFeatureSet(String ver) throws Exception { + TestVersions versions = TestVersions.parse(ver); + + IgniteCoreFeatureSet coreFeatures = createCoreFeatureSet(versions.coreVersion()); + + return versions.containsPlugin() + ? new IgniteNodeFeatureSet(coreFeatures, createPluginFeatureSet(versions.pluginVersion())) + : new IgniteNodeFeatureSet(coreFeatures); + } + /** */ protected void checkVersionUpgradeInactive(String expVer) throws Exception { checkVersionUpgradeEnabledStatus(false); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/AbstractRollingUpgradeManagementApiTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/AbstractRollingUpgradeManagementApiTest.java new file mode 100644 index 0000000000000..fbed834e284b4 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/AbstractRollingUpgradeManagementApiTest.java @@ -0,0 +1,65 @@ +/* + * 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.IgniteClient; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.internal.management.api.CommandUtils; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; + +/** */ +public abstract class AbstractRollingUpgradeManagementApiTest extends AbstractRollingUpgradeTest { + /** */ + protected R executeCommandFromClient( + int connNodeIdx, + int jobNodeIdx, + String cliVer, + Class> taskCls, + A arg + ) throws Exception { + return runWithVersion(cliVer, () -> { + try (IgniteClient cli = startClient(connNodeIdx)) { + return CommandUtils.execute(cli, null, taskCls, arg, F.asList(grid(jobNodeIdx).localNode())); + } + }); + } + + /** */ + private static R runWithVersion(String ver, Callable action) throws Exception { + IgniteCoreFeatureSet prev = IgniteCoreFeatureSet.INSTANCE; + IgniteCoreFeatureSet.INSTANCE = createCoreFeatureSet(ver); + + try { + return action.call(); + } + finally { + IgniteCoreFeatureSet.INSTANCE = prev; + } + } + + /** */ + private IgniteClient startClient(int connNodeIdx) { + String addr = "127.0.0.1:" + grid(connNodeIdx).context().clientListener().port(); + + return Ignition.startClient(new ClientConfiguration().setAddresses(addr).setClusterDiscoveryEnabled(false)); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ComputeTaskOperationContextPropagationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ComputeTaskOperationContextPropagationTest.java new file mode 100644 index 0000000000000..4883f146f4f08 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/ComputeTaskOperationContextPropagationTest.java @@ -0,0 +1,295 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.apache.ignite.cluster.ClusterTopologyException; +import org.apache.ignite.compute.ComputeExecutionRejectedException; +import org.apache.ignite.compute.ComputeJobMasterLeaveAware; +import org.apache.ignite.compute.ComputeTaskSession; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.GridJobExecuteResponse; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.TestCommandArgument; +import org.apache.ignite.internal.TestCommandResponse; +import org.apache.ignite.internal.TestCommandTask; +import org.apache.ignite.internal.TestRecordingCommunicationSpi; +import org.apache.ignite.internal.client.thin.ClientServerError; +import org.apache.ignite.internal.thread.context.OperationContext; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorOneNodeTask; +import org.apache.ignite.spi.IgniteSpiAdapter; +import org.apache.ignite.spi.IgniteSpiException; +import org.apache.ignite.spi.IgniteSpiMultipleInstancesSupport; +import org.apache.ignite.spi.collision.CollisionContext; +import org.apache.ignite.spi.collision.CollisionExternalListener; +import org.apache.ignite.spi.collision.CollisionJobContext; +import org.apache.ignite.spi.collision.CollisionSpi; +import org.junit.Test; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.A; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.B; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.C; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.D; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.runAsync; + +/** */ +public class ComputeTaskOperationContextPropagationTest extends AbstractRollingUpgradeManagementApiTest { + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + if (getTestIgniteInstanceIndex(igniteInstanceName) == 1) + cfg.setCollisionSpi(new TestCollisionSpi()); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + TestCollisionSpi.firstJobQueuedLatch = null; + TestCollisionSpi.cancelFirstJob = false; + + MasterLeaveAwareTask.jobStartedLatch = new CountDownLatch(1); + MasterLeaveAwareTask.jobUnblockedLatch = new CountDownLatch(1); + MasterLeaveAwareTask.masterNodeLeftPrecessedLatch = new CountDownLatch(1); + MasterLeaveAwareTask.masterNodeLeftProcessorFeatures = null; + } + + /** */ + @Test + public void testJobNodeLeftResponseIsProcessedUnderTheTaskContext() throws Exception { + startGrid(0, "2.21.0"); + startGrid(1, "2.21.0"); + + TestRecordingCommunicationSpi.spi(grid(1)).blockMessages((node, msg) -> msg instanceof GridJobExecuteResponse); + + IgniteInternalFuture fut = runAsync(() -> executeCommandFromClient( + 0, + 1, + "2.20.0", + TestCommandTask.class, + new TestCommandArgument(A, B))); + + TestRecordingCommunicationSpi spi = TestRecordingCommunicationSpi.spi(grid(1)); + + spi.waitForBlocked(1, getTestTimeout()); + + stopGrid(1); + + assertThrowsAnyCause(log, () -> fut.get(getTestTimeout()), ClusterTopologyException.class, "Node has left grid"); + } + + /** */ + @Test + public void testRejectionTriggeredByAnotherCommandIsProcessedUnderTheTaskContext() throws Exception { + startGrid(0, "2.21.0"); + startGrid(1, "2.21.0"); + + TestCollisionSpi.firstJobQueuedLatch = new CountDownLatch(1); + TestCollisionSpi.cancelFirstJob = true; + + IgniteInternalFuture firstCmdFut = runAsync(() -> executeCommandFromClient( + 0, + 1, + "2.20.0", + TestCommandTask.class, + new TestCommandArgument(A, B))); + + assertTrue(TestCollisionSpi.firstJobQueuedLatch.await(getTestTimeout(), MILLISECONDS)); + + TestCommandResponse secondCmdRes = executeCommandFromClient(0, 1, "2.21.0", TestCommandTask.class, new TestCommandArgument(A, B)); + + assertEquals(createNodeFeatureSet("2.21.0"), secondCmdRes.taskFeatures); + + assertThrowsAnyCause( + log, + () -> firstCmdFut.get(getTestTimeout()), + ComputeExecutionRejectedException.class, + "Job was cancelled before execution"); + } + + /** */ + @Test + public void testJobActivatedByAnotherCommandRunsUnderTheJobContext() throws Exception { + startGrid(0, "2.21.0"); + startGrid(1, "2.21.0"); + + TestCollisionSpi.firstJobQueuedLatch = new CountDownLatch(1); + + IgniteInternalFuture firstCmdFut = runAsync(() -> executeCommandFromClient( + 0, + 1, + "2.20.0", + TestCommandTask.class, + new TestCommandArgument(A, B))); + + assertTrue(TestCollisionSpi.firstJobQueuedLatch.await(getTestTimeout(), MILLISECONDS)); + + TestCommandResponse secondCmdRes = executeCommandFromClient(0, 1, "2.21.0", TestCommandTask.class, new TestCommandArgument(A, B)); + + assertEquals(createNodeFeatureSet("2.20.0"), firstCmdFut.get(getTestTimeout()).jobFeatures); + assertEquals(createNodeFeatureSet("2.21.0"), secondCmdRes.jobFeatures); + } + + /** */ + @Test + public void testMasterNodeLeftCallbackRunsUnderTheJobContext() throws Exception { + startGrid(0, "2.21.0"); + startGrid(1, "2.21.0"); + + IgniteInternalFuture fut = runAsync(() -> executeCommandFromClient( + 0, + 1, + "2.20.0", + MasterLeaveAwareTask.class, + new TestCommandArgument(A, B))); + + assertTrue(MasterLeaveAwareTask.jobStartedLatch.await(getTestTimeout(), MILLISECONDS)); + + stopGrid(0, true); + + try { + assertTrue(MasterLeaveAwareTask.masterNodeLeftPrecessedLatch.await(getTestTimeout(), MILLISECONDS)); + + assertEquals(createNodeFeatureSet("2.20.0"), MasterLeaveAwareTask.masterNodeLeftProcessorFeatures); + } + finally { + MasterLeaveAwareTask.jobUnblockedLatch.countDown(); + } + + assertThrowsAnyCause( + log, + () -> fut.get(getTestTimeout()), + ClientServerError.class, + "Task cancelled due to stopping of the grid"); + } + + /** */ + @IgniteSpiMultipleInstancesSupport(true) + public static class TestCollisionSpi extends IgniteSpiAdapter implements CollisionSpi { + /** */ + static volatile CountDownLatch firstJobQueuedLatch; + + /** */ + static volatile boolean cancelFirstJob; + + /** {@inheritDoc} */ + @Override public void onCollision(CollisionContext ctx) { + if (firstJobQueuedLatch == null) { + ctx.waitingJobs().forEach(CollisionJobContext::activate); + + return; + } + + List waitingJobs = new ArrayList<>(ctx.waitingJobs()); + + if (waitingJobs.size() == 1) { + firstJobQueuedLatch.countDown(); + + return; + } + + if (cancelFirstJob) + waitingJobs.get(0).cancel(); + else + waitingJobs.get(0).activate(); + + waitingJobs.get(1).activate(); + + firstJobQueuedLatch = null; + } + + /** {@inheritDoc} */ + @Override public void setExternalCollisionListener(CollisionExternalListener lsnr) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void spiStart(String igniteInstanceName) throws IgniteSpiException { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void spiStop() throws IgniteSpiException { + // No-op. + } + } + + /** Command whose job records the operation context its master-leave callback runs under. */ + public static class MasterLeaveAwareTask extends VisorOneNodeTask { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + static volatile CountDownLatch jobStartedLatch; + + /** */ + static volatile CountDownLatch jobUnblockedLatch; + + /** */ + static volatile CountDownLatch masterNodeLeftPrecessedLatch; + + /** */ + static volatile IgniteNodeFeatureSet masterNodeLeftProcessorFeatures; + + /** {@inheritDoc} */ + @Override protected MasterLeaveAwareJob job(TestCommandArgument arg) { + return new MasterLeaveAwareJob(arg, debug); + } + } + + /** */ + private static class MasterLeaveAwareJob extends VisorJob + implements ComputeJobMasterLeaveAware { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + protected MasterLeaveAwareJob(TestCommandArgument arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected TestCommandResponse run(TestCommandArgument arg) { + MasterLeaveAwareTask.jobStartedLatch.countDown(); + + try { + MasterLeaveAwareTask.jobUnblockedLatch.await(); + } + catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + + return new TestCommandResponse(initiatorFeatures(), arg, C, D); + } + + /** {@inheritDoc} */ + @Override public void onMasterNodeLeft(ComputeTaskSession ses) { + MasterLeaveAwareTask.masterNodeLeftProcessorFeatures = OperationContext.get(OP_FEATURES_ATTR); + MasterLeaveAwareTask.masterNodeLeftPrecessedLatch.countDown(); + } + } +} 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 index b8af58f9f7cda..7d56d93c6120f 100644 --- 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 @@ -17,23 +17,16 @@ 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 class ManagementApiVersionValidationTest extends AbstractRollingUpgradeManagementApiTest { /** */ public static final LogListener DESERIALIZATION_FAILED_LSNR = LogListener.builder().andMatches( "Failed to deserialize the Ignite Management API command argument" @@ -50,28 +43,24 @@ public class ManagementApiVersionValidationTest extends AbstractRollingUpgradeTe /** */ @Test - public void testVersionValidation() throws Exception { - withCoreVersion("2.21.0", () -> { - startGrid(0); + public void testCommandAcceptedOnlyFromCompatibleClientVersions() throws Exception { + startGrid(0, "2.21.0"); - checkCommandArgumentDeserializationFailed(0, "2.21.1"); - checkCommandArgumentDeserializationFailed(0, "2.19.0"); + checkCommandArgumentDeserializationFailed("2.21.1"); + checkCommandArgumentDeserializationFailed("2.19.0"); - executeCommand(createCommandArgument(0, "2.21.0")); - executeCommand(createCommandArgument(0, "2.20.0")); - - return null; - }); + executeCommand("2.21.0"); + executeCommand("2.20.0"); } /** */ - private void checkCommandArgumentDeserializationFailed(int destNodeIdx, String ver) throws Exception { + private void checkCommandArgumentDeserializationFailed(String cliVer) throws Exception { DESERIALIZATION_FAILED_LSNR.reset(); GridTestUtils.assertThrowsAnyCause( log, () -> { - executeCommand(createCommandArgument(destNodeIdx, ver)); + executeCommand(cliVer); return null; }, @@ -82,27 +71,7 @@ private void checkCommandArgumentDeserializationFailed(int destNodeIdx, String v } /** */ - 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; - } + private void executeCommand(String cliVer) throws Exception { + executeCommandFromClient(0, 0, cliVer, TestManagementVisorOneNodeTask.class, ""); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/RollingUpgradeManagementApiTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/RollingUpgradeManagementApiTest.java new file mode 100644 index 0000000000000..e426e0dacfd73 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/RollingUpgradeManagementApiTest.java @@ -0,0 +1,131 @@ +/* + * 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 org.apache.ignite.client.ClientException; +import org.apache.ignite.internal.TestCommandArgument; +import org.apache.ignite.internal.TestCommandResponse; +import org.apache.ignite.internal.TestCommandTask; +import org.apache.ignite.lang.IgniteProductVersion; +import org.apache.ignite.testframework.GridTestUtils; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.A; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.B; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.C; +import static org.apache.ignite.internal.processors.rollingupgrade.message.TestMessage.D; + +/** */ +public class RollingUpgradeManagementApiTest extends AbstractRollingUpgradeManagementApiTest { + /** */ + @Test + public void testOlderClientCommandRunsInTheClientDialectOnUpgradedNodes() throws Exception { + TestCommandResponse res = executeCommand("2.20.0", "2.21.0", "2.21.0"); + + assertReceived("2.20.0", A, null, C, null, res); + } + + /** */ + @Test + public void testOlderClientCommandRunsInTheClientDialectOnJobNodeNewerThanTaskNode() throws Exception { + TestCommandResponse res = executeCommand("2.20.0", "2.21.0", "2.21.1"); + + assertReceived("2.20.0", A, null, C, null, res); + } + + /** */ + @Test + public void testCommandRunsInTheClientDialectWhenAllVersionsMatch() throws Exception { + TestCommandResponse res = executeCommand("2.21.0", "2.21.0", "2.21.0"); + + assertReceived("2.21.0", null, B, null, D, res); + } + + /** */ + @Test + public void testCommandMappedToANodeThatDroppedTheClientVersionRejected() { + GridTestUtils.assertThrowsAnyCause( + log, + () -> executeCommand("2.19.0", "2.20.0", "2.21.0"), + ClientException.class, + "Update binary version of the Ignite Management API"); + } + + /** */ + @Test + public void testCommandMappedToANodeOlderThanTheClientRejected() { + GridTestUtils.assertThrowsAnyCause( + log, + () -> executeCommand("2.21.0", "2.21.0", "2.20.0"), + ClientException.class, + "Retry the operation after the Rolling Upgrade has completed"); + } + + /** */ + @Test + public void testCommandMappedToANodeNewerThanTheClientAllowed() throws Exception { + TestCommandResponse res = executeCommand("2.20.0", "2.20.0", "2.21.0"); + + assertReceived("2.20.0", A, null, C, null, res); + } + + /** */ + @Test + public void testCommandMappedFromANewerToAnOlderNodeAllowed() throws Exception { + TestCommandResponse res = executeCommand("2.20.0", "2.21.0", "2.20.0"); + + assertReceived("2.20.0", A, null, C, null, res); + } + + /** */ + private TestCommandResponse executeCommand(String cliVer, String connVer, String jobVer) throws Exception { + boolean jobNodeOlder = IgniteProductVersion.fromString(jobVer).compareTo(IgniteProductVersion.fromString(connVer)) < 0; + + int connNodeIdx = jobNodeOlder ? 1 : 0; + int jobNodeIdx = jobNodeOlder ? 0 : 1; + + startGrid(0, jobNodeOlder ? jobVer : connVer); + + if (!connVer.equals(jobVer)) + ru(0).enableVersionUpgrade(); + + startGrid(1, jobNodeOlder ? connVer : jobVer); + + return executeCommandFromClient(connNodeIdx, jobNodeIdx, cliVer, TestCommandTask.class, new TestCommandArgument(A, B)); + } + + /** */ + private static void assertReceived( + String expVer, + @Nullable String expFldA, + @Nullable String expFldB, + @Nullable String expFldC, + @Nullable String expFldD, + TestCommandResponse res + ) throws Exception { + IgniteNodeFeatureSet expFeatures = createNodeFeatureSet(expVer); + + assertEquals(expFeatures, res.jobFeatures); + assertEquals(expFeatures, res.taskFeatures); + assertEquals(expFldA, res.fldA); + assertEquals(expFldB, res.fldB); + assertEquals(expFldC, res.fldC); + assertEquals(expFldD, res.fldD); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java index 0d00ee7fa128e..ad14282dc0594 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/message/RollingUpgradeDistributedAttributeTest.java @@ -26,6 +26,7 @@ import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage; import org.apache.ignite.internal.processors.authentication.User; import org.apache.ignite.internal.processors.cache.persistence.wal.WALPointer; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; import org.apache.ignite.internal.thread.context.DistributedAttributeKey; import org.apache.ignite.internal.thread.context.OperationContext; import org.apache.ignite.internal.thread.context.OperationContextAttribute; @@ -38,6 +39,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.Test; +import static org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor.OP_FEATURES_ATTR; import static org.apache.ignite.internal.processors.rollingupgrade.feature.TestIgniteReleaseFeatures_2_20_0.VER_2_20_0_ID_3_FEATURE; import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.PTR_VAL; import static org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest.TestIgniteComponent.USR_VAL; @@ -129,6 +131,30 @@ public void testNewAttributeReachesOnlyClientOfNewRelease() throws Exception { assertAttributes(PTR_VAL, null, send(grid(1), oldVerCli, TestCoreMessage.build())); } + /** */ + @Test + public void testInitiatorFeaturesAreCutForPeerWithoutTheFeature() throws Exception { + startServerNodes("2.19.0", "2.20.0"); + + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, createNodeFeatureSet("2.20.0"))) { + assertNull(send(grid(1), grid(0), TestCoreMessage.build()).attribute(OP_FEATURES_ATTR)); + assertNull(send(grid(0), grid(1), TestCoreMessage.build()).attribute(OP_FEATURES_ATTR)); + } + } + + /** */ + @Test + public void testInitiatorFeaturesReachPeerWithTheFeature() throws Exception { + startServerNodes("2.19.2", "2.20.0"); + + IgniteNodeFeatureSet features = createNodeFeatureSet("2.20.0 | 1.0.0"); + + try (Scope ignored = OperationContext.set(OP_FEATURES_ATTR, features)) { + assertEquals(features, send(grid(1), grid(0), TestCoreMessage.build()).attribute(OP_FEATURES_ATTR)); + assertEquals(features, send(grid(0), grid(1), TestCoreMessage.build()).attribute(OP_FEATURES_ATTR)); + } + } + /** */ private void checkMutualSend(IgniteEx first, IgniteEx second, WALPointer expPtr, @Nullable User expUsr) throws Exception { assertAttributes(expPtr, expUsr, send(first, second, TestCoreMessage.build())); diff --git a/modules/core/src/test/java/org/apache/ignite/marshaller/HolderSerializer.java b/modules/core/src/test/java/org/apache/ignite/marshaller/HolderSerializer.java index 464dd10bd9b97..5c2cf5327376a 100644 --- a/modules/core/src/test/java/org/apache/ignite/marshaller/HolderSerializer.java +++ b/modules/core/src/test/java/org/apache/ignite/marshaller/HolderSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.ObjectInputStreamFilteringTest.Holder; @@ -27,12 +28,16 @@ /** */ public class HolderSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(Holder instance, ObjectOutput out) throws IOException { + @Override public void writeExternal(Holder instance, ObjectOutput out, MessageSerializationContext ctx) throws IOException { U.writeMap(out, instance.map); } /** {@inheritDoc} */ - @Override public void readExternal(Holder instance, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + Holder instance, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { instance.map = U.readMap(in); } } 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 d9c760645bdd3..7828d07647a71 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 @@ -65,8 +65,10 @@ 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.ComputeTaskOperationContextPropagationTest; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSetTest; import org.apache.ignite.internal.processors.rollingupgrade.feature.ManagementApiVersionValidationTest; +import org.apache.ignite.internal.processors.rollingupgrade.feature.RollingUpgradeManagementApiTest; import org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeDistributedAttributeTest; import org.apache.ignite.internal.processors.rollingupgrade.message.RollingUpgradeMessageSerializationTest; import org.apache.ignite.internal.product.GridProductVersionSelfTest; @@ -117,6 +119,8 @@ RollingUpgradeMessageSerializationTest.class, RollingUpgradeDistributedAttributeTest.class, ManagementApiVersionValidationTest.class, + RollingUpgradeManagementApiTest.class, + ComputeTaskOperationContextPropagationTest.class, GridProductVersionSelfTest.class, GridAffinityAssignmentV2Test.class, GridAffinityAssignmentV2TestNoOptimizations.class, diff --git a/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObject.java b/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObject.java index afa32f42a3b4f..896743cf621df 100644 --- a/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObject.java +++ b/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObject.java @@ -17,7 +17,6 @@ package org.apache.ignite.internal; -import java.util.Arrays; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.management.api.Argument; @@ -28,4 +27,12 @@ public class TestIgniteDataTransferObject extends IgniteDataTransferObject { @Order(0) @Argument char[] charArray; + + /** */ + @Order(value = 1, deprecatedBy = "ROLLING_UPGRADE_FEATURE") + String deprecatedFld; + + /** */ + @Order(value = 2, introducedBy = "ROLLING_UPGRADE_FEATURE") + String introducedFld; } diff --git a/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObjectSerializer.java b/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObjectSerializer.java index dbeb646753abb..c3b7a748f6d08 100644 --- a/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObjectSerializer.java +++ b/modules/core/src/test/resources/codegen/idto/TestIgniteDataTransferObjectSerializer.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal; + import org.apache.ignite.internal.MessageSerializationContext; + import org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry; import java.io.ObjectOutput; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.TestIgniteDataTransferObject; @@ -31,12 +33,24 @@ */ public class TestIgniteDataTransferObjectSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(TestIgniteDataTransferObject obj, ObjectOutput out) throws IOException { + @Override public void writeExternal(TestIgniteDataTransferObject obj, ObjectOutput out, MessageSerializationContext ctx) throws IOException { U.writeCharArray(out, obj.charArray); + if (ctx.includeFieldDeprecatedBy(SupportedFeatureRegistry.ROLLING_UPGRADE_FEATURE)) { + U.writeString(out, obj.deprecatedFld); + } + if (ctx.includeFieldIntroducedBy(SupportedFeatureRegistry.ROLLING_UPGRADE_FEATURE)) { + U.writeString(out, obj.introducedFld); + } } /** {@inheritDoc} */ - @Override public void readExternal(TestIgniteDataTransferObject obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal(TestIgniteDataTransferObject obj, ObjectInput in, MessageSerializationContext ctx) throws IOException, ClassNotFoundException { obj.charArray = U.readCharArray(in); + if (ctx.includeFieldDeprecatedBy(SupportedFeatureRegistry.ROLLING_UPGRADE_FEATURE)) { + obj.deprecatedFld = U.readString(in); + } + if (ctx.includeFieldIntroducedBy(SupportedFeatureRegistry.ROLLING_UPGRADE_FEATURE)) { + obj.introducedFld = U.readString(in); + } } } diff --git a/modules/extdata/pluggable/src/test/java/org/apache/ignite/internal/commandline/TestCommandCommandArgSerializer.java b/modules/extdata/pluggable/src/test/java/org/apache/ignite/internal/commandline/TestCommandCommandArgSerializer.java index 94d25350cf562..5a5c7ab685bda 100644 --- a/modules/extdata/pluggable/src/test/java/org/apache/ignite/internal/commandline/TestCommandCommandArgSerializer.java +++ b/modules/extdata/pluggable/src/test/java/org/apache/ignite/internal/commandline/TestCommandCommandArgSerializer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import org.apache.ignite.internal.MessageSerializationContext; import org.apache.ignite.internal.commandline.CommandsProviderExtImpl.TestCommandCommandArg; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.dto.IgniteDataTransferObjectSerializer; @@ -36,12 +37,16 @@ */ public class TestCommandCommandArgSerializer implements IgniteDataTransferObjectSerializer { /** {@inheritDoc} */ - @Override public void writeExternal(TestCommandCommandArg obj, ObjectOutput out) throws IOException { + @Override public void writeExternal(TestCommandCommandArg obj, ObjectOutput out, MessageSerializationContext ctx) throws IOException { U.writeString(out, obj.testPrint); } /** {@inheritDoc} */ - @Override public void readExternal(TestCommandCommandArg obj, ObjectInput in) throws IOException, ClassNotFoundException { + @Override public void readExternal( + TestCommandCommandArg obj, + ObjectInput in, + MessageSerializationContext ctx + ) throws IOException, ClassNotFoundException { obj.testPrint = U.readString(in); } }