/* * 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.cluster.topology; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException; import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException; import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientProducer; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; import org.apache.activemq.artemis.core.server.cluster.ClusterManager; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.apache.activemq.artemis.tests.integration.cluster.distribution.ClusterTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.Wait; import org.apache.activemq.artemis.utils.RandomUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static java.util.concurrent.TimeUnit.SECONDS; public abstract class TopologyClusterTestBase extends ClusterTestBase { private static final class LatchListener implements ClusterTopologyListener { private final CountDownLatch upLatch; private final List<String> nodes; private final CountDownLatch downLatch; // we need a separate list of the nodes we've seen go up that we don't remove IDs from // because stale UDP messages can mess up tests once nodes start going down private final List<String> seenUp = new ArrayList<>(); /** * @param upLatch * @param nodes * @param downLatch */ private LatchListener(CountDownLatch upLatch, List<String> nodes, CountDownLatch downLatch) { this.upLatch = upLatch; this.nodes = nodes; this.downLatch = downLatch; } @Override public synchronized void nodeUP(TopologyMember topologyMember, boolean last) { final String nodeID = topologyMember.getNodeId(); if (!seenUp.contains(nodeID)) { nodes.add(nodeID); seenUp.add(nodeID); upLatch.countDown(); } } @Override public synchronized void nodeDown(final long uniqueEventID, String nodeID) { if (nodes.contains(nodeID)) { nodes.remove(nodeID); downLatch.countDown(); } } } private static final IntegrationTestLogger log = IntegrationTestLogger.LOGGER; protected abstract ServerLocator createHAServerLocator(); protected abstract void setupServers() throws Exception; protected abstract void setupCluster() throws Exception; protected abstract boolean isNetty() throws Exception; @Override @Before public void setUp() throws Exception { super.setUp(); setupServers(); setupCluster(); } /** * Check that the actual list of received nodeIDs correspond to the expected order of nodes */ protected void checkOrder(int[] expected, String[] nodeIDs, List<String> actual) { Assert.assertEquals(expected.length, actual.size()); for (int i = 0; i < expected.length; i++) { Assert.assertEquals("did not receive expected nodeID at " + i, nodeIDs[expected[i]], actual.get(i)); } } protected void checkContains(int[] expected, String[] nodeIDs, List<String> actual) { long start = System.currentTimeMillis(); do { if (expected.length != actual.size()) { continue; } boolean ok = true; for (int element : expected) { ok = (ok && actual.contains(nodeIDs[element])); } if (ok) { return; } } while (System.currentTimeMillis() - start < 5000); Assert.fail("did not contain all expected node ID: " + actual); } protected String[] getNodeIDs(int... nodes) { String[] nodeIDs = new String[nodes.length]; for (int i = 0; i < nodes.length; i++) { nodeIDs[i] = servers[i].getNodeID().toString(); } return nodeIDs; } protected ClientSession checkSessionOrReconnect(ClientSession session, ServerLocator locator) throws Exception { try { String rand = RandomUtil.randomString(); session.createQueue(rand, RoutingType.MULTICAST, rand); session.deleteQueue(rand); return session; } catch (ActiveMQObjectClosedException oce) { ClientSessionFactory sf = createSessionFactory(locator); return sf.createSession(); } catch (ActiveMQUnBlockedException obe) { ClientSessionFactory sf = createSessionFactory(locator); return sf.createSession(); } } protected void waitForClusterConnections(final int node, final int expected) throws Exception { ActiveMQServer server = servers[node]; if (server == null) { throw new IllegalArgumentException("No server at " + node); } ClusterManager clusterManager = server.getClusterManager(); long start = System.currentTimeMillis(); int nodesCount; do { nodesCount = 0; for (ClusterConnection clusterConn : clusterManager.getClusterConnections()) { Map<String, String> nodes = clusterConn.getNodes(); for (String id : nodes.keySet()) { if (clusterConn.isNodeActive(id)) { nodesCount++; } } } if (nodesCount == expected) { return; } Thread.sleep(10); } while (System.currentTimeMillis() - start < ActiveMQTestBase.WAIT_TIMEOUT); log.error(clusterDescription(servers[node])); Assert.assertEquals("Timed out waiting for cluster connections for server " + node, expected, nodesCount); } @Test public void testReceiveNotificationsWhenOtherNodesAreStartedAndStopped() throws Throwable { startServers(0); ServerLocator locator = createHAServerLocator(); locator.getTopology().setOwner("testReceive"); final List<String> nodes = Collections.synchronizedList(new ArrayList<String>()); final CountDownLatch upLatch = new CountDownLatch(5); final CountDownLatch downLatch = new CountDownLatch(4); locator.addClusterTopologyListener(new LatchListener(upLatch, nodes, downLatch)); ClientSessionFactory sf = createSessionFactory(locator); startServers(1, 4, 3, 2); String[] nodeIDs = getNodeIDs(0, 1, 2, 3, 4); Assert.assertTrue("Was not notified that all servers are UP", upLatch.await(10, SECONDS)); checkContains(new int[]{0, 1, 4, 3, 2}, nodeIDs, nodes); waitForClusterConnections(0, 4); waitForClusterConnections(1, 4); waitForClusterConnections(2, 4); waitForClusterConnections(3, 4); waitForClusterConnections(4, 4); stopServers(2, 3, 1, 4); Assert.assertTrue("Was not notified that all servers are DOWN", downLatch.await(10, SECONDS)); checkContains(new int[]{0}, nodeIDs, nodes); sf.close(); locator.close(); stopServers(0); } @Test public void testReceiveNotifications() throws Throwable { startServers(0, 1, 2, 3, 4); String[] nodeIDs = getNodeIDs(0, 1, 2, 3, 4); ServerLocator locator = createHAServerLocator(); waitForClusterConnections(0, 4); waitForClusterConnections(1, 4); waitForClusterConnections(2, 4); waitForClusterConnections(3, 4); waitForClusterConnections(4, 4); final List<String> nodes = Collections.synchronizedList(new ArrayList<String>()); final CountDownLatch upLatch = new CountDownLatch(5); final CountDownLatch downLatch = new CountDownLatch(4); locator.addClusterTopologyListener(new LatchListener(upLatch, nodes, downLatch)); ClientSessionFactory sf = createSessionFactory(locator); Assert.assertTrue("Was not notified that all servers are UP", upLatch.await(10, SECONDS)); checkContains(new int[]{0, 1, 2, 3, 4}, nodeIDs, nodes); ClientSession session = sf.createSession(); stopServers(0); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 2, 3, 4}, nodeIDs, nodes); stopServers(2); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 3, 4}, nodeIDs, nodes); stopServers(4); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 3}, nodeIDs, nodes); stopServers(3); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1}, nodeIDs, nodes); stopServers(1); Assert.assertTrue("Was not notified that all servers are DOWN", downLatch.await(10, SECONDS)); checkContains(new int[]{}, nodeIDs, nodes); sf.close(); } @Test public void testStopNodes() throws Throwable { startServers(0, 1, 2, 3, 4); String[] nodeIDs = getNodeIDs(0, 1, 2, 3, 4); ServerLocator locator = createHAServerLocator(); waitForClusterConnections(0, 4); waitForClusterConnections(1, 4); waitForClusterConnections(2, 4); waitForClusterConnections(3, 4); waitForClusterConnections(4, 4); final List<String> nodes = Collections.synchronizedList(new ArrayList<String>()); final CountDownLatch upLatch = new CountDownLatch(5); locator.addClusterTopologyListener(new LatchListener(upLatch, nodes, new CountDownLatch(0))); ClientSessionFactory sf = createSessionFactory(locator); Assert.assertTrue("Was not notified that all servers are UP", upLatch.await(10, SECONDS)); checkContains(new int[]{0, 1, 2, 3, 4}, nodeIDs, nodes); ClientSession session = sf.createSession(); stopServers(0); Assert.assertFalse(servers[0].isStarted()); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 2, 3, 4}, nodeIDs, nodes); stopServers(2); Assert.assertFalse(servers[2].isStarted()); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 3, 4}, nodeIDs, nodes); stopServers(4); Assert.assertFalse(servers[4].isStarted()); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1, 3}, nodeIDs, nodes); stopServers(3); Assert.assertFalse(servers[3].isStarted()); session = checkSessionOrReconnect(session, locator); checkContains(new int[]{1}, nodeIDs, nodes); stopServers(1); Assert.assertFalse(servers[1].isStarted()); try { session = checkSessionOrReconnect(session, locator); Assert.fail(); } catch (ActiveMQException expected) { Assert.assertEquals(ActiveMQExceptionType.NOT_CONNECTED, expected.getType()); } } @Test public void testWrongPasswordTriggersClusterConnectionStop() throws Exception { Configuration config = servers[4].getConfiguration(); for (ActiveMQServer s : servers) { if (s != null) { s.getConfiguration().setSecurityEnabled(true); } } Assert.assertEquals(ActiveMQTestBase.CLUSTER_PASSWORD, config.getClusterPassword()); config.setClusterPassword(config.getClusterPassword() + "-1-2-3-"); startServers(0, 4); Assert.assertTrue("one or the other cluster managers should stop", Wait.waitFor(() -> !servers[4].getClusterManager().isStarted() || !servers[0].getClusterManager().isStarted(), 5000)); final String address = "foo1235"; ServerLocator locator = createNonHALocator(isNetty()); ClientSessionFactory sf = createSessionFactory(locator); ClientSession session = sf.createSession(config.getClusterUser(), ActiveMQTestBase.CLUSTER_PASSWORD, false, true, true, false, 1); session.createQueue(address, address, true); ClientProducer producer = session.createProducer(address); sendMessages(session, producer, 100); ClientConsumer consumer = session.createConsumer(address); session.start(); receiveMessages(consumer, 0, 100, true); } @Test public void testMultipleClientSessionFactories() throws Throwable { startServers(0, 1, 2, 3, 4); String[] nodeIDs = getNodeIDs(0, 1, 2, 3, 4); ServerLocator locator = createHAServerLocator(); waitForClusterConnections(0, 4); waitForClusterConnections(1, 4); waitForClusterConnections(2, 4); waitForClusterConnections(3, 4); waitForClusterConnections(4, 4); final List<String> nodes = Collections.synchronizedList(new ArrayList<String>()); final CountDownLatch upLatch = new CountDownLatch(5); final CountDownLatch downLatch = new CountDownLatch(4); locator.addClusterTopologyListener(new LatchListener(upLatch, nodes, downLatch)); ClientSessionFactory[] sfs = new ClientSessionFactory[]{locator.createSessionFactory(), locator.createSessionFactory(), locator.createSessionFactory(), locator.createSessionFactory(), locator.createSessionFactory()}; Assert.assertTrue("Was not notified that all servers are UP", upLatch.await(10, SECONDS)); checkContains(new int[]{0, 1, 2, 3, 4}, nodeIDs, nodes); // we can't close all of the servers, we need to leave one up to notify us stopServers(4, 2, 3, 1); boolean ok = downLatch.await(10, SECONDS); if (!ok) { log.warn("TopologyClusterTestBase.testMultipleClientSessionFactories will fail"); } Assert.assertTrue("Was not notified that all servers are Down", ok); checkContains(new int[]{0}, nodeIDs, nodes); for (ClientSessionFactory sf : sfs) { sf.close(); } locator.close(); stopServers(0); } }