/* * 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.core.server.cluster.ha; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.server.ActivationParams; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.cluster.ClusterControl; import org.apache.activemq.artemis.core.server.cluster.ClusterController; import org.apache.activemq.artemis.utils.ConfigurationHelper; public class ColocatedHAManager implements HAManager { private final ColocatedPolicy haPolicy; private final ActiveMQServer server; private final Map<String, ActiveMQServer> backupServers = new HashMap<>(); private boolean started; public ColocatedHAManager(ColocatedPolicy haPolicy, ActiveMQServer activeMQServer) { this.haPolicy = haPolicy; server = activeMQServer; } /** * starts the HA manager. */ @Override public void start() { if (started) return; server.getActivation().haStarted(); started = true; } /** * stop any backups */ @Override public void stop() { for (ActiveMQServer activeMQServer : backupServers.values()) { try { activeMQServer.stop(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); } } backupServers.clear(); started = false; } @Override public boolean isStarted() { return started; } public synchronized boolean activateBackup(int backupSize, String journalDirectory, String bindingsDirectory, String largeMessagesDirectory, String pagingDirectory, SimpleString nodeID) throws Exception { if (backupServers.size() >= haPolicy.getMaxBackups() || backupSize != backupServers.size()) { return false; } if (haPolicy.getBackupPolicy().isSharedStore()) { return activateSharedStoreBackup(journalDirectory, bindingsDirectory, largeMessagesDirectory, pagingDirectory); } else { return activateReplicatedBackup(nodeID); } } /** * return the current backup servers * * @return the backups */ @Override public Map<String, ActiveMQServer> getBackupServers() { return backupServers; } /** * send a request to a live server to start a backup for us * * @param connectorPair the connector for the node to request a backup from * @param backupSize the current size of the requested nodes backups * @param replicated * @return true if the request wa successful. * @throws Exception */ public boolean requestBackup(Pair<TransportConfiguration, TransportConfiguration> connectorPair, int backupSize, boolean replicated) throws Exception { ClusterController clusterController = server.getClusterManager().getClusterController(); try ( ClusterControl clusterControl = clusterController.connectToNode(connectorPair.getA()); ) { clusterControl.authorize(); if (replicated) { return clusterControl.requestReplicatedBackup(backupSize, server.getNodeID()); } else { return clusterControl.requestSharedStoreBackup(backupSize, server.getConfiguration().getJournalLocation().getAbsolutePath(), server.getConfiguration().getBindingsLocation().getAbsolutePath(), server.getConfiguration().getLargeMessagesLocation().getAbsolutePath(), server.getConfiguration().getPagingLocation().getAbsolutePath()); } } } private synchronized boolean activateSharedStoreBackup(String journalDirectory, String bindingsDirectory, String largeMessagesDirectory, String pagingDirectory) throws Exception { Configuration configuration = server.getConfiguration().copy(); ActiveMQServer backup = server.createBackupServer(configuration); try { int portOffset = haPolicy.getBackupPortOffset() * (backupServers.size() + 1); String name = "colocated_backup_" + backupServers.size() + 1; //make sure we don't restart as we are colocated haPolicy.getBackupPolicy().setRestartBackup(false); //set the backup policy backup.setHAPolicy(haPolicy.getBackupPolicy()); updateSharedStoreConfiguration(configuration, name, portOffset, haPolicy.getExcludedConnectors(), journalDirectory, bindingsDirectory, largeMessagesDirectory, pagingDirectory, haPolicy.getBackupPolicy().getScaleDownPolicy() == null); backupServers.put(configuration.getName(), backup); backup.start(); } catch (Exception e) { backup.stop(); ActiveMQServerLogger.LOGGER.activateSharedStoreSlaveFailed(e); return false; } ActiveMQServerLogger.LOGGER.activatingSharedStoreSlave(); return true; } /** * activate a backup server replicating from a specified node. * * decline and the requesting server can cast a re vote * * @param nodeID the id of the node to replicate from * @return true if the server was created and started * @throws Exception */ private synchronized boolean activateReplicatedBackup(SimpleString nodeID) throws Exception { Configuration configuration = server.getConfiguration().copy(); ActiveMQServer backup = server.createBackupServer(configuration); try { TopologyMember member = server.getClusterManager().getDefaultConnection(null).getTopology().getMember(nodeID.toString()); int portOffset = haPolicy.getBackupPortOffset() * (backupServers.size() + 1); String name = "colocated_backup_" + backupServers.size() + 1; //make sure we don't restart as we are colocated haPolicy.getBackupPolicy().setRestartBackup(false); //set the backup policy backup.setHAPolicy(haPolicy.getBackupPolicy()); updateReplicatedConfiguration(configuration, name, portOffset, haPolicy.getExcludedConnectors(), haPolicy.getBackupPolicy().getScaleDownPolicy() == null); backup.addActivationParam(ActivationParams.REPLICATION_ENDPOINT, member); backupServers.put(configuration.getName(), backup); backup.start(); } catch (Exception e) { backup.stop(); ActiveMQServerLogger.LOGGER.activateReplicatedBackupFailed(e); return false; } ActiveMQServerLogger.LOGGER.activatingReplica(nodeID); return true; } /** * update the backups configuration * * @param backupConfiguration the configuration to update * @param name the new name of the backup * @param portOffset the offset for the acceptors and any connectors that need changing * @param remoteConnectors the connectors that don't need off setting, typically remote * @param journalDirectory * @param bindingsDirectory * @param largeMessagesDirectory * @param pagingDirectory * @param fullServer */ private static void updateSharedStoreConfiguration(Configuration backupConfiguration, String name, int portOffset, List<String> remoteConnectors, String journalDirectory, String bindingsDirectory, String largeMessagesDirectory, String pagingDirectory, boolean fullServer) { backupConfiguration.setName(name); backupConfiguration.setJournalDirectory(journalDirectory); backupConfiguration.setBindingsDirectory(bindingsDirectory); backupConfiguration.setLargeMessagesDirectory(largeMessagesDirectory); backupConfiguration.setPagingDirectory(pagingDirectory); updateAcceptorsAndConnectors(backupConfiguration, portOffset, remoteConnectors, fullServer); } /** * update the backups configuration * * @param backupConfiguration the configuration to update * @param name the new name of the backup * @param portOffset the offset for the acceptors and any connectors that need changing * @param remoteConnectors the connectors that don't need off setting, typically remote */ private static void updateReplicatedConfiguration(Configuration backupConfiguration, String name, int portOffset, List<String> remoteConnectors, boolean fullServer) { backupConfiguration.setName(name); backupConfiguration.setJournalDirectory(backupConfiguration.getJournalDirectory() + name); backupConfiguration.setPagingDirectory(backupConfiguration.getPagingDirectory() + name); backupConfiguration.setLargeMessagesDirectory(backupConfiguration.getLargeMessagesDirectory() + name); backupConfiguration.setBindingsDirectory(backupConfiguration.getBindingsDirectory() + name); updateAcceptorsAndConnectors(backupConfiguration, portOffset, remoteConnectors, fullServer); } private static void updateAcceptorsAndConnectors(Configuration backupConfiguration, int portOffset, List<String> remoteConnectors, boolean fullServer) { //we only do this if we are a full server, if scale down then our acceptors wont be needed and our connectors will // be the same as the parent server if (fullServer) { Set<TransportConfiguration> acceptors = backupConfiguration.getAcceptorConfigurations(); for (TransportConfiguration acceptor : acceptors) { updatebackupParams(backupConfiguration.getName(), portOffset, acceptor.getParams()); } Map<String, TransportConfiguration> connectorConfigurations = backupConfiguration.getConnectorConfigurations(); for (Map.Entry<String, TransportConfiguration> entry : connectorConfigurations.entrySet()) { //check to make sure we aren't a remote connector as this shouldn't be changed if (!remoteConnectors.contains(entry.getValue().getName())) { updatebackupParams(backupConfiguration.getName(), portOffset, entry.getValue().getParams()); } } } else { //if we are scaling down then we wont need any acceptors but clear anyway for belts and braces backupConfiguration.getAcceptorConfigurations().clear(); } } /** * Offset the port for Netty connector/acceptor (unless HTTP upgrade is enabled) and the server ID for invm connector/acceptor. * * The port is not offset for Netty connector/acceptor when HTTP upgrade is enabled. In this case, the app server that * embed ActiveMQ is "owning" the port and is charge to delegate the HTTP ugprade to the correct broker (that can be * the main one or any colocated backup hosted on the main broker). Delegation to the correct broker is done by looking at the * {@link TransportConstants#ACTIVEMQ_SERVER_NAME} property [ARTEMIS-803] */ private static void updatebackupParams(String name, int portOffset, Map<String, Object> params) { if (params != null) { Object port = params.get(TransportConstants.PORT_PROP_NAME); if (port != null) { boolean httpUpgradeEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.HTTP_UPGRADE_ENABLED_PROP_NAME, TransportConstants.DEFAULT_HTTP_UPGRADE_ENABLED, params); if (!httpUpgradeEnabled) { Integer integer = Integer.valueOf(port.toString()); integer += portOffset; params.put(TransportConstants.PORT_PROP_NAME, integer.toString()); } } Object serverId = params.get(org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants.SERVER_ID_PROP_NAME); if (serverId != null) { Integer newid = Integer.parseInt(serverId.toString()) + portOffset; params.put(org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants.SERVER_ID_PROP_NAME, newid.toString()); } params.put(TransportConstants.ACTIVEMQ_SERVER_NAME, name); } } }