/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.usecases; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Enumeration; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQSession; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.network.DiscoveryNetworkConnector; import org.apache.activemq.network.NetworkConnector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; import static org.junit.Assert.assertTrue; public class RequestReplyToTopicViaThreeNetworkHopsTest { protected static final int CONCURRENT_CLIENT_COUNT = 5; protected static final int CONCURRENT_SERVER_COUNT = 5; protected static final int TOTAL_CLIENT_ITER = 10; protected static int Next_broker_num = 0; protected EmbeddedTcpBroker edge1; protected EmbeddedTcpBroker edge2; protected EmbeddedTcpBroker core1; protected EmbeddedTcpBroker core2; protected boolean testError = false; protected boolean fatalTestError = false; protected int echoResponseFill = 0; // Number of "filler" response messages per request protected static Log LOG; public boolean duplex = true; static { LOG = LogFactory.getLog(RequestReplyToTopicViaThreeNetworkHopsTest.class); } public RequestReplyToTopicViaThreeNetworkHopsTest() throws Exception { edge1 = new EmbeddedTcpBroker("edge", 1); edge2 = new EmbeddedTcpBroker("edge", 2); core1 = new EmbeddedTcpBroker("core", 1); core2 = new EmbeddedTcpBroker("core", 2); // duplex is necessary to serialise sends with consumer/destination creation edge1.coreConnectTo(core1, duplex); edge2.coreConnectTo(core2, duplex); core1.coreConnectTo(core2, duplex); } public void logMessage(String msg) { System.out.println(msg); System.out.flush(); } public void testMessages(Session sess, MessageProducer req_prod, Destination resp_dest, int num_msg) throws Exception { MessageConsumer resp_cons; TextMessage msg; MessageClient cons_client; int cur; int tot_expected; resp_cons = sess.createConsumer(resp_dest); cons_client = new MessageClient(resp_cons, num_msg); cons_client.start(); cur = 0; while ((cur < num_msg) && (!fatalTestError)) { msg = sess.createTextMessage("MSG AAAA " + cur); msg.setIntProperty("SEQ", 100 + cur); msg.setStringProperty("TEST", "TOPO"); msg.setJMSReplyTo(resp_dest); if (cur == (num_msg - 1)) msg.setBooleanProperty("end-of-response", true); sendWithRetryOnDeletedDest(req_prod, msg); LOG.debug("Sent:" + msg); cur++; } // // Give the consumer some time to receive the response. // cons_client.waitShutdown(5000); // // Now shutdown the consumer if it's still running. // if (cons_client.shutdown()) LOG.debug("Consumer client shutdown complete"); else LOG.debug("Consumer client shutdown incomplete!!!"); // // Check that the correct number of messages was received. // tot_expected = num_msg * (echoResponseFill + 1); if (cons_client.getNumMsgReceived() == tot_expected) { LOG.debug("Have " + tot_expected + " messages, as-expected"); } else { testError = true; if (cons_client.getNumMsgReceived() == 0) fatalTestError = true; LOG.error("Have " + cons_client.getNumMsgReceived() + " messages; expected " + tot_expected + " on destination " + resp_dest); } resp_cons.close(); } protected void sendWithRetryOnDeletedDest(MessageProducer prod, Message msg) throws JMSException { try { if (LOG.isDebugEnabled()) LOG.debug("SENDING REQUEST message " + msg); prod.send(msg); } catch (JMSException jms_exc) { System.out.println("AAA: " + jms_exc.getMessage()); throw jms_exc; } } /** * Test one destination between the given "producer broker" and "consumer broker" specified. */ public void testOneDest(Connection conn, Session sess, Destination cons_dest, int num_msg) throws Exception { Destination prod_dest; MessageProducer msg_prod; // // Create the Producer to the echo request Queue // LOG.trace("Creating echo queue and producer"); prod_dest = sess.createQueue("echo"); msg_prod = sess.createProducer(prod_dest); // // Pass messages around. // testMessages(sess, msg_prod, cons_dest, num_msg); msg_prod.close(); } /** * TEST TEMPORARY TOPICS */ public void testTempTopic(String prod_broker_url, String cons_broker_url) throws Exception { Connection conn; Session sess; Destination cons_dest; int num_msg; num_msg = 5; LOG.debug("TESTING TEMP TOPICS " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)"); // // Connect to the bus. // conn = createConnection(cons_broker_url); conn.start(); sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); // // Create the destination on which messages are being tested. // LOG.trace("Creating destination"); cons_dest = sess.createTemporaryTopic(); testOneDest(conn, sess, cons_dest, num_msg); // // Cleanup // sess.close(); conn.close(); } /** * TEST TOPICS */ public void testTopic(String prod_broker_url, String cons_broker_url) throws Exception { int num_msg; Connection conn; Session sess; String topic_name; Destination cons_dest; num_msg = 5; LOG.info("TESTING TOPICS " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)"); conn = createConnection(cons_broker_url); conn.start(); sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); // // Create the destination on which messages are being tested. // topic_name = "topotest2.perm.topic"; LOG.trace("Removing existing Topic"); removeTopic(conn, topic_name); LOG.trace("Creating Topic, " + topic_name); cons_dest = sess.createTopic(topic_name); testOneDest(conn, sess, cons_dest, num_msg); // // Cleanup // removeTopic(conn, topic_name); sess.close(); conn.close(); } /** * TEST TEMPORARY QUEUES */ public void testTempQueue(String prod_broker_url, String cons_broker_url) throws Exception { int num_msg; Connection conn; Session sess; Destination cons_dest; num_msg = 5; LOG.info("TESTING TEMP QUEUES " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)"); // // Connect to the bus. // conn = createConnection(cons_broker_url); conn.start(); sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); // // Create the destination on which messages are being tested. // LOG.trace("Creating destination"); cons_dest = sess.createTemporaryQueue(); testOneDest(conn, sess, cons_dest, num_msg); // // Cleanup // sess.close(); conn.close(); } /** * TEST QUEUES */ public void testQueue(String prod_broker_url, String cons_broker_url) throws Exception { int num_msg; Connection conn; Session sess; String queue_name; Destination cons_dest; num_msg = 5; LOG.info("TESTING QUEUES " + prod_broker_url + " -> " + cons_broker_url + " (" + num_msg + " messages)"); conn = createConnection(cons_broker_url); conn.start(); sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); // // Create the destination on which messages are being tested. // queue_name = "topotest2.perm.queue"; LOG.trace("Removing existing Queue"); removeQueue(conn, queue_name); LOG.trace("Creating Queue, " + queue_name); cons_dest = sess.createQueue(queue_name); testOneDest(conn, sess, cons_dest, num_msg); removeQueue(conn, queue_name); sess.close(); conn.close(); } @Test public void runWithTempTopicReplyTo() throws Exception { EchoService echo_svc; TopicTrafficGenerator traffic_gen; Thread start1; Thread start2; Thread start3; Thread start4; ThreadPoolExecutor clientExecPool; final CountDownLatch clientCompletionLatch; int iter; fatalTestError = false; testError = false; // // Execute up to 20 clients at a time to simulate that load. // clientExecPool = new ThreadPoolExecutor(CONCURRENT_CLIENT_COUNT, CONCURRENT_CLIENT_COUNT, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10000)); clientCompletionLatch = new CountDownLatch(TOTAL_CLIENT_ITER); // Use threads to avoid startup deadlock since the first broker started waits until // it knows the name of the remote broker before finishing its startup, which means // the remote must already be running. start1 = new Thread() { @Override public void run() { try { edge1.start(); } catch (Exception ex) { LOG.error(null, ex); } } }; start2 = new Thread() { @Override public void run() { try { edge2.start(); } catch (Exception ex) { LOG.error(null, ex); } } }; start3 = new Thread() { @Override public void run() { try { core1.start(); } catch (Exception ex) { LOG.error(null, ex); } } }; start4 = new Thread() { @Override public void run() { try { core2.start(); } catch (Exception ex) { LOG.error(null, ex); } } }; start1.start(); start2.start(); start3.start(); start4.start(); start1.join(); start2.join(); start3.join(); start4.join(); traffic_gen = new TopicTrafficGenerator(edge1.getConnectionUrl(), edge2.getConnectionUrl()); traffic_gen.start(); // // Now start the echo service with that queue. // echo_svc = new EchoService("echo", edge1.getConnectionUrl()); echo_svc.start(); // // Run the tests on Temp Topics. // LOG.info("** STARTING TEMP TOPIC TESTS"); iter = 0; while ((iter < TOTAL_CLIENT_ITER) && (!fatalTestError)) { clientExecPool.execute(new Runnable() { @Override public void run() { try { RequestReplyToTopicViaThreeNetworkHopsTest.this.testTempTopic(edge1.getConnectionUrl(), edge2.getConnectionUrl()); } catch (Exception exc) { LOG.error("test exception", exc); fatalTestError = true; testError = true; } clientCompletionLatch.countDown(); } }); iter++; } boolean allDoneOnTime = clientCompletionLatch.await(20, TimeUnit.MINUTES); LOG.info("** FINISHED TEMP TOPIC TESTS AFTER " + iter + " ITERATIONS, testError:" + testError + ", fatal: " + fatalTestError + ", onTime:" + allDoneOnTime); Thread.sleep(100); echo_svc.shutdown(); traffic_gen.shutdown(); shutdown(); assertTrue("test completed in time", allDoneOnTime); assertTrue("no errors", !testError); } public void shutdown() throws Exception { edge1.stop(); edge2.stop(); core1.stop(); core2.stop(); } protected Connection createConnection(String url) throws Exception { return org.apache.activemq.ActiveMQConnection.makeConnection(url); } protected static void removeQueue(Connection conn, String dest_name) throws java.lang.Exception { org.apache.activemq.command.ActiveMQDestination dest; if (conn instanceof org.apache.activemq.ActiveMQConnection) { dest = org.apache.activemq.command.ActiveMQDestination.createDestination(dest_name, org.apache.activemq.command.ActiveMQDestination.QUEUE_TYPE); ((org.apache.activemq.ActiveMQConnection) conn).destroyDestination(dest); } } protected static void removeTopic(Connection conn, String dest_name) throws java.lang.Exception { org.apache.activemq.command.ActiveMQDestination dest; if (conn instanceof org.apache.activemq.ActiveMQConnection) { dest = org.apache.activemq.command.ActiveMQDestination.createDestination(dest_name, org.apache.activemq.command.ActiveMQDestination.TOPIC_TYPE); ((org.apache.activemq.ActiveMQConnection) conn).destroyDestination(dest); } } public static String fmtMsgInfo(Message msg) throws Exception { StringBuilder msg_desc; String prop; Enumeration<?> prop_enum; msg_desc = new StringBuilder(); msg_desc = new StringBuilder(); if (msg instanceof TextMessage) { msg_desc.append(((TextMessage) msg).getText()); } else { msg_desc.append("["); msg_desc.append(msg.getClass().getName()); msg_desc.append("]"); } prop_enum = msg.getPropertyNames(); while (prop_enum.hasMoreElements()) { prop = (String) prop_enum.nextElement(); msg_desc.append("; "); msg_desc.append(prop); msg_desc.append("="); msg_desc.append(msg.getStringProperty(prop)); } return msg_desc.toString(); } protected class EmbeddedTcpBroker { protected BrokerService brokerSvc; protected int brokerNum; protected String brokerName; protected String brokerId; protected int port; protected String tcpUrl; protected String fullUrl; public EmbeddedTcpBroker(String name, int number) throws Exception { brokerSvc = new BrokerService(); synchronized (this.getClass()) { brokerNum = Next_broker_num; Next_broker_num++; } brokerName = name + number; brokerId = brokerName; brokerSvc.setBrokerName(brokerName); brokerSvc.setBrokerId(brokerId); brokerSvc.setPersistent(false); brokerSvc.setUseJmx(false); port = 60000 + (brokerNum * 10); tcpUrl = "tcp://127.0.0.1:" + Integer.toString(port); fullUrl = tcpUrl + "?jms.watchTopicAdvisories=false"; brokerSvc.addConnector(tcpUrl); } public Connection createConnection() throws URISyntaxException, JMSException { Connection result; result = org.apache.activemq.ActiveMQConnection.makeConnection(this.fullUrl); return result; } public String getConnectionUrl() { return this.fullUrl; } public void coreConnectTo(EmbeddedTcpBroker other, boolean duplex_f) throws Exception { this.makeConnectionTo(other, duplex_f, true); this.makeConnectionTo(other, duplex_f, false); if (!duplex_f) { other.makeConnectionTo(this, duplex_f, true); other.makeConnectionTo(this, duplex_f, false); } } public void start() throws Exception { brokerSvc.start(); brokerSvc.waitUntilStarted(); } public void stop() throws Exception { brokerSvc.stop(); } protected void makeConnectionTo(EmbeddedTcpBroker other, boolean duplex_f, boolean queue_f) throws Exception { NetworkConnector nw_conn; String prefix; ActiveMQDestination excl_dest; ArrayList<ActiveMQDestination> excludes; nw_conn = new DiscoveryNetworkConnector(new URI("static:(" + other.tcpUrl + ")")); nw_conn.setDuplex(duplex_f); if (queue_f) nw_conn.setConduitSubscriptions(false); else nw_conn.setConduitSubscriptions(true); nw_conn.setNetworkTTL(3); nw_conn.setSuppressDuplicateQueueSubscriptions(true); nw_conn.setDecreaseNetworkConsumerPriority(true); nw_conn.setBridgeTempDestinations(queue_f); if (queue_f) { prefix = "queue"; excl_dest = ActiveMQDestination.createDestination(">", ActiveMQDestination.TOPIC_TYPE); } else { prefix = "topic"; excl_dest = ActiveMQDestination.createDestination(">", ActiveMQDestination.QUEUE_TYPE); } excludes = new ArrayList<>(); excludes.add(excl_dest); nw_conn.setExcludedDestinations(excludes); if (duplex_f) nw_conn.setName(this.brokerId + "<-" + prefix + "->" + other.brokerId); else nw_conn.setName(this.brokerId + "-" + prefix + "->" + other.brokerId); brokerSvc.addNetworkConnector(nw_conn); } } protected class MessageClient extends java.lang.Thread { protected MessageConsumer msgCons; protected boolean shutdownInd; protected int expectedCount; protected int lastSeq = 0; protected int msgCount = 0; protected boolean haveFirstSeq; protected CountDownLatch shutdownLatch; public MessageClient(MessageConsumer cons, int num_to_expect) { msgCons = cons; expectedCount = (num_to_expect * (echoResponseFill + 1)); shutdownLatch = new CountDownLatch(1); } @Override public void run() { CountDownLatch latch; try { synchronized (this) { latch = shutdownLatch; } shutdownInd = false; processMessages(); latch.countDown(); } catch (Exception exc) { LOG.error("message client error", exc); } } public void waitShutdown(long timeout) { CountDownLatch latch; try { synchronized (this) { latch = shutdownLatch; } if (latch != null) latch.await(timeout, TimeUnit.MILLISECONDS); else LOG.info("echo client shutdown: client does not appear to be active"); } catch (InterruptedException int_exc) { LOG.warn("wait for message client shutdown interrupted", int_exc); } } public boolean shutdown() { boolean down_ind; if (!shutdownInd) { shutdownInd = true; } waitShutdown(200); synchronized (this) { if ((shutdownLatch == null) || (shutdownLatch.getCount() == 0)) down_ind = true; else down_ind = false; } return down_ind; } public int getNumMsgReceived() { return msgCount; } protected void processMessages() throws Exception { Message in_msg; haveFirstSeq = false; // // Stop at shutdown time or after any test error is detected. // while ((!shutdownInd) && (!fatalTestError)) { in_msg = msgCons.receive(100); if (in_msg != null) { msgCount++; checkMessage(in_msg); } } msgCons.close(); } protected void checkMessage(Message in_msg) throws Exception { int seq; LOG.debug("received message " + fmtMsgInfo(in_msg) + " from " + in_msg.getJMSDestination()); // // Only check messages with a sequence number. // if (in_msg.propertyExists("SEQ")) { seq = in_msg.getIntProperty("SEQ"); if ((haveFirstSeq) && (seq != (lastSeq + 1))) { LOG.error("***ERROR*** incorrect sequence number; expected " + Integer.toString(lastSeq + 1) + " but have " + Integer.toString(seq)); testError = true; } lastSeq = seq; if (msgCount > expectedCount) { LOG.error("*** have more messages than expected; have " + msgCount + "; expect " + expectedCount); testError = true; } } if (in_msg.propertyExists("end-of-response")) { LOG.trace("received end-of-response message"); } } } /** * */ protected class EchoService extends java.lang.Thread { protected String destName; protected Connection jmsConn; protected Session sess; protected MessageConsumer msg_cons; protected boolean Shutdown_ind; protected Destination req_dest; protected CountDownLatch waitShutdown; protected ThreadPoolExecutor processorPool; public EchoService(String dest, Connection broker_conn) throws Exception { destName = dest; jmsConn = broker_conn; Shutdown_ind = false; sess = jmsConn.createSession(false, Session.AUTO_ACKNOWLEDGE); req_dest = sess.createQueue(destName); msg_cons = sess.createConsumer(req_dest); jmsConn.start(); waitShutdown = new CountDownLatch(1); processorPool = new ThreadPoolExecutor(CONCURRENT_SERVER_COUNT, CONCURRENT_SERVER_COUNT, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10000)); } public EchoService(String dest, String broker_url) throws Exception { this(dest, ActiveMQConnection.makeConnection(broker_url)); } @Override public void run() { Message req; try { LOG.info("STARTING ECHO SERVICE"); while (!Shutdown_ind) { req = msg_cons.receive(100); if (req != null) { processorPool.execute(new EchoRequestProcessor(sess, req)); } } } catch (Exception ex) { LOG.error("error processing echo service requests", ex); } finally { LOG.info("shutting down test echo service"); try { jmsConn.stop(); } catch (javax.jms.JMSException jms_exc) { LOG.warn("error on shutting down JMS connection", jms_exc); } synchronized (this) { waitShutdown.countDown(); } } } /** * Shut down the service, waiting up to 3 seconds for the service to terminate. */ public void shutdown() { CountDownLatch wait_l; synchronized (this) { wait_l = waitShutdown; } Shutdown_ind = true; try { if (wait_l != null) { if (wait_l.await(3000, TimeUnit.MILLISECONDS)) LOG.info("echo service shutdown complete"); else LOG.warn("timeout waiting for echo service shutdown"); } else { LOG.info("echo service shutdown: service does not appear to be active"); } } catch (InterruptedException int_exc) { LOG.warn("interrupted while waiting for echo service shutdown"); } } } /** * */ protected class EchoRequestProcessor implements Runnable { protected Session session; protected Destination resp_dest; protected MessageProducer msg_prod; protected Message request; public EchoRequestProcessor(Session sess, Message req) throws Exception { this.session = sess; this.request = req; this.resp_dest = req.getJMSReplyTo(); if (resp_dest == null) { throw new Exception("invalid request: no reply-to destination given"); } this.msg_prod = session.createProducer(this.resp_dest); } @Override public void run() { try { this.processRequest(this.request); } catch (Exception ex) { LOG.error("Failed to process request", ex); } } /** * Process one request for the Echo Service. */ protected void processRequest(Message req) throws Exception { if (LOG.isDebugEnabled()) LOG.debug("ECHO request message " + req.toString()); resp_dest = req.getJMSReplyTo(); if (resp_dest != null) { msg_prod = session.createProducer(resp_dest); LOG.debug("SENDING ECHO RESPONSE to:" + resp_dest); msg_prod.send(req); LOG.debug((((ActiveMQSession) session).getConnection()).getBrokerName() + " SENT ECHO RESPONSE to " + resp_dest); msg_prod.close(); msg_prod = null; } else { LOG.warn("invalid request: no reply-to destination given"); } } } protected class TopicTrafficGenerator extends java.lang.Thread { protected Connection conn1; protected Connection conn2; protected Session sess1; protected Session sess2; protected Destination dest; protected MessageProducer prod; protected MessageConsumer cons; protected boolean Shutdown_ind; protected int send_count; public TopicTrafficGenerator(String url1, String url2) throws Exception { conn1 = createConnection(url1); conn2 = createConnection(url2); sess1 = conn1.createSession(false, Session.AUTO_ACKNOWLEDGE); sess2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); conn1.start(); conn2.start(); dest = sess1.createTopic("traffic"); prod = sess1.createProducer(dest); dest = sess2.createTopic("traffic"); cons = sess2.createConsumer(dest); } public void shutdown() { Shutdown_ind = true; } @Override public void run() { Message msg; try { LOG.info("Starting Topic Traffic Generator"); while (!Shutdown_ind) { msg = sess1.createTextMessage("TRAFFIC"); prod.send(msg); send_count++; // // Time out the receipt; early messages may not make it. // msg = cons.receive(250); } } catch (JMSException jms_exc) { LOG.warn("traffic generator failed on jms exception", jms_exc); } finally { LOG.info("Shutdown of Topic Traffic Generator; send count = " + send_count); if (conn1 != null) { try { conn1.stop(); } catch (JMSException jms_exc) { LOG.warn("failed to shutdown connection", jms_exc); } } if (conn2 != null) { try { conn2.stop(); } catch (JMSException jms_exc) { LOG.warn("failed to shutdown connection", jms_exc); } } } } } }