From c11cc675ef69421c3408188626771a916df57757 Mon Sep 17 00:00:00 2001 From: rangao Date: Fri, 23 Sep 2022 21:51:05 +0800 Subject: [PATCH 1/9] new proxy init --- .../handlers/amqp/AmqpProtocolHandler.java | 7 +- .../handlers/amqp/proxy/LookupHandler.java | 2 +- .../proxy/PulsarServiceLookupHandler.java | 2 +- .../amqp/proxy2/ProxyBrokerConnection.java | 61 +++++ .../handlers/amqp/proxy2/ProxyChannel.java | 91 +++++++ .../handlers/amqp/proxy2/ProxyConnection.java | 237 ++++++++++++++++++ .../handlers/amqp/proxy2/ProxyServer.java | 51 ++++ .../amqp/proxy2/ProxyServerChannel.java | 179 +++++++++++++ .../pulsar/handlers/amqp/ProxyTest2.java | 61 +++++ 9 files changed, 687 insertions(+), 4 deletions(-) create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java create mode 100644 tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java index 4742c231..03b46ba6 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java @@ -21,6 +21,7 @@ import io.netty.channel.socket.SocketChannel; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyService; +import io.streamnative.pulsar.handlers.amqp.proxy2.ProxyServer; import io.streamnative.pulsar.handlers.amqp.utils.ConfigurationUtils; import java.net.InetSocketAddress; import java.util.Map; @@ -108,9 +109,11 @@ public void start(BrokerService service) { "plaintext must be configured on internal listener"); proxyConfig.setBrokerServiceURL(internalListener.getBrokerServiceUrl().toString()); - ProxyService proxyService = new ProxyService(proxyConfig, service.getPulsar()); +// ProxyService proxyService = new ProxyService(proxyConfig, service.getPulsar()); + ProxyServer proxyServer = new ProxyServer(proxyConfig, service.getPulsar()); try { - proxyService.start(); +// proxyService.start(); + proxyServer.start(); log.info("Start amqp proxy service at port: {}", proxyConfig.getAmqpProxyPort()); } catch (Exception e) { log.error("Failed to start amqp proxy service."); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/LookupHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/LookupHandler.java index c2a0fce0..96c10097 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/LookupHandler.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/LookupHandler.java @@ -31,6 +31,6 @@ public interface LookupHandler extends Closeable { * @return Pair consist of brokerHost and brokerPort */ CompletableFuture> findBroker(TopicName topicName, - String protocolHandlerName) throws Exception; + String protocolHandlerName); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/PulsarServiceLookupHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/PulsarServiceLookupHandler.java index f4eda176..a3bcd300 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/PulsarServiceLookupHandler.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/PulsarServiceLookupHandler.java @@ -49,7 +49,7 @@ public PulsarServiceLookupHandler(ProxyConfiguration proxyConfig, PulsarService @Override public CompletableFuture> findBroker(TopicName topicName, - String protocolHandlerName) throws Exception { + String protocolHandlerName) { CompletableFuture> lookupResult = new CompletableFuture<>(); // lookup the broker for the given topic diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java new file mode 100644 index 00000000..461af5c3 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java @@ -0,0 +1,61 @@ +package io.streamnative.pulsar.handlers.amqp.proxy2; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.flush.FlushConsolidationHandler; +import io.streamnative.pulsar.handlers.amqp.AmqpEncoder; +import io.streamnative.pulsar.handlers.amqp.proxy.ProxyHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.protocol.v0_8.transport.AMQDataBlock; + +import java.util.concurrent.CompletableFuture; + +/** + * This class is used to transfer data between proxy and broker. + */ +@Slf4j +public class ProxyBrokerConnection extends ChannelInboundHandlerAdapter { + + private ChannelHandlerContext ctx; + private Channel channel; + private final Channel clientChannel; + + public ProxyBrokerConnection(String host, Integer port, Channel clientChannel) throws InterruptedException { + EventLoopGroup group = new NioEventLoopGroup(); + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( + 1000, true)); + ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); + ch.pipeline().addLast("processor", this); + } + }); + channel = bootstrap.connect(host, port).sync().channel(); + this.clientChannel = clientChannel; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + this.ctx = ctx; + } + + public synchronized void writeFrame(AMQDataBlock frame) { + if (log.isInfoEnabled()) { + log.info("xxxx [ProxyBrokerConn] writeFrame: " + frame); + } + this.ctx.writeAndFlush(frame); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java new file mode 100644 index 00000000..5a7590c0 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java @@ -0,0 +1,91 @@ +package io.streamnative.pulsar.handlers.amqp.proxy2; + +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.protocol.ProtocolVersion; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.transport.ClientChannelMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ClientMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class used to handle client request and send to corresponding brokers, + * decode and handle response from brokers. + */ +@Slf4j +public class ProxyChannel implements ClientMethodProcessor { + + private Map connectionMap = new ConcurrentHashMap<>(); + + @Override + public void receiveConnectionStart(short versionMajor, short versionMinor, FieldTable serverProperties, byte[] mechanisms, byte[] locales) { + log.info("xxxx [ProxyClient] receiveConnectionStart"); + } + + @Override + public void receiveConnectionSecure(byte[] challenge) { + log.info("xxxx [ProxyClient] receiveConnectionSecure"); + } + + @Override + public void receiveConnectionRedirect(AMQShortString host, AMQShortString knownHosts) { + log.info("xxxx [ProxyClient] receiveConnectionRedirect"); + } + + @Override + public void receiveConnectionTune(int channelMax, long frameMax, int heartbeat) { + log.info("xxxx [ProxyClient] receiveConnectionTune"); + } + + @Override + public void receiveConnectionOpenOk(AMQShortString knownHosts) { + log.info("xxxx [ProxyClient] receiveConnectionOpenOk"); + } + + @Override + public ProtocolVersion getProtocolVersion() { + log.info("xxxx [ProxyClient] getProtocolVersion"); + return null; + } + + @Override + public ClientChannelMethodProcessor getChannelMethodProcessor(int channelId) { + log.info("xxxx [ProxyClient] getChannelMethodProcessor"); + return null; + } + + @Override + public void receiveConnectionClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + log.info("xxxx [ProxyClient] receiveConnectionClose"); + } + + @Override + public void receiveConnectionCloseOk() { + log.info("xxxx [ProxyClient] receiveConnectionCloseOk"); + } + + @Override + public void receiveHeartbeat() { + log.info("xxxx [ProxyClient] receiveHeartbeat"); + } + + @Override + public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { + log.info("xxxx [ProxyClient] receiveProtocolHeader"); + } + + @Override + public void setCurrentMethod(int classId, int methodId) { + log.info("xxxx [ProxyClient] setCurrentMethod"); + } + + @Override + public boolean ignoreAllButCloseOk() { + log.info("xxxx [ProxyClient] ignoreAllButCloseOk"); + return false; + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java new file mode 100644 index 00000000..a451f375 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java @@ -0,0 +1,237 @@ +package io.streamnative.pulsar.handlers.amqp.proxy2; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.streamnative.pulsar.handlers.amqp.AmqpBrokerDecoder; +import io.streamnative.pulsar.handlers.amqp.AmqpProtocolHandler; +import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; +import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pulsar.common.naming.TopicName; +import org.apache.qpid.server.QpidException; +import org.apache.qpid.server.bytebuffer.QpidByteBuffer; +import org.apache.qpid.server.protocol.ProtocolVersion; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.transport.AMQDataBlock; +import org.apache.qpid.server.protocol.v0_8.transport.AMQMethodBody; +import org.apache.qpid.server.protocol.v0_8.transport.ChannelOpenOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.ConnectionOpenOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.ConnectionTuneBody; +import org.apache.qpid.server.protocol.v0_8.transport.HeartbeatBody; +import org.apache.qpid.server.protocol.v0_8.transport.MethodRegistry; +import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; +import org.apache.qpid.server.protocol.v0_8.transport.ServerChannelMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ServerMethodProcessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * Proxy connection, it used to manage RabbitMQ client connection + * One connection will maintain multi connection with broker + */ +@Slf4j +public class ProxyConnection extends ChannelInboundHandlerAdapter + implements ServerMethodProcessor { + + private final ProxyConfiguration config; + private final Map channelMap = new ConcurrentHashMap<>(); + private final Map connectionMap = new ConcurrentHashMap<>(); + private final Map serverChannelMap = new ConcurrentHashMap<>(); + private final List connectionCommands = new ArrayList<>(); + private final AmqpBrokerDecoder decoder; + private State state; + private final ProtocolVersion protocolVersion; + private final MethodRegistry methodRegistry; + private ChannelHandlerContext ctx; + private final PulsarServiceLookupHandler lookupHandler; + private String vhost; + + enum State { + INIT, + CONNECTED, + FAILED + } + + public ProxyConnection(ProxyConfiguration config, PulsarServiceLookupHandler lookupHandler) { + this.config = config; + this.decoder = new AmqpBrokerDecoder(this); + this.state = State.INIT; + protocolVersion = ProtocolVersion.v0_91; + methodRegistry = new MethodRegistry(protocolVersion); + this.lookupHandler = lookupHandler; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + log.info("xxxx [Netty] channelActive"); + this.ctx = ctx; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + log.info("xxxx [Netty] channelRead"); + ByteBuf byteBuf = (ByteBuf) msg; + if (state.equals(State.INIT)) { + connectionCommands.add(byteBuf); + } + try { + decoder.decodeBuffer(QpidByteBuffer.wrap(byteBuf.nioBuffer())); + } catch (Exception e) { + this.state = State.FAILED; + log.error("Failed to decode requests", e); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("xxxx [Netty] Exception caught in channel", cause); + this.state = State.FAILED; + } + + @Override + public void receiveConnectionStartOk(FieldTable clientProperties, AMQShortString mechanism, byte[] response, AMQShortString locale) { + log.info("xxxx [ProxyServer] receiveConnectionStartOk"); + ConnectionTuneBody tuneBody = + methodRegistry.createConnectionTuneBody(config.getAmqpMaxNoOfChannels(), + config.getAmqpMaxFrameSize(), config.getAmqpHeartBeat()); + writeFrame(tuneBody.generateFrame(0)); + } + + @Override + public void receiveConnectionSecureOk(byte[] response) { + log.info("xxxx [ProxyServer] receiveConnectionSecureOk"); + ConnectionTuneBody tuneBody = + methodRegistry.createConnectionTuneBody(config.getAmqpMaxNoOfChannels(), + config.getAmqpMaxFrameSize(), config.getAmqpHeartBeat()); + writeFrame(tuneBody.generateFrame(0)); + } + + @Override + public void receiveConnectionTuneOk(int channelMax, long frameMax, int heartbeat) { + log.info("xxxx [ProxyServer] receiveConnectionTuneOk"); + } + + @Override + public void receiveConnectionOpen(AMQShortString virtualHost, AMQShortString capabilities, boolean insist) { + log.info("xxxx [ProxyServer] receiveConnectionOpen"); + this.state = State.CONNECTED; + + String virtualHostStr = AMQShortString.toString(virtualHost); + if ((virtualHostStr != null) && virtualHostStr.charAt(0) == '/') { + virtualHostStr = virtualHostStr.substring(1); + if (StringUtils.isEmpty(virtualHostStr)){ + virtualHostStr = "default"; + } + } + this.vhost = virtualHostStr; + + ConnectionOpenOkBody okBody = + methodRegistry.createConnectionOpenOkBody(virtualHost); + writeFrame(okBody.generateFrame(0)); + } + + @Override + public void receiveChannelOpen(int channelId) { + log.info("xxxx [ProxyServer] receiveChannelOpen channelId: {}", channelId); + ProxyServerChannel channel = new ProxyServerChannel(channelId, this); + serverChannelMap.put(channelId, channel); + ChannelOpenOkBody okBody = + methodRegistry.createChannelOpenOkBody(); + writeFrame(okBody.generateFrame(channelId)); + } + + @Override + public ProtocolVersion getProtocolVersion() { + log.info("xxxx [ProxyServer] getProtocolVersion"); + return null; + } + + @Override + public ServerChannelMethodProcessor getChannelMethodProcessor(int channelId) { + log.info("xxxx [ProxyServer] getChannelMethodProcessor"); + return serverChannelMap.get(channelId); + } + + @Override + public void receiveConnectionClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + log.info("xxxx [ProxyServer] receiveConnectionClose"); + } + + @Override + public void receiveConnectionCloseOk() { + log.info("xxxx [ProxyServer] receiveConnectionCloseOk"); + } + + @Override + public void receiveHeartbeat() { + log.info("xxxx [ProxyServer] receiveHeartbeat"); + writeFrame(HeartbeatBody.FRAME); + } + + @Override + public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { + log.info("xxxx [ProxyServer] receiveProtocolHeader"); + decoder.setExpectProtocolInitiation(false); + try { + ProtocolVersion pv = protocolInitiation.checkVersion(); // Fails if not correct + // TODO serverProperties mechanis + AMQMethodBody responseBody = this.methodRegistry.createConnectionStartBody( + protocolVersion.getMajorVersion(), + pv.getActualMinorVersion(), + null, + // TODO temporary modification + "PLAIN token".getBytes(US_ASCII), + "en_US".getBytes(US_ASCII)); + writeFrame(responseBody.generateFrame(0)); + } catch (QpidException e) { + log.error("Received unsupported protocol initiation for protocol version: {} ", getProtocolVersion(), e); + } + } + + @Override + public void setCurrentMethod(int classId, int methodId) { + if (classId != 0 && methodId != 0) { + log.info("xxxx [ProxyServer] setCurrentMethod classId: {}, methodId:{}", classId, methodId); + } + } + + @Override + public boolean ignoreAllButCloseOk() { +// log.info("xxxx [ProxyServer] ignoreAllButCloseOk"); + return false; + } + + public synchronized void writeFrame(AMQDataBlock frame) { + if (log.isInfoEnabled()) { + log.info("xxxx [ProxyServer] writeFrame: " + frame); + } + this.ctx.writeAndFlush(frame); + } + + public CompletableFuture getBrokerConnection(String topic) { + CompletableFuture> future = + lookupHandler.findBroker( + TopicName.get("persistent://public/" + vhost + "/" + topic), + AmqpProtocolHandler.PROTOCOL_NAME); + return future.thenApply(pair -> { + try { + return new ProxyBrokerConnection(pair.getLeft(), pair.getRight(), ctx.channel()); + } catch (InterruptedException e) { + log.error("Failed to create proxy broker connection with address {}:{}", + pair.getLeft(), pair.getRight(), e); + throw new RuntimeException(e); + } + }); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java new file mode 100644 index 00000000..ecfaf74a --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java @@ -0,0 +1,51 @@ +package io.streamnative.pulsar.handlers.amqp.proxy2; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.flush.FlushConsolidationHandler; +import io.streamnative.pulsar.handlers.amqp.AmqpEncoder; +import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; +import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.broker.PulsarService; + +/** + * Proxy server, the proxy server should be an individual service, it could be scale up. + */ +@Slf4j +public class ProxyServer { + + private final ProxyConfiguration config; + private final PulsarService pulsar; + private PulsarServiceLookupHandler lookupHandler; + + public ProxyServer(ProxyConfiguration config, PulsarService pulsarService) { + this.config = config; + this.pulsar = pulsarService; + } + + public void start() throws Exception { + this.lookupHandler = new PulsarServiceLookupHandler(config, pulsar); + // listen to the proxy port to receive amqp commands + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( + config.getAmqpExplicitFlushAfterFlushes(), true)); + ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); + ch.pipeline().addLast("handler", new ProxyConnection(config, lookupHandler)); + } + }); + bootstrap.bind(config.getAmqpProxyPort()).sync(); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java new file mode 100644 index 00000000..5432d805 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java @@ -0,0 +1,179 @@ +package io.streamnative.pulsar.handlers.amqp.proxy2; + +import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.bytebuffer.QpidByteBuffer; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.transport.BasicContentHeaderProperties; +import org.apache.qpid.server.protocol.v0_8.transport.ExchangeDeclareBody; +import org.apache.qpid.server.protocol.v0_8.transport.ServerChannelMethodProcessor; + +import java.util.concurrent.CompletableFuture; + +@Slf4j +public class ProxyServerChannel implements ServerChannelMethodProcessor { + + private Integer channelId; + private ProxyConnection proxyConnection; + + public ProxyServerChannel(Integer channelId, ProxyConnection proxyConnection) { + this.channelId = channelId; + this.proxyConnection = proxyConnection; + } + + public CompletableFuture getConn(String topic) { + return proxyConnection.getBrokerConnection(topic); + } + + @Override + public void receiveAccessRequest(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, boolean read) { + log.info("xxxx [ProxyServerChannel] receiveAccessRequest"); + } + + @Override + public void receiveExchangeDeclare(AMQShortString exchange, AMQShortString type, boolean passive, boolean durable, boolean autoDelete, boolean internal, boolean nowait, FieldTable arguments) { + log.info("xxxx [ProxyServerChannel] receiveExchangeDeclare"); + getConn(exchange.toString()).thenAccept(conn -> { + ExchangeDeclareBody exchangeDeclareBody = new ExchangeDeclareBody(0, exchange, type, passive, durable, autoDelete, internal, nowait, arguments); + conn.writeFrame(exchangeDeclareBody.generateFrame(channelId)); + }); + } + + @Override + public void receiveExchangeDelete(AMQShortString exchange, boolean ifUnused, boolean nowait) { + log.info("xxxx [ProxyServerChannel] receiveExchangeDelete"); + } + + @Override + public void receiveExchangeBound(AMQShortString exchange, AMQShortString routingKey, AMQShortString queue) { + log.info("xxxx [ProxyServerChannel] receiveExchangeBound"); + } + + @Override + public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean durable, boolean exclusive, boolean autoDelete, boolean nowait, FieldTable arguments) { + log.info("xxxx [ProxyServerChannel] receiveQueueDeclare"); + } + + @Override + public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, boolean nowait, FieldTable arguments) { + log.info("xxxx [ProxyServerChannel] receiveQueueBind"); + } + + @Override + public void receiveQueuePurge(AMQShortString queue, boolean nowait) { + log.info("xxxx [ProxyServerChannel] receiveQueuePurge"); + } + + @Override + public void receiveQueueDelete(AMQShortString queue, boolean ifUnused, boolean ifEmpty, boolean nowait) { + log.info("xxxx [ProxyServerChannel] receiveQueueDelete"); + } + + @Override + public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, FieldTable arguments) { + log.info("xxxx [ProxyServerChannel] receiveQueueUnbind"); + } + + @Override + public void receiveBasicRecover(boolean requeue, boolean sync) { + log.info("xxxx [ProxyServerChannel] receiveBasicRecover"); + } + + @Override + public void receiveBasicQos(long prefetchSize, int prefetchCount, boolean global) { + log.info("xxxx [ProxyServerChannel] receiveBasicQos"); + } + + @Override + public void receiveBasicConsume(AMQShortString queue, AMQShortString consumerTag, boolean noLocal, boolean noAck, boolean exclusive, boolean nowait, FieldTable arguments) { + log.info("xxxx [ProxyServerChannel] receiveBasicConsume"); + } + + @Override + public void receiveBasicCancel(AMQShortString consumerTag, boolean noWait) { + log.info("xxxx [ProxyServerChannel] receiveBasicCancel"); + } + + @Override + public void receiveBasicPublish(AMQShortString exchange, AMQShortString routingKey, boolean mandatory, boolean immediate) { + log.info("xxxx [ProxyServerChannel] receiveBasicPublish"); + } + + @Override + public void receiveBasicGet(AMQShortString queue, boolean noAck) { + log.info("xxxx [ProxyServerChannel] receiveBasicGet"); + } + + @Override + public void receiveBasicAck(long deliveryTag, boolean multiple) { + log.info("xxxx [ProxyServerChannel] receiveBasicAck"); + } + + @Override + public void receiveBasicReject(long deliveryTag, boolean requeue) { + log.info("xxxx [ProxyServerChannel] receiveBasicReject"); + } + + @Override + public void receiveTxSelect() { + log.info("xxxx [ProxyServerChannel] receiveTxSelect"); + } + + @Override + public void receiveTxCommit() { + log.info("xxxx [ProxyServerChannel] receiveTxCommit"); + } + + @Override + public void receiveTxRollback() { + log.info("xxxx [ProxyServerChannel] receiveTxRollback"); + } + + @Override + public void receiveConfirmSelect(boolean nowait) { + log.info("xxxx [ProxyServerChannel] receiveConfirmSelect"); + } + + @Override + public void receiveChannelFlow(boolean active) { + log.info("xxxx [ProxyServerChannel] receiveChannelFlow"); + } + + @Override + public void receiveChannelFlowOk(boolean active) { + log.info("xxxx [ProxyServerChannel] receiveChannelFlowOk"); + } + + @Override + public void receiveChannelClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + log.info("xxxx [ProxyServerChannel] receiveChannelClose"); + } + + @Override + public void receiveChannelCloseOk() { + log.info("xxxx [ProxyServerChannel] receiveChannelCloseOk"); + } + + @Override + public void receiveMessageContent(QpidByteBuffer data) { + log.info("xxxx [ProxyServerChannel] receiveMessageContent"); + } + + @Override + public void receiveMessageHeader(BasicContentHeaderProperties properties, long bodySize) { + log.info("xxxx [ProxyServerChannel] receiveMessageHeader"); + } + + @Override + public boolean ignoreAllButCloseOk() { + log.info("xxxx [ProxyServerChannel] ignoreAllButCloseOk"); + return false; + } + + @Override + public void receiveBasicNack(long deliveryTag, boolean multiple, boolean requeue) { + log.info("xxxx [ProxyServerChannel] receiveBasicNack"); + } + +} diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java new file mode 100644 index 00000000..0583f07f --- /dev/null +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java @@ -0,0 +1,61 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; + +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import lombok.Cleanup; +import lombok.extern.slf4j.Slf4j; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * AMQP proxy related test. + */ +@Slf4j +public class ProxyTest2 extends AmqpTestBase { + + @BeforeClass + @Override + public void setup() throws Exception { + setBrokerCount(3); + super.setup(); + } + + @Test + public void rabbitMQProxyTest() throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setVirtualHost("vhost1"); + factory.setPort(getProxyPort()); + Connection coon = factory.newConnection(); + Channel channel = coon.createChannel(); + + System.out.println("xxxx [Client] start exchange declare"); + String ex = "exchange"; + Map map = new HashMap<>(); + map.put("key-a", "a value"); + map.put("key-b", "b value"); + channel.exchangeDeclare(ex, BuiltinExchangeType.FANOUT, true, false, map); + System.out.println("xxxx [Client] finish exchange declare"); + + channel.close(); + coon.close(); + } + +} From ca8848e55a4d243feb3562950666748cb5735e33 Mon Sep 17 00:00:00 2001 From: rangao Date: Mon, 14 Nov 2022 13:38:13 +0800 Subject: [PATCH 2/9] [fea] New proxy to use multi bundles Add a new proxy to use multi bundles feature. - support exchange declare - support queue declare - support queue bind - support queue unbind - support basic publish - support basic consume --- .../handlers/amqp/AmqpBrokerService.java | 2 +- .../pulsar/handlers/amqp/AmqpChannel.java | 90 +++- .../handlers/amqp/AmqpClientDecoder.java | 12 +- .../pulsar/handlers/amqp/AmqpConnection.java | 7 + .../pulsar/handlers/amqp/AmqpConsumer.java | 10 +- .../handlers/amqp/AmqpConsumerOriginal.java | 125 +++++ .../pulsar/handlers/amqp/AmqpDecoder.java | 228 ++++++++++ .../pulsar/handlers/amqp/AmqpExchange.java | 8 + .../handlers/amqp/AmqpOutputConverter.java | 28 +- .../handlers/amqp/AmqpProtocolHandler.java | 13 +- .../handlers/amqp/AmqpProxyDirectHandler.java | 33 ++ .../handlers/amqp/AmqpPulsarServerCnx.java | 5 + .../amqp/AmqpServiceConfiguration.java | 6 + .../handlers/amqp/ExchangeContainer.java | 6 +- .../handlers/amqp/ExchangeMessageRouter.java | 429 ++++++++++++++++++ .../pulsar/handlers/amqp/ExchangeService.java | 7 + .../handlers/amqp/ExchangeServiceImpl.java | 13 + .../amqp/impl/PersistentExchange.java | 175 +++++-- .../amqp/proxy/v2/AmqpProxyClientChannel.java | 182 ++++++++ .../amqp/proxy/v2/AmqpProxyServerChannel.java | 419 +++++++++++++++++ .../amqp/proxy/v2/ProxyBrokerConnection.java | 214 +++++++++ .../v2/ProxyClientConnection.java} | 150 ++++-- .../{proxy2 => proxy/v2}/ProxyServer.java | 40 +- .../amqp/proxy2/ProxyBrokerConnection.java | 61 --- .../handlers/amqp/proxy2/ProxyChannel.java | 91 ---- .../amqp/proxy2/ProxyServerChannel.java | 179 -------- .../handlers/amqp/test/TopicNameTest.java | 9 +- .../amqp/AmqpProtocolHandlerTestBase.java | 1 + .../pulsar/handlers/amqp/AmqpTestBase.java | 3 +- .../pulsar/handlers/amqp/ProxyTest2.java | 61 --- .../pulsar/handlers/amqp/ProxyV2Test.java | 93 ++++ 31 files changed, 2172 insertions(+), 528 deletions(-) create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProxyDirectHandler.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java rename amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/{proxy2/ProxyConnection.java => proxy/v2/ProxyClientConnection.java} (58%) rename amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/{proxy2 => proxy/v2}/ProxyServer.java (55%) delete mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java delete mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java delete mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java delete mode 100644 tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java create mode 100644 tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java index 4c7ddf36..61575123 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java @@ -44,7 +44,7 @@ public AmqpBrokerService(PulsarService pulsarService, AmqpServiceConfiguration c this.pulsarService = pulsarService; this.amqpTopicManager = new AmqpTopicManager(pulsarService); this.exchangeContainer = new ExchangeContainer(amqpTopicManager, pulsarService, initRouteExecutor(config), - config.getAmqpExchangeRouteQueueSize()); + config.getAmqpExchangeRouteQueueSize(), config); this.queueContainer = new QueueContainer(amqpTopicManager, pulsarService, exchangeContainer); this.exchangeService = new ExchangeServiceImpl(exchangeContainer); this.queueService = new QueueServiceImpl(exchangeContainer, queueContainer); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java index b81bb987..683d9b9d 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java @@ -273,17 +273,32 @@ public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQS log.debug("RECV[{}] QueueBind[ queue: {}, exchange: {}, bindingKey:{}, nowait:{}, arguments:{} ]", channelId, queue, exchange, bindingKey, nowait, argumentsTable); } - queueService.queueBind(connection.getNamespaceName(), getQueueName(queue), exchange.toString(), + + if (connection.getAmqpConfig().isAmqpProxyV2Enable()) { + exchangeService.queueBind(connection.getNamespaceName(), exchange.toString(), getQueueName(queue), + bindingKey != null ? bindingKey.toString() : null, FieldTable.convertToMap(argumentsTable)) + .thenAccept(__ -> { + MethodRegistry methodRegistry = connection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); + connection.writeFrame(responseBody.generateFrame(channelId)); + }).exceptionally(t -> { + log.error("Failed to bind queue {} to exchange {} v2.", queue, exchange, t); + handleAoPException(t); + return null; + }); + } else { + queueService.queueBind(connection.getNamespaceName(), getQueueName(queue), exchange.toString(), bindingKey != null ? bindingKey.toString() : null, nowait, argumentsTable, connection.getConnectionId()).thenAccept(__ -> { - MethodRegistry methodRegistry = connection.getMethodRegistry(); - AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); - connection.writeFrame(responseBody.generateFrame(channelId)); - }).exceptionally(t -> { - log.error("Failed to bind queue {} to exchange {}.", queue, exchange, t); - handleAoPException(t); - return null; - }); + MethodRegistry methodRegistry = connection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); + connection.writeFrame(responseBody.generateFrame(channelId)); + }).exceptionally(t -> { + log.error("Failed to bind queue {} to exchange {}.", queue, exchange, t); + handleAoPException(t); + return null; + }); + } } @Override @@ -344,16 +359,31 @@ public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AM log.debug("RECV[{}] QueueUnbind[ queue: {}, exchange:{}, bindingKey:{}, arguments:{} ]", channelId, queue, exchange, bindingKey, arguments); } - queueService.queueUnbind(connection.getNamespaceName(), queue.toString(), exchange.toString(), - bindingKey.toString(), arguments, connection.getConnectionId()).thenAccept(__ -> { - AMQMethodBody responseBody = connection.getMethodRegistry().createQueueUnbindOkBody(); - connection.writeFrame(responseBody.generateFrame(channelId)); - }).exceptionally(t -> { - log.error("Failed to unbind queue {} with exchange {} in vhost {}", - queue, exchange, connection.getNamespaceName(), t); - handleAoPException(t); - return null; - }); + + if (connection.getAmqpConfig().isAmqpProxyV2Enable()) { + exchangeService.queueUnBind(connection.getNamespaceName(), exchange.toString(), queue.toString(), + bindingKey.toString(), FieldTable.convertToMap(arguments)) + .thenAccept(__ -> { + MethodRegistry methodRegistry = connection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueUnbindOkBody(); + connection.writeFrame(responseBody.generateFrame(channelId)); + }).exceptionally(t -> { + log.error("Failed to unbind queue {} to exchange {}.", queue, exchange, t); + handleAoPException(t); + return null; + }); + } else { + queueService.queueUnbind(connection.getNamespaceName(), queue.toString(), exchange.toString(), + bindingKey.toString(), arguments, connection.getConnectionId()).thenAccept(__ -> { + AMQMethodBody responseBody = connection.getMethodRegistry().createQueueUnbindOkBody(); + connection.writeFrame(responseBody.generateFrame(channelId)); + }).exceptionally(t -> { + log.error("Failed to unbind queue {} with exchange {} in vhost {}", + queue, exchange, connection.getNamespaceName(), t); + handleAoPException(t); + return null; + }); + } } @Override @@ -431,12 +461,22 @@ private synchronized void subscribe(String consumerTag, String queueName, Topic CompletableFuture subscriptionFuture = topic.createSubscription( defaultSubscription, CommandSubscribe.InitialPosition.Earliest, false, null); subscriptionFuture.thenAccept(subscription -> { - AmqpConsumer consumer = new AmqpConsumer(queueContainer, subscription, - exclusive ? CommandSubscribe.SubType.Exclusive : CommandSubscribe.SubType.Shared, - topic.getName(), CONSUMER_ID.incrementAndGet(), 0, - consumerTag, true, connection.getServerCnx(), "", null, - false, MessageId.latest, - null, this, consumerTag, queueName, ack); + AmqpConsumer consumer; + if (connection.getAmqpConfig().isAmqpProxyV2Enable()) { + consumer = new AmqpConsumer(queueContainer, subscription, + exclusive ? CommandSubscribe.SubType.Exclusive : CommandSubscribe.SubType.Shared, + topic.getName(), CONSUMER_ID.incrementAndGet(), 0, + consumerTag, true, connection.getServerCnx(), "", null, + false, MessageId.latest, + null, this, consumerTag, queueName, ack); + } else { + consumer = new AmqpConsumer(queueContainer, subscription, exclusive + ? CommandSubscribe.SubType.Exclusive : + CommandSubscribe.SubType.Shared, topic.getName(), CONSUMER_ID.incrementAndGet(), 0, + consumerTag, true, connection.getServerCnx(), "", null, + false, MessageId.latest, + null, this, consumerTag, queueName, ack); + } subscription.addConsumer(consumer).thenAccept(__ -> { consumer.handleFlow(DEFAULT_CONSUMER_PERMIT); tag2ConsumersMap.put(consumerTag, consumer); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpClientDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpClientDecoder.java index fd2d65c8..59536497 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpClientDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpClientDecoder.java @@ -15,6 +15,8 @@ import java.nio.ByteBuffer; import java.util.Objects; + +import io.netty.channel.Channel; import org.apache.qpid.server.bytebuffer.QpidByteBuffer; import org.apache.qpid.server.protocol.ProtocolVersion; import org.apache.qpid.server.protocol.v0_8.AMQDecoder; @@ -51,7 +53,7 @@ * Client decoder for client-server interaction tests. * Copied from Qpid tests lib. */ -public class AmqpClientDecoder extends AMQDecoder> +public class AmqpClientDecoder extends AmqpDecoder> { private QpidByteBuffer _incompleteBuffer; @@ -62,7 +64,13 @@ public class AmqpClientDecoder extends AMQDecoder methodProcessor) { - super(false, methodProcessor); + super(false, methodProcessor, null); + } + + public AmqpClientDecoder(final ClientMethodProcessor methodProcessor, + Channel clientChannel) + { + super(false, methodProcessor, clientChannel); } public void decodeBuffer(ByteBuffer incomingBuffer) throws AMQFrameDecodingException, AMQProtocolVersionException diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConnection.java index 3c971e60..b5cdd87a 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConnection.java @@ -561,6 +561,13 @@ public synchronized void writeFrame(AMQDataBlock frame) { getCtx().writeAndFlush(frame); } + public synchronized void writeData(Object obj) { + if (log.isDebugEnabled()) { + log.debug("write data to client: " + obj); + } + getCtx().channel().writeAndFlush(obj); + } + public MethodRegistry getMethodRegistry() { return methodRegistry; } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumer.java index 688850cc..fca34d37 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumer.java @@ -52,20 +52,20 @@ @Slf4j public class AmqpConsumer extends Consumer { - private final AmqpChannel channel; + protected final AmqpChannel channel; private QueueContainer queueContainer; - private final boolean autoAck; + protected final boolean autoAck; - private final String consumerTag; + protected final String consumerTag; - private final String queueName; + protected final String queueName; /** * map(exchangeName,treeMap(indexPosition,msgPosition)) . */ private final Map> unAckMessages; - private static final AtomicIntegerFieldUpdater MESSAGE_PERMITS_UPDATER = + protected static final AtomicIntegerFieldUpdater MESSAGE_PERMITS_UPDATER = AtomicIntegerFieldUpdater.newUpdater(AmqpConsumer.class, "availablePermits"); private volatile int availablePermits; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java new file mode 100644 index 00000000..6faf261c --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java @@ -0,0 +1,125 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelPromise; +import io.netty.util.concurrent.Future; +import io.streamnative.pulsar.handlers.amqp.utils.MessageConvertUtils; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.bookkeeper.mledger.Entry; +import org.apache.pulsar.broker.service.BrokerServiceException; +import org.apache.pulsar.broker.service.EntryBatchIndexesAcks; +import org.apache.pulsar.broker.service.EntryBatchSizes; +import org.apache.pulsar.broker.service.RedeliveryTracker; +import org.apache.pulsar.broker.service.ServerCnx; +import org.apache.pulsar.broker.service.Subscription; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.common.api.proto.CommandSubscribe; +import org.apache.pulsar.common.api.proto.KeySharedMeta; +import org.apache.pulsar.common.protocol.Commands; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; + + +/** + * Amqp consumer Used to forward messages. + */ +@Slf4j +public class AmqpConsumerOriginal extends AmqpConsumer { + + public AmqpConsumerOriginal(QueueContainer queueContainer, Subscription subscription, + CommandSubscribe.SubType subType, String topicName, long consumerId, + int priorityLevel, String consumerName, boolean isDurable, ServerCnx cnx, + String appId, Map metadata, boolean readCompacted, + MessageId messageId, + KeySharedMeta keySharedMeta, AmqpChannel channel, String consumerTag, String queueName, + boolean autoAck) throws BrokerServiceException { + super(queueContainer, subscription, subType, topicName, consumerId, priorityLevel, consumerName, isDurable, + cnx, appId, metadata, readCompacted, messageId, keySharedMeta, channel, consumerTag, + queueName, autoAck); + } + + @Override + public Future sendMessages(final List entries, EntryBatchSizes batchSizes, + EntryBatchIndexesAcks batchIndexesAcks, int totalMessages, long totalBytes, + long totalChunkedMessages, RedeliveryTracker redeliveryTracker) { + return sendMessages(entries, batchSizes, batchIndexesAcks, totalMessages, totalBytes, + totalChunkedMessages, redeliveryTracker, Commands.DEFAULT_CONSUMER_EPOCH); + } + + @Override + public Future sendMessages(final List entries, EntryBatchSizes batchSizes, + EntryBatchIndexesAcks batchIndexesAcks, int totalMessages, long totalBytes, + long totalChunkedMessages, RedeliveryTracker redeliveryTracker, long epoch) { + ChannelPromise writePromise = this.channel.getConnection().getCtx().newPromise(); + if (entries.isEmpty() || totalMessages == 0) { + if (log.isDebugEnabled()) { + log.debug("[{}]({}) List of messages is empty, trigger write future immediately for consumerId {}", + queueName, consumerTag, consumerId()); + } + writePromise.setSuccess(null); + return writePromise; + } + if (!autoAck) { + channel.getCreditManager().useCreditForMessages(totalMessages, 0); + if (!channel.getCreditManager().hasCredit()) { + channel.setBlockedOnCredit(); + } + } + MESSAGE_PERMITS_UPDATER.addAndGet(this, -totalMessages); + final AmqpConnection connection = channel.getConnection(); + connection.ctx.channel().eventLoop().execute(() -> { + double count = 0; + for (Entry entry : entries) { + if (entry == null) { + // Entry was filtered out + continue; + } + count++; + sendMessage(entry); + } + connection.getCtx().writeAndFlush(Unpooled.EMPTY_BUFFER, writePromise); + batchSizes.recyle(); + }); + return writePromise; + } + + private void sendMessage(Entry entry) { + try { + long deliveryTag = channel.getNextDeliveryTag(); + if (!autoAck) { + channel.getUnacknowledgedMessageMap().add(deliveryTag, + entry.getPosition(), this, entry.getLength()); + } + + channel.getConnection().getAmqpOutputConverter().writeDeliver( + MessageConvertUtils.entryToAmqpBody(entry), + channel.getChannelId(), + getRedeliveryTracker().contains(entry.getPosition()), + deliveryTag, + AMQShortString.createAMQShortString(consumerTag)); + + if (autoAck) { + messagesAck(entry.getPosition()); + } + } catch (Exception e) { + log.error("[{}]({}) Failed to send message to consumer.", queueName, consumerTag, e); + } finally { + entry.release(); + } + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java new file mode 100644 index 00000000..65dade7f --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -0,0 +1,228 @@ +package io.streamnative.pulsar.handlers.amqp; + +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.bytebuffer.QpidByteBuffer; +import org.apache.qpid.server.protocol.ErrorCodes; +import org.apache.qpid.server.protocol.ProtocolVersion; +import org.apache.qpid.server.protocol.v0_8.AMQDecoder; +import org.apache.qpid.server.protocol.v0_8.AMQFrameDecodingException; +import org.apache.qpid.server.protocol.v0_8.transport.AMQProtocolVersionException; +import org.apache.qpid.server.protocol.v0_8.transport.ContentBody; +import org.apache.qpid.server.protocol.v0_8.transport.ContentHeaderBody; +import org.apache.qpid.server.protocol.v0_8.transport.HeartbeatBody; +import org.apache.qpid.server.protocol.v0_8.transport.MethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Slf4j +public abstract class AmqpDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(AMQDecoder.class); + public static final int FRAME_HEADER_SIZE = 7; + private final T _methodProcessor; + + /** Holds the protocol initiation decoder. */ + private ProtocolInitiation.Decoder _piDecoder = new ProtocolInitiation.Decoder(); + + /** Flag to indicate whether this decoder needs to handle protocol initiation. */ + private boolean _expectProtocolInitiation; + + + private boolean _firstRead = true; + + public static final int FRAME_MIN_SIZE = 4096; + private int _maxFrameSize = FRAME_MIN_SIZE; + + private final Channel _clientChannel; + + /** + * Creates a new AMQP decoder. + * @param expectProtocolInitiation true if this decoder needs to handle protocol initiation. + * @param methodProcessor method processor + */ + protected AmqpDecoder(boolean expectProtocolInitiation, T methodProcessor, Channel clientChannel) + { + _expectProtocolInitiation = expectProtocolInitiation; + _methodProcessor = methodProcessor; + _clientChannel = clientChannel; + } + + /** + * Sets the protocol initation flag, that determines whether decoding is handled by the data decoder of the protocol + * initation decoder. This method is expected to be called with false once protocol initation completes. + * + * @param expectProtocolInitiation true to use the protocol initiation decoder, false to use the + * data decoder. + */ + public void setExpectProtocolInitiation(boolean expectProtocolInitiation) + { + _expectProtocolInitiation = expectProtocolInitiation; + } + + public void setMaxFrameSize(final int frameMax) + { + _maxFrameSize = frameMax; + } + + public T getMethodProcessor() + { + return _methodProcessor; + } + + protected final int decode(final QpidByteBuffer buf) throws AMQFrameDecodingException + { + // If this is the first read then we may be getting a protocol initiation back if we tried to negotiate + // an unsupported version + if(_firstRead && buf.hasRemaining()) + { + _firstRead = false; + if(!_expectProtocolInitiation && (((int)buf.get(buf.position())) &0xff) > 8) + { + _expectProtocolInitiation = true; + } + } + + int required = 0; + while (required == 0) + { + if(!_expectProtocolInitiation) + { + required = processAMQPFrames(buf); + } + else + { + required = _piDecoder.decodable(buf); + if (required == 0) + { + _methodProcessor.receiveProtocolHeader(new ProtocolInitiation(buf)); + } + + } + } + return buf.hasRemaining() ? required : 0; + } + + protected int processAMQPFrames(final QpidByteBuffer buf) throws AMQFrameDecodingException + { + final int required = decodable(buf); + if (required == 0) + { + processInput(buf); + } + return required; + } + + protected int decodable(final QpidByteBuffer in) throws AMQFrameDecodingException + { + final int remainingAfterAttributes = in.remaining() - FRAME_HEADER_SIZE; + // type, channel, body length and end byte + if (remainingAfterAttributes < 0) + { + return -remainingAfterAttributes; + } + + + // Get an unsigned int, lifted from MINA ByteBuffer getUnsignedInt() + final long bodySize = ((long)in.getInt(in.position()+3)) & 0xffffffffL; + if (bodySize > _maxFrameSize) + { + throw new AMQFrameDecodingException( + "Incoming frame size of " + + bodySize + + " is larger than negotiated maximum of " + + _maxFrameSize); + } + + long required = (1L+bodySize)-remainingAfterAttributes; + return required > 0 ? (int) required : 0; + + } + + protected void processInput(final QpidByteBuffer in) + throws AMQFrameDecodingException, AMQProtocolVersionException + { + final byte type = in.get(); + + final int channel = in.getUnsignedShort(); + final long bodySize = in.getUnsignedInt(); + + // bodySize can be zero + if ((channel < 0) || (bodySize < 0)) + { + throw new AMQFrameDecodingException( + "Undecodable frame: type = " + type + " channel = " + channel + + " bodySize = " + bodySize); + } + + processFrame(channel, type, bodySize, in); + + byte marker = in.get(); + if ((marker & 0xFF) != 0xCE) + { + throw new AMQFrameDecodingException( + "End of frame marker not found. Read " + marker + " length=" + bodySize + + " type=" + type); + } + + } + + protected void processFrame(final int channel, final byte type, final long bodySize, final QpidByteBuffer in) + throws AMQFrameDecodingException + { + System.out.println("xxxx process frame " + type); + switch (type) + { + case 1: + processMethod(channel, in); + break; + case 2: + ContentHeaderBody.process(in, _methodProcessor.getChannelMethodProcessor(channel), bodySize); + break; + case 3: + ContentBody.process(in, _methodProcessor.getChannelMethodProcessor(channel), bodySize); + break; + case 8: +// HeartbeatBody.process(channel, in, _methodProcessor, bodySize); + redirectData(in, bodySize, 8); + break; + case 9: + redirectData(in, bodySize, 9); + break; + default: + throw new AMQFrameDecodingException("Unsupported frame type: " + type); + } + } + + + protected abstract void processMethod(int channelId, + QpidByteBuffer in) + throws AMQFrameDecodingException; + + protected AMQFrameDecodingException newUnknownMethodException(final int classId, + final int methodId, + ProtocolVersion protocolVersion) + { + return new AMQFrameDecodingException(ErrorCodes.COMMAND_INVALID, + "Method " + + methodId + + " unknown in AMQP version " + + protocolVersion + + " (while trying to decode class " + + classId + + " method " + + methodId + + ".", null); + } + + protected void redirectData(QpidByteBuffer in, long messageSize, int type) { + if (log.isDebugEnabled()) { + log.debug("redirect data type: {}, messageSize: {}", type, messageSize); + } + QpidByteBuffer buffer = in.view(0, (int) messageSize); + in.position((int) (in.position() + messageSize)); + _clientChannel.writeAndFlush(buffer); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java index 1ba8816b..4ebf8705 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java @@ -153,4 +153,12 @@ public static Type value(String type) { int getQueueSize(); + default CompletableFuture queueBind(String queue, String routingKey, Map arguments) { + return null; + } + + default CompletableFuture queueUnBind(String queue, String routingKey, Map arguments) { + return null; + }; + } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java index 74931042..5cea08ce 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java @@ -148,7 +148,7 @@ private void writeMessageDeliveryUnchanged(MessageContentSource content, writeFrame(new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, - new MessageContentSourceBody(chunk))); + new MessageContentSourceBody(chunk), connection.getAmqpConfig().isAmqpProxyV2Enable())); int writtenSize = contentChunkSize; while (writtenSize < bodySize) { @@ -346,27 +346,49 @@ public static final class CompositeAMQBodyBlock extends AMQDataBlock { private final AMQBody headerBody; private final AMQBody contentBody; private final int channel; + private final boolean amqpProxyV2Enable; - public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody) { + public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody, + boolean amqpProxyV2Enable) { this.channel = channel; this.methodBody = methodBody; this.headerBody = headerBody; this.contentBody = contentBody; + this.amqpProxyV2Enable = amqpProxyV2Enable; } @Override public long getSize() { - return OVERHEAD + (long) methodBody.getSize() + (long) headerBody.getSize() + (long) contentBody.getSize(); + return (amqpProxyV2Enable ? 8 : 0) + OVERHEAD + (long) methodBody.getSize() + (long) headerBody.getSize() + + (long) contentBody.getSize(); } @Override public long writePayload(final ByteBufferSender sender) { + if (amqpProxyV2Enable) { + // wrap the delivery message data with a special type 9 to skip data decode in proxy + QpidByteBuffer buffer = QpidByteBuffer.allocate(7); + buffer.put((byte) 9); + buffer.putUnsignedShort(0); + buffer.putUnsignedInt(getSize() - 8); + buffer.flip(); + sender.send(buffer); + } + long size = (new AMQFrame(channel, methodBody)).writePayload(sender); size += (new AMQFrame(channel, headerBody)).writePayload(sender); size += (new AMQFrame(channel, contentBody)).writePayload(sender); + if (amqpProxyV2Enable) { + // wrap the delivery message data to skip data decode in proxy + QpidByteBuffer endBuffer = QpidByteBuffer.allocate(1); + endBuffer.put((byte) 0xCE); + endBuffer.flip(); + sender.send(endBuffer); + size += 8; + } return size; } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java index 03b46ba6..3b273943 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java @@ -21,7 +21,7 @@ import io.netty.channel.socket.SocketChannel; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyService; -import io.streamnative.pulsar.handlers.amqp.proxy2.ProxyServer; +import io.streamnative.pulsar.handlers.amqp.proxy.v2.ProxyServer; import io.streamnative.pulsar.handlers.amqp.utils.ConfigurationUtils; import java.net.InetSocketAddress; import java.util.Map; @@ -109,11 +109,14 @@ public void start(BrokerService service) { "plaintext must be configured on internal listener"); proxyConfig.setBrokerServiceURL(internalListener.getBrokerServiceUrl().toString()); -// ProxyService proxyService = new ProxyService(proxyConfig, service.getPulsar()); - ProxyServer proxyServer = new ProxyServer(proxyConfig, service.getPulsar()); try { -// proxyService.start(); - proxyServer.start(); + if (amqpConfig.isAmqpProxyV2Enable()) { + ProxyServer proxyServer = new ProxyServer(proxyConfig, service.getPulsar()); + proxyServer.start(); + } else { + ProxyService proxyService = new ProxyService(proxyConfig, service.getPulsar()); + proxyService.start(); + } log.info("Start amqp proxy service at port: {}", proxyConfig.getAmqpProxyPort()); } catch (Exception e) { log.error("Failed to start amqp proxy service."); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProxyDirectHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProxyDirectHandler.java new file mode 100644 index 00000000..288c4289 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProxyDirectHandler.java @@ -0,0 +1,33 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.extern.log4j.Log4j2; +import org.apache.qpid.server.bytebuffer.SingleQpidByteBuffer; + +/** + * amqp data direct handler. + */ +@Log4j2 +public class AmqpProxyDirectHandler extends MessageToByteEncoder { + + @Override + public void encode(ChannelHandlerContext ctx, SingleQpidByteBuffer buffer, ByteBuf out) { + out.writeBytes(buffer.getUnderlyingBuffer()); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java index cf3d2912..475aa4d1 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java @@ -15,6 +15,7 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.pulsar.broker.PulsarService; +import org.apache.pulsar.broker.service.Consumer; import org.apache.pulsar.broker.service.ServerCnx; /** @@ -28,4 +29,8 @@ public AmqpPulsarServerCnx(PulsarService pulsar, ChannelHandlerContext ctx) { this.remoteAddress = ctx.channel().remoteAddress(); } + @Override + public void closeConsumer(Consumer consumer) { + // avoid close the connection when close the consumer + } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpServiceConfiguration.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpServiceConfiguration.java index 39b73e31..8be665cd 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpServiceConfiguration.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpServiceConfiguration.java @@ -125,4 +125,10 @@ public class AmqpServiceConfiguration extends ServiceConfiguration { ) private int amqpExchangeRouteExecutorThreads = Runtime.getRuntime().availableProcessors(); + @FieldContext( + category = CATEGORY_AMQP, + required = false + ) + private boolean amqpProxyV2Enable = false; + } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java index 37672280..ee0b901b 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java @@ -49,13 +49,15 @@ public class ExchangeContainer { private PulsarService pulsarService; private final Executor routeExecutor; private final int routeQueueSize; + private final AmqpServiceConfiguration configuration; protected ExchangeContainer(AmqpTopicManager amqpTopicManager, PulsarService pulsarService, - Executor routeExecutor, int routeQueueSize) { + Executor routeExecutor, int routeQueueSize, AmqpServiceConfiguration configuration) { this.amqpTopicManager = amqpTopicManager; this.pulsarService = pulsarService; this.routeExecutor = routeExecutor; this.routeQueueSize = routeQueueSize; + this.configuration = configuration; } @Getter @@ -164,7 +166,7 @@ public CompletableFuture asyncGetExchange(NamespaceName namespaceN amqpExchange = new PersistentExchange(exchangeName, AmqpExchange.Type.value(currentType), persistentTopic, currentDurable, currentAutoDelete, currentInternal, - currentArguments, routeExecutor, routeQueueSize); + currentArguments, routeExecutor, routeQueueSize, configuration); } catch (Exception e) { log.error("Failed to init exchange {} in vhost {}.", exchangeName, namespaceName.getLocalName(), e); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java new file mode 100644 index 00000000..6226a195 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -0,0 +1,429 @@ +package io.streamnative.pulsar.handlers.amqp; + +import static org.apache.bookkeeper.mledger.impl.ManagedCursorImpl.FALSE; +import static org.apache.bookkeeper.mledger.impl.ManagedCursorImpl.TRUE; +import static org.apache.pulsar.broker.service.persistent.PersistentTopic.MESSAGE_RATE_BACKOFF_MS; + +import com.google.common.collect.Sets; +import io.netty.buffer.ByteBuf; +import io.streamnative.pulsar.handlers.amqp.impl.HeadersMessageRouter; +import io.streamnative.pulsar.handlers.amqp.impl.PersistentExchange; +import io.streamnative.pulsar.handlers.amqp.impl.PersistentQueue; +import io.streamnative.pulsar.handlers.amqp.utils.MessageConvertUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.apache.bookkeeper.mledger.AsyncCallbacks; +import org.apache.bookkeeper.mledger.Entry; +import org.apache.bookkeeper.mledger.ManagedCursor; +import org.apache.bookkeeper.mledger.ManagedLedgerException; +import org.apache.bookkeeper.mledger.impl.ManagedCursorImpl; +import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl; +import org.apache.pulsar.broker.PulsarServerException; +import org.apache.pulsar.broker.service.persistent.PersistentTopic; +import org.apache.pulsar.client.api.Producer; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.impl.MessageImpl; +import org.apache.pulsar.common.api.proto.CommandSubscribe; +import org.apache.pulsar.common.api.proto.KeyValue; +import org.apache.pulsar.common.naming.NamespaceName; +import org.apache.pulsar.common.naming.TopicDomain; +import org.apache.pulsar.common.naming.TopicName; +import org.apache.pulsar.common.util.FutureUtil; +import org.apache.qpid.server.exchange.topic.TopicParser; + +@Slf4j +public abstract class ExchangeMessageRouter { + + private final PersistentExchange exchange; + + private ManagedCursorImpl cursor; + + private final Map>> producerMap = new ConcurrentHashMap<>(); + + private static final int defaultReadMaxSizeBytes = 4 * 1024 * 1024; + private static final int replicatorQueueSize = 1000; + private volatile int pendingQueueSize = 0; + private static final AtomicIntegerFieldUpdater PENDING_SIZE_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "pendingQueueSize"); + + private static final AtomicIntegerFieldUpdater HAVE_PENDING_READ_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "havePendingRead"); + private volatile int havePendingRead = FALSE; + + private enum State { + INIT, + READY, + ERROR + } + + @AllArgsConstructor + @EqualsAndHashCode + private static class Destination { + String name; + String type; + } + + public ExchangeMessageRouter(PersistentExchange exchange) { + this.exchange = exchange; + } + + public abstract void addBinding(String des, String desType, String routingKey, Map arguments); + + public abstract void removeBinding(String des, String desType, String routingKey, Map arguments); + + abstract Set getDestinations(String routingKey, Map headers); + + public void start() { + start0((ManagedLedgerImpl) ((PersistentTopic) exchange.getTopic()).getManagedLedger()); + } + + private void start0(ManagedLedgerImpl managedLedger) { + managedLedger.asyncOpenCursor("amqp-router", CommandSubscribe.InitialPosition.Earliest, + new AsyncCallbacks.OpenCursorCallback() { + @Override + public void openCursorComplete(ManagedCursor cursor, Object ctx) { + log.info("Start to route messages for exchange {}", exchange.getName()); + ExchangeMessageRouter.this.cursor = (ManagedCursorImpl) cursor; + readMoreEntries(); + } + + @Override + public void openCursorFailed(ManagedLedgerException exception, Object ctx) { + log.error("Failed to open cursor for exchange topic {}, retry", exchange.getName(), exception); + start0(managedLedger); + } + }, null); + } + + private void readMoreEntries() { + int availablePermits = getAvailablePermits(); + if (availablePermits > 0) { + if (HAVE_PENDING_READ_UPDATER.compareAndSet(this, FALSE, TRUE)) { + if (log.isDebugEnabled()) { + log.debug("{} Schedule read of {} messages.", exchange.getName(), availablePermits); + } + cursor.asyncReadEntriesOrWait(100, defaultReadMaxSizeBytes, + new AsyncCallbacks.ReadEntriesCallback() { + @Override + public void readEntriesComplete(List entries, Object ctx) { + HAVE_PENDING_READ_UPDATER.set(ExchangeMessageRouter.this, FALSE); + processMessages(entries); + } + + @Override + public void readEntriesFailed(ManagedLedgerException exception, Object ctx) { + HAVE_PENDING_READ_UPDATER.set(ExchangeMessageRouter.this, FALSE); + log.error("Failed to read entries from exchange {}", exchange.getName(), exception); + } + }, null, null); + } else { + if (log.isDebugEnabled()) { + log.debug("{} Not schedule read due to pending read. Messages to read {}.", + exchange.getName(), availablePermits); + } + } + } else { + // no permits from rate limit + exchange.getTopic().getBrokerService().getPulsar().getExecutor() + .schedule(this::readMoreEntries, MESSAGE_RATE_BACKOFF_MS, TimeUnit.MILLISECONDS); + } + } + + private int getAvailablePermits() { + int availablePermits = replicatorQueueSize - PENDING_SIZE_UPDATER.get(this); + if (availablePermits <= 0) { + if (log.isDebugEnabled()) { + log.debug("{} Replicator queue is full, availablePermits: {}, pause route.", + exchange.getName(), availablePermits); + } + return 0; + } + return availablePermits; + } + + private void processMessages(List entries) { + for (Entry entry : entries) { + PENDING_SIZE_UPDATER.incrementAndGet(this); + Map props; + MessageImpl message; + try { + message = MessageImpl.deserialize(entry.getDataBuffer()); + props = message.getMessageBuilder().getPropertiesList().stream() + .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); + } catch (IOException e) { + log.error("Deserialize entry dataBuffer failed. exchangeName: {}, skip it first.", + exchange.getName(), e); + PENDING_SIZE_UPDATER.decrementAndGet(this); + continue; + } + String routingKey = props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(); + Set destinations = getDestinations(routingKey, getMessageHeaders()); + List> futures = new ArrayList<>(); + for (Destination des : destinations) { + futures.add(sendMessage(des, message, props)); + } + FutureUtil.waitForAll(futures).whenComplete((__, t) -> { + if (t != null) { + log.error("Failed to route message {}", entry.getPosition(), t); + cursor.rewind(); + sendComplete(); + return; + } + sendComplete(); + cursor.asyncDelete(entry.getPosition(), new AsyncCallbacks.DeleteCallback() { + @Override + public void deleteComplete(Object ctx) { + if (log.isDebugEnabled()) { + log.debug("{} Deleted message at {}", exchange.getName(), ctx); + } + } + + @Override + public void deleteFailed(ManagedLedgerException exception, Object ctx) { + log.error("{} Failed to delete message at {}", exchange.getName(), ctx, exception); + } + }, entry.getPosition()); + }); + } + } + + private void sendComplete() { + int pending = PENDING_SIZE_UPDATER.decrementAndGet(this); + if (pending == 0 && HAVE_PENDING_READ_UPDATER.get(this) == FALSE) { + this.readMoreEntries(); + } + } + + private CompletableFuture sendMessage(Destination des, + MessageImpl message, + Map props) { + CompletableFuture future = new CompletableFuture<>(); + ByteBuf payload = message.getDataBuffer().slice(); + byte[] data = new byte[payload.readableBytes()]; + payload.readBytes(data); + String msg = new String(data); + getProducer(des.name, des.type).thenCompose(producer -> { + log.info("try to send message `" + msg + "` to des `" + des + "`"); + return producer.newMessage() + .value(data) + .properties(props) + .sendAsync(); + }) + .thenAccept(__ -> { + log.info("success to send message `" + msg + "` to des `" + des + "`"); + future.complete(null); + }) + .exceptionally(t -> { + future.completeExceptionally(t); + return null; + }); + return future; + } + + private CompletableFuture> getProducer(String des, String desType) { + PulsarClient pulsarClient; + try { + pulsarClient = exchange.getTopic().getBrokerService().pulsar().getClient(); + } catch (PulsarServerException e) { + log.error("Failed to get pulsar client", e); + return CompletableFuture.failedFuture(e); + } + NamespaceName namespaceName = TopicName.get(exchange.getTopic().getName()).getNamespaceObject(); + String prefix = desType.equals("queue") ? PersistentQueue.TOPIC_PREFIX : PersistentExchange.TOPIC_PREFIX; + return producerMap.computeIfAbsent(des, k -> pulsarClient.newProducer() + .topic(TopicName.get(TopicDomain.persistent.toString(), namespaceName, prefix + des).toString()) + .enableBatching(false) + .createAsync()); + } + + protected Map getMessageHeaders() { + return null; + } + + public static ExchangeMessageRouter getInstance(PersistentExchange exchange) { + return switch (exchange.getType()) { + case Fanout -> new FanoutExchangeMessageRouter(exchange); + case Direct -> new DirectExchangeMessageRouter(exchange); + case Topic -> new TopicExchangeMessageRouter(exchange); + case Headers -> new HeadersExchangeMessageRouter(exchange); + }; + } + + static class FanoutExchangeMessageRouter extends ExchangeMessageRouter { + + private final Set destinationSet; + + public FanoutExchangeMessageRouter(PersistentExchange exchange) { + super(exchange); + destinationSet = Sets.newConcurrentHashSet(); + } + + @Override + public synchronized void addBinding(String des, String desType, String routingKey, + Map arguments) { + destinationSet.add(new Destination(des, desType)); + } + + @Override + public synchronized void removeBinding(String des, String desType, String routingKey, + Map arguments) { + destinationSet.remove(new Destination(des, desType)); + } + + @Override + Set getDestinations(String routingKey, Map headers) { + return destinationSet; + } + + } + + static class DirectExchangeMessageRouter extends ExchangeMessageRouter { + + private final Map> destinationMap; + + public DirectExchangeMessageRouter(PersistentExchange exchange) { + super(exchange); + destinationMap = new ConcurrentHashMap<>(); + } + + @Override + public synchronized void addBinding(String des, String desType, String routingKey, + Map arguments) { + destinationMap.computeIfAbsent(routingKey, k -> Sets.newConcurrentHashSet()) + .add(new Destination(des, desType)); + } + + @Override + public synchronized void removeBinding(String des, String desType, String routingKey, + Map arguments) { + destinationMap.computeIfPresent(routingKey, (k, v) -> { + v.remove(new Destination(des, desType)); + if (v.isEmpty()) { + return null; + } + return v; + }); + } + + @Override + Set getDestinations(String routingKey, Map headers) { + return destinationMap.get(routingKey); + } + } + + static class TopicExchangeMessageRouter extends ExchangeMessageRouter { + + private final Map destinationMap; + + public TopicExchangeMessageRouter(PersistentExchange exchange) { + super(exchange); + destinationMap = new ConcurrentHashMap<>(); + } + + static class TopicRoutingKeyParser { + + Set bindingKeys; + TopicParser topicParser; + + void addBinding(String routingKey) { + if (bindingKeys.add(routingKey)) { + topicParser = new TopicParser(); + topicParser.addBinding(routingKey, null); + } + } + + void unbind(String routingKey) { + bindingKeys.remove(routingKey); + topicParser = new TopicParser(); + for (String bindingKey : bindingKeys) { + topicParser.addBinding(bindingKey, null); + } + } + + } + + + @Override + public synchronized void addBinding(String des, String desType, String routingKey, + Map arguments) { + destinationMap.computeIfAbsent(new Destination(des, desType), k -> new TopicRoutingKeyParser()) + .addBinding(routingKey); + } + + @Override + public synchronized void removeBinding(String des, String desType, String routingKey, + Map arguments) { + destinationMap.computeIfPresent(new Destination(des, desType), (k, v) -> { + v.unbind(routingKey); + if (v.bindingKeys.isEmpty()) { + return null; + } + return v; + }); + } + + @Override + Set getDestinations(String routingKey, Map headers) { + Set destinations = new HashSet<>(); + for (Map.Entry entry : destinationMap.entrySet()) { + if (!entry.getValue().topicParser.parse(routingKey).isEmpty()) { + destinations.add(entry.getKey()); + } + } + return destinations; + } + } + + static class HeadersExchangeMessageRouter extends ExchangeMessageRouter { + + private final Map messageRouterMap; + + public HeadersExchangeMessageRouter(PersistentExchange exchange) { + super(exchange); + messageRouterMap = new ConcurrentHashMap<>(); + } + + @Override + public synchronized void addBinding(String des, String desType, String routingKey, + Map arguments) { + messageRouterMap.computeIfAbsent(new Destination(des, desType), k -> new HeadersMessageRouter()) + .getArguments().putAll(arguments); + } + + @Override + public synchronized void removeBinding(String des, String desType, String routingKey, + Map arguments) { + messageRouterMap.computeIfPresent(new Destination(des, desType), (k, v) -> { + v.getArguments().putAll(arguments); + if (v.getArguments().isEmpty()) { + return null; + } + return v; + }); + } + + @Override + Set getDestinations(String routingKey, Map headers) { + Set destinations = new HashSet<>(); + for (Map.Entry entry : messageRouterMap.entrySet()) { + if (entry.getValue().isMatch(headers)) { + destinations.add(entry.getKey()); + } + } + return destinations; + } + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeService.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeService.java index 3c3015db..be97e2f4 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeService.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeService.java @@ -57,4 +57,11 @@ CompletableFuture exchangeDeclare(NamespaceName namespaceName, Str */ CompletableFuture exchangeBound(NamespaceName namespaceName, String exchange, String routingKey, String queueName); + + CompletableFuture queueBind(NamespaceName namespaceName, String exchange, String queue, String routingKey, + Map arguments); + + CompletableFuture queueUnBind(NamespaceName namespaceName, String exchange, String queue, String routingKey, + Map arguments); + } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeServiceImpl.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeServiceImpl.java index 2d9d1cc2..aede9728 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeServiceImpl.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeServiceImpl.java @@ -189,4 +189,17 @@ public CompletableFuture exchangeBound(NamespaceName namespaceName, Str return future; } + @Override + public CompletableFuture queueBind(NamespaceName namespaceName, String exchange, String queue, + String routingKey, Map arguments) { + return exchangeContainer.asyncGetExchange(namespaceName, exchange, false, null) + .thenCompose(amqpExchange -> amqpExchange.queueBind(queue, routingKey, arguments)); + } + + @Override + public CompletableFuture queueUnBind(NamespaceName namespaceName, String exchange, String queue, + String routingKey, Map arguments) { + return exchangeContainer.asyncGetExchange(namespaceName, exchange, false, null) + .thenCompose(amqpExchange -> amqpExchange.queueUnBind(queue, routingKey, arguments)); + } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java index 97659c6b..f606bbdc 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java @@ -17,14 +17,18 @@ import static org.apache.curator.shaded.com.google.common.base.Preconditions.checkArgument; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Sets; import io.netty.buffer.ByteBuf; import io.streamnative.pulsar.handlers.amqp.AbstractAmqpExchange; import io.streamnative.pulsar.handlers.amqp.AmqpEntryWriter; import io.streamnative.pulsar.handlers.amqp.AmqpExchangeReplicator; import io.streamnative.pulsar.handlers.amqp.AmqpQueue; +import io.streamnative.pulsar.handlers.amqp.AmqpServiceConfiguration; +import io.streamnative.pulsar.handlers.amqp.ExchangeMessageRouter; import io.streamnative.pulsar.handlers.amqp.utils.MessageConvertUtils; import io.streamnative.pulsar.handlers.amqp.utils.PulsarTopicMetadataUtils; +import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -33,6 +37,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.bookkeeper.mledger.AsyncCallbacks; import org.apache.bookkeeper.mledger.Entry; @@ -66,15 +74,30 @@ public class PersistentExchange extends AbstractAmqpExchange { public static final String INTERNAL = "INTERNAL"; public static final String ARGUMENTS = "ARGUMENTS"; public static final String TOPIC_PREFIX = "__amqp_exchange__"; + private static final String BINDINGS = "BINDINGS"; private PersistentTopic persistentTopic; private final ConcurrentOpenHashMap> cursors; private AmqpExchangeReplicator messageReplicator; private AmqpEntryWriter amqpEntryWriter; + private ExchangeMessageRouter exchangeMessageRouter; + private Set bindings; + + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode + @Data + static class Binding implements Serializable { + private String des; + private String desType; + private String key; + private Map arguments; + } + public PersistentExchange(String exchangeName, Type type, PersistentTopic persistentTopic, boolean durable, boolean autoDelete, boolean internal, Map arguments, - Executor routeExecutor, int routeQueueSize) { + Executor routeExecutor, int routeQueueSize, AmqpServiceConfiguration configuration) { super(exchangeName, type, Sets.newConcurrentHashSet(), durable, autoDelete, internal, arguments); this.persistentTopic = persistentTopic; topicNameValidate(); @@ -85,54 +108,68 @@ public PersistentExchange(String exchangeName, Type type, PersistentTopic persis cursor.setInactive(); } - if (messageReplicator == null) { - messageReplicator = new AmqpExchangeReplicator(this, routeExecutor, routeQueueSize) { - @Override - public CompletableFuture readProcess(ByteBuf data, Position position) { - Map props; - try { - MessageImpl message = MessageImpl.deserialize(data); - props = message.getMessageBuilder().getPropertiesList().stream() - .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); - } catch (Exception e) { - log.error("Failed to deserialize entry dataBuffer. exchangeName: {}", exchangeName, e); - return FutureUtil.failedFuture(e); - } + if (configuration.isAmqpProxyV2Enable()) { + bindings = Sets.newConcurrentHashSet(); + if (persistentTopic.getManagedLedger().getProperties().containsKey(BINDINGS)) { + List amqpQueueProperties = jsonMapper.readValue( + persistentTopic.getManagedLedger().getProperties().get(BINDINGS), new TypeReference<>() {}); + this.bindings.addAll(amqpQueueProperties); + } + this.exchangeMessageRouter = ExchangeMessageRouter.getInstance(this); + for (Binding binding : this.bindings) { + this.exchangeMessageRouter.addBinding(binding.des, binding.desType, binding.key, binding.arguments); + } + this.exchangeMessageRouter.start(); + } else { + if (messageReplicator == null) { + messageReplicator = new AmqpExchangeReplicator(this, routeExecutor, routeQueueSize) { + @Override + public CompletableFuture readProcess(ByteBuf data, Position position) { + Map props; + try { + MessageImpl message = MessageImpl.deserialize(data); + props = message.getMessageBuilder().getPropertiesList().stream() + .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); + } catch (Exception e) { + log.error("Failed to deserialize entry dataBuffer. exchangeName: {}", exchangeName, e); + return FutureUtil.failedFuture(e); + } - List> routeFutureList = new ArrayList<>(); - if (exchangeType == Type.Direct) { - String bindingKey = props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(); - Set queueSet = bindingKeyQueueMap.get(bindingKey); - if (queueSet == null) { - if (log.isDebugEnabled()) { - log.debug("The queue set of the bindingKey {} is not exist.", bindingKey); + List> routeFutureList = new ArrayList<>(); + if (exchangeType == Type.Direct) { + String bindingKey = props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(); + Set queueSet = bindingKeyQueueMap.get(bindingKey); + if (queueSet == null) { + if (log.isDebugEnabled()) { + log.debug("The queue set of the bindingKey {} is not exist.", bindingKey); + } + } else { + for (AmqpQueue queue : queueSet) { + routeFutureList.add( + queue.writeIndexMessageAsync( + exchangeName, position.getLedgerId(), position.getEntryId(), props)); + } } - } else { - for (AmqpQueue queue : queueSet) { + } else if (exchangeType == Type.Fanout) { + for (AmqpQueue queue : queues) { routeFutureList.add( queue.writeIndexMessageAsync( exchangeName, position.getLedgerId(), position.getEntryId(), props)); } + } else { + for (AmqpQueue queue : queues) { + CompletableFuture routeFuture = queue.getRouter(exchangeName).routingMessage( + position.getLedgerId(), position.getEntryId(), + props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(), + props); + routeFutureList.add(routeFuture); + } } - } else if (exchangeType == Type.Fanout) { - for (AmqpQueue queue : queues) { - routeFutureList.add( - queue.writeIndexMessageAsync( - exchangeName, position.getLedgerId(), position.getEntryId(), props)); - } - } else { - for (AmqpQueue queue : queues) { - CompletableFuture routeFuture = queue.getRouter(exchangeName).routingMessage( - position.getLedgerId(), position.getEntryId(), - props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(), - props); - routeFutureList.add(routeFuture); - } + return FutureUtil.waitForAll(routeFutureList); } - return FutureUtil.waitForAll(routeFutureList); - } - }; - messageReplicator.startReplicate(); + }; + messageReplicator.startReplicate(); + } } this.amqpEntryWriter = new AmqpEntryWriter(persistentTopic); } @@ -357,4 +394,60 @@ public void topicNameValidate() { "The exchange topic name does not conform to the rules(__amqp_exchange__exchangeName)."); } + @Override + public CompletableFuture queueBind(String queue, String routingKey, Map arguments) { + this.bindings.add(new Binding(queue, "queue", routingKey, arguments)); + String bindingsJson; + try { + bindingsJson = jsonMapper.writeValueAsString(this.bindings); + } catch (JsonProcessingException e) { + log.error("Failed to bind queue {} to exchange {}", queue, exchangeName, e); + return CompletableFuture.failedFuture(e); + } + CompletableFuture future = new CompletableFuture<>(); + this.persistentTopic.getManagedLedger().asyncSetProperty(BINDINGS, bindingsJson, + new AsyncCallbacks.UpdatePropertiesCallback() { + @Override + public void updatePropertiesComplete(Map properties, Object ctx) { + PersistentExchange.this.exchangeMessageRouter.addBinding(queue, "queue", routingKey, arguments); + future.complete(null); + } + + @Override + public void updatePropertiesFailed(ManagedLedgerException exception, Object ctx) { + log.error("Failed to save binding metadata for bind operation.", exception); + future.completeExceptionally(exception); + } + }, null); + return future; + } + + @Override + public CompletableFuture queueUnBind(String queue, String routingKey, Map arguments) { + this.bindings.remove(new Binding(queue, "queue", routingKey, arguments)); + String bindingsJson; + try { + bindingsJson = jsonMapper.writeValueAsString(this.bindings); + } catch (JsonProcessingException e) { + log.error("Failed to unbind queue {} to exchange {}", queue, exchangeName, e); + return CompletableFuture.failedFuture(e); + } + CompletableFuture future = new CompletableFuture<>(); + this.persistentTopic.getManagedLedger().asyncSetProperty("BINDINGS", bindingsJson, + new AsyncCallbacks.UpdatePropertiesCallback() { + @Override + public void updatePropertiesComplete(Map properties, Object ctx) { + PersistentExchange.this.exchangeMessageRouter.removeBinding(queue, "queue", routingKey, arguments); + future.complete(null); + } + + @Override + public void updatePropertiesFailed(ManagedLedgerException exception, Object ctx) { + log.error("Failed to save binding metadata for unbind operation.", exception); + future.completeExceptionally(exception); + } + }, null); + return future; + } + } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java new file mode 100644 index 00000000..8b3670fa --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java @@ -0,0 +1,182 @@ +package io.streamnative.pulsar.handlers.amqp.proxy.v2; + +import java.util.concurrent.atomic.AtomicBoolean; +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.bytebuffer.QpidByteBuffer; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.transport.BasicConsumeOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.BasicContentHeaderProperties; +import org.apache.qpid.server.protocol.v0_8.transport.ChannelCloseOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.ClientChannelMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ExchangeDeclareOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.QueueBindOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.QueueDeclareOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.QueueUnbindOkBody; + + +@Slf4j +public class AmqpProxyClientChannel implements ClientChannelMethodProcessor { + + private final Integer channelId; + private ProxyBrokerConnection conn; + + public AmqpProxyClientChannel(Integer channelId, ProxyBrokerConnection conn) { + this.channelId = channelId; + this.conn = conn; + } + + @Override + public void receiveChannelOpenOk() { + conn.getChannelState().put(channelId, ProxyBrokerConnection.ChannelState.OPEN); + conn.getProxyConnection().getServerChannelMap().get(channelId).initComplete(); + } + + @Override + public void receiveChannelAlert(int replyCode, AMQShortString replyText, FieldTable details) { + } + + @Override + public void receiveAccessRequestOk(int ticket) { + } + + @Override + public void receiveExchangeDeclareOk() { + conn.getClientChannel().writeAndFlush(new ExchangeDeclareOkBody().generateFrame(channelId)); + } + + @Override + public void receiveExchangeDeleteOk() { + } + + @Override + public void receiveExchangeBoundOk(int replyCode, AMQShortString replyText) { + } + + @Override + public void receiveQueueBindOk() { + conn.getClientChannel().writeAndFlush(new QueueBindOkBody().generateFrame(channelId)); + } + + @Override + public void receiveQueueUnbindOk() { + conn.getClientChannel().writeAndFlush(new QueueUnbindOkBody().generateFrame(channelId)); + } + + @Override + public void receiveQueueDeclareOk(AMQShortString queue, long messageCount, long consumerCount) { + conn.getClientChannel().writeAndFlush(new QueueDeclareOkBody(queue, messageCount, consumerCount).generateFrame(channelId)); + } + + @Override + public void receiveQueuePurgeOk(long messageCount) { + log.info("xxxx [ClientProcess] receiveQueuePurgeOk"); + } + + @Override + public void receiveQueueDeleteOk(long messageCount) { + log.info("xxxx [ClientProcess] receiveQueueDeleteOk"); + } + + @Override + public void receiveBasicRecoverSyncOk() { + log.info("xxxx [ClientProcess] receiveBasicRecoverSyncOk"); + } + + @Override + public void receiveBasicQosOk() { + log.info("xxxx [ClientProcess] receiveBasicQosOk"); + } + + @Override + public void receiveBasicConsumeOk(AMQShortString consumerTag) { + conn.getClientChannel().writeAndFlush(new BasicConsumeOkBody(consumerTag).generateFrame(channelId)); + } + + @Override + public void receiveBasicCancelOk(AMQShortString consumerTag) { + } + + @Override + public void receiveBasicReturn(int replyCode, AMQShortString replyText, AMQShortString exchange, AMQShortString routingKey) { + } + + @Override + public void receiveBasicDeliver(AMQShortString consumerTag, long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey) { + } + + @Override + public void receiveBasicGetOk(long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey, long messageCount) { + } + + @Override + public void receiveBasicGetEmpty() { + } + + @Override + public void receiveTxSelectOk() { + } + + @Override + public void receiveTxCommitOk() { + } + + @Override + public void receiveTxRollbackOk() { + } + + @Override + public void receiveConfirmSelectOk() { + } + + @Override + public void receiveChannelFlow(boolean active) { + } + + @Override + public void receiveChannelFlowOk(boolean active) { + } + + @Override + public void receiveChannelClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + } + + @Override + public void receiveChannelCloseOk() { + if (log.isDebugEnabled()) { + log.debug("ProxyClientChannel receive channel close ok request."); + } + conn.getChannelState().put(channelId, ProxyBrokerConnection.ChannelState.CLOSE); + AtomicBoolean allClose = new AtomicBoolean(true); + conn.getProxyConnection().getConnectionMap().values().forEach(conn -> { + if (conn.getChannelState().getOrDefault(channelId, null) == ProxyBrokerConnection.ChannelState.OPEN) { + allClose.set(false); + } + }); + if (allClose.get()) { + conn.getProxyConnection().writeFrame(ChannelCloseOkBody.INSTANCE.generateFrame(channelId)); + } + } + + @Override + public void receiveMessageContent(QpidByteBuffer data) { + } + + @Override + public void receiveMessageHeader(BasicContentHeaderProperties properties, long bodySize) { + } + + @Override + public boolean ignoreAllButCloseOk() { + return false; + } + + @Override + public void receiveBasicNack(long deliveryTag, boolean multiple, boolean requeue) { + } + + @Override + public void receiveBasicAck(long deliveryTag, boolean multiple) { + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java new file mode 100644 index 00000000..88dd73a5 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java @@ -0,0 +1,419 @@ +package io.streamnative.pulsar.handlers.amqp.proxy.v2; + +import static org.apache.qpid.server.protocol.ErrorCodes.COMMAND_INVALID; +import static org.apache.qpid.server.protocol.ErrorCodes.FRAME_ERROR; +import static org.apache.qpid.server.protocol.ErrorCodes.INTERNAL_ERROR; +import static org.apache.qpid.server.protocol.ErrorCodes.MESSAGE_TOO_LARGE; +import static org.apache.qpid.server.transport.util.Functions.hex; + +import io.streamnative.pulsar.handlers.amqp.AbstractAmqpExchange; +import io.streamnative.pulsar.handlers.amqp.utils.MessageConvertUtils; +import java.io.UnsupportedEncodingException; +import java.util.concurrent.CompletableFuture; +import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.client.api.Message; +import org.apache.qpid.server.bytebuffer.QpidByteBuffer; +import org.apache.qpid.server.message.MessageDestination; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.IncomingMessage; +import org.apache.qpid.server.protocol.v0_8.transport.BasicConsumeBody; +import org.apache.qpid.server.protocol.v0_8.transport.BasicContentHeaderProperties; +import org.apache.qpid.server.protocol.v0_8.transport.ChannelCloseBody; +import org.apache.qpid.server.protocol.v0_8.transport.ChannelOpenBody; +import org.apache.qpid.server.protocol.v0_8.transport.ContentBody; +import org.apache.qpid.server.protocol.v0_8.transport.ContentHeaderBody; +import org.apache.qpid.server.protocol.v0_8.transport.ExchangeDeclareBody; +import org.apache.qpid.server.protocol.v0_8.transport.MessagePublishInfo; +import org.apache.qpid.server.protocol.v0_8.transport.QueueBindBody; +import org.apache.qpid.server.protocol.v0_8.transport.QueueDeclareBody; +import org.apache.qpid.server.protocol.v0_8.transport.QueueUnbindBody; +import org.apache.qpid.server.protocol.v0_8.transport.ServerChannelMethodProcessor; + +@Slf4j +public class AmqpProxyServerChannel implements ServerChannelMethodProcessor { + + private final Integer channelId; + private final ProxyClientConnection proxyConnection; + private boolean isInit; + + private IncomingMessage currentMessage; + + public AmqpProxyServerChannel(Integer channelId, ProxyClientConnection proxyConnection) { + this.channelId = channelId; + this.proxyConnection = proxyConnection; + } + + public CompletableFuture getConn(String topic) { + return proxyConnection.getBrokerConnection(topic); + } + + @Override + public void receiveAccessRequest(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, boolean read) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive access request."); + } + } + + @Override + public void receiveExchangeDeclare(AMQShortString exchange, AMQShortString type, boolean passive, boolean durable, boolean autoDelete, boolean internal, boolean nowait, FieldTable arguments) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive exchange declare request, exchange: {}, type: {}, passive: {}," + + "durable: {}, autoDelete: {}, internal: {}, nowait: {}, arguments: {}.", + exchange, type, passive, durable, autoDelete, internal, nowait, arguments); + } + getConn(exchange.toString()).thenAccept(conn -> { + initChannelIfNeeded(conn); + ExchangeDeclareBody exchangeDeclareBody = new ExchangeDeclareBody( + 0, exchange, type, passive, durable, autoDelete, internal, nowait, arguments); + conn.getChannel().writeAndFlush(exchangeDeclareBody.generateFrame(channelId)); + }); + } + + @Override + public void receiveExchangeDelete(AMQShortString exchange, boolean ifUnused, boolean nowait) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive exchange delete request, exchange: {}, ifUnused: {}, nowait: {}.", + exchange, ifUnused, nowait); + } + } + + @Override + public void receiveExchangeBound(AMQShortString exchange, AMQShortString routingKey, AMQShortString queue) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive exchange bound request, exchange: {}, routingKey: {}, queue: {}.", + exchange, routingKey, queue); + } + } + + @Override + public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean durable, boolean exclusive, boolean autoDelete, boolean nowait, FieldTable arguments) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive queue declare request, queue: {}, passive: {}," + + "durable: {}, exclusive: {}, autoDelete: {}, nowait: {}, arguments: {}.", + queue, passive, durable, exclusive, autoDelete, nowait, arguments); + } + getConn(queue.toString()).thenAccept(conn -> { + initChannelIfNeeded(conn); + log.info("[ProxyServerChannel] write receiveExchangeDeclare frame"); + QueueDeclareBody queueDeclareBody = new QueueDeclareBody( + 0, queue, passive, durable, exclusive, autoDelete, nowait, arguments); + conn.getChannel().writeAndFlush(queueDeclareBody.generateFrame(channelId)); + }); + } + + @Override + public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, boolean nowait, FieldTable arguments) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive queue bind request, queue: {}, exchange: {}, bindingKey: {}, " + + "nowait: {}, arguments: {}.", queue, exchange, bindingKey, nowait, arguments); + } + getConn(exchange.toString()).thenAccept(conn -> { + initChannelIfNeeded(conn); + QueueBindBody queueBindBody = new QueueBindBody(0, queue, exchange, bindingKey, nowait, arguments); + conn.getChannel().writeAndFlush(queueBindBody.generateFrame(channelId)); + }); + } + + @Override + public void receiveQueuePurge(AMQShortString queue, boolean nowait) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive queue purge request, queue: {}, nowait: {}.", queue, nowait); + } + } + + @Override + public void receiveQueueDelete(AMQShortString queue, boolean ifUnused, boolean ifEmpty, boolean nowait) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive queue purge request, queue: {}, ifUnused: {}, nowait: {}.", + queue, ifUnused, nowait); + } + } + + @Override + public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, FieldTable arguments) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive queue unbind request, queue: {}, exchange: {}, bindingKey: {}, " + + "arguments: {}.", queue, exchange, bindingKey, arguments); + } + getConn(exchange.toString()).thenAccept(conn -> { + initChannelIfNeeded(conn); + QueueUnbindBody command = new QueueUnbindBody(0, queue, exchange, bindingKey, arguments); + conn.getChannel().writeAndFlush(command.generateFrame(channelId)); + }); + } + + @Override + public void receiveBasicRecover(boolean requeue, boolean sync) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic recover request, requeue: {}, sync: {}.", requeue, sync); + } + } + + @Override + public void receiveBasicQos(long prefetchSize, int prefetchCount, boolean global) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic recover request, prefetchSize: {}, prefetchCount: {}, " + + "global: {}.", prefetchSize, prefetchCount, global); + } + } + + @Override + public void receiveBasicConsume(AMQShortString queue, AMQShortString consumerTag, boolean noLocal, boolean noAck, boolean exclusive, boolean nowait, FieldTable arguments) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic consume request, queue: {}, consumerTag: {}, noLocal: {}, " + + "noAck: {}, exclusive: {}, nowait: {}, arguments: {}.", + queue, consumerTag, noLocal, noAck, exclusive, noAck, arguments); + } + getConn(queue.toString()).thenAccept(conn -> { + initChannelIfNeeded(conn); + BasicConsumeBody basicConsumeBody = + new BasicConsumeBody(0, queue, consumerTag, noLocal, noAck, exclusive, nowait, arguments); + conn.getChannel().writeAndFlush(basicConsumeBody.generateFrame(channelId)); + }); + } + + @Override + public void receiveBasicCancel(AMQShortString consumerTag, boolean noWait) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic cancel request, consumerTag: {}, noWait: {}.", + consumerTag, noWait); + } + } + + @Override + public void receiveBasicPublish(AMQShortString exchange, AMQShortString routingKey, boolean mandatory, boolean immediate) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic publish request, exchange: {}, routingKey: {}, mandatory: {}, " + + "immediate: {}.", exchange, routingKey, mandatory, immediate); + } + MessagePublishInfo info = new MessagePublishInfo(exchange, immediate, + mandatory, routingKey); + setPublishFrame(info, null); + } + + private void setPublishFrame(MessagePublishInfo info, final MessageDestination e) { + currentMessage = new IncomingMessage(info); + currentMessage.setMessageDestination(e); + } + + @Override + public void receiveBasicGet(AMQShortString queue, boolean noAck) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic get request, queue: {}, noAck: {}.", queue, noAck); + } + } + + @Override + public void receiveBasicAck(long deliveryTag, boolean multiple) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic ack request, deliveryTag: {}, multiple: {}.", + deliveryTag, multiple); + } + } + + @Override + public void receiveBasicReject(long deliveryTag, boolean requeue) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic reject request, deliveryTag: {}, requeue: {}.", + deliveryTag, requeue); + } + } + + @Override + public void receiveTxSelect() { + } + + @Override + public void receiveTxCommit() { + } + + @Override + public void receiveTxRollback() { + } + + @Override + public void receiveConfirmSelect(boolean nowait) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive confirm select request, nowait: {}.", nowait); + } + } + + @Override + public void receiveChannelFlow(boolean active) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive channel flow request, active: {}.", active); + } + } + + @Override + public void receiveChannelFlowOk(boolean active) { + } + + @Override + public void receiveChannelClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive channel close request, replyCode: {}, replyText: {}, classId: {}, " + + "methodId: {}.", replyCode, replyText, classId, methodId); + } + proxyConnection.getConnectionMap().values().forEach(conn -> { + if (conn.getChannelState().getOrDefault(channelId, null) + == ProxyBrokerConnection.ChannelState.OPEN) { + conn.getChannel().writeAndFlush( + new ChannelCloseBody(replyCode, replyText, classId, methodId).generateFrame(channelId)); + } + }); + } + + @Override + public void receiveChannelCloseOk() { + } + + @Override + public void receiveMessageContent(QpidByteBuffer data) { + if (log.isDebugEnabled()) { + int binaryDataLimit = 2000; + log.debug("RECV[{}] MessageContent[data:{}]", channelId, hex(data, binaryDataLimit)); + } + + if (hasCurrentMessage()) { + publishContentBody(new ContentBody(data)); + } else { + proxyConnection.sendConnectionClose(COMMAND_INVALID, + "Attempt to send a content header without first sending a publish frame."); + } + } + + private void publishContentBody(ContentBody contentBody) { + if (log.isDebugEnabled()) { + log.debug("content body received on channel {}", channelId); + } + + try { + long currentSize = currentMessage.addContentBodyFrame(contentBody); + if (currentSize > currentMessage.getSize()) { + proxyConnection.sendConnectionClose(FRAME_ERROR, + "More message data received than content header defined"); + } else { + deliverCurrentMessageIfComplete(); + } + } catch (RuntimeException e) { + // we want to make sure we don't keep a reference to the message in the + // event of an error + currentMessage = null; + throw e; + } + } + + @Override + public void receiveMessageHeader(BasicContentHeaderProperties properties, long bodySize) { + if (log.isDebugEnabled()) { + log.debug("RECV[{}] MessageHeader[ properties: {{}} bodySize: {}]", channelId, properties, bodySize); + } + + // TODO - maxMessageSize ? + long maxMessageSize = 1024 * 1024 * 10; + if (hasCurrentMessage()) { + if (bodySize > maxMessageSize) { + properties.dispose(); + proxyConnection.sendChannelClose(channelId, MESSAGE_TOO_LARGE, + "Message size of " + bodySize + " greater than allowed maximum of " + maxMessageSize); + } else { + publishContentHeader(new ContentHeaderBody(properties, bodySize)); + } + } else { + properties.dispose(); + proxyConnection.sendConnectionClose(COMMAND_INVALID, + "Attempt to send a content header without first sending a publish frame"); + } + } + + private boolean hasCurrentMessage() { + return currentMessage != null; + } + + private void publishContentHeader(ContentHeaderBody contentHeaderBody) { + if (log.isDebugEnabled()) { + log.debug("Content header received on channel {}", channelId); + } + + currentMessage.setContentHeaderBody(contentHeaderBody); + + deliverCurrentMessageIfComplete(); + } + + private void deliverCurrentMessageIfComplete() { + if (currentMessage.allContentReceived()) { + MessagePublishInfo info = currentMessage.getMessagePublishInfo(); + String routingKey = AMQShortString.toString(info.getRoutingKey()); + String exchangeName = AMQShortString.toString(info.getExchange()); + Message message; + try { + message = MessageConvertUtils.toPulsarMessage(currentMessage); + } catch (UnsupportedEncodingException e) { + proxyConnection.sendConnectionClose(INTERNAL_ERROR, "Message encoding fail."); + return; + } + boolean createIfMissing = false; + String exchangeType = null; +// if (isDefaultExchange(AMQShortString.valueOf(exchangeName)) +// || isBuildInExchange(exchangeName)) { +// // Auto create default and buildIn exchanges if use. +// createIfMissing = true; +// exchangeType = getExchangeType(exchangeName); +// } + + if (exchangeName == null || exchangeName.length() == 0) { + exchangeName = AbstractAmqpExchange.DEFAULT_EXCHANGE_DURABLE; + } + + String msg = new String(message.getData()); + final String exName = exchangeName; + proxyConnection.getProxyServer() + .getProducer("persistent://public/" + proxyConnection.getVhost() + "/__amqp_exchange__" + exchangeName) + .thenCompose(producer -> { + return producer.newMessage() + .value(message.getData()) + .properties(message.getProperties()) + .sendAsync(); + }) + .thenAccept(position -> { + if (log.isDebugEnabled()) { + log.debug("Publish message success, position {}", position.toString()); + } + }) + .exceptionally(t -> { + log.error("Failed to write message to exchange", t); + return null; + }); + } + } + + @Override + public boolean ignoreAllButCloseOk() { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel ignore all but close ok."); + } + return false; + } + + @Override + public void receiveBasicNack(long deliveryTag, boolean multiple, boolean requeue) { + if (log.isDebugEnabled()) { + log.debug("ProxyServerChannel receive basic nack request, deliveryTag: {}, multiple: {}, requeue: {}.", + deliveryTag, multiple, requeue); + } + } + + public void initChannelIfNeeded(ProxyBrokerConnection coon) { + if (!isInit) { + ChannelOpenBody channelOpenBody = new ChannelOpenBody(); + coon.getChannel().writeAndFlush(channelOpenBody.generateFrame(channelId)); + } + } + + public void initComplete() { + isInit = true; + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java new file mode 100644 index 00000000..fc0e70f2 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java @@ -0,0 +1,214 @@ +package io.streamnative.pulsar.handlers.amqp.proxy.v2; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.flush.FlushConsolidationHandler; +import io.streamnative.pulsar.handlers.amqp.AmqpClientDecoder; +import io.streamnative.pulsar.handlers.amqp.AmqpEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.qpid.server.protocol.ProtocolVersion; +import org.apache.qpid.server.protocol.v0_8.AMQShortString; +import org.apache.qpid.server.protocol.v0_8.FieldTable; +import org.apache.qpid.server.protocol.v0_8.transport.AMQDataBlock; +import org.apache.qpid.server.protocol.v0_8.transport.ClientChannelMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ClientMethodProcessor; +import org.apache.qpid.server.protocol.v0_8.transport.ConnectionCloseBody; +import org.apache.qpid.server.protocol.v0_8.transport.ConnectionCloseOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; + +/** + * This class is used to transfer data between proxy and broker. + */ +@Slf4j +public class ProxyBrokerConnection { + + @Getter + private Channel channel; + @Getter + private final Channel clientChannel; + private List connectionCommands; + private Map clientProcessorMap; + @Getter + private ProxyClientConnection proxyConnection; + @Getter + private Map channelState = new ConcurrentHashMap<>(); + @Getter + private boolean isClose = false; + + public ProxyBrokerConnection(String host, Integer port, Channel clientChannel, List connectionCommands, + ProxyClientConnection proxyConnection) + throws InterruptedException { + this.connectionCommands = connectionCommands; + this.clientChannel = clientChannel; + EventLoopGroup group = new NioEventLoopGroup(); + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + log.info("channel pipeline {}", ch.pipeline()); + ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( + 1000, true)); + ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); + ch.pipeline().addLast("processor", new ProxyBrokerProcessor(clientChannel)); + } + }); + channel = bootstrap.connect(host, port).sync().channel(); + this.clientProcessorMap = new HashMap<>(); + this.proxyConnection = proxyConnection; + } + + protected enum ChannelState { + OPEN, + CLOSE + } + + private class ProxyBrokerProcessor extends ChannelInboundHandlerAdapter + implements ClientMethodProcessor { + + private ChannelHandlerContext ctx; + private final AmqpClientDecoder clientDecoder; + + public ProxyBrokerProcessor(Channel clientChannel) { + clientDecoder = new AmqpClientDecoder(this, clientChannel); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + this.ctx = ctx; + ChannelFuture future = null; + for (ByteBuf command : connectionCommands) { + command.retain(); + future = ctx.channel().writeAndFlush(command); + } + if (future != null) { + future.addListener(future1 -> { + ctx.channel().read(); + }); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + clientDecoder.decodeBuffer(((ByteBuf) msg).nioBuffer()); + } + + public synchronized void writeFrame(AMQDataBlock frame) { + if (log.isDebugEnabled()) { + log.debug("Write data to broker by proxy, frame: {}.", frame); + } + this.ctx.writeAndFlush(frame); + } + + @Override + public void receiveConnectionStart(short versionMajor, short versionMinor, FieldTable serverProperties, + byte[] mechanisms, byte[] locales) { + } + + @Override + public void receiveConnectionSecure(byte[] challenge) { + } + + @Override + public void receiveConnectionRedirect(AMQShortString host, AMQShortString knownHosts) { + } + + @Override + public void receiveConnectionTune(int channelMax, long frameMax, int heartbeat) { + } + + @Override + public void receiveConnectionOpenOk(AMQShortString knownHosts) { + } + + @Override + public ProtocolVersion getProtocolVersion() { + return ProtocolVersion.v0_91; + } + + @Override + public ClientChannelMethodProcessor getChannelMethodProcessor(int channelId) { + return clientProcessorMap.computeIfAbsent(channelId, + __ -> new AmqpProxyClientChannel(channelId, ProxyBrokerConnection.this)); + } + + @Override + public void receiveConnectionClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + for (ProxyBrokerConnection conn : proxyConnection.getConnectionMap().values()) { + conn.getChannel().writeAndFlush( + new ConnectionCloseBody(getProtocolVersion(), replyCode, replyText, classId, methodId) + .generateFrame(0)); + } + } + + @Override + public void receiveConnectionCloseOk() { + isClose = true; + boolean allClose = true; + for (ProxyBrokerConnection conn : proxyConnection.getConnectionMap().values()) { + if (!conn.isClose()) { + allClose = false; + break; + } + } + if (allClose) { + proxyConnection.writeFrame(ConnectionCloseOkBody.CONNECTION_CLOSE_OK_0_9.generateFrame(0)); + } + } + + @Override + public void receiveHeartbeat() { + log.info("receiveHeartbeat"); + } + + @Override + public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { + log.info("receiveProtocolHeader"); + } + + @Override + public void setCurrentMethod(int classId, int methodId) { + log.info("setCurrentMethod"); + } + + @Override + public boolean ignoreAllButCloseOk() { + return false; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + isClose = true; + boolean allClose = true; + for (ProxyBrokerConnection conn : proxyConnection.getConnectionMap().values()) { + if (!conn.isClose()) { + allClose = false; + break; + } else { + conn.channel.close(); + } + } + if (allClose) { + proxyConnection.getCtx().close(); + } + } + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java similarity index 58% rename from amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java rename to amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index a451f375..8b947491 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -1,4 +1,6 @@ -package io.streamnative.pulsar.handlers.amqp.proxy2; +package io.streamnative.pulsar.handlers.amqp.proxy.v2; + +import static java.nio.charset.StandardCharsets.US_ASCII; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -7,6 +9,12 @@ import io.streamnative.pulsar.handlers.amqp.AmqpProtocolHandler; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -18,7 +26,9 @@ import org.apache.qpid.server.protocol.v0_8.FieldTable; import org.apache.qpid.server.protocol.v0_8.transport.AMQDataBlock; import org.apache.qpid.server.protocol.v0_8.transport.AMQMethodBody; +import org.apache.qpid.server.protocol.v0_8.transport.ChannelCloseBody; import org.apache.qpid.server.protocol.v0_8.transport.ChannelOpenOkBody; +import org.apache.qpid.server.protocol.v0_8.transport.ConnectionCloseBody; import org.apache.qpid.server.protocol.v0_8.transport.ConnectionOpenOkBody; import org.apache.qpid.server.protocol.v0_8.transport.ConnectionTuneBody; import org.apache.qpid.server.protocol.v0_8.transport.HeartbeatBody; @@ -27,34 +37,35 @@ import org.apache.qpid.server.protocol.v0_8.transport.ServerChannelMethodProcessor; import org.apache.qpid.server.protocol.v0_8.transport.ServerMethodProcessor; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -import static java.nio.charset.StandardCharsets.US_ASCII; - /** * Proxy connection, it used to manage RabbitMQ client connection * One connection will maintain multi connection with broker */ @Slf4j -public class ProxyConnection extends ChannelInboundHandlerAdapter +public class ProxyClientConnection extends ChannelInboundHandlerAdapter implements ServerMethodProcessor { private final ProxyConfiguration config; - private final Map channelMap = new ConcurrentHashMap<>(); - private final Map connectionMap = new ConcurrentHashMap<>(); - private final Map serverChannelMap = new ConcurrentHashMap<>(); + @Getter + private final Map connectionMap = new ConcurrentHashMap<>(); + @Getter + private final Map serverChannelMap = new ConcurrentHashMap<>(); private final List connectionCommands = new ArrayList<>(); private final AmqpBrokerDecoder decoder; private State state; private final ProtocolVersion protocolVersion; private final MethodRegistry methodRegistry; + @Getter private ChannelHandlerContext ctx; private final PulsarServiceLookupHandler lookupHandler; + @Getter private String vhost; + @Getter + private final ProxyServer proxyServer; + @Getter + private int currentClassId; + @Getter + private int currentMethodId; enum State { INIT, @@ -62,24 +73,28 @@ enum State { FAILED } - public ProxyConnection(ProxyConfiguration config, PulsarServiceLookupHandler lookupHandler) { + public ProxyClientConnection(ProxyConfiguration config, + PulsarServiceLookupHandler lookupHandler, + ProxyServer proxyServer) { this.config = config; this.decoder = new AmqpBrokerDecoder(this); this.state = State.INIT; protocolVersion = ProtocolVersion.v0_91; methodRegistry = new MethodRegistry(protocolVersion); this.lookupHandler = lookupHandler; + this.proxyServer = proxyServer; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - log.info("xxxx [Netty] channelActive"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection channel active."); + } this.ctx = ctx; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - log.info("xxxx [Netty] channelRead"); ByteBuf byteBuf = (ByteBuf) msg; if (state.equals(State.INIT)) { connectionCommands.add(byteBuf); @@ -88,19 +103,22 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception decoder.decodeBuffer(QpidByteBuffer.wrap(byteBuf.nioBuffer())); } catch (Exception e) { this.state = State.FAILED; - log.error("Failed to decode requests", e); + log.error("ProxyClientConnection failed to decode requests.", e); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - log.error("xxxx [Netty] Exception caught in channel", cause); + log.error("ProxyClientConnection exception caught in channel", cause); this.state = State.FAILED; } @Override public void receiveConnectionStartOk(FieldTable clientProperties, AMQShortString mechanism, byte[] response, AMQShortString locale) { - log.info("xxxx [ProxyServer] receiveConnectionStartOk"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection start request, clientProperties: {}, mechanism: {}, " + + "response: {}, locale: {}.", clientProperties, mechanism, response, locale); + } ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody(config.getAmqpMaxNoOfChannels(), config.getAmqpMaxFrameSize(), config.getAmqpHeartBeat()); @@ -109,7 +127,9 @@ public void receiveConnectionStartOk(FieldTable clientProperties, AMQShortString @Override public void receiveConnectionSecureOk(byte[] response) { - log.info("xxxx [ProxyServer] receiveConnectionSecureOk"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection secure ok request, response: {}.", response); + } ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody(config.getAmqpMaxNoOfChannels(), config.getAmqpMaxFrameSize(), config.getAmqpHeartBeat()); @@ -118,12 +138,18 @@ public void receiveConnectionSecureOk(byte[] response) { @Override public void receiveConnectionTuneOk(int channelMax, long frameMax, int heartbeat) { - log.info("xxxx [ProxyServer] receiveConnectionTuneOk"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection tune ok request, channelMax: {}, frameMax: {}, " + + "heartbeat: {}.", channelMax, frameMax, heartbeat); + } } @Override public void receiveConnectionOpen(AMQShortString virtualHost, AMQShortString capabilities, boolean insist) { - log.info("xxxx [ProxyServer] receiveConnectionOpen"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection open request, virtualHost: {}, capabilities: {}, " + + "insist: {}.", virtualHost, capabilities, insist); + } this.state = State.CONNECTED; String virtualHostStr = AMQShortString.toString(virtualHost); @@ -142,8 +168,10 @@ public void receiveConnectionOpen(AMQShortString virtualHost, AMQShortString cap @Override public void receiveChannelOpen(int channelId) { - log.info("xxxx [ProxyServer] receiveChannelOpen channelId: {}", channelId); - ProxyServerChannel channel = new ProxyServerChannel(channelId, this); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive channel open request, channelId: {}.", channelId); + } + AmqpProxyServerChannel channel = new AmqpProxyServerChannel(channelId, this); serverChannelMap.put(channelId, channel); ChannelOpenOkBody okBody = methodRegistry.createChannelOpenOkBody(); @@ -152,86 +180,120 @@ public void receiveChannelOpen(int channelId) { @Override public ProtocolVersion getProtocolVersion() { - log.info("xxxx [ProxyServer] getProtocolVersion"); - return null; + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection get protocol version."); + } + return ProtocolVersion.v0_91; } @Override public ServerChannelMethodProcessor getChannelMethodProcessor(int channelId) { - log.info("xxxx [ProxyServer] getChannelMethodProcessor"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection get channel method processor, channelId: {}.", channelId); + } return serverChannelMap.get(channelId); } @Override public void receiveConnectionClose(int replyCode, AMQShortString replyText, int classId, int methodId) { - log.info("xxxx [ProxyServer] receiveConnectionClose"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection close request, replyCode: {}, replyText: {}, " + + "classId: {}, methodId: {}.", replyCode, replyText, classId, methodId); + } + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive connection close request, replyCode: {}, replyText: {}, " + + "classId: {}, methodId: {}", replyCode, replyText, classId, methodId); + } + for (ProxyBrokerConnection conn : getConnectionMap().values()) { + conn.getChannel().writeAndFlush( + new ConnectionCloseBody( + getProtocolVersion(), replyCode, replyText, classId, methodId).generateFrame(0)); + } } @Override public void receiveConnectionCloseOk() { - log.info("xxxx [ProxyServer] receiveConnectionCloseOk"); } @Override public void receiveHeartbeat() { - log.info("xxxx [ProxyServer] receiveHeartbeat"); - writeFrame(HeartbeatBody.FRAME); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive heart beat request."); + } + for (ProxyBrokerConnection conn : getConnectionMap().values()) { + conn.getChannel().writeAndFlush(new HeartbeatBody()); + } } @Override public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { - log.info("xxxx [ProxyServer] receiveProtocolHeader"); + if (log.isDebugEnabled()) { + log.debug("ProxyClientConnection receive protocol header request, protocolInitiation: {}", + protocolInitiation); + } decoder.setExpectProtocolInitiation(false); try { ProtocolVersion pv = protocolInitiation.checkVersion(); // Fails if not correct - // TODO serverProperties mechanis AMQMethodBody responseBody = this.methodRegistry.createConnectionStartBody( protocolVersion.getMajorVersion(), pv.getActualMinorVersion(), null, - // TODO temporary modification "PLAIN token".getBytes(US_ASCII), "en_US".getBytes(US_ASCII)); writeFrame(responseBody.generateFrame(0)); } catch (QpidException e) { log.error("Received unsupported protocol initiation for protocol version: {} ", getProtocolVersion(), e); + writeFrame(new ProtocolInitiation(ProtocolVersion.v0_91)); + throw new RuntimeException(e); } } @Override public void setCurrentMethod(int classId, int methodId) { if (classId != 0 && methodId != 0) { - log.info("xxxx [ProxyServer] setCurrentMethod classId: {}, methodId:{}", classId, methodId); + this.currentClassId = classId; + this.currentMethodId = methodId; } } @Override public boolean ignoreAllButCloseOk() { -// log.info("xxxx [ProxyServer] ignoreAllButCloseOk"); return false; } public synchronized void writeFrame(AMQDataBlock frame) { - if (log.isInfoEnabled()) { - log.info("xxxx [ProxyServer] writeFrame: " + frame); + if (log.isDebugEnabled()) { + log.info("ProxyClientConnection write frame: {}", frame); } this.ctx.writeAndFlush(frame); } + public void sendChannelClose(int channelId, int replyCode, final String replyText) { + this.ctx.writeAndFlush(new ChannelCloseBody(replyCode, AMQShortString.createAMQShortString(replyText), + currentClassId, currentMethodId).generateFrame(channelId)); + } + + public void sendConnectionClose(int replyCode, String replyText) { + this.ctx.writeAndFlush(new ConnectionCloseBody(getProtocolVersion(), replyCode, + AMQShortString.createAMQShortString(replyText), currentClassId, currentMethodId).generateFrame(0)); + } + public CompletableFuture getBrokerConnection(String topic) { CompletableFuture> future = lookupHandler.findBroker( TopicName.get("persistent://public/" + vhost + "/" + topic), AmqpProtocolHandler.PROTOCOL_NAME); - return future.thenApply(pair -> { + return future.thenApply(pair -> connectionMap.computeIfAbsent( + pair.getLeft() + ":" + pair.getRight(), __ -> { try { - return new ProxyBrokerConnection(pair.getLeft(), pair.getRight(), ctx.channel()); + return new ProxyBrokerConnection( + pair.getLeft(), pair.getRight(), ctx.channel(), connectionCommands, ProxyClientConnection.this); } catch (InterruptedException e) { - log.error("Failed to create proxy broker connection with address {}:{}", - pair.getLeft(), pair.getRight(), e); - throw new RuntimeException(e); + log.error("Failed to create proxy broker connection with address {}:{} for topic {}", + pair.getLeft(), pair.getRight(), topic, e); + return null; } - }); + })); } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java similarity index 55% rename from amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java rename to amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java index ecfaf74a..9d4eff79 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java @@ -1,4 +1,4 @@ -package io.streamnative.pulsar.handlers.amqp.proxy2; +package io.streamnative.pulsar.handlers.amqp.proxy.v2; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; @@ -8,10 +8,17 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.flush.FlushConsolidationHandler; import io.streamnative.pulsar.handlers.amqp.AmqpEncoder; +import io.streamnative.pulsar.handlers.amqp.AmqpProxyDirectHandler; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.broker.PulsarServerException; import org.apache.pulsar.broker.PulsarService; +import org.apache.pulsar.client.api.Producer; +import org.apache.pulsar.client.api.PulsarClient; /** * Proxy server, the proxy server should be an individual service, it could be scale up. @@ -21,11 +28,13 @@ public class ProxyServer { private final ProxyConfiguration config; private final PulsarService pulsar; + private final Map>> producerMap; private PulsarServiceLookupHandler lookupHandler; public ProxyServer(ProxyConfiguration config, PulsarService pulsarService) { this.config = config; this.pulsar = pulsarService; + this.producerMap = new ConcurrentHashMap<>(); } public void start() throws Exception { @@ -39,13 +48,38 @@ public void start() throws Exception { .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( config.getAmqpExplicitFlushAfterFlushes(), true)); - ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); - ch.pipeline().addLast("handler", new ProxyConnection(config, lookupHandler)); + ch.pipeline().addLast("handler", + new ProxyClientConnection(config, lookupHandler, ProxyServer.this)); + ch.pipeline().addLast("directHandler", new AmqpProxyDirectHandler()); } }); bootstrap.bind(config.getAmqpProxyPort()).sync(); } + public CompletableFuture> getProducer(String topic) { + PulsarClient client; + try { + client = pulsar.getClient(); + } catch (PulsarServerException e) { + return CompletableFuture.failedFuture(e); + } + return producerMap.computeIfAbsent(topic, k -> { + CompletableFuture> producerFuture = new CompletableFuture<>(); + client.newProducer() + .topic(topic) + .enableBatching(false) + .createAsync() + .thenAccept(producerFuture::complete) + .exceptionally(t -> { + producerFuture.completeExceptionally(t); + producerMap.remove(topic, producerFuture); + return null; + }); + return producerFuture; + }); + } + } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java deleted file mode 100644 index 461af5c3..00000000 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyBrokerConnection.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.streamnative.pulsar.handlers.amqp.proxy2; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.flush.FlushConsolidationHandler; -import io.streamnative.pulsar.handlers.amqp.AmqpEncoder; -import io.streamnative.pulsar.handlers.amqp.proxy.ProxyHandler; -import lombok.extern.slf4j.Slf4j; -import org.apache.qpid.server.protocol.v0_8.transport.AMQDataBlock; - -import java.util.concurrent.CompletableFuture; - -/** - * This class is used to transfer data between proxy and broker. - */ -@Slf4j -public class ProxyBrokerConnection extends ChannelInboundHandlerAdapter { - - private ChannelHandlerContext ctx; - private Channel channel; - private final Channel clientChannel; - - public ProxyBrokerConnection(String host, Integer port, Channel clientChannel) throws InterruptedException { - EventLoopGroup group = new NioEventLoopGroup(); - Bootstrap bootstrap = new Bootstrap(); - bootstrap.group(group) - .channel(NioSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) throws Exception { - ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( - 1000, true)); - ch.pipeline().addLast("frameEncoder", new AmqpEncoder()); - ch.pipeline().addLast("processor", this); - } - }); - channel = bootstrap.connect(host, port).sync().channel(); - this.clientChannel = clientChannel; - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - super.channelActive(ctx); - this.ctx = ctx; - } - - public synchronized void writeFrame(AMQDataBlock frame) { - if (log.isInfoEnabled()) { - log.info("xxxx [ProxyBrokerConn] writeFrame: " + frame); - } - this.ctx.writeAndFlush(frame); - } - -} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java deleted file mode 100644 index 5a7590c0..00000000 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyChannel.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.streamnative.pulsar.handlers.amqp.proxy2; - -import lombok.extern.slf4j.Slf4j; -import org.apache.qpid.server.protocol.ProtocolVersion; -import org.apache.qpid.server.protocol.v0_8.AMQShortString; -import org.apache.qpid.server.protocol.v0_8.FieldTable; -import org.apache.qpid.server.protocol.v0_8.transport.ClientChannelMethodProcessor; -import org.apache.qpid.server.protocol.v0_8.transport.ClientMethodProcessor; -import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * This class used to handle client request and send to corresponding brokers, - * decode and handle response from brokers. - */ -@Slf4j -public class ProxyChannel implements ClientMethodProcessor { - - private Map connectionMap = new ConcurrentHashMap<>(); - - @Override - public void receiveConnectionStart(short versionMajor, short versionMinor, FieldTable serverProperties, byte[] mechanisms, byte[] locales) { - log.info("xxxx [ProxyClient] receiveConnectionStart"); - } - - @Override - public void receiveConnectionSecure(byte[] challenge) { - log.info("xxxx [ProxyClient] receiveConnectionSecure"); - } - - @Override - public void receiveConnectionRedirect(AMQShortString host, AMQShortString knownHosts) { - log.info("xxxx [ProxyClient] receiveConnectionRedirect"); - } - - @Override - public void receiveConnectionTune(int channelMax, long frameMax, int heartbeat) { - log.info("xxxx [ProxyClient] receiveConnectionTune"); - } - - @Override - public void receiveConnectionOpenOk(AMQShortString knownHosts) { - log.info("xxxx [ProxyClient] receiveConnectionOpenOk"); - } - - @Override - public ProtocolVersion getProtocolVersion() { - log.info("xxxx [ProxyClient] getProtocolVersion"); - return null; - } - - @Override - public ClientChannelMethodProcessor getChannelMethodProcessor(int channelId) { - log.info("xxxx [ProxyClient] getChannelMethodProcessor"); - return null; - } - - @Override - public void receiveConnectionClose(int replyCode, AMQShortString replyText, int classId, int methodId) { - log.info("xxxx [ProxyClient] receiveConnectionClose"); - } - - @Override - public void receiveConnectionCloseOk() { - log.info("xxxx [ProxyClient] receiveConnectionCloseOk"); - } - - @Override - public void receiveHeartbeat() { - log.info("xxxx [ProxyClient] receiveHeartbeat"); - } - - @Override - public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { - log.info("xxxx [ProxyClient] receiveProtocolHeader"); - } - - @Override - public void setCurrentMethod(int classId, int methodId) { - log.info("xxxx [ProxyClient] setCurrentMethod"); - } - - @Override - public boolean ignoreAllButCloseOk() { - log.info("xxxx [ProxyClient] ignoreAllButCloseOk"); - return false; - } - -} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java deleted file mode 100644 index 5432d805..00000000 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy2/ProxyServerChannel.java +++ /dev/null @@ -1,179 +0,0 @@ -package io.streamnative.pulsar.handlers.amqp.proxy2; - -import io.streamnative.pulsar.handlers.amqp.proxy.PulsarServiceLookupHandler; -import lombok.extern.slf4j.Slf4j; -import org.apache.qpid.server.bytebuffer.QpidByteBuffer; -import org.apache.qpid.server.protocol.v0_8.AMQShortString; -import org.apache.qpid.server.protocol.v0_8.FieldTable; -import org.apache.qpid.server.protocol.v0_8.transport.BasicContentHeaderProperties; -import org.apache.qpid.server.protocol.v0_8.transport.ExchangeDeclareBody; -import org.apache.qpid.server.protocol.v0_8.transport.ServerChannelMethodProcessor; - -import java.util.concurrent.CompletableFuture; - -@Slf4j -public class ProxyServerChannel implements ServerChannelMethodProcessor { - - private Integer channelId; - private ProxyConnection proxyConnection; - - public ProxyServerChannel(Integer channelId, ProxyConnection proxyConnection) { - this.channelId = channelId; - this.proxyConnection = proxyConnection; - } - - public CompletableFuture getConn(String topic) { - return proxyConnection.getBrokerConnection(topic); - } - - @Override - public void receiveAccessRequest(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, boolean read) { - log.info("xxxx [ProxyServerChannel] receiveAccessRequest"); - } - - @Override - public void receiveExchangeDeclare(AMQShortString exchange, AMQShortString type, boolean passive, boolean durable, boolean autoDelete, boolean internal, boolean nowait, FieldTable arguments) { - log.info("xxxx [ProxyServerChannel] receiveExchangeDeclare"); - getConn(exchange.toString()).thenAccept(conn -> { - ExchangeDeclareBody exchangeDeclareBody = new ExchangeDeclareBody(0, exchange, type, passive, durable, autoDelete, internal, nowait, arguments); - conn.writeFrame(exchangeDeclareBody.generateFrame(channelId)); - }); - } - - @Override - public void receiveExchangeDelete(AMQShortString exchange, boolean ifUnused, boolean nowait) { - log.info("xxxx [ProxyServerChannel] receiveExchangeDelete"); - } - - @Override - public void receiveExchangeBound(AMQShortString exchange, AMQShortString routingKey, AMQShortString queue) { - log.info("xxxx [ProxyServerChannel] receiveExchangeBound"); - } - - @Override - public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean durable, boolean exclusive, boolean autoDelete, boolean nowait, FieldTable arguments) { - log.info("xxxx [ProxyServerChannel] receiveQueueDeclare"); - } - - @Override - public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, boolean nowait, FieldTable arguments) { - log.info("xxxx [ProxyServerChannel] receiveQueueBind"); - } - - @Override - public void receiveQueuePurge(AMQShortString queue, boolean nowait) { - log.info("xxxx [ProxyServerChannel] receiveQueuePurge"); - } - - @Override - public void receiveQueueDelete(AMQShortString queue, boolean ifUnused, boolean ifEmpty, boolean nowait) { - log.info("xxxx [ProxyServerChannel] receiveQueueDelete"); - } - - @Override - public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, FieldTable arguments) { - log.info("xxxx [ProxyServerChannel] receiveQueueUnbind"); - } - - @Override - public void receiveBasicRecover(boolean requeue, boolean sync) { - log.info("xxxx [ProxyServerChannel] receiveBasicRecover"); - } - - @Override - public void receiveBasicQos(long prefetchSize, int prefetchCount, boolean global) { - log.info("xxxx [ProxyServerChannel] receiveBasicQos"); - } - - @Override - public void receiveBasicConsume(AMQShortString queue, AMQShortString consumerTag, boolean noLocal, boolean noAck, boolean exclusive, boolean nowait, FieldTable arguments) { - log.info("xxxx [ProxyServerChannel] receiveBasicConsume"); - } - - @Override - public void receiveBasicCancel(AMQShortString consumerTag, boolean noWait) { - log.info("xxxx [ProxyServerChannel] receiveBasicCancel"); - } - - @Override - public void receiveBasicPublish(AMQShortString exchange, AMQShortString routingKey, boolean mandatory, boolean immediate) { - log.info("xxxx [ProxyServerChannel] receiveBasicPublish"); - } - - @Override - public void receiveBasicGet(AMQShortString queue, boolean noAck) { - log.info("xxxx [ProxyServerChannel] receiveBasicGet"); - } - - @Override - public void receiveBasicAck(long deliveryTag, boolean multiple) { - log.info("xxxx [ProxyServerChannel] receiveBasicAck"); - } - - @Override - public void receiveBasicReject(long deliveryTag, boolean requeue) { - log.info("xxxx [ProxyServerChannel] receiveBasicReject"); - } - - @Override - public void receiveTxSelect() { - log.info("xxxx [ProxyServerChannel] receiveTxSelect"); - } - - @Override - public void receiveTxCommit() { - log.info("xxxx [ProxyServerChannel] receiveTxCommit"); - } - - @Override - public void receiveTxRollback() { - log.info("xxxx [ProxyServerChannel] receiveTxRollback"); - } - - @Override - public void receiveConfirmSelect(boolean nowait) { - log.info("xxxx [ProxyServerChannel] receiveConfirmSelect"); - } - - @Override - public void receiveChannelFlow(boolean active) { - log.info("xxxx [ProxyServerChannel] receiveChannelFlow"); - } - - @Override - public void receiveChannelFlowOk(boolean active) { - log.info("xxxx [ProxyServerChannel] receiveChannelFlowOk"); - } - - @Override - public void receiveChannelClose(int replyCode, AMQShortString replyText, int classId, int methodId) { - log.info("xxxx [ProxyServerChannel] receiveChannelClose"); - } - - @Override - public void receiveChannelCloseOk() { - log.info("xxxx [ProxyServerChannel] receiveChannelCloseOk"); - } - - @Override - public void receiveMessageContent(QpidByteBuffer data) { - log.info("xxxx [ProxyServerChannel] receiveMessageContent"); - } - - @Override - public void receiveMessageHeader(BasicContentHeaderProperties properties, long bodySize) { - log.info("xxxx [ProxyServerChannel] receiveMessageHeader"); - } - - @Override - public boolean ignoreAllButCloseOk() { - log.info("xxxx [ProxyServerChannel] ignoreAllButCloseOk"); - return false; - } - - @Override - public void receiveBasicNack(long deliveryTag, boolean multiple, boolean requeue) { - log.info("xxxx [ProxyServerChannel] receiveBasicNack"); - } - -} diff --git a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java index 6af83ccf..9b00195e 100644 --- a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java +++ b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java @@ -19,6 +19,7 @@ import io.netty.channel.EventLoopGroup; import io.streamnative.pulsar.handlers.amqp.AbstractAmqpExchange; +import io.streamnative.pulsar.handlers.amqp.AmqpServiceConfiguration; import io.streamnative.pulsar.handlers.amqp.impl.PersistentExchange; import io.streamnative.pulsar.handlers.amqp.impl.PersistentQueue; import java.util.concurrent.Executors; @@ -52,8 +53,8 @@ public void exchangeTopicNameValidate() { try { new PersistentExchange( exchangeName, exchangeType, exchangeTopic1, true, false, false, null, - Executors.newSingleThreadExecutor(), 200); - } catch (IllegalArgumentException e) { + Executors.newSingleThreadExecutor(), 200, new AmqpServiceConfiguration()); + } catch (Exception e) { fail("Failed to new PersistentExchange. errorMsg: " + e.getMessage()); } @@ -63,8 +64,8 @@ public void exchangeTopicNameValidate() { try { new PersistentExchange( exchangeName, exchangeType, exchangeTopic2, true, false, false, null, - Executors.newSingleThreadExecutor(), 200); - } catch (IllegalArgumentException e) { + Executors.newSingleThreadExecutor(), 200, new AmqpServiceConfiguration()); + } catch (Exception e) { assertNotNull(e); log.info("This is expected behavior."); } diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandlerTestBase.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandlerTestBase.java index 56c162e0..5616cdd6 100644 --- a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandlerTestBase.java +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandlerTestBase.java @@ -124,6 +124,7 @@ protected void resetConfig() { amqpConfig.setBrokerEntryMetadataInterceptors( Sets.newHashSet("org.apache.pulsar.common.intercept.AppendIndexMetadataInterceptor")); amqpConfig.setBrokerShutdownTimeoutMs(0L); + amqpConfig.setDefaultNumPartitions(1); // set protocol related config URL testHandlerUrl = this.getClass().getClassLoader().getResource("test-protocol-handler.nar"); diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpTestBase.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpTestBase.java index 48e66ebf..673c7b02 100644 --- a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpTestBase.java +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/AmqpTestBase.java @@ -37,6 +37,7 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; + /** * Base test class for RabbitMQ Client. */ @@ -114,7 +115,7 @@ protected void basicDirectConsume(String vhost, boolean exclusiveConsume) throws String routingKey = "test.key"; String queueName = randQuName(); - Connection conn = getConnection(vhost, false); + Connection conn = getConnection(vhost, true); Channel channel = conn.createChannel(); channel.exchangeDeclare(exchangeName, "direct", true); diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java deleted file mode 100644 index 0583f07f..00000000 --- a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyTest2.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed 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 io.streamnative.pulsar.handlers.amqp; - -import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.Cleanup; -import lombok.extern.slf4j.Slf4j; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.util.HashMap; -import java.util.Map; - -/** - * AMQP proxy related test. - */ -@Slf4j -public class ProxyTest2 extends AmqpTestBase { - - @BeforeClass - @Override - public void setup() throws Exception { - setBrokerCount(3); - super.setup(); - } - - @Test - public void rabbitMQProxyTest() throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setVirtualHost("vhost1"); - factory.setPort(getProxyPort()); - Connection coon = factory.newConnection(); - Channel channel = coon.createChannel(); - - System.out.println("xxxx [Client] start exchange declare"); - String ex = "exchange"; - Map map = new HashMap<>(); - map.put("key-a", "a value"); - map.put("key-b", "b value"); - channel.exchangeDeclare(ex, BuiltinExchangeType.FANOUT, true, false, map); - System.out.println("xxxx [Client] finish exchange declare"); - - channel.close(); - coon.close(); - } - -} diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java new file mode 100644 index 00000000..6a5900e6 --- /dev/null +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java @@ -0,0 +1,93 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; + +import io.streamnative.pulsar.handlers.amqp.rabbitmq.RabbitMQTestCase; +import lombok.extern.slf4j.Slf4j; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.concurrent.CountDownLatch; + +/** + * AMQP proxy related test. + */ +@Slf4j +public class ProxyV2Test extends AmqpTestBase { + + @BeforeClass + @Override + public void setup() throws Exception { + setBrokerCount(3); + ((AmqpServiceConfiguration) this.conf).setAmqpProxyV2Enable(true); + super.setup(); + } + + @Test + public void rabbitMQProxyTest() throws Exception { + int proxyPort = getProxyPort(); + RabbitMQTestCase rabbitMQTestCase = new RabbitMQTestCase(admin); + + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test1", + "vhost1", false, 2); + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test2", + "vhost2", false, 3); + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test3", + "vhost3", false, 2); + + CountDownLatch countDownLatch = new CountDownLatch(3); + new Thread(() -> { + try { + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test4", + "vhost1", false, 2); + countDownLatch.countDown(); + } catch (Exception e) { + log.error("Test4 error for vhost1.", e); + } + }).start(); + new Thread(() -> { + try { + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test5", + "vhost2", false, 3); + countDownLatch.countDown(); + } catch (Exception e) { + log.error("Test5 error for vhost2.", e); + } + }).start(); + new Thread(() -> { + try { + rabbitMQTestCase.basicFanoutTest(proxyPort, "rabbitmq-proxy-test6", + "vhost3", false, 3); + countDownLatch.countDown(); + } catch (Exception e) { + log.error("Test6 error for vhost3.", e); + } + }).start(); + countDownLatch.await(); + } + + @Test + public void unloadBundleTest() throws Exception { + int proxyPort = getProxyPort(); + + RabbitMQTestCase rabbitMQTestCase = new RabbitMQTestCase(admin); + rabbitMQTestCase.basicFanoutTest(proxyPort, "unload-bundle-test1", + "vhost1", true, 3); + rabbitMQTestCase.basicFanoutTest(proxyPort, "unload-bundle-test2", + "vhost2", true, 2); + rabbitMQTestCase.basicFanoutTest(proxyPort, "unload-bundle-test3", + "vhost3", true, 2); + } + +} From c235f307ceb47f564a3b22a4fbf7f749be632aa8 Mon Sep 17 00:00:00 2001 From: rangao Date: Mon, 14 Nov 2022 17:41:32 +0800 Subject: [PATCH 3/9] fix --- .../pulsar/handlers/amqp/AmqpChannel.java | 2 +- .../handlers/amqp/AmqpConsumerOriginal.java | 2 +- .../pulsar/handlers/amqp/AmqpDecoder.java | 13 +++++++++++++ .../pulsar/handlers/amqp/ExchangeContainer.java | 6 +++--- .../handlers/amqp/ExchangeMessageRouter.java | 13 +++++++++++++ .../handlers/amqp/impl/PersistentExchange.java | 11 ++++++----- .../amqp/proxy/v2/AmqpProxyClientChannel.java | 17 +++++++++++++---- .../amqp/proxy/v2/AmqpProxyServerChannel.java | 13 +++++++++++++ .../amqp/proxy/v2/ProxyBrokerConnection.java | 13 +++++++++++++ .../amqp/proxy/v2/ProxyClientConnection.java | 13 +++++++++++++ .../handlers/amqp/proxy/v2/ProxyServer.java | 13 +++++++++++++ .../handlers/amqp/test/TopicNameTest.java | 4 ++-- 12 files changed, 104 insertions(+), 16 deletions(-) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java index 683d9b9d..b335650a 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpChannel.java @@ -463,7 +463,7 @@ private synchronized void subscribe(String consumerTag, String queueName, Topic subscriptionFuture.thenAccept(subscription -> { AmqpConsumer consumer; if (connection.getAmqpConfig().isAmqpProxyV2Enable()) { - consumer = new AmqpConsumer(queueContainer, subscription, + consumer = new AmqpConsumerOriginal(queueContainer, subscription, exclusive ? CommandSubscribe.SubType.Exclusive : CommandSubscribe.SubType.Shared, topic.getName(), CONSUMER_ID.incrementAndGet(), 0, consumerTag, true, connection.getServerCnx(), "", null, diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java index 6faf261c..fd2c8cde 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java @@ -46,7 +46,7 @@ public AmqpConsumerOriginal(QueueContainer queueContainer, Subscription subscrip String appId, Map metadata, boolean readCompacted, MessageId messageId, KeySharedMeta keySharedMeta, AmqpChannel channel, String consumerTag, String queueName, - boolean autoAck) throws BrokerServiceException { + boolean autoAck) { super(queueContainer, subscription, subType, topicName, consumerId, priorityLevel, consumerName, isDurable, cnx, appId, metadata, readCompacted, messageId, keySharedMeta, channel, consumerTag, queueName, autoAck); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 65dade7f..4a1cf45d 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; import io.netty.channel.Channel; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java index ee0b901b..c324a9d8 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java @@ -164,9 +164,9 @@ public CompletableFuture asyncGetExchange(NamespaceName namespaceN boolean currentInternal = Boolean.parseBoolean( properties.getOrDefault(INTERNAL, "false")); amqpExchange = new PersistentExchange(exchangeName, - AmqpExchange.Type.value(currentType), - persistentTopic, currentDurable, currentAutoDelete, currentInternal, - currentArguments, routeExecutor, routeQueueSize, configuration); + AmqpExchange.Type.value(currentType), persistentTopic, currentDurable, + currentAutoDelete, currentInternal, currentArguments, routeExecutor, + routeQueueSize, configuration.isAmqpProxyV2Enable()); } catch (Exception e) { log.error("Failed to init exchange {} in vhost {}.", exchangeName, namespaceName.getLocalName(), e); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java index 6226a195..69335c31 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp; import static org.apache.bookkeeper.mledger.impl.ManagedCursorImpl.FALSE; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java index f606bbdc..ba77f15c 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java @@ -97,7 +97,8 @@ static class Binding implements Serializable { public PersistentExchange(String exchangeName, Type type, PersistentTopic persistentTopic, boolean durable, boolean autoDelete, boolean internal, Map arguments, - Executor routeExecutor, int routeQueueSize, AmqpServiceConfiguration configuration) { + Executor routeExecutor, int routeQueueSize, boolean proxyV2Enable) + throws JsonProcessingException { super(exchangeName, type, Sets.newConcurrentHashSet(), durable, autoDelete, internal, arguments); this.persistentTopic = persistentTopic; topicNameValidate(); @@ -108,10 +109,10 @@ public PersistentExchange(String exchangeName, Type type, PersistentTopic persis cursor.setInactive(); } - if (configuration.isAmqpProxyV2Enable()) { + if (proxyV2Enable) { bindings = Sets.newConcurrentHashSet(); if (persistentTopic.getManagedLedger().getProperties().containsKey(BINDINGS)) { - List amqpQueueProperties = jsonMapper.readValue( + List amqpQueueProperties = JSON_MAPPER.readValue( persistentTopic.getManagedLedger().getProperties().get(BINDINGS), new TypeReference<>() {}); this.bindings.addAll(amqpQueueProperties); } @@ -399,7 +400,7 @@ public CompletableFuture queueBind(String queue, String routingKey, Map queueUnBind(String queue, String routingKey, Map< this.bindings.remove(new Binding(queue, "queue", routingKey, arguments)); String bindingsJson; try { - bindingsJson = jsonMapper.writeValueAsString(this.bindings); + bindingsJson = JSON_MAPPER.writeValueAsString(this.bindings); } catch (JsonProcessingException e) { log.error("Failed to unbind queue {} to exchange {}", queue, exchangeName, e); return CompletableFuture.failedFuture(e); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java index 8b3670fa..92fe1c67 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; import java.util.concurrent.atomic.AtomicBoolean; @@ -70,22 +83,18 @@ public void receiveQueueDeclareOk(AMQShortString queue, long messageCount, long @Override public void receiveQueuePurgeOk(long messageCount) { - log.info("xxxx [ClientProcess] receiveQueuePurgeOk"); } @Override public void receiveQueueDeleteOk(long messageCount) { - log.info("xxxx [ClientProcess] receiveQueueDeleteOk"); } @Override public void receiveBasicRecoverSyncOk() { - log.info("xxxx [ClientProcess] receiveBasicRecoverSyncOk"); } @Override public void receiveBasicQosOk() { - log.info("xxxx [ClientProcess] receiveBasicQosOk"); } @Override diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java index 88dd73a5..af749300 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; import static org.apache.qpid.server.protocol.ErrorCodes.COMMAND_INVALID; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java index fc0e70f2..2b680f6a 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; import io.netty.bootstrap.Bootstrap; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index 8b947491..5ef88abc 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; import static java.nio.charset.StandardCharsets.US_ASCII; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java index 9d4eff79..b533e2b6 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java @@ -1,3 +1,16 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; import io.netty.bootstrap.ServerBootstrap; diff --git a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java index 9b00195e..7dd1b196 100644 --- a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java +++ b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java @@ -53,7 +53,7 @@ public void exchangeTopicNameValidate() { try { new PersistentExchange( exchangeName, exchangeType, exchangeTopic1, true, false, false, null, - Executors.newSingleThreadExecutor(), 200, new AmqpServiceConfiguration()); + Executors.newSingleThreadExecutor(), 200, true); } catch (Exception e) { fail("Failed to new PersistentExchange. errorMsg: " + e.getMessage()); } @@ -64,7 +64,7 @@ public void exchangeTopicNameValidate() { try { new PersistentExchange( exchangeName, exchangeType, exchangeTopic2, true, false, false, null, - Executors.newSingleThreadExecutor(), 200, new AmqpServiceConfiguration()); + Executors.newSingleThreadExecutor(), 200, false); } catch (Exception e) { assertNotNull(e); log.info("This is expected behavior."); From b466c6445adf83f1a3becbf9ed6b604f6da4aed5 Mon Sep 17 00:00:00 2001 From: rangao Date: Mon, 14 Nov 2022 18:04:37 +0800 Subject: [PATCH 4/9] fix check style --- .../handlers/amqp/AmqpConsumerOriginal.java | 1 - .../pulsar/handlers/amqp/AmqpDecoder.java | 138 +++++++----------- .../amqp/impl/PersistentExchange.java | 14 +- .../amqp/proxy/v2/AmqpProxyClientChannel.java | 15 +- .../amqp/proxy/v2/AmqpProxyServerChannel.java | 30 ++-- .../amqp/proxy/v2/ProxyClientConnection.java | 6 +- .../handlers/amqp/proxy/v2/package-info.java | 18 +++ .../handlers/amqp/test/TopicNameTest.java | 1 - pom.xml | 4 +- .../pulsar/handlers/amqp/ProxyV2Test.java | 3 +- 10 files changed, 108 insertions(+), 122 deletions(-) create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/package-info.java diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java index fd2c8cde..a02399d5 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java @@ -21,7 +21,6 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.bookkeeper.mledger.Entry; -import org.apache.pulsar.broker.service.BrokerServiceException; import org.apache.pulsar.broker.service.EntryBatchIndexesAcks; import org.apache.pulsar.broker.service.EntryBatchSizes; import org.apache.pulsar.broker.service.RedeliveryTracker; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 4a1cf45d..97eb4801 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -23,7 +23,6 @@ import org.apache.qpid.server.protocol.v0_8.transport.AMQProtocolVersionException; import org.apache.qpid.server.protocol.v0_8.transport.ContentBody; import org.apache.qpid.server.protocol.v0_8.transport.ContentHeaderBody; -import org.apache.qpid.server.protocol.v0_8.transport.HeartbeatBody; import org.apache.qpid.server.protocol.v0_8.transport.MethodProcessor; import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; import org.slf4j.Logger; @@ -34,32 +33,31 @@ public abstract class AmqpDecoder { private static final Logger LOGGER = LoggerFactory.getLogger(AMQDecoder.class); public static final int FRAME_HEADER_SIZE = 7; - private final T _methodProcessor; + private final T methodProcessor; /** Holds the protocol initiation decoder. */ - private ProtocolInitiation.Decoder _piDecoder = new ProtocolInitiation.Decoder(); + private ProtocolInitiation.Decoder piDecoder = new ProtocolInitiation.Decoder(); /** Flag to indicate whether this decoder needs to handle protocol initiation. */ - private boolean _expectProtocolInitiation; + private boolean expectProtocolInitiation; - private boolean _firstRead = true; + private boolean firstRead = true; public static final int FRAME_MIN_SIZE = 4096; - private int _maxFrameSize = FRAME_MIN_SIZE; + private int maxFrameSize = FRAME_MIN_SIZE; - private final Channel _clientChannel; + private final Channel clientChannel; /** * Creates a new AMQP decoder. * @param expectProtocolInitiation true if this decoder needs to handle protocol initiation. * @param methodProcessor method processor */ - protected AmqpDecoder(boolean expectProtocolInitiation, T methodProcessor, Channel clientChannel) - { - _expectProtocolInitiation = expectProtocolInitiation; - _methodProcessor = methodProcessor; - _clientChannel = clientChannel; + protected AmqpDecoder(boolean expectProtocolInitiation, T methodProcessor, Channel clientChannel) { + this.expectProtocolInitiation = expectProtocolInitiation; + this.methodProcessor = methodProcessor; + this.clientChannel = clientChannel; } /** @@ -69,101 +67,82 @@ protected AmqpDecoder(boolean expectProtocolInitiation, T methodProcessor, Chann * @param expectProtocolInitiation true to use the protocol initiation decoder, false to use the * data decoder. */ - public void setExpectProtocolInitiation(boolean expectProtocolInitiation) - { - _expectProtocolInitiation = expectProtocolInitiation; + public void setExpectProtocolInitiation(boolean expectProtocolInitiation) { + this.expectProtocolInitiation = expectProtocolInitiation; } - public void setMaxFrameSize(final int frameMax) - { - _maxFrameSize = frameMax; + public void setMaxFrameSize(final int frameMax) { + maxFrameSize = frameMax; } - public T getMethodProcessor() - { - return _methodProcessor; + public T getMethodProcessor() { + return methodProcessor; } - protected final int decode(final QpidByteBuffer buf) throws AMQFrameDecodingException - { + protected final int decode(final QpidByteBuffer buf) throws AMQFrameDecodingException { // If this is the first read then we may be getting a protocol initiation back if we tried to negotiate // an unsupported version - if(_firstRead && buf.hasRemaining()) - { - _firstRead = false; - if(!_expectProtocolInitiation && (((int)buf.get(buf.position())) &0xff) > 8) - { - _expectProtocolInitiation = true; + if (firstRead && buf.hasRemaining()) { + firstRead = false; + if (!expectProtocolInitiation && (((int) buf.get(buf.position())) & 0xff) > 8) { + expectProtocolInitiation = true; } } int required = 0; - while (required == 0) - { - if(!_expectProtocolInitiation) - { + while (required == 0) { + if (!expectProtocolInitiation) { required = processAMQPFrames(buf); - } - else - { - required = _piDecoder.decodable(buf); - if (required == 0) - { - _methodProcessor.receiveProtocolHeader(new ProtocolInitiation(buf)); + } else { + required = piDecoder.decodable(buf); + if (required == 0) { + methodProcessor.receiveProtocolHeader(new ProtocolInitiation(buf)); } - } } return buf.hasRemaining() ? required : 0; } - protected int processAMQPFrames(final QpidByteBuffer buf) throws AMQFrameDecodingException - { + protected int processAMQPFrames(final QpidByteBuffer buf) throws AMQFrameDecodingException { final int required = decodable(buf); - if (required == 0) - { + if (required == 0) { processInput(buf); } return required; } - protected int decodable(final QpidByteBuffer in) throws AMQFrameDecodingException - { + protected int decodable(final QpidByteBuffer in) throws AMQFrameDecodingException { final int remainingAfterAttributes = in.remaining() - FRAME_HEADER_SIZE; // type, channel, body length and end byte - if (remainingAfterAttributes < 0) - { + if (remainingAfterAttributes < 0) { return -remainingAfterAttributes; } // Get an unsigned int, lifted from MINA ByteBuffer getUnsignedInt() - final long bodySize = ((long)in.getInt(in.position()+3)) & 0xffffffffL; - if (bodySize > _maxFrameSize) - { + final long bodySize = ((long) in.getInt(in.position() + 3)) & 0xffffffffL; + if (bodySize > maxFrameSize) { throw new AMQFrameDecodingException( "Incoming frame size of " + bodySize + " is larger than negotiated maximum of " - + _maxFrameSize); + + maxFrameSize); } - long required = (1L+bodySize)-remainingAfterAttributes; + long required = (1L + bodySize) - remainingAfterAttributes; return required > 0 ? (int) required : 0; } protected void processInput(final QpidByteBuffer in) - throws AMQFrameDecodingException, AMQProtocolVersionException - { + throws AMQFrameDecodingException, AMQProtocolVersionException { final byte type = in.get(); final int channel = in.getUnsignedShort(); final long bodySize = in.getUnsignedInt(); // bodySize can be zero - if ((channel < 0) || (bodySize < 0)) - { + if ((channel < 0) || (bodySize < 0)) { throw new AMQFrameDecodingException( "Undecodable frame: type = " + type + " channel = " + channel + " bodySize = " + bodySize); @@ -172,8 +151,7 @@ protected void processInput(final QpidByteBuffer in) processFrame(channel, type, bodySize, in); byte marker = in.get(); - if ((marker & 0xFF) != 0xCE) - { + if ((marker & 0xFF) != 0xCE) { throw new AMQFrameDecodingException( "End of frame marker not found. Read " + marker + " length=" + bodySize + " type=" + type); @@ -182,41 +160,25 @@ protected void processInput(final QpidByteBuffer in) } protected void processFrame(final int channel, final byte type, final long bodySize, final QpidByteBuffer in) - throws AMQFrameDecodingException - { + throws AMQFrameDecodingException { System.out.println("xxxx process frame " + type); - switch (type) - { - case 1: - processMethod(channel, in); - break; - case 2: - ContentHeaderBody.process(in, _methodProcessor.getChannelMethodProcessor(channel), bodySize); - break; - case 3: - ContentBody.process(in, _methodProcessor.getChannelMethodProcessor(channel), bodySize); - break; - case 8: + switch (type) { + case 1 -> processMethod(channel, in); + case 2 -> ContentHeaderBody.process(in, methodProcessor.getChannelMethodProcessor(channel), bodySize); + case 3 -> ContentBody.process(in, methodProcessor.getChannelMethodProcessor(channel), bodySize); + case 8 -> // HeartbeatBody.process(channel, in, _methodProcessor, bodySize); - redirectData(in, bodySize, 8); - break; - case 9: - redirectData(in, bodySize, 9); - break; - default: - throw new AMQFrameDecodingException("Unsupported frame type: " + type); + redirectData(in, bodySize, 8); + case 9 -> redirectData(in, bodySize, 9); + default -> throw new AMQFrameDecodingException("Unsupported frame type: " + type); } } - - protected abstract void processMethod(int channelId, - QpidByteBuffer in) - throws AMQFrameDecodingException; + protected abstract void processMethod(int channelId, QpidByteBuffer in) throws AMQFrameDecodingException; protected AMQFrameDecodingException newUnknownMethodException(final int classId, final int methodId, - ProtocolVersion protocolVersion) - { + ProtocolVersion protocolVersion) { return new AMQFrameDecodingException(ErrorCodes.COMMAND_INVALID, "Method " + methodId @@ -235,7 +197,7 @@ protected void redirectData(QpidByteBuffer in, long messageSize, int type) { } QpidByteBuffer buffer = in.view(0, (int) messageSize); in.position((int) (in.position() + messageSize)); - _clientChannel.writeAndFlush(buffer); + clientChannel.writeAndFlush(buffer); } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java index ba77f15c..7834fd83 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java @@ -24,7 +24,6 @@ import io.streamnative.pulsar.handlers.amqp.AmqpEntryWriter; import io.streamnative.pulsar.handlers.amqp.AmqpExchangeReplicator; import io.streamnative.pulsar.handlers.amqp.AmqpQueue; -import io.streamnative.pulsar.handlers.amqp.AmqpServiceConfiguration; import io.streamnative.pulsar.handlers.amqp.ExchangeMessageRouter; import io.streamnative.pulsar.handlers.amqp.utils.MessageConvertUtils; import io.streamnative.pulsar.handlers.amqp.utils.PulsarTopicMetadataUtils; @@ -146,16 +145,14 @@ public CompletableFuture readProcess(ByteBuf data, Position position) { } } else { for (AmqpQueue queue : queueSet) { - routeFutureList.add( - queue.writeIndexMessageAsync( - exchangeName, position.getLedgerId(), position.getEntryId(), props)); + routeFutureList.add(queue.writeIndexMessageAsync( + exchangeName, position.getLedgerId(), position.getEntryId(), props)); } } } else if (exchangeType == Type.Fanout) { for (AmqpQueue queue : queues) { - routeFutureList.add( - queue.writeIndexMessageAsync( - exchangeName, position.getLedgerId(), position.getEntryId(), props)); + routeFutureList.add(queue.writeIndexMessageAsync( + exchangeName, position.getLedgerId(), position.getEntryId(), props)); } } else { for (AmqpQueue queue : queues) { @@ -438,7 +435,8 @@ public CompletableFuture queueUnBind(String queue, String routingKey, Map< new AsyncCallbacks.UpdatePropertiesCallback() { @Override public void updatePropertiesComplete(Map properties, Object ctx) { - PersistentExchange.this.exchangeMessageRouter.removeBinding(queue, "queue", routingKey, arguments); + PersistentExchange.this.exchangeMessageRouter.removeBinding(queue, "queue", + routingKey, arguments); future.complete(null); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java index 92fe1c67..dd59a2a8 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java @@ -78,7 +78,8 @@ public void receiveQueueUnbindOk() { @Override public void receiveQueueDeclareOk(AMQShortString queue, long messageCount, long consumerCount) { - conn.getClientChannel().writeAndFlush(new QueueDeclareOkBody(queue, messageCount, consumerCount).generateFrame(channelId)); + conn.getClientChannel().writeAndFlush( + new QueueDeclareOkBody(queue, messageCount, consumerCount).generateFrame(channelId)); } @Override @@ -107,15 +108,18 @@ public void receiveBasicCancelOk(AMQShortString consumerTag) { } @Override - public void receiveBasicReturn(int replyCode, AMQShortString replyText, AMQShortString exchange, AMQShortString routingKey) { + public void receiveBasicReturn(int replyCode, AMQShortString replyText, AMQShortString exchange, + AMQShortString routingKey) { } @Override - public void receiveBasicDeliver(AMQShortString consumerTag, long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey) { + public void receiveBasicDeliver(AMQShortString consumerTag, long deliveryTag, boolean redelivered, + AMQShortString exchange, AMQShortString routingKey) { } @Override - public void receiveBasicGetOk(long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey, long messageCount) { + public void receiveBasicGetOk(long deliveryTag, boolean redelivered, AMQShortString exchange, + AMQShortString routingKey, long messageCount) { } @Override @@ -158,7 +162,8 @@ public void receiveChannelCloseOk() { conn.getChannelState().put(channelId, ProxyBrokerConnection.ChannelState.CLOSE); AtomicBoolean allClose = new AtomicBoolean(true); conn.getProxyConnection().getConnectionMap().values().forEach(conn -> { - if (conn.getChannelState().getOrDefault(channelId, null) == ProxyBrokerConnection.ChannelState.OPEN) { + if (conn.getChannelState().getOrDefault(channelId, null) + == ProxyBrokerConnection.ChannelState.OPEN) { allClose.set(false); } }); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java index af749300..f7c0e4f1 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java @@ -62,14 +62,16 @@ public CompletableFuture getConn(String topic) { } @Override - public void receiveAccessRequest(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, boolean read) { + public void receiveAccessRequest(AMQShortString realm, boolean exclusive, boolean passive, boolean active, + boolean write, boolean read) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive access request."); } } @Override - public void receiveExchangeDeclare(AMQShortString exchange, AMQShortString type, boolean passive, boolean durable, boolean autoDelete, boolean internal, boolean nowait, FieldTable arguments) { + public void receiveExchangeDeclare(AMQShortString exchange, AMQShortString type, boolean passive, boolean durable, + boolean autoDelete, boolean internal, boolean nowait, FieldTable arguments) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive exchange declare request, exchange: {}, type: {}, passive: {}," + "durable: {}, autoDelete: {}, internal: {}, nowait: {}, arguments: {}.", @@ -100,7 +102,8 @@ public void receiveExchangeBound(AMQShortString exchange, AMQShortString routing } @Override - public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean durable, boolean exclusive, boolean autoDelete, boolean nowait, FieldTable arguments) { + public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean durable, boolean exclusive, + boolean autoDelete, boolean nowait, FieldTable arguments) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive queue declare request, queue: {}, passive: {}," + "durable: {}, exclusive: {}, autoDelete: {}, nowait: {}, arguments: {}.", @@ -116,7 +119,8 @@ public void receiveQueueDeclare(AMQShortString queue, boolean passive, boolean d } @Override - public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, boolean nowait, FieldTable arguments) { + public void receiveQueueBind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, + boolean nowait, FieldTable arguments) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive queue bind request, queue: {}, exchange: {}, bindingKey: {}, " + "nowait: {}, arguments: {}.", queue, exchange, bindingKey, nowait, arguments); @@ -144,7 +148,8 @@ public void receiveQueueDelete(AMQShortString queue, boolean ifUnused, boolean i } @Override - public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, FieldTable arguments) { + public void receiveQueueUnbind(AMQShortString queue, AMQShortString exchange, AMQShortString bindingKey, + FieldTable arguments) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive queue unbind request, queue: {}, exchange: {}, bindingKey: {}, " + "arguments: {}.", queue, exchange, bindingKey, arguments); @@ -172,7 +177,8 @@ public void receiveBasicQos(long prefetchSize, int prefetchCount, boolean global } @Override - public void receiveBasicConsume(AMQShortString queue, AMQShortString consumerTag, boolean noLocal, boolean noAck, boolean exclusive, boolean nowait, FieldTable arguments) { + public void receiveBasicConsume(AMQShortString queue, AMQShortString consumerTag, boolean noLocal, boolean noAck, + boolean exclusive, boolean nowait, FieldTable arguments) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive basic consume request, queue: {}, consumerTag: {}, noLocal: {}, " + "noAck: {}, exclusive: {}, nowait: {}, arguments: {}.", @@ -195,7 +201,8 @@ public void receiveBasicCancel(AMQShortString consumerTag, boolean noWait) { } @Override - public void receiveBasicPublish(AMQShortString exchange, AMQShortString routingKey, boolean mandatory, boolean immediate) { + public void receiveBasicPublish(AMQShortString exchange, AMQShortString routingKey, boolean mandatory, + boolean immediate) { if (log.isDebugEnabled()) { log.debug("ProxyServerChannel receive basic publish request, exchange: {}, routingKey: {}, mandatory: {}, " + "immediate: {}.", exchange, routingKey, mandatory, immediate); @@ -367,8 +374,8 @@ private void deliverCurrentMessageIfComplete() { proxyConnection.sendConnectionClose(INTERNAL_ERROR, "Message encoding fail."); return; } - boolean createIfMissing = false; - String exchangeType = null; +// boolean createIfMissing = false; +// String exchangeType = null; // if (isDefaultExchange(AMQShortString.valueOf(exchangeName)) // || isBuildInExchange(exchangeName)) { // // Auto create default and buildIn exchanges if use. @@ -380,10 +387,9 @@ private void deliverCurrentMessageIfComplete() { exchangeName = AbstractAmqpExchange.DEFAULT_EXCHANGE_DURABLE; } - String msg = new String(message.getData()); - final String exName = exchangeName; + String topic = "persistent://public/" + proxyConnection.getVhost() + "/__amqp_exchange__" + exchangeName; proxyConnection.getProxyServer() - .getProducer("persistent://public/" + proxyConnection.getVhost() + "/__amqp_exchange__" + exchangeName) + .getProducer(topic) .thenCompose(producer -> { return producer.newMessage() .value(message.getData()) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index 5ef88abc..250df35c 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -51,8 +51,7 @@ import org.apache.qpid.server.protocol.v0_8.transport.ServerMethodProcessor; /** - * Proxy connection, it used to manage RabbitMQ client connection - * One connection will maintain multi connection with broker + * Proxy connection, it used to manage RabbitMQ client connection. */ @Slf4j public class ProxyClientConnection extends ChannelInboundHandlerAdapter @@ -127,7 +126,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } @Override - public void receiveConnectionStartOk(FieldTable clientProperties, AMQShortString mechanism, byte[] response, AMQShortString locale) { + public void receiveConnectionStartOk(FieldTable clientProperties, AMQShortString mechanism, byte[] response, + AMQShortString locale) { if (log.isDebugEnabled()) { log.debug("ProxyClientConnection receive connection start request, clientProperties: {}, mechanism: {}, " + "response: {}, locale: {}.", clientProperties, mechanism, response, locale); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/package-info.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/package-info.java new file mode 100644 index 00000000..370b4bb1 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/package-info.java @@ -0,0 +1,18 @@ +/** + * Licensed 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 info. + */ +package io.streamnative.pulsar.handlers.amqp.proxy.v2; \ No newline at end of file diff --git a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java index 7dd1b196..c74f847c 100644 --- a/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java +++ b/amqp-impl/src/test/java/io/streamnative/pulsar/handlers/amqp/test/TopicNameTest.java @@ -19,7 +19,6 @@ import io.netty.channel.EventLoopGroup; import io.streamnative.pulsar.handlers.amqp.AbstractAmqpExchange; -import io.streamnative.pulsar.handlers.amqp.AmqpServiceConfiguration; import io.streamnative.pulsar.handlers.amqp.impl.PersistentExchange; import io.streamnative.pulsar.handlers.amqp.impl.PersistentQueue; import java.util.concurrent.Executors; diff --git a/pom.xml b/pom.xml index 625c2c57..89a4b964 100644 --- a/pom.xml +++ b/pom.xml @@ -48,11 +48,11 @@ 1.4.9 3.0.rc1 - 3.1.1 + 3.1.2 3.10.1 3.0.0-M1 1.4.1.Final - 8.29 + 8.37 4.2.2 8.0.0 5.8.0 diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java index 6a5900e6..10bb5481 100644 --- a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/ProxyV2Test.java @@ -14,12 +14,11 @@ package io.streamnative.pulsar.handlers.amqp; import io.streamnative.pulsar.handlers.amqp.rabbitmq.RabbitMQTestCase; +import java.util.concurrent.CountDownLatch; import lombok.extern.slf4j.Slf4j; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import java.util.concurrent.CountDownLatch; - /** * AMQP proxy related test. */ From ef45f4eaed0795fcef17a1ffb925e1f4858b20ba Mon Sep 17 00:00:00 2001 From: rangao Date: Mon, 14 Nov 2022 20:32:39 +0800 Subject: [PATCH 5/9] fix code analysis --- .../handlers/amqp/AmqpConsumerOriginal.java | 2 -- .../pulsar/handlers/amqp/AmqpDecoder.java | 1 - .../handlers/amqp/ExchangeMessageRouter.java | 2 +- .../amqp/proxy/v2/AmqpProxyClientChannel.java | 26 ++++++++++++++++++- .../amqp/proxy/v2/AmqpProxyServerChannel.java | 7 ++++- .../amqp/proxy/v2/ProxyBrokerConnection.java | 11 +++++--- .../amqp/proxy/v2/ProxyClientConnection.java | 5 ++-- .../proxy/v2/ProxyV2ServiceException.java | 22 ++++++++++++++++ 8 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java index a02399d5..52d02b74 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java @@ -81,13 +81,11 @@ public Future sendMessages(final List entries, EntryBatch MESSAGE_PERMITS_UPDATER.addAndGet(this, -totalMessages); final AmqpConnection connection = channel.getConnection(); connection.ctx.channel().eventLoop().execute(() -> { - double count = 0; for (Entry entry : entries) { if (entry == null) { // Entry was filtered out continue; } - count++; sendMessage(entry); } connection.getCtx().writeAndFlush(Unpooled.EMPTY_BUFFER, writePromise); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 97eb4801..11bbe752 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -31,7 +31,6 @@ @Slf4j public abstract class AmqpDecoder { - private static final Logger LOGGER = LoggerFactory.getLogger(AMQDecoder.class); public static final int FRAME_HEADER_SIZE = 7; private final T methodProcessor; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java index 69335c31..d19a4e44 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -71,9 +71,9 @@ public abstract class ExchangeMessageRouter { private static final AtomicIntegerFieldUpdater PENDING_SIZE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "pendingQueueSize"); + private volatile int havePendingRead = FALSE; private static final AtomicIntegerFieldUpdater HAVE_PENDING_READ_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "havePendingRead"); - private volatile int havePendingRead = FALSE; private enum State { INIT, diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java index dd59a2a8..59519871 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyClientChannel.java @@ -32,7 +32,7 @@ public class AmqpProxyClientChannel implements ClientChannelMethodProcessor { private final Integer channelId; - private ProxyBrokerConnection conn; + private final ProxyBrokerConnection conn; public AmqpProxyClientChannel(Integer channelId, ProxyBrokerConnection conn) { this.channelId = channelId; @@ -47,10 +47,12 @@ public void receiveChannelOpenOk() { @Override public void receiveChannelAlert(int replyCode, AMQShortString replyText, FieldTable details) { + // nothing to do } @Override public void receiveAccessRequestOk(int ticket) { + // nothing to do } @Override @@ -60,10 +62,12 @@ public void receiveExchangeDeclareOk() { @Override public void receiveExchangeDeleteOk() { + // nothing to do } @Override public void receiveExchangeBoundOk(int replyCode, AMQShortString replyText) { + // nothing to do } @Override @@ -84,18 +88,22 @@ public void receiveQueueDeclareOk(AMQShortString queue, long messageCount, long @Override public void receiveQueuePurgeOk(long messageCount) { + // nothing to do } @Override public void receiveQueueDeleteOk(long messageCount) { + // nothing to do } @Override public void receiveBasicRecoverSyncOk() { + // nothing to do } @Override public void receiveBasicQosOk() { + // nothing to do } @Override @@ -105,53 +113,65 @@ public void receiveBasicConsumeOk(AMQShortString consumerTag) { @Override public void receiveBasicCancelOk(AMQShortString consumerTag) { + // nothing to do } @Override public void receiveBasicReturn(int replyCode, AMQShortString replyText, AMQShortString exchange, AMQShortString routingKey) { + // nothing to do } @Override public void receiveBasicDeliver(AMQShortString consumerTag, long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey) { + // nothing to do } @Override public void receiveBasicGetOk(long deliveryTag, boolean redelivered, AMQShortString exchange, AMQShortString routingKey, long messageCount) { + // nothing to do } @Override public void receiveBasicGetEmpty() { + // nothing to do } @Override public void receiveTxSelectOk() { + // nothing to do } @Override public void receiveTxCommitOk() { + // nothing to do } @Override public void receiveTxRollbackOk() { + // nothing to do } @Override public void receiveConfirmSelectOk() { + // nothing to do } @Override public void receiveChannelFlow(boolean active) { + // nothing to do } @Override public void receiveChannelFlowOk(boolean active) { + // nothing to do } @Override public void receiveChannelClose(int replyCode, AMQShortString replyText, int classId, int methodId) { + // nothing to do } @Override @@ -174,10 +194,12 @@ public void receiveChannelCloseOk() { @Override public void receiveMessageContent(QpidByteBuffer data) { + // nothing to do } @Override public void receiveMessageHeader(BasicContentHeaderProperties properties, long bodySize) { + // nothing to do } @Override @@ -187,10 +209,12 @@ public boolean ignoreAllButCloseOk() { @Override public void receiveBasicNack(long deliveryTag, boolean multiple, boolean requeue) { + // nothing to do } @Override public void receiveBasicAck(long deliveryTag, boolean multiple) { + // nothing to do } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java index f7c0e4f1..f4f5308a 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/AmqpProxyServerChannel.java @@ -242,14 +242,17 @@ public void receiveBasicReject(long deliveryTag, boolean requeue) { @Override public void receiveTxSelect() { + // nothing to do } @Override public void receiveTxCommit() { + // nothing to do } @Override public void receiveTxRollback() { + // nothing to do } @Override @@ -268,6 +271,7 @@ public void receiveChannelFlow(boolean active) { @Override public void receiveChannelFlowOk(boolean active) { + // nothing to do } @Override @@ -287,6 +291,7 @@ public void receiveChannelClose(int replyCode, AMQShortString replyText, int cla @Override public void receiveChannelCloseOk() { + // nothing to do } @Override @@ -365,7 +370,7 @@ private void publishContentHeader(ContentHeaderBody contentHeaderBody) { private void deliverCurrentMessageIfComplete() { if (currentMessage.allContentReceived()) { MessagePublishInfo info = currentMessage.getMessagePublishInfo(); - String routingKey = AMQShortString.toString(info.getRoutingKey()); +// String routingKey = AMQShortString.toString(info.getRoutingKey()); String exchangeName = AMQShortString.toString(info.getExchange()); Message message; try { diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java index 2b680f6a..a3e5e153 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java @@ -132,22 +132,27 @@ public synchronized void writeFrame(AMQDataBlock frame) { @Override public void receiveConnectionStart(short versionMajor, short versionMinor, FieldTable serverProperties, byte[] mechanisms, byte[] locales) { + // nothing to do } @Override public void receiveConnectionSecure(byte[] challenge) { + // nothing to do } @Override public void receiveConnectionRedirect(AMQShortString host, AMQShortString knownHosts) { + // nothing to do } @Override public void receiveConnectionTune(int channelMax, long frameMax, int heartbeat) { + // nothing to do } @Override public void receiveConnectionOpenOk(AMQShortString knownHosts) { + // nothing to do } @Override @@ -187,17 +192,17 @@ public void receiveConnectionCloseOk() { @Override public void receiveHeartbeat() { - log.info("receiveHeartbeat"); + // nothing to do } @Override public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { - log.info("receiveProtocolHeader"); + // nothing to do } @Override public void setCurrentMethod(int classId, int methodId) { - log.info("setCurrentMethod"); + // nothing to do } @Override diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index 250df35c..c77941b8 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -226,6 +226,7 @@ public void receiveConnectionClose(int replyCode, AMQShortString replyText, int @Override public void receiveConnectionCloseOk() { + // nothing to do } @Override @@ -257,7 +258,7 @@ public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { } catch (QpidException e) { log.error("Received unsupported protocol initiation for protocol version: {} ", getProtocolVersion(), e); writeFrame(new ProtocolInitiation(ProtocolVersion.v0_91)); - throw new RuntimeException(e); + throw new ProxyV2ServiceException(e); } } @@ -276,7 +277,7 @@ public boolean ignoreAllButCloseOk() { public synchronized void writeFrame(AMQDataBlock frame) { if (log.isDebugEnabled()) { - log.info("ProxyClientConnection write frame: {}", frame); + log.debug("ProxyClientConnection write frame: {}", frame); } this.ctx.writeAndFlush(frame); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java new file mode 100644 index 00000000..ff6507f8 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java @@ -0,0 +1,22 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.proxy.v2; + +public class ProxyV2ServiceException extends RuntimeException{ + + public ProxyV2ServiceException(Exception e) { + super(e); + } + +} From b113c35bc936882bc3897c1de2b5b9d088365c33 Mon Sep 17 00:00:00 2001 From: rangao Date: Mon, 14 Nov 2022 20:36:19 +0800 Subject: [PATCH 6/9] fix check style --- .../java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 11bbe752..9651f248 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -18,15 +18,12 @@ import org.apache.qpid.server.bytebuffer.QpidByteBuffer; import org.apache.qpid.server.protocol.ErrorCodes; import org.apache.qpid.server.protocol.ProtocolVersion; -import org.apache.qpid.server.protocol.v0_8.AMQDecoder; import org.apache.qpid.server.protocol.v0_8.AMQFrameDecodingException; import org.apache.qpid.server.protocol.v0_8.transport.AMQProtocolVersionException; import org.apache.qpid.server.protocol.v0_8.transport.ContentBody; import org.apache.qpid.server.protocol.v0_8.transport.ContentHeaderBody; import org.apache.qpid.server.protocol.v0_8.transport.MethodProcessor; import org.apache.qpid.server.protocol.v0_8.transport.ProtocolInitiation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Slf4j public abstract class AmqpDecoder { From 9abc6eb2a2bfc1208d2d71bd053540e5e1c00e67 Mon Sep 17 00:00:00 2001 From: rangao Date: Wed, 16 Nov 2022 10:15:37 +0800 Subject: [PATCH 7/9] fix check style --- .../handlers/amqp/AmqpBrokerService.java | 4 +-- .../handlers/amqp/AmqpConsumerOriginal.java | 3 +- .../pulsar/handlers/amqp/AmqpDecoder.java | 8 +++-- .../pulsar/handlers/amqp/AmqpExchange.java | 9 ++++-- .../handlers/amqp/AmqpOutputConverter.java | 30 +++++++++++++------ .../handlers/amqp/AmqpProtocolHandler.java | 4 +-- .../handlers/amqp/AmqpPulsarServerCnx.java | 2 +- .../handlers/amqp/ExchangeContainer.java | 10 +++---- .../handlers/amqp/ExchangeMessageRouter.java | 7 +---- .../NotSupportedOperationException.java | 25 ++++++++++++++++ .../amqp/proxy/v2/ProxyClientConnection.java | 6 ++-- .../{ProxyServer.java => ProxyServiceV2.java} | 6 ++-- ...tion.java => ProxyServiceV2Exception.java} | 4 +-- 13 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/common/exception/NotSupportedOperationException.java rename amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/{ProxyServer.java => ProxyServiceV2.java} (96%) rename amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/{ProxyV2ServiceException.java => ProxyServiceV2Exception.java} (85%) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java index 61575123..b0b9bb27 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java @@ -43,8 +43,8 @@ public class AmqpBrokerService { public AmqpBrokerService(PulsarService pulsarService, AmqpServiceConfiguration config) { this.pulsarService = pulsarService; this.amqpTopicManager = new AmqpTopicManager(pulsarService); - this.exchangeContainer = new ExchangeContainer(amqpTopicManager, pulsarService, initRouteExecutor(config), - config.getAmqpExchangeRouteQueueSize(), config); + this.exchangeContainer = new ExchangeContainer(amqpTopicManager, pulsarService, + initRouteExecutor(config), config); this.queueContainer = new QueueContainer(amqpTopicManager, pulsarService, exchangeContainer); this.exchangeService = new ExchangeServiceImpl(exchangeContainer); this.queueService = new QueueServiceImpl(exchangeContainer, queueContainer); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java index 52d02b74..62287d0f 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpConsumerOriginal.java @@ -32,9 +32,8 @@ import org.apache.pulsar.common.protocol.Commands; import org.apache.qpid.server.protocol.v0_8.AMQShortString; - /** - * Amqp consumer Used to forward messages. + * AMQP consumer original is used to forward original messages, not index messages. */ @Slf4j public class AmqpConsumerOriginal extends AmqpConsumer { diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 9651f248..36e9a48d 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -13,6 +13,8 @@ */ package io.streamnative.pulsar.handlers.amqp; +import static io.streamnative.pulsar.handlers.amqp.AmqpOutputConverter.PROXY_V2_DIRECT_TYPE; + import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import org.apache.qpid.server.bytebuffer.QpidByteBuffer; @@ -164,8 +166,8 @@ protected void processFrame(final int channel, final byte type, final long bodyS case 3 -> ContentBody.process(in, methodProcessor.getChannelMethodProcessor(channel), bodySize); case 8 -> // HeartbeatBody.process(channel, in, _methodProcessor, bodySize); - redirectData(in, bodySize, 8); - case 9 -> redirectData(in, bodySize, 9); + redirectData(in, bodySize, (byte) 8); + case PROXY_V2_DIRECT_TYPE -> redirectData(in, bodySize, PROXY_V2_DIRECT_TYPE); default -> throw new AMQFrameDecodingException("Unsupported frame type: " + type); } } @@ -187,7 +189,7 @@ protected AMQFrameDecodingException newUnknownMethodException(final int classId, + ".", null); } - protected void redirectData(QpidByteBuffer in, long messageSize, int type) { + protected void redirectData(QpidByteBuffer in, long messageSize, byte type) { if (log.isDebugEnabled()) { log.debug("redirect data type: {}, messageSize: {}", type, messageSize); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java index 4ebf8705..e3ee548f 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java @@ -13,6 +13,7 @@ */ package io.streamnative.pulsar.handlers.amqp; +import io.streamnative.pulsar.handlers.amqp.common.exception.NotSupportedOperationException; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.apache.bookkeeper.mledger.Entry; @@ -154,11 +155,13 @@ public static Type value(String type) { int getQueueSize(); default CompletableFuture queueBind(String queue, String routingKey, Map arguments) { - return null; + return CompletableFuture.failedFuture( + new NotSupportedOperationException("Amqp exchange queue bind operation is not supported.")); } default CompletableFuture queueUnBind(String queue, String routingKey, Map arguments) { - return null; - }; + return CompletableFuture.failedFuture( + new NotSupportedOperationException("Amqp exchange queue unbind operation is not supported.")); + } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java index 5cea08ce..d83bfcbd 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpOutputConverter.java @@ -13,6 +13,8 @@ */ package io.streamnative.pulsar.handlers.amqp; +import static org.apache.qpid.server.protocol.v0_8.transport.AMQFrame.FRAME_END_BYTE; + import java.io.IOException; import lombok.extern.log4j.Log4j2; import org.apache.qpid.server.QpidException; @@ -32,6 +34,7 @@ import org.apache.qpid.server.transport.ByteBufferSender; import org.apache.qpid.server.util.GZIPUtils; + /** * Used to process command output. */ @@ -42,6 +45,16 @@ public class AmqpOutputConverter { private final AmqpConnection connection; private static final AMQShortString GZIP_ENCODING = AMQShortString.valueOf(GZIPUtils.GZIP_CONTENT_ENCODING); + // data with this type will not be decoded by proxy v2. + public static final byte PROXY_V2_DIRECT_TYPE = 9; + private static final int PROXY_V2_DIRECT_EXTENDS_SIZE = 8; + private static final QpidByteBuffer FRAME_END_BYTE_BUFFER = QpidByteBuffer.allocateDirect(1); + + static { + FRAME_END_BYTE_BUFFER.put(FRAME_END_BYTE); + FRAME_END_BYTE_BUFFER.flip(); + } + public AmqpOutputConverter(AmqpConnection connection) { this.connection = connection; } @@ -359,16 +372,16 @@ public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody @Override public long getSize() { - return (amqpProxyV2Enable ? 8 : 0) + OVERHEAD + (long) methodBody.getSize() + (long) headerBody.getSize() - + (long) contentBody.getSize(); + return (amqpProxyV2Enable ? PROXY_V2_DIRECT_EXTENDS_SIZE : 0) + OVERHEAD + (long) methodBody.getSize() + + (long) headerBody.getSize() + (long) contentBody.getSize(); } @Override public long writePayload(final ByteBufferSender sender) { if (amqpProxyV2Enable) { // wrap the delivery message data with a special type 9 to skip data decode in proxy - QpidByteBuffer buffer = QpidByteBuffer.allocate(7); - buffer.put((byte) 9); + QpidByteBuffer buffer = QpidByteBuffer.allocate(PROXY_V2_DIRECT_EXTENDS_SIZE - 1); + buffer.put(PROXY_V2_DIRECT_TYPE); buffer.putUnsignedShort(0); buffer.putUnsignedInt(getSize() - 8); buffer.flip(); @@ -383,11 +396,10 @@ public long writePayload(final ByteBufferSender sender) { if (amqpProxyV2Enable) { // wrap the delivery message data to skip data decode in proxy - QpidByteBuffer endBuffer = QpidByteBuffer.allocate(1); - endBuffer.put((byte) 0xCE); - endBuffer.flip(); - sender.send(endBuffer); - size += 8; + try (QpidByteBuffer endFrame = FRAME_END_BYTE_BUFFER.duplicate()) { + sender.send(endFrame); + } + size += PROXY_V2_DIRECT_EXTENDS_SIZE; } return size; } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java index 3b273943..24b0bad2 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpProtocolHandler.java @@ -21,7 +21,7 @@ import io.netty.channel.socket.SocketChannel; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyService; -import io.streamnative.pulsar.handlers.amqp.proxy.v2.ProxyServer; +import io.streamnative.pulsar.handlers.amqp.proxy.v2.ProxyServiceV2; import io.streamnative.pulsar.handlers.amqp.utils.ConfigurationUtils; import java.net.InetSocketAddress; import java.util.Map; @@ -111,7 +111,7 @@ public void start(BrokerService service) { try { if (amqpConfig.isAmqpProxyV2Enable()) { - ProxyServer proxyServer = new ProxyServer(proxyConfig, service.getPulsar()); + ProxyServiceV2 proxyServer = new ProxyServiceV2(proxyConfig, service.getPulsar()); proxyServer.start(); } else { ProxyService proxyService = new ProxyService(proxyConfig, service.getPulsar()); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java index 475aa4d1..ac1e5714 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpPulsarServerCnx.java @@ -31,6 +31,6 @@ public AmqpPulsarServerCnx(PulsarService pulsar, ChannelHandlerContext ctx) { @Override public void closeConsumer(Consumer consumer) { - // avoid close the connection when close the consumer + // avoid close the connection when closing the consumer } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java index c324a9d8..807d2d9c 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeContainer.java @@ -48,16 +48,14 @@ public class ExchangeContainer { private AmqpTopicManager amqpTopicManager; private PulsarService pulsarService; private final Executor routeExecutor; - private final int routeQueueSize; - private final AmqpServiceConfiguration configuration; + private final AmqpServiceConfiguration config; protected ExchangeContainer(AmqpTopicManager amqpTopicManager, PulsarService pulsarService, - Executor routeExecutor, int routeQueueSize, AmqpServiceConfiguration configuration) { + Executor routeExecutor, AmqpServiceConfiguration config) { this.amqpTopicManager = amqpTopicManager; this.pulsarService = pulsarService; this.routeExecutor = routeExecutor; - this.routeQueueSize = routeQueueSize; - this.configuration = configuration; + this.config = config; } @Getter @@ -166,7 +164,7 @@ public CompletableFuture asyncGetExchange(NamespaceName namespaceN amqpExchange = new PersistentExchange(exchangeName, AmqpExchange.Type.value(currentType), persistentTopic, currentDurable, currentAutoDelete, currentInternal, currentArguments, routeExecutor, - routeQueueSize, configuration.isAmqpProxyV2Enable()); + config.getAmqpExchangeRouteQueueSize(), config.isAmqpProxyV2Enable()); } catch (Exception e) { log.error("Failed to init exchange {} in vhost {}.", exchangeName, namespaceName.getLocalName(), e); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java index d19a4e44..a7ced7c7 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -68,6 +68,7 @@ public abstract class ExchangeMessageRouter { private static final int defaultReadMaxSizeBytes = 4 * 1024 * 1024; private static final int replicatorQueueSize = 1000; private volatile int pendingQueueSize = 0; + private static final AtomicIntegerFieldUpdater PENDING_SIZE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "pendingQueueSize"); @@ -75,12 +76,6 @@ public abstract class ExchangeMessageRouter { private static final AtomicIntegerFieldUpdater HAVE_PENDING_READ_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ExchangeMessageRouter.class, "havePendingRead"); - private enum State { - INIT, - READY, - ERROR - } - @AllArgsConstructor @EqualsAndHashCode private static class Destination { diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/common/exception/NotSupportedOperationException.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/common/exception/NotSupportedOperationException.java new file mode 100644 index 00000000..1a5a15b7 --- /dev/null +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/common/exception/NotSupportedOperationException.java @@ -0,0 +1,25 @@ +/** + * Licensed 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 io.streamnative.pulsar.handlers.amqp.common.exception; + +/** + * Not supported operation exception. + */ +public class NotSupportedOperationException extends RuntimeException{ + + public NotSupportedOperationException(String message) { + super(message); + } + +} diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index c77941b8..59338749 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -73,7 +73,7 @@ public class ProxyClientConnection extends ChannelInboundHandlerAdapter @Getter private String vhost; @Getter - private final ProxyServer proxyServer; + private final ProxyServiceV2 proxyServer; @Getter private int currentClassId; @Getter @@ -87,7 +87,7 @@ enum State { public ProxyClientConnection(ProxyConfiguration config, PulsarServiceLookupHandler lookupHandler, - ProxyServer proxyServer) { + ProxyServiceV2 proxyServer) { this.config = config; this.decoder = new AmqpBrokerDecoder(this); this.state = State.INIT; @@ -258,7 +258,7 @@ public void receiveProtocolHeader(ProtocolInitiation protocolInitiation) { } catch (QpidException e) { log.error("Received unsupported protocol initiation for protocol version: {} ", getProtocolVersion(), e); writeFrame(new ProtocolInitiation(ProtocolVersion.v0_91)); - throw new ProxyV2ServiceException(e); + throw new ProxyServiceV2Exception(e); } } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java similarity index 96% rename from amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java rename to amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java index b533e2b6..afcc498f 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java @@ -37,14 +37,14 @@ * Proxy server, the proxy server should be an individual service, it could be scale up. */ @Slf4j -public class ProxyServer { +public class ProxyServiceV2 { private final ProxyConfiguration config; private final PulsarService pulsar; private final Map>> producerMap; private PulsarServiceLookupHandler lookupHandler; - public ProxyServer(ProxyConfiguration config, PulsarService pulsarService) { + public ProxyServiceV2(ProxyConfiguration config, PulsarService pulsarService) { this.config = config; this.pulsar = pulsarService; this.producerMap = new ConcurrentHashMap<>(); @@ -65,7 +65,7 @@ protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("consolidation", new FlushConsolidationHandler( config.getAmqpExplicitFlushAfterFlushes(), true)); ch.pipeline().addLast("handler", - new ProxyClientConnection(config, lookupHandler, ProxyServer.this)); + new ProxyClientConnection(config, lookupHandler, ProxyServiceV2.this)); ch.pipeline().addLast("directHandler", new AmqpProxyDirectHandler()); } }); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2Exception.java similarity index 85% rename from amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java rename to amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2Exception.java index ff6507f8..b5e73244 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyV2ServiceException.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2Exception.java @@ -13,9 +13,9 @@ */ package io.streamnative.pulsar.handlers.amqp.proxy.v2; -public class ProxyV2ServiceException extends RuntimeException{ +public class ProxyServiceV2Exception extends RuntimeException{ - public ProxyV2ServiceException(Exception e) { + public ProxyServiceV2Exception(Exception e) { super(e); } From 526f6fdc6e8061b277b9f3b69ef0286128290aef Mon Sep 17 00:00:00 2001 From: rangao Date: Wed, 16 Nov 2022 15:25:18 +0800 Subject: [PATCH 8/9] fix --- .../handlers/amqp/AmqpBrokerService.java | 2 +- .../pulsar/handlers/amqp/AmqpDecoder.java | 1 - .../handlers/amqp/ExchangeMessageRouter.java | 45 ++++++++----------- .../pulsar/handlers/amqp/QueueContainer.java | 24 +++++----- .../amqp/proxy/v2/ProxyBrokerConnection.java | 6 ++- .../amqp/proxy/v2/ProxyClientConnection.java | 16 +++++-- pom.xml | 20 +++++++-- 7 files changed, 67 insertions(+), 47 deletions(-) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java index b0b9bb27..c87e44a1 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpBrokerService.java @@ -45,7 +45,7 @@ public AmqpBrokerService(PulsarService pulsarService, AmqpServiceConfiguration c this.amqpTopicManager = new AmqpTopicManager(pulsarService); this.exchangeContainer = new ExchangeContainer(amqpTopicManager, pulsarService, initRouteExecutor(config), config); - this.queueContainer = new QueueContainer(amqpTopicManager, pulsarService, exchangeContainer); + this.queueContainer = new QueueContainer(amqpTopicManager, pulsarService, exchangeContainer, config); this.exchangeService = new ExchangeServiceImpl(exchangeContainer); this.queueService = new QueueServiceImpl(exchangeContainer, queueContainer); this.connectionContainer = new ConnectionContainer(pulsarService, exchangeContainer, queueContainer); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java index 36e9a48d..73a5f065 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpDecoder.java @@ -159,7 +159,6 @@ protected void processInput(final QpidByteBuffer in) protected void processFrame(final int channel, final byte type, final long bodySize, final QpidByteBuffer in) throws AMQFrameDecodingException { - System.out.println("xxxx process frame " + type); switch (type) { case 1 -> processMethod(channel, in); case 2 -> ContentHeaderBody.process(in, methodProcessor.getChannelMethodProcessor(channel), bodySize); diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java index a7ced7c7..3be76029 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -18,7 +18,6 @@ import static org.apache.pulsar.broker.service.persistent.PersistentTopic.MESSAGE_RATE_BACKOFF_MS; import com.google.common.collect.Sets; -import io.netty.buffer.ByteBuf; import io.streamnative.pulsar.handlers.amqp.impl.HeadersMessageRouter; import io.streamnative.pulsar.handlers.amqp.impl.PersistentExchange; import io.streamnative.pulsar.handlers.amqp.impl.PersistentQueue; @@ -41,6 +40,7 @@ import org.apache.bookkeeper.mledger.Entry; import org.apache.bookkeeper.mledger.ManagedCursor; import org.apache.bookkeeper.mledger.ManagedLedgerException; +import org.apache.bookkeeper.mledger.Position; import org.apache.bookkeeper.mledger.impl.ManagedCursorImpl; import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl; import org.apache.pulsar.broker.PulsarServerException; @@ -179,18 +179,25 @@ private void processMessages(List entries) { String routingKey = props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(); Set destinations = getDestinations(routingKey, getMessageHeaders()); List> futures = new ArrayList<>(); - for (Destination des : destinations) { - futures.add(sendMessage(des, message, props)); + byte[] data; + if (!destinations.isEmpty()) { + data = new byte[message.getDataBuffer().readableBytes()]; + message.getDataBuffer().readBytes(data); + for (Destination des : destinations) { + futures.add(sendMessage(des, data, props)); + } } + final Position position = entry.getPosition(); + entry.release(); FutureUtil.waitForAll(futures).whenComplete((__, t) -> { if (t != null) { - log.error("Failed to route message {}", entry.getPosition(), t); + log.error("Failed to route message {}", position, t); cursor.rewind(); sendComplete(); return; } sendComplete(); - cursor.asyncDelete(entry.getPosition(), new AsyncCallbacks.DeleteCallback() { + cursor.asyncDelete(position, new AsyncCallbacks.DeleteCallback() { @Override public void deleteComplete(Object ctx) { if (log.isDebugEnabled()) { @@ -215,29 +222,13 @@ private void sendComplete() { } private CompletableFuture sendMessage(Destination des, - MessageImpl message, + byte[] data, Map props) { - CompletableFuture future = new CompletableFuture<>(); - ByteBuf payload = message.getDataBuffer().slice(); - byte[] data = new byte[payload.readableBytes()]; - payload.readBytes(data); - String msg = new String(data); - getProducer(des.name, des.type).thenCompose(producer -> { - log.info("try to send message `" + msg + "` to des `" + des + "`"); - return producer.newMessage() - .value(data) - .properties(props) - .sendAsync(); - }) - .thenAccept(__ -> { - log.info("success to send message `" + msg + "` to des `" + des + "`"); - future.complete(null); - }) - .exceptionally(t -> { - future.completeExceptionally(t); - return null; - }); - return future; + return getProducer(des.name, des.type).thenCompose(producer -> producer.newMessage() + .value(data) + .properties(props) + .sendAsync()) + .thenApply(__ -> null); } private CompletableFuture> getProducer(String des, String desType) { diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/QueueContainer.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/QueueContainer.java index bbe47bec..3521a2e7 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/QueueContainer.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/QueueContainer.java @@ -36,12 +36,14 @@ public class QueueContainer { private AmqpTopicManager amqpTopicManager; private PulsarService pulsarService; private ExchangeContainer exchangeContainer; + private AmqpServiceConfiguration config; protected QueueContainer(AmqpTopicManager amqpTopicManager, PulsarService pulsarService, - ExchangeContainer exchangeContainer) { + ExchangeContainer exchangeContainer, AmqpServiceConfiguration config) { this.amqpTopicManager = amqpTopicManager; this.pulsarService = pulsarService; this.exchangeContainer = exchangeContainer; + this.config = config; } @Getter @@ -97,15 +99,17 @@ public CompletableFuture asyncGetQueue(NamespaceName namespaceName, S // TODO: reset connectionId, exclusive and autoDelete PersistentQueue amqpQueue = new PersistentQueue(queueName, persistentTopic, 0, false, false); - try { - amqpQueue.recoverRoutersFromQueueProperties(properties, exchangeContainer, - namespaceName); - } catch (Exception e) { - log.error("[{}][{}] Failed to recover routers for queue from properties.", - namespaceName, queueName, e); - queueCompletableFuture.completeExceptionally(e); - removeQueueFuture(namespaceName, queueName); - return; + if (!config.isAmqpProxyV2Enable()) { + try { + amqpQueue.recoverRoutersFromQueueProperties(properties, exchangeContainer, + namespaceName); + } catch (Exception e) { + log.error("[{}][{}] Failed to recover routers for queue from properties.", + namespaceName, queueName, e); + queueCompletableFuture.completeExceptionally(e); + removeQueueFuture(namespaceName, queueName); + return; + } } queueCompletableFuture.complete(amqpQueue); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java index a3e5e153..26fb8543 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyBrokerConnection.java @@ -119,7 +119,11 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - clientDecoder.decodeBuffer(((ByteBuf) msg).nioBuffer()); + try { + clientDecoder.decodeBuffer(((ByteBuf) msg).nioBuffer()); + } finally { + ((ByteBuf) msg).release(); + } } public synchronized void writeFrame(AMQDataBlock frame) { diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java index 59338749..0de8a26a 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyClientConnection.java @@ -18,6 +18,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; import io.streamnative.pulsar.handlers.amqp.AmqpBrokerDecoder; import io.streamnative.pulsar.handlers.amqp.AmqpProtocolHandler; import io.streamnative.pulsar.handlers.amqp.proxy.ProxyConfiguration; @@ -109,6 +110,7 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; if (state.equals(State.INIT)) { + byteBuf.retain(); connectionCommands.add(byteBuf); } try { @@ -116,6 +118,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } catch (Exception e) { this.state = State.FAILED; log.error("ProxyClientConnection failed to decode requests.", e); + } finally { + byteBuf.release(); } } @@ -213,10 +217,6 @@ public void receiveConnectionClose(int replyCode, AMQShortString replyText, int log.debug("ProxyClientConnection receive connection close request, replyCode: {}, replyText: {}, " + "classId: {}, methodId: {}.", replyCode, replyText, classId, methodId); } - if (log.isDebugEnabled()) { - log.debug("ProxyClientConnection receive connection close request, replyCode: {}, replyText: {}, " - + "classId: {}, methodId: {}", replyCode, replyText, classId, methodId); - } for (ProxyBrokerConnection conn : getConnectionMap().values()) { conn.getChannel().writeAndFlush( new ConnectionCloseBody( @@ -282,6 +282,14 @@ public synchronized void writeFrame(AMQDataBlock frame) { this.ctx.writeAndFlush(frame); } + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + for (ByteBuf command : connectionCommands) { + ReferenceCountUtil.safeRelease(command); + } + } + public void sendChannelClose(int channelId, int replyCode, final String replyText) { this.ctx.writeAndFlush(new ChannelCloseBody(replyCode, AMQShortString.createAMQShortString(replyText), currentClassId, currentMethodId).generateFrame(channelId)); diff --git a/pom.xml b/pom.xml index 89a4b964..d4fe011a 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ 1.4.1.Final 8.37 4.2.2 + 4.2.2 8.0.0 5.8.0 3.15.0 @@ -300,11 +301,24 @@ com.github.spotbugs spotbugs-maven-plugin ${spotbugs-maven-plugin.version} - - resources/findbugsExclude.xml - + + + com.github.spotbugs + spotbugs + ${spotbugs.version} + + + + + + + + + + + maven-compiler-plugin ${maven-compiler-plugin.version} From 6d009c06f7a0c3cb7110c828b4d52a251057aba1 Mon Sep 17 00:00:00 2001 From: rangao Date: Fri, 18 Nov 2022 16:23:37 +0800 Subject: [PATCH 9/9] fix --- .../pulsar/handlers/amqp/AmqpExchange.java | 5 +-- .../handlers/amqp/ExchangeMessageRouter.java | 32 +++++++++++-------- .../amqp/impl/PersistentExchange.java | 4 +-- .../amqp/proxy/v2/ProxyServiceV2.java | 3 +- .../amqp/rabbitmq/RabbitMQTestCase.java | 2 +- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java index e3ee548f..39242817 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/AmqpExchange.java @@ -20,6 +20,7 @@ import org.apache.bookkeeper.mledger.Position; import org.apache.pulsar.broker.service.Topic; import org.apache.pulsar.client.api.Message; +import org.apache.pulsar.common.util.FutureUtil; /** * Interface of the AMQP exchange. @@ -155,12 +156,12 @@ public static Type value(String type) { int getQueueSize(); default CompletableFuture queueBind(String queue, String routingKey, Map arguments) { - return CompletableFuture.failedFuture( + return FutureUtil.failedFuture( new NotSupportedOperationException("Amqp exchange queue bind operation is not supported.")); } default CompletableFuture queueUnBind(String queue, String routingKey, Map arguments) { - return CompletableFuture.failedFuture( + return FutureUtil.failedFuture( new NotSupportedOperationException("Amqp exchange queue unbind operation is not supported.")); } diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java index 3be76029..144674d9 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/ExchangeMessageRouter.java @@ -48,6 +48,7 @@ import org.apache.pulsar.client.api.Producer; import org.apache.pulsar.client.api.PulsarClient; import org.apache.pulsar.client.impl.MessageImpl; +import org.apache.pulsar.client.impl.ProducerImpl; import org.apache.pulsar.common.api.proto.CommandSubscribe; import org.apache.pulsar.common.api.proto.KeyValue; import org.apache.pulsar.common.naming.NamespaceName; @@ -171,23 +172,24 @@ private void processMessages(List entries) { props = message.getMessageBuilder().getPropertiesList().stream() .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); } catch (IOException e) { - log.error("Deserialize entry dataBuffer failed. exchangeName: {}, skip it first.", + log.error("Deserialize entry dataBuffer failed for exchange {}, skip it first.", exchange.getName(), e); PENDING_SIZE_UPDATER.decrementAndGet(this); + entry.release(); continue; } String routingKey = props.getOrDefault(MessageConvertUtils.PROP_ROUTING_KEY, "").toString(); Set destinations = getDestinations(routingKey, getMessageHeaders()); + + final Position position = entry.getPosition(); + List> futures = new ArrayList<>(); - byte[] data; if (!destinations.isEmpty()) { - data = new byte[message.getDataBuffer().readableBytes()]; - message.getDataBuffer().readBytes(data); + final int readerIndex = message.getDataBuffer().readerIndex(); for (Destination des : destinations) { - futures.add(sendMessage(des, data, props)); + futures.add(sendMessage(message, des, readerIndex)); } } - final Position position = entry.getPosition(); entry.release(); FutureUtil.waitForAll(futures).whenComplete((__, t) -> { if (t != null) { @@ -221,13 +223,15 @@ private void sendComplete() { } } - private CompletableFuture sendMessage(Destination des, - byte[] data, - Map props) { - return getProducer(des.name, des.type).thenCompose(producer -> producer.newMessage() - .value(data) - .properties(props) - .sendAsync()) + private CompletableFuture sendMessage(MessageImpl msg, Destination des, int readerIndex) { + return getProducer(des.name, des.type) + .thenCompose(producer -> { + msg.getDataBuffer().retain(); + msg.getMessageBuilder().clearProducerName(); + msg.getMessageBuilder().clearPublishTime(); + msg.getDataBuffer().readerIndex(readerIndex); + return ((ProducerImpl) producer).sendAsync(msg); + }) .thenApply(__ -> null); } @@ -237,7 +241,7 @@ private CompletableFuture> getProducer(String des, String desTy pulsarClient = exchange.getTopic().getBrokerService().pulsar().getClient(); } catch (PulsarServerException e) { log.error("Failed to get pulsar client", e); - return CompletableFuture.failedFuture(e); + return FutureUtil.failedFuture(e); } NamespaceName namespaceName = TopicName.get(exchange.getTopic().getName()).getNamespaceObject(); String prefix = desType.equals("queue") ? PersistentQueue.TOPIC_PREFIX : PersistentExchange.TOPIC_PREFIX; diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java index 7834fd83..9c3e44e3 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/impl/PersistentExchange.java @@ -400,7 +400,7 @@ public CompletableFuture queueBind(String queue, String routingKey, Map future = new CompletableFuture<>(); this.persistentTopic.getManagedLedger().asyncSetProperty(BINDINGS, bindingsJson, @@ -428,7 +428,7 @@ public CompletableFuture queueUnBind(String queue, String routingKey, Map< bindingsJson = JSON_MAPPER.writeValueAsString(this.bindings); } catch (JsonProcessingException e) { log.error("Failed to unbind queue {} to exchange {}", queue, exchangeName, e); - return CompletableFuture.failedFuture(e); + return FutureUtil.failedFuture(e); } CompletableFuture future = new CompletableFuture<>(); this.persistentTopic.getManagedLedger().asyncSetProperty("BINDINGS", bindingsJson, diff --git a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java index afcc498f..148e665c 100644 --- a/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java +++ b/amqp-impl/src/main/java/io/streamnative/pulsar/handlers/amqp/proxy/v2/ProxyServiceV2.java @@ -32,6 +32,7 @@ import org.apache.pulsar.broker.PulsarService; import org.apache.pulsar.client.api.Producer; import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.common.util.FutureUtil; /** * Proxy server, the proxy server should be an individual service, it could be scale up. @@ -77,7 +78,7 @@ public CompletableFuture> getProducer(String topic) { try { client = pulsar.getClient(); } catch (PulsarServerException e) { - return CompletableFuture.failedFuture(e); + return FutureUtil.failedFuture(e); } return producerMap.computeIfAbsent(topic, k -> { CompletableFuture> producerFuture = new CompletableFuture<>(); diff --git a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/rabbitmq/RabbitMQTestCase.java b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/rabbitmq/RabbitMQTestCase.java index db768082..16b81e53 100644 --- a/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/rabbitmq/RabbitMQTestCase.java +++ b/tests/src/test/java/io/streamnative/pulsar/handlers/amqp/rabbitmq/RabbitMQTestCase.java @@ -154,7 +154,7 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp countDownLatch.await(); System.out.println("[" + testName + "] Test finish. Receive total msg cnt: " + totalReceiveMsgCnt); - Assert.assertEquals(expectedMsgCntPerQueue * queueList.size(), totalReceiveMsgCnt.get()); + Assert.assertEquals(totalReceiveMsgCnt.get(), expectedMsgCntPerQueue * queueList.size()); } public void defaultEmptyExchangeTest(int aopPort, String vhost) throws Exception {