/** * 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.transport.discovery; import javax.management.ObjectInstance; import javax.management.ObjectName; import java.net.URI; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.jmx.ManagementContext; import org.apache.activemq.util.SocketProxy; import org.apache.activemq.util.Wait; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.api.Invocation; import org.jmock.integration.junit4.JMock; import org.jmock.integration.junit4.JUnit4Mockery; import org.jmock.lib.action.CustomAction; import org.jmock.lib.legacy.ClassImposteriser; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.Assert.assertTrue; @RunWith(JMock.class) public class DiscoveryNetworkReconnectTest { private static final Logger LOG = LoggerFactory.getLogger(DiscoveryNetworkReconnectTest.class); final int maxReconnects = 2; final String groupName = "GroupID-" + "DiscoveryNetworkReconnectTest"; final String discoveryAddress = "multicast://default?group=" + groupName + "&initialReconnectDelay=1000"; final Semaphore mbeanRegistered = new Semaphore(0); final Semaphore mbeanUnregistered = new Semaphore(0); BrokerService brokerA, brokerB; Mockery context; ManagementContext managementContext; DiscoveryAgent agent; SocketProxy proxy; // ignore the hostname resolution component as this is machine dependent class NetworkBridgeObjectNameMatcher<T> extends BaseMatcher<T> { T name; NetworkBridgeObjectNameMatcher(T o) { name = o; } @Override public boolean matches(Object arg0) { ObjectName other = (ObjectName) arg0; ObjectName mine = (ObjectName) name; LOG.info("Match: " + mine + " vs: " + other); if (!"networkConnectors".equals(other.getKeyProperty("connector"))) { return false; } return other.getKeyProperty("connector").equals(mine.getKeyProperty("connector")) && other.getKeyProperty("networkBridge") != null && mine.getKeyProperty("networkBridge") != null; } @Override public void describeTo(Description arg0) { arg0.appendText(this.getClass().getName()); } } @Before public void setUp() throws Exception { context = new JUnit4Mockery() {{ setImposteriser(ClassImposteriser.INSTANCE); }}; brokerA = new BrokerService(); brokerA.setBrokerName("BrokerA"); configure(brokerA); brokerA.addConnector("tcp://localhost:0"); brokerA.start(); brokerA.waitUntilStarted(); proxy = new SocketProxy(brokerA.getTransportConnectors().get(0).getConnectUri()); managementContext = context.mock(ManagementContext.class); context.checking(new Expectations() {{ allowing(managementContext).getJmxDomainName(); will(returnValue("Test")); allowing(managementContext).setBrokerName("BrokerNC"); allowing(managementContext).start(); allowing(managementContext).isCreateConnector(); allowing(managementContext).stop(); allowing(managementContext).isConnectorStarted(); // expected MBeans allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,service=Health")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,connector=networkConnectors,networkConnectorName=NC")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,service=Log4JConfiguration")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.Connection")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.NetworkBridge")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.MasterBroker")))); allowing(managementContext).registerMBean(with(any(Object.class)), with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,service=jobScheduler,jobSchedulerName=JMS")))); atLeast(maxReconnects - 1).of(managementContext).registerMBean(with(any(Object.class)), with(new NetworkBridgeObjectNameMatcher<>(new ObjectName("Test:type=Broker,brokerName=BrokerNC,connector=networkConnectors,networkConnectorName=NC,networkBridge=localhost/127.0.0.1_" + proxy.getUrl().getPort())))); will(new CustomAction("signal register network mbean") { @Override public Object invoke(Invocation invocation) throws Throwable { LOG.info("Mbean Registered: " + invocation.getParameter(0)); mbeanRegistered.release(); return new ObjectInstance((ObjectName) invocation.getParameter(1), "discription"); } }); atLeast(maxReconnects - 1).of(managementContext).unregisterMBean(with(new NetworkBridgeObjectNameMatcher<>(new ObjectName("Test:type=Broker,brokerName=BrokerNC,connector=networkConnectors,networkConnectorName=NC,networkBridge=localhost/127.0.0.1_" + proxy.getUrl().getPort())))); will(new CustomAction("signal unregister network mbean") { @Override public Object invoke(Invocation invocation) throws Throwable { LOG.info("Mbean Unregistered: " + invocation.getParameter(0)); mbeanUnregistered.release(); return null; } }); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,service=Health")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,connector=networkConnectors,networkConnectorName=NC")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,service=Log4JConfiguration")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.Connection")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.NetworkBridge")))); allowing(managementContext).unregisterMBean(with(equal(new ObjectName("Test:type=Broker,brokerName=BrokerNC,destinationType=Topic,destinationName=ActiveMQ.Advisory.MasterBroker")))); }}); brokerB = new BrokerService(); brokerB.setManagementContext(managementContext); brokerB.setBrokerName("BrokerNC"); configure(brokerB); } @After public void tearDown() throws Exception { brokerA.stop(); brokerA.waitUntilStopped(); brokerB.stop(); brokerB.waitUntilStopped(); proxy.close(); } private void configure(BrokerService broker) { broker.setPersistent(false); broker.setUseJmx(true); } @Test public void testMulicastReconnect() throws Exception { brokerB.addNetworkConnector(discoveryAddress + "&discovered.trace=true&discovered.wireFormat.maxInactivityDuration=1000&discovered.wireFormat.maxInactivityDurationInitalDelay=1000"); brokerB.start(); brokerB.waitUntilStarted(); // control multicast advertise agent to inject proxy agent = DiscoveryAgentFactory.createDiscoveryAgent(new URI(discoveryAddress)); agent.registerService(proxy.getUrl().toString()); agent.start(); doReconnect(); } @Test public void testSimpleReconnect() throws Exception { brokerB.addNetworkConnector("simple://(" + proxy.getUrl() + ")?useExponentialBackOff=false&initialReconnectDelay=500&discovered.wireFormat.maxInactivityDuration=1000&discovered.wireFormat.maxInactivityDurationInitalDelay=1000"); brokerB.start(); brokerB.waitUntilStarted(); doReconnect(); } private void doReconnect() throws Exception { for (int i = 0; i < maxReconnects; i++) { // Wait for connection assertTrue("we got a network connection in a timely manner", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return proxy.connections.size() >= 1; } })); // wait for network connector assertTrue("network connector mbean registered within 1 minute", mbeanRegistered.tryAcquire(60, TimeUnit.SECONDS)); // force an inactivity timeout via the proxy proxy.pause(); // wait for the inactivity timeout and network shutdown assertTrue("network connector mbean unregistered within 1 minute", mbeanUnregistered.tryAcquire(60, TimeUnit.SECONDS)); // whack all connections proxy.close(); // let a reconnect succeed proxy.reopen(); } } }