/* * 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.artemis.tests.integration.jms.cluster; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; import org.apache.activemq.artemis.api.jms.JMSFactoryType; import org.apache.activemq.artemis.core.remoting.FailureListener; import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory; import org.apache.activemq.artemis.core.server.cluster.Bridge; import org.apache.activemq.artemis.core.server.cluster.impl.BridgeImpl; import org.apache.activemq.artemis.core.server.cluster.impl.ClusterConnectionImpl; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.tests.util.JMSClusteredTestBase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(value = Parameterized.class) public class BindingsClusterTest extends JMSClusteredTestBase { // TODO: find a solution to this // the "jms." prefix is needed because the cluster connection is matching on this public static final String TOPIC = "jms.t1"; private final boolean crash; public BindingsClusterTest(boolean crash) { this.crash = crash; } @Parameterized.Parameters(name = "crash={0}") public static Collection getParameters() { return Arrays.asList(new Object[][]{{true}, {false}}); } @Override @Before public void setUp() throws Exception { //todo fix if needed super.setUp(); jmsServer1.getActiveMQServer().setIdentity("Server 1"); jmsServer2.getActiveMQServer().setIdentity("Server 2"); } @Override protected boolean enablePersistence() { return true; } @Test public void testSendToSingleDisconnectedBinding() throws Exception { Connection conn1 = cf1.createConnection(); conn1.setClientID("someClient1"); Connection conn2 = cf2.createConnection(); conn2.setClientID("someClient2"); conn1.start(); conn2.start(); try { Topic topic1 = createTopic(TOPIC, true); Topic topic2 = (Topic) context1.lookup("topic/" + TOPIC); Session session1 = conn1.createSession(false, Session.AUTO_ACKNOWLEDGE); Session session2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer cons2 = session2.createDurableSubscriber(topic2, "sub2"); cons2.close(); session2.close(); conn2.close(); Thread.sleep(500); MessageProducer prod1 = session1.createProducer(topic1); prod1.setDeliveryMode(DeliveryMode.PERSISTENT); prod1.send(session1.createTextMessage("m1")); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); crash(); printBindings(jmsServer1.getActiveMQServer(), TOPIC); prod1.send(session1.createTextMessage("m2")); restart(); Thread.sleep(2000); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); prod1.send(session1.createTextMessage("m3")); cf2 = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(InVMConnectorFactory.class.getName(), generateInVMParams(2))); conn2 = cf2.createConnection(); conn2.setClientID("someClient2"); session2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); cons2 = session2.createDurableSubscriber(topic2, "sub2"); conn2.start(); TextMessage received = (TextMessage) cons2.receive(5000); assertNotNull(received); assertEquals("m1", received.getText()); received = (TextMessage) cons2.receive(5000); assertNotNull(received); assertEquals("m2", received.getText()); received = (TextMessage) cons2.receive(5000); assertNotNull(received); assertEquals("m3", received.getText()); cons2.close(); } finally { conn1.close(); conn2.close(); } jmsServer1.destroyTopic(TOPIC); jmsServer2.destroyTopic(TOPIC); } @Test public void testSendToSingleDisconnectedBindingWhenLocalAvailable() throws Exception { Connection conn1 = cf1.createConnection(); conn1.setClientID("someClient2"); Connection conn2 = cf2.createConnection(); conn2.setClientID("someClient2"); conn1.start(); conn2.start(); try { Topic topic1 = createTopic(TOPIC, true); Topic topic2 = (Topic) context1.lookup("topic/" + TOPIC); Session session1 = conn1.createSession(false, Session.AUTO_ACKNOWLEDGE); Session session2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer cons1 = session1.createDurableSubscriber(topic1, "sub2"); MessageConsumer cons2 = session2.createDurableSubscriber(topic2, "sub2"); cons2.close(); session2.close(); conn2.close(); Thread.sleep(500); MessageProducer prod1 = session1.createProducer(topic1); prod1.setDeliveryMode(DeliveryMode.PERSISTENT); prod1.send(session1.createTextMessage("m1")); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); crash(); printBindings(jmsServer1.getActiveMQServer(), TOPIC); //send a few messages while the binding is disconnected prod1.send(session1.createTextMessage("m2")); prod1.send(session1.createTextMessage("m3")); prod1.send(session1.createTextMessage("m4")); restart(); Thread.sleep(2000); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); prod1.send(session1.createTextMessage("m5")); prod1.send(session1.createTextMessage("m6")); cf2 = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(InVMConnectorFactory.class.getName(), generateInVMParams(2))); conn2 = cf2.createConnection(); conn2.setClientID("someClient2"); session2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); cons2 = session2.createDurableSubscriber(topic2, "sub2"); conn2.start(); TextMessage received = (TextMessage) cons2.receiveNoWait(); assertNull(received); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m1", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m2", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m3", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m4", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m5", received.getText()); cons2.close(); } finally { conn1.close(); conn2.close(); } jmsServer1.destroyTopic(TOPIC); jmsServer2.destroyTopic(TOPIC); } @Test public void testRemoteBindingRemovedOnReconnectLocalAvailable() throws Exception { Connection conn1 = cf1.createConnection(); conn1.setClientID("someClient2"); Connection conn2 = cf2.createConnection(); conn2.setClientID("someClient2"); conn1.start(); conn2.start(); try { Topic topic1 = createTopic(TOPIC, true); Topic topic2 = (Topic) context1.lookup("topic/" + TOPIC); Session session1 = conn1.createSession(false, Session.AUTO_ACKNOWLEDGE); Session session2 = conn2.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer cons1 = session1.createSharedConsumer(topic1, "sub2"); MessageConsumer cons2 = session2.createSharedConsumer(topic2, "sub2"); Thread.sleep(500); MessageProducer prod1 = session1.createProducer(topic1); prod1.setDeliveryMode(DeliveryMode.PERSISTENT); prod1.send(session1.createTextMessage("m1")); prod1.send(session1.createTextMessage("m2")); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); crash(); //this may or may not be closed, if the server was crashed then it would have been closed on failure. cons2.close(); printBindings(jmsServer1.getActiveMQServer(), TOPIC); //send a few messages while the binding is disconnected prod1.send(session1.createTextMessage("m3")); prod1.send(session1.createTextMessage("m4")); prod1.send(session1.createTextMessage("m5")); restart(); Thread.sleep(2000); printBindings(jmsServer1.getActiveMQServer(), TOPIC); printBindings(jmsServer2.getActiveMQServer(), TOPIC); prod1.send(session1.createTextMessage("m6")); prod1.send(session1.createTextMessage("m7")); TextMessage received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m1", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m3", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m4", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m5", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m6", received.getText()); received = (TextMessage) cons1.receive(5000); assertNotNull(received); assertEquals("m7", received.getText()); cons2.close(); } finally { conn1.close(); conn2.close(); } jmsServer1.destroyTopic(TOPIC); jmsServer2.destroyTopic(TOPIC); } private void crash() throws Exception { /* * Rather than just calling stop() on the server here we want to simulate an actual node crash or bridge failure * so the bridge's failure listener needs to get something other than a DISCONNECTED message. In this case we * simulate a NOT_CONNECTED exception. */ final CountDownLatch latch = new CountDownLatch(1); ClusterConnectionImpl next = (ClusterConnectionImpl) server1.getClusterManager().getClusterConnections().iterator().next(); BridgeImpl bridge = (BridgeImpl) next.getRecords().values().iterator().next().getBridge(); RemotingConnection forwardingConnection = getForwardingConnection(bridge); forwardingConnection.addFailureListener(new FailureListener() { @Override public void connectionFailed(ActiveMQException exception, boolean failedOver) { latch.countDown(); } @Override public void connectionFailed(final ActiveMQException me, boolean failedOver, String scaleDownTargetNodeID) { connectionFailed(me, failedOver); } }); forwardingConnection.fail(new ActiveMQNotConnectedException()); assertTrue(latch.await(5000, TimeUnit.MILLISECONDS)); if (crash) { jmsServer2.stop(); } } private void restart() throws Exception { if (crash) { jmsServer2.start(); } } private RemotingConnection getForwardingConnection(final Bridge bridge) throws Exception { long start = System.currentTimeMillis(); do { RemotingConnection forwardingConnection = ((BridgeImpl) bridge).getForwardingConnection(); if (forwardingConnection != null) { return forwardingConnection; } Thread.sleep(10); } while (System.currentTimeMillis() - start < 50000); throw new IllegalStateException("Failed to get forwarding connection"); } }