diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java index ff1057522d..3d7019b8e4 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java @@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import io.airlift.airline.Option; -import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.api.core.management.ActiveMQManagementProxy; import org.apache.activemq.artemis.cli.CLIException; import org.apache.activemq.artemis.cli.commands.AbstractAction; @@ -72,10 +71,7 @@ public abstract class CheckAbstract extends AbstractAction { int successTasks = 0; try (ActiveMQConnectionFactory factory = createCoreConnectionFactory(); - ServerLocator serverLocator = factory.getServerLocator(); - ActiveMQManagementProxy managementProxy = new ActiveMQManagementProxy(serverLocator, user, password)) { - - managementProxy.start(); + ActiveMQManagementProxy managementProxy = new ActiveMQManagementProxy(factory.getServerLocator(), user, password)) { StopWatch watch = new StopWatch(); CheckTask[] checkTasks = getCheckTasks(); diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java index d1e8ca57ff..617b603f83 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java @@ -61,7 +61,7 @@ public class CheckContext extends ActionContext { public String getNodeId() throws Exception { if (nodeId == null) { - nodeId = managementProxy.invokeOperation(String.class, "broker", "getNodeID"); + nodeId = managementProxy.getAttribute("broker", "NodeID", String.class, 0); } return nodeId; @@ -69,8 +69,8 @@ public class CheckContext extends ActionContext { public Map getTopology() throws Exception { if (topology == null) { - topology = Arrays.stream(NodeInfo.from(managementProxy.invokeOperation( - String.class, "broker", "listNetworkTopology"))). + topology = Arrays.stream(NodeInfo.from((String)managementProxy. + invokeOperation("broker", "listNetworkTopology", null, null, 0))). collect(Collectors.toMap(node -> node.getId(), node -> node)); } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java index 601d254016..3897a15b2b 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java @@ -138,7 +138,7 @@ public class NodeCheck extends CheckAbstract { } private void checkNodeUp(final CheckContext context) throws Exception { - if (!context.getManagementProxy().invokeOperation(Boolean.class, "broker", "isStarted")) { + if (!context.getManagementProxy().getAttribute("broker", "Started", Boolean.class, 0)) { throw new CheckException("The node isn't started."); } } @@ -182,28 +182,31 @@ public class NodeCheck extends CheckAbstract { } private void checkNodeDiskUsage(final CheckContext context) throws Exception { - int thresholdValue; + Integer maxDiskUsage; if (diskUsage == -1) { - thresholdValue = context.getManagementProxy().invokeOperation( - int.class, "broker", "getMaxDiskUsage"); + maxDiskUsage = context.getManagementProxy(). + getAttribute("broker", "MaxDiskUsage", Integer.class, 0); } else { - thresholdValue = diskUsage; + maxDiskUsage = diskUsage; } - checkNodeUsage(context, "getDiskStoreUsage", thresholdValue); + Double diskStoreUsage = context.getManagementProxy(). + getAttribute("broker", "DiskStoreUsage", Double.class, 0); + + checkNodeResourceUsage("DiskStoreUsage", (int)(diskStoreUsage * 100), maxDiskUsage); } private void checkNodeMemoryUsage(final CheckContext context) throws Exception { - checkNodeUsage(context, "getAddressMemoryUsagePercentage", memoryUsage); + int addressMemoryUsagePercentage = context.getManagementProxy(). + getAttribute("broker", "AddressMemoryUsagePercentage", Integer.class, 0); + + checkNodeResourceUsage("MemoryUsage", addressMemoryUsagePercentage, memoryUsage); } - private void checkNodeUsage(final CheckContext context, final String name, final int thresholdValue) throws Exception { - int usageValue = context.getManagementProxy().invokeOperation(int.class, "broker", name); - + private void checkNodeResourceUsage(final String resourceName, final int usageValue, final int thresholdValue) throws Exception { if (usageValue > thresholdValue) { - throw new CheckException("The " + (name.startsWith("get") ? name.substring(3) : name) + - " " + usageValue + " is less than " + thresholdValue); + throw new CheckException("The " + resourceName + " " + usageValue + " is less than " + thresholdValue); } } } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java index 5bd4fd9d76..ed9a1629c0 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java @@ -132,7 +132,7 @@ public class QueueCheck extends CheckAbstract { } private void checkQueueUp(final CheckContext context) throws Exception { - if (context.getManagementProxy().invokeOperation(Boolean.class,ResourceNames.QUEUE + getName(), "isPaused")) { + if (context.getManagementProxy().getAttribute(ResourceNames.QUEUE + getName(), "Paused", Boolean.class, 0)) { throw new CheckException("The queue is paused."); } } diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java index 8bb51c3dcf..2c6e585260 100644 --- a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java @@ -267,6 +267,12 @@ public enum ActiveMQExceptionType { public ActiveMQException createException(String msg) { return new ActiveMQDivertDoesNotExistException(msg); } + }, + REDIRECTED(222) { + @Override + public ActiveMQException createException(String msg) { + return new ActiveMQRedirectedException(msg); + } }; private static final Map TYPE_MAP; diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java new file mode 100644 index 0000000000..ed52b04f23 --- /dev/null +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java @@ -0,0 +1,34 @@ +/* + * 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.api.core; + +/** + * A client was redirected. + */ +public final class ActiveMQRedirectedException extends ActiveMQException { + + private static final long serialVersionUID = 7414966383933311627L; + + public ActiveMQRedirectedException() { + super(ActiveMQExceptionType.REDIRECTED); + } + + public ActiveMQRedirectedException(String message) { + super(ActiveMQExceptionType.REDIRECTED, message); + } +} + diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java new file mode 100644 index 0000000000..ff5831e716 --- /dev/null +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java @@ -0,0 +1,73 @@ +/* + * 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.api.core; + +public enum DisconnectReason { + REDIRECT((byte)0, false), + REDIRECT_ON_CRITICAL_ERROR((byte)1, true), + SCALE_DOWN((byte)2, false), + SCALE_DOWN_ON_CRITICAL_ERROR((byte)3, true), + SHOUT_DOWN((byte)4, false), + SHOUT_DOWN_ON_CRITICAL_ERROR((byte)5, true); + + private final byte type; + private final boolean criticalError; + + DisconnectReason(byte type, boolean criticalError) { + this.type = type; + this.criticalError = criticalError; + } + + public byte getType() { + return type; + } + + public boolean isCriticalError() { + return criticalError; + } + + public boolean isRedirect() { + return this == REDIRECT || this == REDIRECT_ON_CRITICAL_ERROR; + } + + public boolean isScaleDown() { + return this == SCALE_DOWN || this == SCALE_DOWN_ON_CRITICAL_ERROR; + } + + public boolean isShutDown() { + return this == SHOUT_DOWN || this == SHOUT_DOWN_ON_CRITICAL_ERROR; + } + + public static DisconnectReason getType(byte type) { + switch (type) { + case 0: + return REDIRECT; + case 1: + return REDIRECT_ON_CRITICAL_ERROR; + case 2: + return SCALE_DOWN; + case 3: + return SCALE_DOWN_ON_CRITICAL_ERROR; + case 4: + return SHOUT_DOWN; + case 5: + return SHOUT_DOWN_ON_CRITICAL_ERROR; + default: + return null; + } + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java index ee285a314e..74657ccc1f 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java @@ -255,7 +255,7 @@ public class TransportConfiguration implements Serializable { public String toString() { StringBuilder str = new StringBuilder(TransportConfiguration.class.getSimpleName()); str.append("(name=" + name + ", "); - str.append("factory=" + replaceWildcardChars(factoryClassName)); + str.append("factory=" + (factoryClassName == null ? "null" : replaceWildcardChars(factoryClassName))); str.append(") "); str.append(toStringParameters(params, extraProps)); return str.toString(); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java index ad7e702dc8..51df731bc6 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java @@ -39,6 +39,14 @@ public interface ClientSession extends XAResource, AutoCloseable { */ String JMS_SESSION_IDENTIFIER_PROPERTY = "jms-session"; + /** + * Just like {@link ClientSession.AddressQuery#JMS_SESSION_IDENTIFIER_PROPERTY} this is + * used to identify the ClientID over JMS Session. + * However this is only used when the JMS Session.clientID is set (which is optional). + * With this property management tools and the server can identify the jms-client-id used over JMS + */ + String JMS_SESSION_CLIENT_ID_PROPERTY = "jms-client-id"; + /** * Information returned by a binding query * diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java index 9fc1f489df..e1e18e927e 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java @@ -135,6 +135,32 @@ public interface ClientSessionFactory extends AutoCloseable { boolean preAcknowledge, int ackBatchSize) throws ActiveMQException; + /** + * Creates an authenticated session. + *

+ * It is possible to pre-acknowledge messages on the server so that the client can avoid additional network trip + * to the server to acknowledge messages. While this increase performance, this does not guarantee delivery (as messages + * can be lost after being pre-acknowledged on the server). Use with caution if your application design permits it. + * + * @param username the user name + * @param password the user password + * @param xa whether the session support XA transaction semantic or not + * @param autoCommitSends true to automatically commit message sends, false to commit manually + * @param autoCommitAcks true to automatically commit message acknowledgement, false to commit manually + * @param preAcknowledge true to pre-acknowledge messages on the server, false to let the client acknowledge the messages + * @param clientID the session clientID + * @return a ClientSession + * @throws ActiveMQException if an exception occurs while creating the session + */ + ClientSession createSession(String username, + String password, + boolean xa, + boolean autoCommitSends, + boolean autoCommitAcks, + boolean preAcknowledge, + int ackBatchSize, + String clientID) throws ActiveMQException; + /** * Closes this factory and any session created by it. */ diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java index 599ff738ae..7d77a877f9 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java @@ -18,7 +18,6 @@ package org.apache.activemq.artemis.api.core.management; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; -import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientRequestor; @@ -28,51 +27,67 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator; public class ActiveMQManagementProxy implements AutoCloseable { - private final String username; - private final String password; - private final ServerLocator locator; + private final ServerLocator serverLocator; - private ClientSessionFactory sessionFactory; - private ClientSession session; - private ClientRequestor requestor; + private final ClientSessionFactory sessionFactory; - public ActiveMQManagementProxy(final ServerLocator locator, final String username, final String password) { - this.locator = locator; - this.username = username; - this.password = password; + private final ClientSession clientSession; + + + public ActiveMQManagementProxy(final ClientSession session) { + serverLocator = null; + sessionFactory = null; + clientSession = session; } - public void start() throws Exception { + public ActiveMQManagementProxy(final ServerLocator locator, final String username, final String password) throws Exception { + serverLocator = locator; sessionFactory = locator.createSessionFactory(); - session = sessionFactory.createSession(username, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE); - requestor = new ClientRequestor(session, ActiveMQDefaultConfiguration.getDefaultManagementAddress()); - - session.start(); + clientSession = sessionFactory.createSession(username, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE).start(); } - public T invokeOperation(final Class type, final String resourceName, final String operationName, final Object... operationArgs) throws Exception { - ClientMessage request = session.createMessage(false); + public T getAttribute(final String resourceName, final String attributeName, final Class attributeClass, final int timeout) throws Exception { + try (ClientRequestor requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress())) { + ClientMessage request = clientSession.createMessage(false); - ManagementHelper.putOperationInvocation(request, resourceName, operationName, operationArgs); + ManagementHelper.putAttribute(request, resourceName, attributeName); - ClientMessage reply = requestor.request(request); + ClientMessage reply = requestor.request(request, timeout); - if (ManagementHelper.hasOperationSucceeded(reply)) { - return (T)ManagementHelper.getResult(reply, type); - } else { - throw new Exception("Failed to invoke " + resourceName + "." + operationName + ". Reason: " + ManagementHelper.getResult(reply, String.class)); + if (ManagementHelper.hasOperationSucceeded(reply)) { + return (T)ManagementHelper.getResult(reply, attributeClass); + } else { + throw new Exception("Failed to get " + resourceName + "." + attributeName + ". Reason: " + ManagementHelper.getResult(reply, String.class)); + } } } + public T invokeOperation(final String resourceName, final String operationName, final Object[] operationParams, final Class operationClass, final int timeout) throws Exception { + try (ClientRequestor requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress())) { + ClientMessage request = clientSession.createMessage(false); - public void stop() throws ActiveMQException { - session.stop(); + ManagementHelper.putOperationInvocation(request, resourceName, operationName, operationParams); + + ClientMessage reply = requestor.request(request, timeout); + + if (ManagementHelper.hasOperationSucceeded(reply)) { + return (T)ManagementHelper.getResult(reply, operationClass); + } else { + throw new Exception("Failed to invoke " + resourceName + "." + operationName + ". Reason: " + ManagementHelper.getResult(reply, String.class)); + } + } } @Override public void close() throws Exception { - requestor.close(); - session.close(); - sessionFactory.close(); + clientSession.close(); + + if (sessionFactory != null) { + sessionFactory.close(); + } + + if (serverLocator != null) { + serverLocator.close(); + } } } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java new file mode 100644 index 0000000000..983888f0be --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java @@ -0,0 +1,31 @@ +/* + * 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.api.core.management; + +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.CompositeData; + +/** + * A BrokerBalancerControl is used to manage a BrokerBalancer. + */ +public interface BrokerBalancerControl { + @Operation(desc = "Get the target associated with key", impact = MBeanOperationInfo.INFO) + CompositeData getTarget(@Parameter(desc = "a key", name = "key") String key) throws Exception; + + @Operation(desc = "Get the target associated with key as JSON", impact = MBeanOperationInfo.INFO) + String getTargetAsJSON(@Parameter(desc = "a key", name = "key") String key); +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java index dacc91f554..a97ff7cefe 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java @@ -149,6 +149,15 @@ public final class ObjectNameBuilder { return createObjectName("cluster-connection", name); } + /** + * Returns the ObjectName used by BrokerBalancerControl. + * + * @see BrokerBalancerControl + */ + public ObjectName getBrokerBalancerObjectName(final String name) throws Exception { + return createObjectName("broker-balancer", name); + } + private ObjectName createObjectName(final String type, final String name) throws Exception { return ObjectName.getInstance(String.format("%s,component=%ss,name=%s", getActiveMQServerName(), type, ObjectName.quote(name))); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java index 7aaa54d517..0d45dd9ea5 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java @@ -44,6 +44,8 @@ public final class ResourceNames { public static final String BROADCAST_GROUP = "broadcastgroup."; + public static final String BROKER_BALANCER = "brokerbalancer."; + public static final String RETROACTIVE_SUFFIX = "retro"; public static SimpleString getRetroactiveResourceQueueName(String prefix, String delimiter, SimpleString address, RoutingType routingType) { diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java index 8468e84bb4..3bbf51be71 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java @@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQLargeMessageException; import org.apache.activemq.artemis.api.core.ActiveMQLargeMessageInterruptedException; import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException; import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException; +import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException; import org.apache.activemq.artemis.api.core.ActiveMQTransactionOutcomeUnknownException; import org.apache.activemq.artemis.api.core.ActiveMQTransactionRolledBackException; import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException; @@ -237,4 +238,7 @@ public interface ActiveMQClientMessageBundle { @Message(id = 219065, value = "Failed to handle packet.") RuntimeException failedToHandlePacket(@Cause Exception e); + + @Message(id = 219066, value = "The connection was redirected") + ActiveMQRedirectedException redirected(); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java index 92236f4aa4..80a2428554 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java @@ -37,6 +37,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException; import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException; +import org.apache.activemq.artemis.api.core.DisconnectReason; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.TransportConfiguration; @@ -79,11 +80,13 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C private final ClientProtocolManager clientProtocolManager; - private TransportConfiguration connectorConfig; + private final TransportConfiguration connectorConfig; - private TransportConfiguration currentConnectorConfig; + private TransportConfiguration previousConnectorConfig; - private volatile TransportConfiguration backupConfig; + private volatile TransportConfiguration currentConnectorConfig; + + private volatile TransportConfiguration backupConnectorConfig; private ConnectorFactory connectorFactory; @@ -184,6 +187,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C this.clientProtocolManager.setSessionFactory(this); + this.connectorConfig = connectorConfig.getA(); + this.currentConnectorConfig = connectorConfig.getA(); connectorFactory = instantiateConnectorFactory(connectorConfig.getA().getFactoryClassName()); @@ -231,7 +236,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C connectionReadyForWrites = true; if (connectorConfig.getB() != null) { - this.backupConfig = connectorConfig.getB(); + this.backupConnectorConfig = connectorConfig.getB(); } } @@ -253,8 +258,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C if (connection == null) { StringBuilder msg = new StringBuilder("Unable to connect to server using configuration ").append(currentConnectorConfig); - if (backupConfig != null) { - msg.append(" and backup configuration ").append(backupConfig); + if (backupConnectorConfig != null) { + msg.append(" and backup configuration ").append(backupConnectorConfig); } throw new ActiveMQNotConnectedException(msg.toString()); } @@ -288,7 +293,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C if (logger.isDebugEnabled()) { logger.debug("Setting up backup config = " + backUp + " for live = " + live); } - backupConfig = backUp; + backupConnectorConfig = backUp; } else { if (logger.isDebugEnabled()) { logger.debug("ClientSessionFactoryImpl received backup update for live/backup pair = " + live + @@ -302,7 +307,19 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C @Override public Object getBackupConnector() { - return backupConfig; + return backupConnectorConfig; + } + + @Override + public ClientSession createSession(final String username, + final String password, + final boolean xa, + final boolean autoCommitSends, + final boolean autoCommitAcks, + final boolean preAcknowledge, + final int ackBatchSize, + final String clientID) throws ActiveMQException { + return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize, clientID); } @Override @@ -313,42 +330,42 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C final boolean autoCommitAcks, final boolean preAcknowledge, final int ackBatchSize) throws ActiveMQException { - return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize); + return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize, null); } @Override public ClientSession createSession(final boolean autoCommitSends, final boolean autoCommitAcks, final int ackBatchSize) throws ActiveMQException { - return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), ackBatchSize); + return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), ackBatchSize, null); } @Override public ClientSession createXASession() throws ActiveMQException { - return createSessionInternal(null, null, true, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, true, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null); } @Override public ClientSession createTransactedSession() throws ActiveMQException { - return createSessionInternal(null, null, false, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, false, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null); } @Override public ClientSession createSession() throws ActiveMQException { - return createSessionInternal(null, null, false, true, true, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, false, true, true, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null); } @Override public ClientSession createSession(final boolean autoCommitSends, final boolean autoCommitAcks) throws ActiveMQException { - return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null); } @Override public ClientSession createSession(final boolean xa, final boolean autoCommitSends, final boolean autoCommitAcks) throws ActiveMQException { - return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null); } @Override @@ -356,7 +373,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C final boolean autoCommitSends, final boolean autoCommitAcks, final boolean preAcknowledge) throws ActiveMQException { - return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getAckBatchSize()); + return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getAckBatchSize(), null); } // ClientConnectionLifeCycleListener implementation -------------------------------------------------- @@ -717,10 +734,11 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C final boolean autoCommitSends, final boolean autoCommitAcks, final boolean preAcknowledge, - final int ackBatchSize) throws ActiveMQException { + final int ackBatchSize, + final String clientID) throws ActiveMQException { String name = UUIDGenerator.getInstance().generateStringUUID(); - SessionContext context = createSessionChannel(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge); + SessionContext context = createSessionChannel(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, clientID); ClientSessionInternal session = new ClientSessionImpl(this, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.isBlockOnAcknowledge(), serverLocator.isAutoGroup(), ackBatchSize, serverLocator.getConsumerWindowSize(), serverLocator.getConsumerMaxRate(), serverLocator.getConfirmationWindowSize(), serverLocator.getProducerWindowSize(), serverLocator.getProducerMaxRate(), serverLocator.isBlockOnNonDurableSend(), serverLocator.isBlockOnDurableSend(), serverLocator.isCacheLargeMessagesClient(), serverLocator.getMinLargeMessageSize(), serverLocator.isCompressLargeMessage(), serverLocator.getInitialMessagePacketSize(), serverLocator.getGroupID(), context, orderedExecutorFactory.getExecutor(), orderedExecutorFactory.getExecutor(), orderedExecutorFactory.getExecutor()); @@ -1050,11 +1068,13 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C public class CloseRunnable implements Runnable { private final RemotingConnection conn; - private final String scaleDownTargetNodeID; + private final DisconnectReason reason; + private final String targetNodeID; - public CloseRunnable(RemotingConnection conn, String scaleDownTargetNodeID) { + public CloseRunnable(RemotingConnection conn, DisconnectReason reason, String targetNodeID) { this.conn = conn; - this.scaleDownTargetNodeID = scaleDownTargetNodeID; + this.reason = reason; + this.targetNodeID = targetNodeID; } // Must be executed on new thread since cannot block the Netty thread for a long time and fail @@ -1063,10 +1083,12 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C public void run() { try { CLOSE_RUNNABLES.add(this); - if (scaleDownTargetNodeID == null) { - conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected()); + if (reason.isRedirect()) { + conn.fail(ActiveMQClientMessageBundle.BUNDLE.redirected()); + } else if (reason.isScaleDown()) { + conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected(), targetNodeID); } else { - conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected(), scaleDownTargetNodeID); + conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected()); } } finally { CLOSE_RUNNABLES.remove(this); @@ -1146,72 +1168,39 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C Connection transportConnection = null; try { - if (logger.isDebugEnabled()) { - logger.debug("Trying to connect with connectorFactory = " + connectorFactory + - ", connectorConfig=" + currentConnectorConfig); + //Try to connect with the current connector configuration + transportConnection = createTransportConnection("current", currentConnectorConfig); + if (transportConnection != null) { + return transportConnection; } - Connector liveConnector = createConnector(connectorFactory, currentConnectorConfig); - - if ((transportConnection = openTransportConnection(liveConnector)) != null) { - // if we can't connect the connect method will return null, hence we have to try the backup - connector = liveConnector; - return transportConnection; - } else if (backupConfig != null) { - if (logger.isDebugEnabled()) { - logger.debug("Trying backup config = " + backupConfig); - } - - ConnectorFactory backupConnectorFactory = instantiateConnectorFactory(backupConfig.getFactoryClassName()); - - Connector backupConnector = createConnector(backupConnectorFactory, backupConfig); - - transportConnection = openTransportConnection(backupConnector); + if (backupConnectorConfig != null) { + //Try to connect with the backup connector configuration + transportConnection = createTransportConnection("backup", backupConnectorConfig); if (transportConnection != null) { - /*looks like the backup is now live, let's use that*/ - - if (logger.isDebugEnabled()) { - logger.debug("Connected to the backup at " + backupConfig); - } - - // Switching backup as live - connector = backupConnector; - connectorConfig = currentConnectorConfig; - currentConnectorConfig = backupConfig; - connectorFactory = backupConnectorFactory; return transportConnection; } } - if (logger.isDebugEnabled()) { - logger.debug("Backup is not active, trying original connection configuration now."); + if (previousConnectorConfig != null && !currentConnectorConfig.equals(previousConnectorConfig)) { + //Try to connect with the previous connector configuration + transportConnection = createTransportConnection("previous", previousConnectorConfig); + if (transportConnection != null) { + return transportConnection; + } } - - if (currentConnectorConfig.equals(connectorConfig) || connectorConfig == null) { - - // There was no changes on current and original connectors, just return null here and let the retry happen at the first portion of this method on the next retry - return null; + if (!currentConnectorConfig.equals(connectorConfig)) { + //Try to connect with the initial connector configuration + transportConnection = createTransportConnection("initial", connectorConfig); + if (transportConnection != null) { + return transportConnection; + } } - ConnectorFactory lastConnectorFactory = instantiateConnectorFactory(connectorConfig.getFactoryClassName()); - - Connector lastConnector = createConnector(lastConnectorFactory, connectorConfig); - - transportConnection = openTransportConnection(lastConnector); - - if (transportConnection != null) { - logger.debug("Returning into original connector"); - connector = lastConnector; - TransportConfiguration temp = currentConnectorConfig; - currentConnectorConfig = connectorConfig; - connectorConfig = temp; - return transportConnection; - } else { - logger.debug("no connection been made, returning null"); - return null; - } + logger.debug("no connection been made, returning null"); + return null; } catch (Exception cause) { // Sanity catch for badly behaved remoting plugins @@ -1236,6 +1225,33 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C } + private Connection createTransportConnection(String name, TransportConfiguration transportConnectorConfig) { + ConnectorFactory transportConnectorFactory = instantiateConnectorFactory( + transportConnectorConfig.getFactoryClassName()); + + if (logger.isDebugEnabled()) { + logger.debug("Trying to connect with connectorFactory=" + transportConnectorFactory + + " and " + name + "ConnectorConfig: " + transportConnectorConfig); + } + + Connector transportConnector = createConnector(transportConnectorFactory, transportConnectorConfig); + + Connection transportConnection = openTransportConnection(transportConnector); + + if (transportConnection != null) { + if (logger.isDebugEnabled()) { + logger.debug("Connected with the " + name + "ConnectorConfig=" + transportConnectorConfig); + } + + connector = transportConnector; + connectorFactory = transportConnectorFactory; + previousConnectorConfig = currentConnectorConfig; + currentConnectorConfig = transportConnectorConfig; + } + + return transportConnection; + } + private class DelegatingBufferHandler implements BufferHandler { @Override @@ -1413,9 +1429,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C final boolean xa, final boolean autoCommitSends, final boolean autoCommitAcks, - final boolean preAcknowledge) throws ActiveMQException { + final boolean preAcknowledge, + final String clientID) throws ActiveMQException { synchronized (createSessionLock) { - return clientProtocolManager.createSessionContext(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getMinLargeMessageSize(), serverLocator.getConfirmationWindowSize()); + return clientProtocolManager.createSessionContext(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getMinLargeMessageSize(), serverLocator.getConfirmationWindowSize(), clientID); } } @@ -1427,20 +1444,39 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C class SessionFactoryTopologyHandler implements TopologyResponseHandler { @Override - public void nodeDisconnected(RemotingConnection conn, String nodeID, String scaleDownTargetNodeID) { + public void nodeDisconnected(RemotingConnection conn, String nodeID, DisconnectReason reason, String targetNodeID, TransportConfiguration tagetConnector) { if (logger.isTraceEnabled()) { logger.trace("Disconnect being called on client:" + - " server locator = " + - serverLocator + - " notifying node " + - nodeID + - " as down", new Exception("trace")); + " server locator = " + + serverLocator + + " notifying node " + + nodeID + + " as down with reason " + + reason, new Exception("trace")); } serverLocator.notifyNodeDown(System.currentTimeMillis(), nodeID); - closeExecutor.execute(new CloseRunnable(conn, scaleDownTargetNodeID)); + if (reason.isRedirect()) { + if (serverLocator.isHA()) { + TopologyMemberImpl topologyMember = serverLocator.getTopology().getMember(nodeID); + + if (topologyMember != null) { + if (topologyMember.getConnector().getB() != null) { + backupConnectorConfig = topologyMember.getConnector().getB(); + } else if (logger.isDebugEnabled()) { + logger.debug("The topology member " + nodeID + " with connector " + tagetConnector + " has no backup"); + } + } else if (logger.isDebugEnabled()) { + logger.debug("The topology member " + nodeID + " with connector " + tagetConnector + " not found"); + } + } + + currentConnectorConfig = tagetConnector; + } + + closeExecutor.execute(new CloseRunnable(conn, reason, targetNodeID)); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java index 33e5a77f6c..5d831daf79 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java @@ -33,6 +33,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; +import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.QueueAttributes; import org.apache.activemq.artemis.api.core.QueueConfiguration; @@ -1460,6 +1461,9 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi sessionContext.returnBlocking(cause); } + } catch (ActiveMQRedirectedException e) { + logger.info("failedToHandleFailover.ActiveMQRedirectedException"); + suc = false; } catch (Throwable t) { ActiveMQClientLogger.LOGGER.failedToHandleFailover(t); suc = false; diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java index 76f87cf106..61a01dc9fb 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java @@ -51,6 +51,16 @@ public interface CoreRemotingConnection extends RemotingConnection { return version >= PacketImpl.ARTEMIS_2_7_0_VERSION; } + default boolean isVersionSupportClientID() { + int version = getChannelVersion(); + return version >= PacketImpl.ARTEMIS_2_18_0_VERSION; + } + + default boolean isVersionSupportRedirect() { + int version = getChannelVersion(); + return version >= PacketImpl.ARTEMIS_2_18_0_VERSION; + } + /** * Sets the client protocol used on the communication. This will determine if the client has * support for certain packet types diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java index b1d6cc8d59..84ae30b196 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java @@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException; +import org.apache.activemq.artemis.api.core.DisconnectReason; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.SimpleString; @@ -45,10 +46,13 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2; import org.apache.activemq.artemis.core.remoting.impl.netty.ActiveMQFrameDecoder2; @@ -243,10 +247,11 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { boolean autoCommitAcks, boolean preAcknowledge, int minLargeMessageSize, - int confirmationWindowSize) throws ActiveMQException { + int confirmationWindowSize, + String clientID) throws ActiveMQException { for (Version clientVersion : VersionLoader.getClientVersions()) { try { - return createSessionContext(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize); + return createSessionContext(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, clientID); } catch (ActiveMQException e) { if (e.getType() != ActiveMQExceptionType.INCOMPATIBLE_CLIENT_SERVER_VERSIONS) { throw e; @@ -266,7 +271,8 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { boolean autoCommitAcks, boolean preAcknowledge, int minLargeMessageSize, - int confirmationWindowSize) throws ActiveMQException { + int confirmationWindowSize, + String clientID) throws ActiveMQException { if (!isAlive()) throw ActiveMQClientMessageBundle.BUNDLE.clientSessionClosed(); @@ -293,7 +299,7 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { long sessionChannelID = connection.generateChannelID(); - Packet request = newCreateSessionPacket(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID); + Packet request = newCreateSessionPacket(clientVersion.getIncrementingVersion(), name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID, clientID); try { // channel1 reference here has to go away @@ -302,7 +308,8 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { if (!isAlive()) throw cause; - if (cause.getType() == ActiveMQExceptionType.UNBLOCKED) { + if (cause.getType() == ActiveMQExceptionType.UNBLOCKED || + cause.getType() == ActiveMQExceptionType.REDIRECTED) { // This means the thread was blocked on create session and failover unblocked it // so failover could occur @@ -339,11 +346,11 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { } while (retry); sessionChannel.getConnection().setChannelVersion(response.getServerVersion()); - return newSessionContext(name, confirmationWindowSize, sessionChannel, response); + return newSessionContext(name, confirmationWindowSize, sessionChannel, response); } - protected Packet newCreateSessionPacket(Version clientVersion, + protected Packet newCreateSessionPacket(int clientVersion, String name, String username, String password, @@ -353,8 +360,13 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { boolean preAcknowledge, int minLargeMessageSize, int confirmationWindowSize, - long sessionChannelID) { - return new CreateSessionMessage(name, sessionChannelID, clientVersion.getIncrementingVersion(), username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null); + long sessionChannelID, + String clientID) { + if (connection.isVersionSupportClientID()) { + return new CreateSessionMessage_V2(name, sessionChannelID, clientVersion, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null, clientID); + } else { + return new CreateSessionMessage(name, sessionChannelID, clientVersion, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null); + } } protected SessionContext newSessionContext(String name, @@ -459,19 +471,15 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { public void handlePacket(final Packet packet) { final byte type = packet.getType(); - if (type == PacketImpl.DISCONNECT || type == PacketImpl.DISCONNECT_V2) { - final DisconnectMessage msg = (DisconnectMessage) packet; - String scaleDownTargetNodeID = null; - - SimpleString nodeID = msg.getNodeID(); - - if (packet instanceof DisconnectMessage_V2) { - final DisconnectMessage_V2 msg_v2 = (DisconnectMessage_V2) packet; - scaleDownTargetNodeID = msg_v2.getScaleDownNodeID() == null ? null : msg_v2.getScaleDownNodeID().toString(); - } - - if (topologyResponseHandler != null) - topologyResponseHandler.nodeDisconnected(conn, nodeID == null ? null : nodeID.toString(), scaleDownTargetNodeID); + if (type == PacketImpl.DISCONNECT) { + final DisconnectMessage disMessage = (DisconnectMessage) packet; + handleDisconnect(disMessage.getNodeID(), null, null, null); + } else if (type == PacketImpl.DISCONNECT_V2) { + final DisconnectMessage_V2 disMessage = (DisconnectMessage_V2) packet; + handleDisconnect(disMessage.getNodeID(), DisconnectReason.SCALE_DOWN, disMessage.getScaleDownNodeID(), null); + } else if (type == PacketImpl.DISCONNECT_V3) { + final DisconnectMessage_V3 disMessage = (DisconnectMessage_V3) packet; + handleDisconnect(disMessage.getNodeID(), disMessage.getReason(), disMessage.getTargetNodeID(), disMessage.getTargetConnector()); } else if (type == PacketImpl.CLUSTER_TOPOLOGY) { ClusterTopologyChangeMessage topMessage = (ClusterTopologyChangeMessage) packet; notifyTopologyChange(updateTransportConfiguration(topMessage)); @@ -481,11 +489,22 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager { } else if (type == PacketImpl.CLUSTER_TOPOLOGY_V3) { ClusterTopologyChangeMessage_V3 topMessage = (ClusterTopologyChangeMessage_V3) packet; notifyTopologyChange(updateTransportConfiguration(topMessage)); + } else if (type == PacketImpl.CLUSTER_TOPOLOGY_V4) { + ClusterTopologyChangeMessage_V4 topMessage = (ClusterTopologyChangeMessage_V4) packet; + notifyTopologyChange(updateTransportConfiguration(topMessage)); + connection.setChannelVersion(topMessage.getServerVersion()); } else if (type == PacketImpl.CHECK_FOR_FAILOVER_REPLY) { System.out.println("Channel0Handler.handlePacket"); } } + private void handleDisconnect(SimpleString nodeID, DisconnectReason reason, SimpleString targetNodeID, TransportConfiguration tagetConnector) { + if (topologyResponseHandler != null) { + topologyResponseHandler.nodeDisconnected(conn, nodeID == null ? null : nodeID.toString(), reason, + targetNodeID == null ? null : targetNodeID.toString(), tagetConnector); + } + } + /** * @param topMessage */ diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java index 18bb08cc55..d133826d04 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java @@ -184,6 +184,10 @@ public final class ChannelImpl implements Channel { return version >= 129; case PacketImpl.SESS_BINDINGQUERY_RESP_V4: return version >= 129; + case PacketImpl.CLUSTER_TOPOLOGY_V4: + case PacketImpl.CREATESESSION_V2: + case PacketImpl.DISCONNECT_V3: + return version >= PacketImpl.ARTEMIS_2_18_0_VERSION; default: return true; } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java index 9cc62b25a6..640e079cd1 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java @@ -29,10 +29,12 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateAddressMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSharedQueueMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSharedQueueMessage_V2; @@ -40,6 +42,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Disconnect import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage_V2; @@ -98,8 +101,10 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CHE import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V2; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V3; +import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V4; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION_RESP; +import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION_V2; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_ADDRESS; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_QUEUE; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_QUEUE_V2; @@ -109,6 +114,7 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DEL import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_CONSUMER; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_V2; +import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_V3; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.EXCEPTION; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.FEDERATION_DOWNSTREAM_CONNECT; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.NULL_RESPONSE; @@ -477,6 +483,18 @@ public abstract class PacketDecoder implements Serializable { packet = new FederationDownstreamConnectMessage(); break; } + case CLUSTER_TOPOLOGY_V4: { + packet = new ClusterTopologyChangeMessage_V4(); + break; + } + case CREATESESSION_V2: { + packet = new CreateSessionMessage_V2(); + break; + } + case DISCONNECT_V3: { + packet = new DisconnectMessage_V3(); + break; + } default: { throw ActiveMQClientMessageBundle.BUNDLE.invalidType(packetType); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java index e4a759b774..85275bf609 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java @@ -38,6 +38,8 @@ public class PacketImpl implements Packet { public static final int CONSUMER_PRIORITY_CHANGE_VERSION = ARTEMIS_2_7_0_VERSION; public static final int FQQN_CHANGE_VERSION = ARTEMIS_2_7_0_VERSION; + // 2.18.0 + public static final int ARTEMIS_2_18_0_VERSION = 131; public static final SimpleString OLD_QUEUE_PREFIX = new SimpleString("jms.queue."); public static final SimpleString OLD_TEMP_QUEUE_PREFIX = new SimpleString("jms.tempqueue."); @@ -279,6 +281,11 @@ public class PacketImpl implements Packet { public static final byte FEDERATION_DOWNSTREAM_CONNECT = -16; + public static final byte CLUSTER_TOPOLOGY_V4 = -17; + + public static final byte CREATESESSION_V2 = -18; + + public static final byte DISCONNECT_V3 = -19; // Static -------------------------------------------------------- diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java index 8f4e1b7c6a..c62e0a2a77 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java @@ -26,9 +26,12 @@ import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException; import org.apache.activemq.artemis.api.core.ActiveMQRemoteDisconnectException; +import org.apache.activemq.artemis.api.core.DisconnectReason; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.protocol.core.Channel; @@ -38,6 +41,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_I import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3; import org.apache.activemq.artemis.core.security.ActiveMQPrincipal; import org.apache.activemq.artemis.spi.core.protocol.AbstractRemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -206,7 +210,7 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement destroyed = true; } - if (!(me instanceof ActiveMQRemoteDisconnectException)) { + if (!(me instanceof ActiveMQRemoteDisconnectException) && !(me instanceof ActiveMQRedirectedException)) { ActiveMQClientLogger.LOGGER.connectionFailureDetected(transportConnection.getRemoteAddress(), me.getMessage(), me.getType()); } @@ -250,11 +254,16 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement @Override public void disconnect(final boolean criticalError) { - disconnect(null, criticalError); + disconnect(criticalError ? DisconnectReason.SHOUT_DOWN_ON_CRITICAL_ERROR : DisconnectReason.SHOUT_DOWN, null, null); } @Override public void disconnect(String scaleDownNodeID, final boolean criticalError) { + disconnect(criticalError ? DisconnectReason.SCALE_DOWN_ON_CRITICAL_ERROR : DisconnectReason.SCALE_DOWN, scaleDownNodeID, null); + } + + @Override + public void disconnect(DisconnectReason reason, String targetNodeID, TransportConfiguration targetConnector) { Channel channel0 = getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1); // And we remove all channels from the connection, this ensures no more packets will be processed after this @@ -263,7 +272,7 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement Set allChannels = new HashSet<>(channels.values()); - if (!criticalError) { + if (!reason.isCriticalError()) { removeAllChannels(); } else { // We can't hold a lock if a critical error is happening... @@ -273,15 +282,17 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement // Now we are 100% sure that no more packets will be processed we can flush then send the disconnect - if (!criticalError) { + if (!reason.isCriticalError()) { for (Channel channel : allChannels) { channel.flushConfirmations(); } } Packet disconnect; - if (channel0.supports(PacketImpl.DISCONNECT_V2)) { - disconnect = new DisconnectMessage_V2(nodeID, scaleDownNodeID); + if (channel0.supports(PacketImpl.DISCONNECT_V3)) { + disconnect = new DisconnectMessage_V3(nodeID, reason, SimpleString.toSimpleString(targetNodeID), targetConnector); + } else if (channel0.supports(PacketImpl.DISCONNECT_V2)) { + disconnect = new DisconnectMessage_V2(nodeID, reason.isScaleDown() ? targetNodeID : null); } else { disconnect = new DisconnectMessage(nodeID); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java index d371eb56cb..f3bcfd7416 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java @@ -30,7 +30,17 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag final String scaleDownGroupName, final Pair pair, final boolean last) { - super(CLUSTER_TOPOLOGY_V3); + this(CLUSTER_TOPOLOGY_V3, uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, pair, last); + } + + protected ClusterTopologyChangeMessage_V3(final byte type, + final long uniqueEventID, + final String nodeID, + final String backupGroupName, + final String scaleDownGroupName, + final Pair pair, + final boolean last) { + super(type); this.nodeID = nodeID; @@ -51,6 +61,10 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag super(CLUSTER_TOPOLOGY_V3); } + public ClusterTopologyChangeMessage_V3(byte type) { + super(type); + } + public String getScaleDownGroupName() { return scaleDownGroupName; } @@ -75,8 +89,17 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag return result; } + @Override + protected String getParentString() { + return toString(false); + } + @Override public String toString() { + return toString(true); + } + + private String toString(boolean closed) { StringBuffer buff = new StringBuffer(getParentString()); buff.append(", exit=" + exit); buff.append(", last=" + last); @@ -85,7 +108,9 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag buff.append(", backupGroupName=" + backupGroupName); buff.append(", uniqueEventID=" + uniqueEventID); buff.append(", scaleDownGroupName=" + scaleDownGroupName); - buff.append("]"); + if (closed) { + buff.append("]"); + } return buff.toString(); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java new file mode 100644 index 0000000000..fde8cd88c3 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java @@ -0,0 +1,91 @@ +/* + * 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.protocol.core.impl.wireformat; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.api.core.Pair; +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public class ClusterTopologyChangeMessage_V4 extends ClusterTopologyChangeMessage_V3 { + + private int serverVersion; + + public ClusterTopologyChangeMessage_V4(final long uniqueEventID, + final String nodeID, + final String backupGroupName, + final String scaleDownGroupName, + final Pair pair, + final boolean last, + final int serverVersion) { + super(CLUSTER_TOPOLOGY_V4, uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, pair, last); + + this.serverVersion = serverVersion; + } + + public ClusterTopologyChangeMessage_V4() { + super(CLUSTER_TOPOLOGY_V4); + } + + public int getServerVersion() { + return serverVersion; + } + + @Override + public void encodeRest(final ActiveMQBuffer buffer) { + super.encodeRest(buffer); + + buffer.writeInt(serverVersion); + } + + @Override + public void decodeRest(final ActiveMQBuffer buffer) { + super.decodeRest(buffer); + + serverVersion = buffer.readInt(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + serverVersion; + return result; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(getParentString()); + buf.append(", clientVersion=" + serverVersion); + buf.append("]"); + return buf.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof ClusterTopologyChangeMessage_V4)) { + return false; + } + ClusterTopologyChangeMessage_V4 other = (ClusterTopologyChangeMessage_V4) obj; + return serverVersion == other.serverVersion; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java index 1315249e4d..7f82c83924 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java @@ -57,7 +57,23 @@ public class CreateSessionMessage extends PacketImpl { final boolean preAcknowledge, final int windowSize, final String defaultAddress) { - super(CREATESESSION); + this(CREATESESSION, name, sessionChannelID, version, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, windowSize, defaultAddress); + } + + protected CreateSessionMessage(final byte type, + final String name, + final long sessionChannelID, + final int version, + final String username, + final String password, + final int minLargeMessageSize, + final boolean xa, + final boolean autoCommitSends, + final boolean autoCommitAcks, + final boolean preAcknowledge, + final int windowSize, + final String defaultAddress) { + super(type); this.name = name; @@ -88,6 +104,10 @@ public class CreateSessionMessage extends PacketImpl { super(CREATESESSION); } + protected CreateSessionMessage(final byte type) { + super(type); + } + // Public -------------------------------------------------------- public String getName() { @@ -194,9 +214,18 @@ public class CreateSessionMessage extends PacketImpl { return result; } + @Override + protected String getParentString() { + return toString(false); + } + @Override public String toString() { - StringBuffer buff = new StringBuffer(getParentString()); + return toString(true); + } + + private String toString(boolean closed) { + StringBuffer buff = new StringBuffer(super.getParentString()); buff.append(", autoCommitAcks=" + autoCommitAcks); buff.append(", autoCommitSends=" + autoCommitSends); buff.append(", defaultAddress=" + defaultAddress); @@ -209,7 +238,9 @@ public class CreateSessionMessage extends PacketImpl { buff.append(", version=" + version); buff.append(", windowSize=" + windowSize); buff.append(", xa=" + xa); - buff.append("]"); + if (closed) { + buff.append("]"); + } return buff.toString(); } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java new file mode 100644 index 0000000000..be2f8d343e --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java @@ -0,0 +1,103 @@ +/* + * 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.protocol.core.impl.wireformat; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; + +public class CreateSessionMessage_V2 extends CreateSessionMessage { + + private String clientID = null; + + public CreateSessionMessage_V2(final String name, + final long sessionChannelID, + final int version, + final String username, + final String password, + final int minLargeMessageSize, + final boolean xa, + final boolean autoCommitSends, + final boolean autoCommitAcks, + final boolean preAcknowledge, + final int windowSize, + final String defaultAddress, + final String clientID) { + super(CREATESESSION_V2, name, sessionChannelID, version, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, windowSize, defaultAddress); + + this.clientID = clientID; + } + + public CreateSessionMessage_V2() { + super(CREATESESSION_V2); + } + + // Public -------------------------------------------------------- + + + public String getClientID() { + return clientID; + } + + @Override + public void encodeRest(final ActiveMQBuffer buffer) { + super.encodeRest(buffer); + + buffer.writeNullableString(clientID); + } + + @Override + public void decodeRest(final ActiveMQBuffer buffer) { + super.decodeRest(buffer); + + clientID = buffer.readNullableString(); + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(getParentString()); + buf.append(", metadata=" + clientID); + buf.append("]"); + return buf.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((clientID == null) ? 0 : clientID.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof CreateSessionMessage_V2)) { + return false; + } + CreateSessionMessage_V2 other = (CreateSessionMessage_V2) obj; + if (clientID == null) { + if (other.clientID != null) + return false; + } else if (!clientID.equals(other.clientID)) + return false; + return true; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java new file mode 100644 index 0000000000..671e1aa11b --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java @@ -0,0 +1,137 @@ +/* + * 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.protocol.core.impl.wireformat; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.api.core.DisconnectReason; +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public class DisconnectMessage_V3 extends DisconnectMessage { + + private DisconnectReason reason; + private SimpleString targetNodeID; + private TransportConfiguration targetConnector; + + public DisconnectMessage_V3(final SimpleString nodeID, + final DisconnectReason reason, + final SimpleString targetNodeID, + final TransportConfiguration targetConnector) { + super(DISCONNECT_V3); + + this.nodeID = nodeID; + + this.reason = reason; + + this.targetNodeID = targetNodeID; + + this.targetConnector = targetConnector; + } + + public DisconnectMessage_V3() { + super(DISCONNECT_V3); + } + + // Public -------------------------------------------------------- + + public DisconnectReason getReason() { + return reason; + } + + public SimpleString getTargetNodeID() { + return targetNodeID; + } + + public TransportConfiguration getTargetConnector() { + return targetConnector; + } + + @Override + public void encodeRest(final ActiveMQBuffer buffer) { + super.encodeRest(buffer); + buffer.writeByte(reason == null ? -1 : reason.getType()); + buffer.writeNullableSimpleString(targetNodeID); + if (targetConnector != null) { + buffer.writeBoolean(true); + targetConnector.encode(buffer); + } else { + buffer.writeBoolean(false); + } + } + + @Override + public void decodeRest(final ActiveMQBuffer buffer) { + super.decodeRest(buffer); + reason = DisconnectReason.getType(buffer.readByte()); + targetNodeID = buffer.readNullableSimpleString(); + boolean hasTargetConnector = buffer.readBoolean(); + if (hasTargetConnector) { + targetConnector = new TransportConfiguration(); + targetConnector.decode(buffer); + } else { + targetConnector = null; + } + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(getParentString()); + buf.append(", nodeID=" + nodeID); + buf.append(", reason=" + reason); + buf.append(", targetNodeID=" + targetNodeID); + buf.append(", targetConnector=" + targetConnector); + buf.append("]"); + return buf.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (reason.getType()); + result = prime * result + ((targetNodeID == null) ? 0 : targetNodeID.hashCode()); + result = prime * result + ((targetConnector == null) ? 0 : targetConnector.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof DisconnectMessage_V3)) { + return false; + } + DisconnectMessage_V3 other = (DisconnectMessage_V3) obj; + if (reason == null) { + if (other.reason != null) + return false; + } else if (!reason.equals(other.reason)) + return false; + if (targetNodeID == null) { + if (other.targetNodeID != null) { + return false; + } + } else if (!targetNodeID.equals(other.targetNodeID)) { + return false; + } + return true; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java index 37100875f3..37a4e80bb6 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java @@ -361,6 +361,10 @@ public class TransportConstants { public static final boolean DEFAULT_PROXY_REMOTE_DNS = false; + public static final String REDIRECT_TO = "redirect-to"; + + public static final String DEFAULT_REDIRECT_TO = null; + private static int parseDefaultVariable(String variableName, int defaultValue) { try { String variable = System.getProperty(TransportConstants.class.getName() + "." + variableName); @@ -437,6 +441,7 @@ public class TransportConstants { allowableAcceptorKeys.add(TransportConstants.QUIET_PERIOD); allowableAcceptorKeys.add(TransportConstants.DISABLE_STOMP_SERVER_HEADER); allowableAcceptorKeys.add(TransportConstants.AUTO_START); + allowableAcceptorKeys.add(TransportConstants.REDIRECT_TO); ALLOWABLE_ACCEPTOR_KEYS = Collections.unmodifiableSet(allowableAcceptorKeys); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java index f9f4fa508a..81f378e8fa 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java @@ -21,6 +21,7 @@ import java.util.concurrent.Future; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.DisconnectReason; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.core.remoting.CloseListener; @@ -183,6 +184,13 @@ public interface RemotingConnection extends BufferHandler { */ void disconnect(String scaleDownNodeID, boolean criticalError); + /** + * Disconnect the connection, closing all channels + */ + default void disconnect(DisconnectReason reason, String targetNodeID, TransportConfiguration targetConnector) { + disconnect(reason.isScaleDown() ? targetNodeID : null, reason.isCriticalError()); + } + /** * returns true if any data has been received since the last time this method was called. * diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java index 37e699ea2f..764c2841a8 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java @@ -69,7 +69,8 @@ public interface ClientProtocolManager { boolean autoCommitAcks, boolean preAcknowledge, int minLargeMessageSize, - int confirmationWindowSize) throws ActiveMQException; + int confirmationWindowSize, + String clientID) throws ActiveMQException; boolean cleanupBeforeFailover(ActiveMQException cause); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java index 28584aee76..0067c1e3ea 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java @@ -172,4 +172,12 @@ public interface Connection { //returns true if one of the configs points to the same //node as this connection does. boolean isSameTarget(TransportConfiguration... configs); + + default String getSNIHostName() { + return null; + } + + default String getRedirectTo() { + return null; + } } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java index 55e202c94b..c987339fa2 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java @@ -16,6 +16,7 @@ */ package org.apache.activemq.artemis.spi.core.remoting; +import org.apache.activemq.artemis.api.core.DisconnectReason; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; @@ -23,7 +24,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; public interface TopologyResponseHandler { // This is sent when the server is telling the client the node is being disconnected - void nodeDisconnected(RemotingConnection conn, String nodeID, String scaleDownTargetNodeID); + void nodeDisconnected(RemotingConnection conn, String nodeID, DisconnectReason reason, String targetNodeID, TransportConfiguration tagetConnector); void notifyNodeUp(long uniqueEventID, String backupGroupName, diff --git a/artemis-core-client/src/main/resources/activemq-version.properties b/artemis-core-client/src/main/resources/activemq-version.properties index ff65ff998c..e8ece7632d 100644 --- a/artemis-core-client/src/main/resources/activemq-version.properties +++ b/artemis-core-client/src/main/resources/activemq-version.properties @@ -20,4 +20,4 @@ activemq.version.minorVersion=${activemq.version.minorVersion} activemq.version.microVersion=${activemq.version.microVersion} activemq.version.incrementingVersion=${activemq.version.incrementingVersion} activemq.version.versionTag=${activemq.version.versionTag} -activemq.version.compatibleVersionList=121,122,123,124,125,126,127,128,129,130 +activemq.version.compatibleVersionList=121,122,123,124,125,126,127,128,129,130,131 diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java index d59799aff6..1451f1593e 100644 --- a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java +++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java @@ -42,7 +42,8 @@ public class OperationAnnotationTest { {DivertControl.class}, {AcceptorControl.class}, {ClusterConnectionControl.class}, - {BroadcastGroupControl.class}}); + {BroadcastGroupControl.class}, + {BrokerBalancerControl.class}}); } private Class managementClass; diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java index b3026ad030..61f5f3fbfb 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java @@ -79,14 +79,6 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme public static final SimpleString CONNECTION_ID_PROPERTY_NAME = MessageUtil.CONNECTION_ID_PROPERTY_NAME; - /** - * Just like {@link ClientSession.AddressQuery#JMS_SESSION_IDENTIFIER_PROPERTY} this is - * used to identify the ClientID over JMS Session. - * However this is only used when the JMS Session.clientID is set (which is optional). - * With this property management tools and the server can identify the jms-client-id used over JMS - */ - public static String JMS_SESSION_CLIENT_ID_PROPERTY = "jms-client-id"; - // Static --------------------------------------------------------------------------------------- // Attributes ----------------------------------------------------------------------------------- @@ -271,7 +263,7 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme private void validateClientID(ClientSession validateSession, String clientID) throws InvalidClientIDException, ActiveMQException { try { - validateSession.addUniqueMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID); + validateSession.addUniqueMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID); } catch (ActiveMQException e) { if (e.getType() == ActiveMQExceptionType.DUPLICATE_METADATA) { throw new InvalidClientIDException("clientID=" + clientID + " was already set into another connection"); @@ -605,17 +597,17 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme boolean isBlockOnAcknowledge = sessionFactory.getServerLocator().isBlockOnAcknowledge(); int ackBatchSize = sessionFactory.getServerLocator().getAckBatchSize(); if (acknowledgeMode == Session.SESSION_TRANSACTED) { - session = sessionFactory.createSession(username, password, isXA, false, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize); + session = sessionFactory.createSession(username, password, isXA, false, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize, clientID); } else if (acknowledgeMode == Session.AUTO_ACKNOWLEDGE) { - session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), 0); + session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), 0, clientID); } else if (acknowledgeMode == Session.DUPS_OK_ACKNOWLEDGE) { - session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), dupsOKBatchSize); + session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), dupsOKBatchSize, clientID); } else if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) { - session = sessionFactory.createSession(username, password, isXA, true, false, sessionFactory.getServerLocator().isPreAcknowledge(), isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize); + session = sessionFactory.createSession(username, password, isXA, true, false, sessionFactory.getServerLocator().isPreAcknowledge(), isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize, clientID); } else if (acknowledgeMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE) { - session = sessionFactory.createSession(username, password, isXA, true, false, false, isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize); + session = sessionFactory.createSession(username, password, isXA, true, false, false, isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize, clientID); } else if (acknowledgeMode == ActiveMQJMSConstants.PRE_ACKNOWLEDGE) { - session = sessionFactory.createSession(username, password, isXA, true, false, true, transactionBatchSize); + session = sessionFactory.createSession(username, password, isXA, true, false, true, transactionBatchSize, clientID); } else { throw new JMSRuntimeException("Invalid ackmode: " + acknowledgeMode); } @@ -636,8 +628,6 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme session.start(); } - this.addSessionMetaData(session); - return jbs; } catch (ActiveMQException e) { throw JMSExceptionHelper.convertFromActiveMQException(e); @@ -681,13 +671,13 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme public void authorize(boolean validateClientId) throws JMSException { try { - initialSession = sessionFactory.createSession(username, password, false, false, false, false, 0); + initialSession = sessionFactory.createSession(username, password, false, false, false, false, 0, clientID); if (clientID != null) { if (validateClientId) { validateClientID(initialSession, clientID); } else { - initialSession.addMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID); + initialSession.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID); } } @@ -703,7 +693,7 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme private void addSessionMetaData(ClientSession session) throws ActiveMQException { session.addMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY, ""); if (clientID != null) { - session.addMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID); + session.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID); } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java index 6168c31c76..30b4b51532 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java @@ -36,6 +36,7 @@ import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientProtocolMana import org.apache.activemq.artemis.protocol.amqp.connect.mirror.ReferenceNodeStore; import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext; import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConstants; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport; import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory; import org.apache.activemq.artemis.protocol.amqp.sasl.MechanismFinder; @@ -53,7 +54,7 @@ import org.jboss.logging.Logger; /** * A proton protocol manager, basically reads the Proton Input and maps proton resources to ActiveMQ Artemis resources */ -public class ProtonProtocolManager extends AbstractProtocolManager implements NotificationListener { +public class ProtonProtocolManager extends AbstractProtocolManager implements NotificationListener { private static final Logger logger = Logger.getLogger(ProtonProtocolManager.class); @@ -105,6 +106,7 @@ public class ProtonProtocolManager extends AbstractProtocolManager + * 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.protocol.amqp.proton; + +import org.apache.activemq.artemis.core.server.balancing.RedirectContext; +import org.apache.qpid.proton.engine.Connection; + +public class AMQPRedirectContext extends RedirectContext { + private final Connection protonConnection; + + + public Connection getProtonConnection() { + return protonConnection; + } + + + public AMQPRedirectContext(AMQPConnectionContext connectionContext, Connection protonConnection) { + super(connectionContext.getConnectionCallback().getProtonConnectionDelegate(), connectionContext.getRemoteContainer(), + connectionContext.getSASLResult() != null ? connectionContext.getSASLResult().getUser() : null); + this.protonConnection = protonConnection; + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java new file mode 100644 index 0000000000..d852a3aab1 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java @@ -0,0 +1,64 @@ +/** + * 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.protocol.amqp.proton; + +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; +import org.apache.activemq.artemis.utils.ConfigurationHelper; +import org.apache.qpid.proton.amqp.transport.ConnectionError; +import org.apache.qpid.proton.amqp.transport.ErrorCondition; +import org.apache.qpid.proton.engine.Connection; + +import java.util.HashMap; +import java.util.Map; + +public class AMQPRedirectHandler extends RedirectHandler { + + public AMQPRedirectHandler(ActiveMQServer server) { + super(server); + } + + + public boolean redirect(AMQPConnectionContext connectionContext, Connection protonConnection) throws Exception { + return redirect(new AMQPRedirectContext(connectionContext, protonConnection)); + } + + @Override + protected void cannotRedirect(AMQPRedirectContext context) throws Exception { + ErrorCondition error = new ErrorCondition(); + error.setCondition(ConnectionError.CONNECTION_FORCED); + error.setDescription(String.format("Broker balancer %s is not ready to redirect", context.getConnection().getTransportConnection().getRedirectTo())); + context.getProtonConnection().setCondition(error); + } + + @Override + protected void redirectTo(AMQPRedirectContext context) throws Exception { + String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams()); + int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams()); + + ErrorCondition error = new ErrorCondition(); + error.setCondition(ConnectionError.REDIRECT); + error.setDescription(String.format("Connection redirected to %s:%d by broker balancer %s", host, port, context.getConnection().getTransportConnection().getRedirectTo())); + Map info = new HashMap(); + info.put(AmqpSupport.NETWORK_HOST, host); + info.put(AmqpSupport.PORT, port); + error.setInfo(info); + context.getProtonConnection().setCondition(error); + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java index 82d3ec17a7..485b0efd62 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java @@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -30,7 +31,7 @@ public class AnonymousServerSASLFactory implements ServerSASLFactory { } @Override - public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection) { return new AnonymousServerSASL(); } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java index e9087bec9d..9bcaf05f2a 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java @@ -21,6 +21,7 @@ import java.security.Principal; import org.apache.activemq.artemis.core.remoting.CertificateUtil; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -39,7 +40,7 @@ public class ExternalServerSASLFactory implements ServerSASLFactory { } @Override - public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection) { // validate ssl cert present Principal principal = CertificateUtil.getPeerPrincipalFromConnection(remotingConnection); diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java index e31632a607..098668e9f8 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java @@ -19,6 +19,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -34,7 +35,7 @@ public class GSSAPIServerSASLFactory implements ServerSASLFactory { } @Override - public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection) { if (manager instanceof ProtonProtocolManager) { GSSAPIServerSASL gssapiServerSASL = new GSSAPIServerSASL(); diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java index 5a88a82a9d..2596c820b8 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java @@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -30,7 +31,7 @@ public class PlainServerSASLFactory implements ServerSASLFactory { } @Override - public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection) { return new PlainSASL(server.getSecurityStore(), manager.getSecurityDomain(), connection.getProtocolConnection()); } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java index 9831652a61..ded4b2739f 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java @@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; @@ -40,7 +41,7 @@ public interface ServerSASLFactory { * @param remotingConnection * @return a new instance of {@link ServerSASL} that implements the provided mechanism */ - ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection); /** diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java index 67ec428efd..37190052f7 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java @@ -31,6 +31,7 @@ import javax.security.auth.login.LoginException; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor; import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager; +import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler; import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASL; import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; @@ -67,7 +68,7 @@ public abstract class SCRAMServerSASLFactory implements ServerSASLFactory { } @Override - public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, + public ServerSASL create(ActiveMQServer server, ProtocolManager manager, Connection connection, RemotingConnection remotingConnection) { try { if (manager instanceof ProtonProtocolManager) { diff --git a/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java b/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java index 273038362c..84498d9600 100644 --- a/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java +++ b/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java @@ -23,11 +23,9 @@ import org.apache.activemq.artemis.core.protocol.core.Channel; import org.apache.activemq.artemis.core.protocol.core.Packet; import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQClientProtocolManager; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage; -import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory; -import org.apache.activemq.artemis.core.version.Version; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.remoting.SessionContext; @@ -49,7 +47,7 @@ public class HornetQClientProtocolManager extends ActiveMQClientProtocolManager } @Override - protected Packet newCreateSessionPacket(Version clientVersion, + protected Packet newCreateSessionPacket(int clientVersion, String name, String username, String password, @@ -59,8 +57,9 @@ public class HornetQClientProtocolManager extends ActiveMQClientProtocolManager boolean preAcknowledge, int minLargeMessageSize, int confirmationWindowSize, - long sessionChannelID) { - return new CreateSessionMessage(name, sessionChannelID, VERSION_PLAYED, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null); + long sessionChannelID, + String clientID) { + return super.newCreateSessionPacket(VERSION_PLAYED, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID, clientID); } @Override diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java index d34ade5180..82bf22192a 100644 --- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java +++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java @@ -28,6 +28,7 @@ import io.netty.handler.codec.mqtt.MqttFixedHeader; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttProperties; import io.netty.handler.codec.mqtt.MqttPubAckMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; @@ -176,10 +177,13 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter { * @param connect */ void handleConnect(MqttConnectMessage connect) throws Exception { - connectionEntry.ttl = connect.variableHeader().keepAliveTimeSeconds() * 1500L; + if (connection.getTransportConnection().getRedirectTo() == null || + !protocolManager.getRedirectHandler().redirect(connection, session, connect)) { + connectionEntry.ttl = connect.variableHeader().keepAliveTimeSeconds() * 1500L; - String clientId = connect.payload().clientIdentifier(); - session.getConnectionManager().connect(clientId, connect.payload().userName(), connect.payload().passwordInBytes(), connect.variableHeader().isWillFlag(), connect.payload().willMessageInBytes(), connect.payload().willTopic(), connect.variableHeader().isWillRetain(), connect.variableHeader().willQos(), connect.variableHeader().isCleanSession()); + String clientId = connect.payload().clientIdentifier(); + session.getConnectionManager().connect(clientId, connect.payload().userName(), connect.payload().passwordInBytes(), connect.variableHeader().isWillFlag(), connect.payload().willMessageInBytes(), connect.payload().willTopic(), connect.variableHeader().isWillRetain(), connect.variableHeader().willQos(), connect.variableHeader().isCleanSession()); + } } void disconnect(boolean error) { @@ -187,8 +191,12 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter { } void sendConnack(MqttConnectReturnCode returnCode) { + sendConnack(returnCode, MqttProperties.NO_PROPERTIES); + } + + void sendConnack(MqttConnectReturnCode returnCode, MqttProperties properties) { MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); - MqttConnAckVariableHeader varHeader = new MqttConnAckVariableHeader(returnCode, true); + MqttConnAckVariableHeader varHeader = new MqttConnAckVariableHeader(returnCode, true, properties); MqttConnAckMessage message = new MqttConnAckMessage(fixedHeader, varHeader); sendToClient(message); } diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java index d0f5d1283f..6513247328 100644 --- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java +++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java @@ -48,7 +48,7 @@ import org.apache.activemq.artemis.utils.collections.TypedProperties; /** * MQTTProtocolManager */ -public class MQTTProtocolManager extends AbstractProtocolManager implements NotificationListener { +public class MQTTProtocolManager extends AbstractProtocolManager implements NotificationListener { private static final List websocketRegistryNames = Arrays.asList("mqtt", "mqttv3.1"); @@ -62,6 +62,8 @@ public class MQTTProtocolManager extends AbstractProtocolManager connectedClients; private final Map sessionStates; + private final MQTTRedirectHandler redirectHandler; + MQTTProtocolManager(ActiveMQServer server, Map connectedClients, Map sessionStates, @@ -72,6 +74,7 @@ public class MQTTProtocolManager extends AbstractProtocolManager + * 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.protocol.mqtt; + +import io.netty.handler.codec.mqtt.MqttConnectMessage; +import org.apache.activemq.artemis.core.server.balancing.RedirectContext; + +public class MQTTRedirectContext extends RedirectContext { + + private final MQTTSession mqttSession; + + + public MQTTSession getMQTTSession() { + return mqttSession; + } + + + public MQTTRedirectContext(MQTTConnection mqttConnection, MQTTSession mqttSession, MqttConnectMessage connect) { + super(mqttConnection, connect.payload().clientIdentifier(), connect.payload().userName()); + this.mqttSession = mqttSession; + } +} diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java new file mode 100644 index 0000000000..3b372032c3 --- /dev/null +++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java @@ -0,0 +1,55 @@ +/** + * 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.protocol.mqtt; + +import io.netty.handler.codec.mqtt.MqttConnectMessage; +import io.netty.handler.codec.mqtt.MqttConnectReturnCode; +import io.netty.handler.codec.mqtt.MqttProperties; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; +import org.apache.activemq.artemis.utils.ConfigurationHelper; + +public class MQTTRedirectHandler extends RedirectHandler { + + protected MQTTRedirectHandler(ActiveMQServer server) { + super(server); + } + + public boolean redirect(MQTTConnection mqttConnection, MQTTSession mqttSession, MqttConnectMessage connect) throws Exception { + return redirect(new MQTTRedirectContext(mqttConnection, mqttSession, connect)); + } + + @Override + protected void cannotRedirect(MQTTRedirectContext context) throws Exception { + context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE); + context.getMQTTSession().getProtocolHandler().disconnect(true); + } + + @Override + protected void redirectTo(MQTTRedirectContext context) throws Exception { + String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams()); + int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams()); + + MqttProperties mqttProperties = new MqttProperties(); + mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.SERVER_REFERENCE.value(), String.format("%s:%d", host, port))); + + context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER, mqttProperties); + context.getMQTTSession().getProtocolHandler().disconnect(true); + } +} diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java index 5b4586d5b0..a77fec6df8 100644 --- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java @@ -1146,6 +1146,12 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se @Override public Response processAddConnection(ConnectionInfo info) throws Exception { try { + if (transportConnection.getRedirectTo() != null && protocolManager.getRedirectHandler() + .redirect(OpenWireConnection.this, info)) { + shutdown(true); + return null; + } + protocolManager.addConnection(OpenWireConnection.this, info); } catch (Exception e) { Response resp = new ExceptionResponse(e); diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java index bcc39feb3e..852f4dcfda 100644 --- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java @@ -81,7 +81,7 @@ import org.apache.activemq.util.LongSequenceGenerator; import static org.apache.activemq.artemis.core.protocol.openwire.util.OpenWireUtil.SELECTOR_AWARE_OPTION; -public class OpenWireProtocolManager extends AbstractProtocolManager implements ClusterTopologyListener { +public class OpenWireProtocolManager extends AbstractProtocolManager implements ClusterTopologyListener { private static final List websocketRegistryNames = Collections.EMPTY_LIST; @@ -137,6 +137,7 @@ public class OpenWireProtocolManager extends AbstractProtocolManager incomingInterceptors = new ArrayList<>(); private final List outgoingInterceptors = new ArrayList<>(); + private final OpenWireRedirectHandler redirectHandler; protected static class VirtualTopicConfig { public int filterPathTerminus; @@ -187,6 +188,8 @@ public class OpenWireProtocolManager extends AbstractProtocolManager + * 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.protocol.openwire; + +import org.apache.activemq.artemis.core.server.balancing.RedirectContext; +import org.apache.activemq.command.ConnectionInfo; + +public class OpenWireRedirectContext extends RedirectContext { + + private final OpenWireConnection openWireConnection; + + + public OpenWireConnection getOpenWireConnection() { + return openWireConnection; + } + + + public OpenWireRedirectContext(OpenWireConnection openWireConnection, ConnectionInfo connectionInfo) { + super(openWireConnection.getRemotingConnection(), connectionInfo.getClientId(), connectionInfo.getUserName()); + this.openWireConnection = openWireConnection; + } +} diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java new file mode 100644 index 0000000000..83510afad0 --- /dev/null +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java @@ -0,0 +1,58 @@ +/** + * 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.protocol.openwire; + +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; +import org.apache.activemq.artemis.utils.ConfigurationHelper; +import org.apache.activemq.command.ConnectionControl; +import org.apache.activemq.command.ConnectionInfo; + +public class OpenWireRedirectHandler extends RedirectHandler { + + private final OpenWireProtocolManager protocolManager; + + protected OpenWireRedirectHandler(ActiveMQServer server, OpenWireProtocolManager protocolManager) { + super(server); + this.protocolManager = protocolManager; + } + + public boolean redirect(OpenWireConnection openWireConnection, ConnectionInfo connectionInfo) throws Exception { + if (!connectionInfo.isFaultTolerant()) { + throw new java.lang.IllegalStateException("Client not fault tolerant"); + } + + return redirect(new OpenWireRedirectContext(openWireConnection, connectionInfo)); + } + + @Override + protected void cannotRedirect(OpenWireRedirectContext context) throws Exception { + } + + @Override + protected void redirectTo(OpenWireRedirectContext context) throws Exception { + String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams()); + int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams()); + + ConnectionControl command = protocolManager.newConnectionControl(); + command.setConnectedBrokers(String.format("tcp://%s:%d", host, port)); + command.setRebalanceConnection(true); + context.getOpenWireConnection().dispatchSync(command); + } +} diff --git a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java index 8546f9aa82..e0bfecd611 100644 --- a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java +++ b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java @@ -38,6 +38,7 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.ServerSession; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; import org.apache.activemq.artemis.logs.AuditLogger; import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry; @@ -52,7 +53,7 @@ import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProto /** * StompProtocolManager */ -public class StompProtocolManager extends AbstractProtocolManager { +public class StompProtocolManager extends AbstractProtocolManager { private static final List websocketRegistryNames = Arrays.asList("v10.stomp", "v11.stomp", "v12.stomp"); @@ -190,6 +191,11 @@ public class StompProtocolManager extends AbstractProtocolManager getBalancerConfigurations(); + + /** + * Sets the redirects configured for this server. + */ + Configuration setBalancerConfigurations(List configs); + + Configuration addBalancerConfiguration(BrokerBalancerConfiguration config); + /** * Returns the cluster connections configured for this server. *

diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java new file mode 100644 index 0000000000..1da1c04742 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java @@ -0,0 +1,95 @@ +/* + * 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.config.balancing; + +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; + +import java.io.Serializable; + +public class BrokerBalancerConfiguration implements Serializable { + + private String name = null; + private TargetKey targetKey = TargetKey.SOURCE_IP; + private String targetKeyFilter = null; + private String localTargetFilter = null; + private int cacheTimeout = -1; + private PoolConfiguration poolConfiguration = null; + private PolicyConfiguration policyConfiguration = null; + + public String getName() { + return name; + } + + public BrokerBalancerConfiguration setName(String name) { + this.name = name; + return this; + } + + public TargetKey getTargetKey() { + return targetKey; + } + + public BrokerBalancerConfiguration setTargetKey(TargetKey targetKey) { + this.targetKey = targetKey; + return this; + } + + public String getTargetKeyFilter() { + return targetKeyFilter; + } + + public BrokerBalancerConfiguration setTargetKeyFilter(String targetKeyFilter) { + this.targetKeyFilter = targetKeyFilter; + return this; + } + + public String getLocalTargetFilter() { + return localTargetFilter; + } + + public BrokerBalancerConfiguration setLocalTargetFilter(String localTargetFilter) { + this.localTargetFilter = localTargetFilter; + return this; + } + + public int getCacheTimeout() { + return cacheTimeout; + } + + public BrokerBalancerConfiguration setCacheTimeout(int cacheTimeout) { + this.cacheTimeout = cacheTimeout; + return this; + } + + public PolicyConfiguration getPolicyConfiguration() { + return policyConfiguration; + } + + public BrokerBalancerConfiguration setPolicyConfiguration(PolicyConfiguration policyConfiguration) { + this.policyConfiguration = policyConfiguration; + return this; + } + + public PoolConfiguration getPoolConfiguration() { + return poolConfiguration; + } + + public BrokerBalancerConfiguration setPoolConfiguration(PoolConfiguration poolConfiguration) { + this.poolConfiguration = poolConfiguration; + return this; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java new file mode 100644 index 0000000000..f1f863055d --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java @@ -0,0 +1,45 @@ +/** + * 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.config.balancing; + +import java.io.Serializable; +import java.util.Map; + +public class PolicyConfiguration implements Serializable { + private String name; + + private Map properties; + + public String getName() { + return name; + } + + public PolicyConfiguration setName(String name) { + this.name = name; + return this; + } + + public Map getProperties() { + return properties; + } + + public PolicyConfiguration setProperties(Map properties) { + this.properties = properties; + return this; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java new file mode 100644 index 0000000000..699184a22f --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java @@ -0,0 +1,123 @@ +/** + * 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.config.balancing; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +public class PoolConfiguration implements Serializable { + private String username; + + private String password; + + private boolean localTargetEnabled = false; + + private String clusterConnection = null; + + private List staticConnectors = Collections.emptyList(); + + private String discoveryGroupName = null; + + private int checkPeriod = 5000; + + private int quorumSize = 1; + + private int quorumTimeout = 3000; + + public String getUsername() { + return username; + } + + public PoolConfiguration setUsername(String username) { + this.username = username; + return this; + } + + public String getPassword() { + return password; + } + + public PoolConfiguration setPassword(String password) { + this.password = password; + return this; + } + + public int getCheckPeriod() { + return checkPeriod; + } + + public PoolConfiguration setCheckPeriod(int checkPeriod) { + this.checkPeriod = checkPeriod; + return this; + } + + public int getQuorumSize() { + return quorumSize; + } + + public PoolConfiguration setQuorumSize(int quorumSize) { + this.quorumSize = quorumSize; + return this; + } + + public int getQuorumTimeout() { + return quorumTimeout; + } + + public PoolConfiguration setQuorumTimeout(int quorumTimeout) { + this.quorumTimeout = quorumTimeout; + return this; + } + + public boolean isLocalTargetEnabled() { + return localTargetEnabled; + } + + public PoolConfiguration setLocalTargetEnabled(boolean localTargetEnabled) { + this.localTargetEnabled = localTargetEnabled; + return this; + } + + public String getClusterConnection() { + return clusterConnection; + } + + public PoolConfiguration setClusterConnection(String clusterConnection) { + this.clusterConnection = clusterConnection; + return this; + } + + public List getStaticConnectors() { + return staticConnectors; + } + + public PoolConfiguration setStaticConnectors(List staticConnectors) { + this.staticConnectors = staticConnectors; + return this; + } + + public String getDiscoveryGroupName() { + return discoveryGroupName; + } + + public PoolConfiguration setDiscoveryGroupName(String discoveryGroupName) { + this.discoveryGroupName = discoveryGroupName; + return this; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java index d36a804566..6aa7785f7c 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java @@ -47,6 +47,7 @@ import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; import org.apache.activemq.artemis.api.core.QueueConfiguration; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration; import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration; import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration; @@ -166,6 +167,8 @@ public class ConfigurationImpl implements Configuration, Serializable { protected List divertConfigurations = new ArrayList<>(); + protected List brokerBalancerConfigurations = new ArrayList<>(); + protected List clusterConfigurations = new ArrayList<>(); protected List amqpBrokerConnectConfigurations = new ArrayList<>(); @@ -820,6 +823,23 @@ public class ConfigurationImpl implements Configuration, Serializable { return this; } + @Override + public List getBalancerConfigurations() { + return brokerBalancerConfigurations; + } + + @Override + public ConfigurationImpl setBalancerConfigurations(final List configs) { + brokerBalancerConfigurations = configs; + return this; + } + + @Override + public ConfigurationImpl addBalancerConfiguration(final BrokerBalancerConfiguration config) { + brokerBalancerConfigurations.add(config); + return this; + } + @Deprecated @Override public List getQueueConfigurations() { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java index a6e04a89e1..4f1e2eedab 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java @@ -18,6 +18,7 @@ package org.apache.activemq.artemis.core.config.impl; import java.util.EnumSet; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType; import org.apache.activemq.artemis.core.server.JournalType; @@ -272,4 +273,14 @@ public final class Validators { } } }; + + public static final Validator TARGET_KEY = new Validator() { + @Override + public void validate(final String name, final Object value) { + String val = (String) value; + if (val == null || !EnumSet.allOf(TargetKey.class).contains(TargetKey.valueOf(val))) { + throw ActiveMQMessageBundle.BUNDLE.invalidTargetKey(val); + } + } + }; } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java index 93eb2dbb0e..d2e1d75cab 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java @@ -46,6 +46,8 @@ import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; +import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration; +import org.apache.activemq.artemis.core.config.balancing.PolicyConfiguration; import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration; import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration; @@ -62,6 +64,7 @@ import org.apache.activemq.artemis.core.config.WildcardConfiguration; import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement; import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType; import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement; +import org.apache.activemq.artemis.core.config.balancing.PoolConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationPolicySet; @@ -88,6 +91,8 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType; import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.core.server.SecuritySettingPlugin; +import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactoryResolver; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType; import org.apache.activemq.artemis.core.server.group.impl.GroupingHandlerConfiguration; import org.apache.activemq.artemis.core.server.metrics.ActiveMQMetricsPlugin; @@ -624,6 +629,21 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { parseDivertConfiguration(dvNode, config); } + + NodeList ccBalancers = e.getElementsByTagName("broker-balancers"); + + if (ccBalancers != null) { + NodeList ccBalancer = e.getElementsByTagName("broker-balancer"); + + if (ccBalancer != null) { + for (int i = 0; i < ccBalancer.getLength(); i++) { + Element ccNode = (Element) ccBalancer.item(i); + + parseBalancerConfiguration(ccNode, config); + } + } + } + // Persistence config config.setLargeMessagesDirectory(getString(e, "large-messages-directory", config.getLargeMessagesDirectory(), Validators.NOT_NULL_OR_EMPTY)); @@ -2620,7 +2640,87 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { mainConfig.getDivertConfigurations().add(config); } - /** + private void parseBalancerConfiguration(final Element e, final Configuration config) throws Exception { + BrokerBalancerConfiguration brokerBalancerConfiguration = new BrokerBalancerConfiguration(); + + brokerBalancerConfiguration.setName(e.getAttribute("name")); + + brokerBalancerConfiguration.setTargetKey(TargetKey.valueOf(getString(e, "target-key", brokerBalancerConfiguration.getTargetKey().name(), Validators.TARGET_KEY))); + + brokerBalancerConfiguration.setTargetKeyFilter(getString(e, "target-key-filter", brokerBalancerConfiguration.getTargetKeyFilter(), Validators.NO_CHECK)); + + brokerBalancerConfiguration.setLocalTargetFilter(getString(e, "local-target-filter", brokerBalancerConfiguration.getLocalTargetFilter(), Validators.NO_CHECK)); + + brokerBalancerConfiguration.setCacheTimeout(getInteger(e, "cache-timeout", + brokerBalancerConfiguration.getCacheTimeout(), Validators.MINUS_ONE_OR_GE_ZERO)); + + PolicyConfiguration policyConfiguration = null; + PoolConfiguration poolConfiguration = null; + NodeList children = e.getChildNodes(); + + for (int j = 0; j < children.getLength(); j++) { + Node child = children.item(j); + + if (child.getNodeName().equals("policy")) { + policyConfiguration = new PolicyConfiguration(); + parsePolicyConfiguration((Element)child, policyConfiguration); + brokerBalancerConfiguration.setPolicyConfiguration(policyConfiguration); + } else if (child.getNodeName().equals("pool")) { + poolConfiguration = new PoolConfiguration(); + parsePoolConfiguration((Element) child, config, poolConfiguration); + brokerBalancerConfiguration.setPoolConfiguration(poolConfiguration); + } + } + + config.getBalancerConfigurations().add(brokerBalancerConfiguration); + } + + private void parsePolicyConfiguration(final Element e, final PolicyConfiguration policyConfiguration) throws ClassNotFoundException { + String name = e.getAttribute("name"); + + PolicyFactoryResolver.getInstance().resolve(name); + + policyConfiguration.setName(name); + + policyConfiguration.setProperties(getMapOfChildPropertyElements(e)); + } + + private void parsePoolConfiguration(final Element e, final Configuration config, final PoolConfiguration poolConfiguration) throws Exception { + poolConfiguration.setUsername(getString(e, "username", null, Validators.NO_CHECK)); + + String password = getString(e, "password", null, Validators.NO_CHECK); + poolConfiguration.setPassword(password != null ? PasswordMaskingUtil.resolveMask( + config.isMaskPassword(), password, config.getPasswordCodec()) : null); + + poolConfiguration.setCheckPeriod(getInteger(e, "check-period", + poolConfiguration.getCheckPeriod(), Validators.GT_ZERO)); + + poolConfiguration.setQuorumSize(getInteger(e, "quorum-size", + poolConfiguration.getQuorumSize(), Validators.GT_ZERO)); + + poolConfiguration.setQuorumTimeout(getInteger(e, "quorum-timeout", + poolConfiguration.getQuorumTimeout(), Validators.GE_ZERO)); + + poolConfiguration.setLocalTargetEnabled(getBoolean(e, "local-target-enabled", poolConfiguration.isLocalTargetEnabled())); + + poolConfiguration.setClusterConnection(getString(e, "cluster-connection", null, Validators.NO_CHECK)); + + NodeList children = e.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeName().equals("discovery-group-ref")) { + poolConfiguration.setDiscoveryGroupName(child.getAttributes().getNamedItem("discovery-group-name").getNodeValue()); + } else if (child.getNodeName().equals("static-connectors")) { + List staticConnectorNames = new ArrayList<>(); + getStaticConnectors(staticConnectorNames, child); + poolConfiguration.setStaticConnectors(staticConnectorNames); + } + } + } + + /**RedirectConfiguration * @param e */ protected void parseWildcardConfiguration(final Element e, final Configuration mainConfig) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java new file mode 100644 index 0000000000..72963bb0c4 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java @@ -0,0 +1,160 @@ +/** + * 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.management.impl; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl; +import org.apache.activemq.artemis.core.persistence.StorageManager; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.utils.JsonLoader; + +import javax.json.JsonObjectBuilder; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanOperationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import java.util.Map; + +public class BrokerBalancerControlImpl extends AbstractControl implements BrokerBalancerControl { + private final BrokerBalancer balancer; + + + private static CompositeType parameterType; + + private static TabularType parametersType; + + private static CompositeType transportConfigurationType; + + private static CompositeType targetType; + + + public BrokerBalancerControlImpl(final BrokerBalancer balancer, final StorageManager storageManager) throws NotCompliantMBeanException { + super(BrokerBalancerControl.class, storageManager); + this.balancer = balancer; + } + + @Override + public CompositeData getTarget(String key) throws Exception { + Target target = balancer.getTarget(key); + + if (target != null) { + CompositeData connectorData = null; + TransportConfiguration connector = target.getConnector(); + + if (connector != null) { + TabularData paramsData = new TabularDataSupport(getParametersType()); + for (Map.Entry param : connector.getParams().entrySet()) { + paramsData.put(new CompositeDataSupport(getParameterType(), new String[]{"key", "value"}, + new Object[]{param.getKey(), param == null ? param : param.getValue().toString()})); + } + + connectorData = new CompositeDataSupport(getTransportConfigurationType(), + new String[]{"name", "factoryClassName", "params"}, + new Object[]{connector.getName(), connector.getFactoryClassName(), paramsData}); + } + + CompositeData targetData = new CompositeDataSupport(getTargetCompositeType(), + new String[]{"nodeID", "local", "connector"}, + new Object[]{target.getNodeID(), target.isLocal(), connectorData}); + + return targetData; + } + + return null; + } + + @Override + public String getTargetAsJSON(String key) { + Target target = balancer.getTarget(key); + + if (target != null) { + TransportConfiguration connector = target.getConnector(); + + JsonObjectBuilder targetDataBuilder = JsonLoader.createObjectBuilder() + .add("nodeID", target.getNodeID()) + .add("local", target.isLocal()); + + if (connector == null) { + targetDataBuilder.addNull("connector"); + } else { + targetDataBuilder.add("connector", connector.toJson()); + } + + return targetDataBuilder.build().toString(); + } + + return null; + } + + @Override + protected MBeanOperationInfo[] fillMBeanOperationInfo() { + return MBeanInfoHelper.getMBeanOperationsInfo(BrokerBalancerControl.class); + } + + @Override + protected MBeanAttributeInfo[] fillMBeanAttributeInfo() { + return MBeanInfoHelper.getMBeanAttributesInfo(BrokerBalancerControl.class); + } + + + private CompositeType getParameterType() throws OpenDataException { + if (parameterType == null) { + parameterType = new CompositeType("java.util.Map.Entry", + "Parameter", new String[]{"key", "value"}, new String[]{"Parameter key", "Parameter value"}, + new OpenType[]{SimpleType.STRING, SimpleType.STRING}); + } + return parameterType; + } + + private TabularType getParametersType() throws OpenDataException { + if (parametersType == null) { + parametersType = new TabularType("java.util.Map", + "Parameters", getParameterType(), new String[]{"key"}); + } + return parametersType; + } + + private CompositeType getTransportConfigurationType() throws OpenDataException { + if (transportConfigurationType == null) { + transportConfigurationType = new CompositeType(TransportConfiguration.class.getName(), + "TransportConfiguration", new String[]{"name", "factoryClassName", "params"}, + new String[]{"TransportConfiguration name", "TransportConfiguration factoryClassName", "TransportConfiguration params"}, + new OpenType[]{SimpleType.STRING, SimpleType.STRING, getParametersType()}); + } + return transportConfigurationType; + } + + private CompositeType getTargetCompositeType() throws OpenDataException { + if (targetType == null) { + targetType = new CompositeType(Target.class.getName(), + "Target", new String[]{"nodeID", "local", "connector"}, + new String[]{"Target nodeID", "Target local", "Target connector"}, + new OpenType[]{SimpleType.STRING, SimpleType.BOOLEAN, getTransportConfigurationType()}); + } + return targetType; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java index 34eeffb74c..17919cada0 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java @@ -55,7 +55,7 @@ public class ConsumerView extends ActiveMQAbstractView { String consumerClientID = consumer.getConnectionClientID(); if (consumerClientID == null && session.getMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY) != null) { //for the special case for JMS - consumerClientID = session.getMetaData("jms-client-id"); + consumerClientID = session.getMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY); } JsonObjectBuilder obj = JsonLoader.createObjectBuilder() diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java index ac3fcf6ddd..fb4a6d6ddf 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java @@ -54,7 +54,7 @@ public class ProducerView extends ActiveMQAbstractView { String sessionClientID = session.getRemotingConnection().getClientID(); //for the special case for JMS if (sessionClientID == null && session.getMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY) != null) { - sessionClientID = session.getMetaData("jms-client-id"); + sessionClientID = session.getMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY); } JsonObjectBuilder obj = JsonLoader.createObjectBuilder() diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java index 6bd0d2b4d2..4286eac032 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java @@ -44,6 +44,7 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.HttpAcceptorHandler; import org.apache.activemq.artemis.core.remoting.impl.netty.HttpKeepAliveRunnable; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector; +import org.apache.activemq.artemis.core.remoting.impl.netty.NettySNIHostnameHandler; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; @@ -111,6 +112,7 @@ public class ProtocolHandler { private int handshakeTimeout; + private NettySNIHostnameHandler nettySNIHostnameHandler; ProtocolDecoder(boolean http, boolean httpEnabled) { this.http = http; @@ -120,6 +122,8 @@ public class ProtocolHandler { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { + nettySNIHostnameHandler = ctx.pipeline().get(NettySNIHostnameHandler.class); + if (handshakeTimeout > 0) { timeoutFuture = scheduledThreadPool.schedule( () -> { ActiveMQServerLogger.LOGGER.handshakeTimeout(handshakeTimeout, nettyAcceptor.getName(), ctx.channel().remoteAddress().toString()); @@ -220,6 +224,7 @@ public class ProtocolHandler { protocolManagerToUse.addChannelHandlers(pipeline); pipeline.addLast("handler", channelHandler); NettyServerConnection connection = channelHandler.createConnection(ctx, protocolToUse, httpEnabled); + connection.setSNIHostname(nettySNIHostnameHandler != null ? nettySNIHostnameHandler.getHostname() : null); protocolManagerToUse.handshake(connection, new ChannelBufferWrapper(in)); pipeline.remove(this); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java index e90bcb2208..e63dbf5fc4 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java @@ -37,6 +37,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverReplyMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionResponseMessage; @@ -89,7 +90,8 @@ public class ActiveMQPacketHandler implements ChannelHandler { } switch (type) { - case PacketImpl.CREATESESSION: { + case PacketImpl.CREATESESSION: + case PacketImpl.CREATESESSION_V2: { CreateSessionMessage request = (CreateSessionMessage) packet; handleCreateSession(request); @@ -157,6 +159,14 @@ public class ActiveMQPacketHandler implements ChannelHandler { ActiveMQServerLogger.LOGGER.incompatibleVersionAfterConnect(request.getVersion(), connection.getChannelVersion()); } + if (request instanceof CreateSessionMessage_V2) { + connection.setClientID(((CreateSessionMessage_V2) request).getClientID()); + } + + if (connection.getTransportConnection().getRedirectTo() != null) { + protocolManager.getRedirectHandler().redirect(connection, request); + } + Channel channel = connection.getChannel(request.getSessionChannelID(), request.getWindowSize()); ActiveMQPrincipal activeMQPrincipal = null; @@ -187,6 +197,8 @@ public class ActiveMQPacketHandler implements ChannelHandler { if (e.getType() == ActiveMQExceptionType.INCOMPATIBLE_CLIENT_SERVER_VERSIONS) { incompatibleVersion = true; logger.debug("Sending ActiveMQException after Incompatible client", e); + } else if (e.getType() == ActiveMQExceptionType.REDIRECTED) { + logger.debug("Sending ActiveMQException after redirected client", e); } else { ActiveMQServerLogger.LOGGER.failedToCreateSession(e); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java new file mode 100644 index 0000000000..575c9c0475 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java @@ -0,0 +1,28 @@ +/** + * 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.protocol.core.impl; + +import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; +import org.apache.activemq.artemis.core.server.balancing.RedirectContext; + +public class ActiveMQRedirectContext extends RedirectContext { + public ActiveMQRedirectContext(CoreRemotingConnection connection, CreateSessionMessage message) { + super(connection, connection.getClientID(), message.getUsername()); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java new file mode 100644 index 0000000000..937bd273bd --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java @@ -0,0 +1,52 @@ +/** + * 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.protocol.core.impl; + +import org.apache.activemq.artemis.api.core.DisconnectReason; +import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage; +import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; + +public class ActiveMQRedirectHandler extends RedirectHandler { + + public ActiveMQRedirectHandler(ActiveMQServer server) { + super(server); + } + + public boolean redirect(CoreRemotingConnection connection, CreateSessionMessage message) throws Exception { + if (!connection.isVersionSupportRedirect()) { + throw ActiveMQMessageBundle.BUNDLE.incompatibleClientServer(); + } + + return redirect(new ActiveMQRedirectContext(connection, message)); + } + + @Override + public void cannotRedirect(ActiveMQRedirectContext context) throws Exception { + throw ActiveMQMessageBundle.BUNDLE.cannotRedirect(); + } + + @Override + public void redirectTo(ActiveMQRedirectContext context) throws Exception { + context.getConnection().disconnect(DisconnectReason.REDIRECT, context.getTarget().getNodeID(), context.getTarget().getConnector()); + + throw ActiveMQMessageBundle.BUNDLE.redirectConnection(context.getTarget().getConnector()); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java index 84baf255c1..c56513b171 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java @@ -55,6 +55,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_I import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage; @@ -72,7 +73,7 @@ import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.jboss.logging.Logger; -public class CoreProtocolManager implements ProtocolManager { +public class CoreProtocolManager implements ProtocolManager { private static final Logger logger = Logger.getLogger(CoreProtocolManager.class); @@ -90,6 +91,8 @@ public class CoreProtocolManager implements ProtocolManager { private String securityDomain; + private final ActiveMQRedirectHandler redirectHandler; + public CoreProtocolManager(final CoreProtocolManagerFactory factory, final ActiveMQServer server, final List incomingInterceptors, @@ -101,6 +104,8 @@ public class CoreProtocolManager implements ProtocolManager { this.incomingInterceptors = incomingInterceptors; this.outgoingInterceptors = outgoingInterceptors; + + this.redirectHandler = new ActiveMQRedirectHandler(server); } @Override @@ -233,6 +238,11 @@ public class CoreProtocolManager implements ProtocolManager { return securityDomain; } + @Override + public ActiveMQRedirectHandler getRedirectHandler() { + return redirectHandler; + } + private boolean isArtemis(ActiveMQBuffer buffer) { return buffer.getByte(0) == 'A' && buffer.getByte(1) == 'R' && @@ -304,7 +314,13 @@ public class CoreProtocolManager implements ProtocolManager { entry.connectionExecutor.execute(new Runnable() { @Override public void run() { - if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) { + if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V4)) { + channel0.send(new ClusterTopologyChangeMessage_V4( + topologyMember.getUniqueEventID(), nodeID, + topologyMember.getBackupGroupName(), + topologyMember.getScaleDownGroupName(), connectorPair, + last, server.getVersion().getIncrementingVersion())); + } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) { channel0.send(new ClusterTopologyChangeMessage_V3( topologyMember.getUniqueEventID(), nodeID, topologyMember.getBackupGroupName(), @@ -380,7 +396,10 @@ public class CoreProtocolManager implements ProtocolManager { String nodeId = server.getNodeID().toString(); Pair emptyConfig = new Pair<>( null, null); - if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { + if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V4)) { + channel0.send(new ClusterTopologyChangeMessage_V4(System.currentTimeMillis(), nodeId, + null, null, emptyConfig, true, server.getVersion().getIncrementingVersion())); + } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { channel0.send( new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(), nodeId, null, emptyConfig, true)); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java index 72c732f620..2005c2dad6 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java @@ -237,6 +237,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final boolean autoStart; + private final String redirectTo; + final AtomicBoolean warningPrinted = new AtomicBoolean(false); final Executor failureExecutor; @@ -378,6 +380,8 @@ public class NettyAcceptor extends AbstractAcceptor { connectionsAllowed = ConfigurationHelper.getLongProperty(TransportConstants.CONNECTIONS_ALLOWED, TransportConstants.DEFAULT_CONNECTIONS_ALLOWED, configuration); autoStart = ConfigurationHelper.getBooleanProperty(TransportConstants.AUTO_START, TransportConstants.DEFAULT_AUTO_START, configuration); + + redirectTo = ConfigurationHelper.getStringProperty(TransportConstants.REDIRECT_TO, TransportConstants.DEFAULT_REDIRECT_TO, configuration); } private Object loadSSLContext() { @@ -460,6 +464,7 @@ public class NettyAcceptor extends AbstractAcceptor { if (sslEnabled) { final Pair peerInfo = getPeerInfo(channel); try { + pipeline.addLast("sni", new NettySNIHostnameHandler()); pipeline.addLast("ssl", getSslHandler(channel.alloc(), peerInfo.getA(), peerInfo.getB())); pipeline.addLast("sslHandshakeExceptionHandler", new SslHandshakeExceptionHandler()); } catch (Exception e) { @@ -930,7 +935,7 @@ public class NettyAcceptor extends AbstractAcceptor { super.channelActive(ctx); Listener connectionListener = new Listener(); - NettyServerConnection nc = new NettyServerConnection(configuration, ctx.channel(), connectionListener, !httpEnabled && batchDelay > 0, directDeliver); + NettyServerConnection nc = new NettyServerConnection(configuration, ctx.channel(), connectionListener, !httpEnabled && batchDelay > 0, directDeliver, redirectTo); connectionListener.connectionCreated(NettyAcceptor.this, nc, protocolHandler.getProtocol(protocol)); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java new file mode 100644 index 0000000000..9345738a6e --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java @@ -0,0 +1,42 @@ +/** + * 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.remoting.impl.netty; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.ssl.AbstractSniHandler; +import io.netty.util.concurrent.Future; + +public class NettySNIHostnameHandler extends AbstractSniHandler { + + private String hostname = null; + + public String getHostname() { + return hostname; + } + + @Override + protected Future lookup(ChannelHandlerContext ctx, String hostname) throws Exception { + return ctx.executor().newPromise().setSuccess(null); + } + + @Override + protected void onLookupComplete(ChannelHandlerContext ctx, String hostname, Future future) throws Exception { + this.hostname = hostname; + ctx.pipeline().remove(this); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java index f9e1b3d2f2..7fadb1864a 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java @@ -23,12 +23,32 @@ import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleLi public class NettyServerConnection extends NettyConnection { + private String sniHostname; + + private final String redirectTo; + public NettyServerConnection(Map configuration, Channel channel, ServerConnectionLifeCycleListener listener, boolean batchingEnabled, - boolean directDeliver) { + boolean directDeliver, + String redirectTo) { super(configuration, channel, listener, batchingEnabled, directDeliver); + + this.redirectTo = redirectTo; } + @Override + public String getSNIHostName() { + return sniHostname; + } + + public void setSNIHostname(String sniHostname) { + this.sniHostname = sniHostname; + } + + @Override + public String getRedirectTo() { + return redirectTo; + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java index e37fddd82f..f1e6170920 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java @@ -39,6 +39,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQInvalidTransientQueueUseExce import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException; import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException; import org.apache.activemq.artemis.api.core.ActiveMQQueueMaxConsumerLimitReached; +import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException; import org.apache.activemq.artemis.api.core.ActiveMQReplicationTimeooutException; import org.apache.activemq.artemis.api.core.ActiveMQSecurityException; import org.apache.activemq.artemis.api.core.ActiveMQSessionCreationException; @@ -46,6 +47,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQUnexpectedRoutingTypeForAddr import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.postoffice.Binding; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReplicationSyncFileMessage; @@ -504,4 +506,13 @@ public interface ActiveMQMessageBundle { @Message(id = 229235, value = "Incompatible binding with name {0} already exists: {1}", format = Message.Format.MESSAGE_FORMAT) ActiveMQIllegalStateException bindingAlreadyExists(String name, String binding); + + @Message(id = 229236, value = "Invalid target key {0}", format = Message.Format.MESSAGE_FORMAT) + IllegalArgumentException invalidTargetKey(String val); + + @Message(id = 229237, value = "Connection redirected to {0}", format = Message.Format.MESSAGE_FORMAT) + ActiveMQRedirectedException redirectConnection(TransportConfiguration connector); + + @Message(id = 229238, value = "No target to redirect the connection") + ActiveMQRedirectedException cannotRedirect(); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java index e3249793e6..27cb0d6d1b 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java @@ -67,6 +67,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugi import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancerManager; import org.apache.activemq.artemis.core.server.reload.ReloadManager; import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; @@ -943,4 +944,6 @@ public interface ActiveMQServer extends ServiceComponent { double getDiskStoreUsage(); void reloadConfigurationFile() throws Exception; + + BrokerBalancerManager getBalancerManager(); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java index 7d46a0d6a6..bb72f57ebd 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java @@ -40,6 +40,7 @@ import org.apache.activemq.artemis.core.persistence.OperationContext; import org.apache.activemq.artemis.core.protocol.core.Packet; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.BackupReplicationStartFailedMessage; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; 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; @@ -47,6 +48,7 @@ import org.apache.activemq.artemis.core.server.cluster.qourum.ServerConnectVote; import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl; import org.apache.activemq.artemis.core.server.impl.ServerSessionImpl; import org.apache.activemq.artemis.core.server.management.Notification; +import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; @@ -451,6 +453,14 @@ public interface ActiveMQServerLogger extends BasicLogger { @Message(id = 221084, value = "Requested {0} quorum votes", format = Message.Format.MESSAGE_FORMAT) void requestedQuorumVotes(int vote); + @LogMessage(level = Logger.Level.INFO) + @Message(id = 221085, value = "Redirect {0} to {1}", format = Message.Format.MESSAGE_FORMAT) + void redirectClientConnection(Connection connection, Target target); + + @LogMessage(level = Logger.Level.INFO) + @Message(id = 221086, value = "Cannot redirect {0}", format = Message.Format.MESSAGE_FORMAT) + void cannotRedirectClientConnection(Connection connection); + @LogMessage(level = Logger.Level.WARN) @Message(id = 222000, value = "ActiveMQServer is being finalized and has not been stopped. Please remember to stop the server before letting it go out of scope", format = Message.Format.MESSAGE_FORMAT) @@ -2156,4 +2166,8 @@ public interface ActiveMQServerLogger extends BasicLogger { @LogMessage(level = Logger.Level.WARN) @Message(id = 224108, value = "Stopped paging on address ''{0}''; size is currently: {1} bytes; max-size-bytes: {2}; global-size-bytes: {3}", format = Message.Format.MESSAGE_FORMAT) void pageStoreStop(SimpleString storeName, long addressSize, long maxSize, long globalMaxSize); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 224109, value = "BrokerBalancer {0} not found", format = Message.Format.MESSAGE_FORMAT) + void brokerBalancerNotFound(String name); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java new file mode 100644 index 0000000000..93e38b1613 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java @@ -0,0 +1,193 @@ +/** + * 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.balancing; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; +import org.apache.activemq.artemis.core.server.ActiveMQComponent; +import org.apache.activemq.artemis.core.server.balancing.policies.Policy; +import org.apache.activemq.artemis.core.server.balancing.pools.Pool; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKeyResolver; +import org.apache.activemq.artemis.spi.core.remoting.Connection; +import org.jboss.logging.Logger; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +public class BrokerBalancer implements ActiveMQComponent { + private static final Logger logger = Logger.getLogger(BrokerBalancer.class); + + + public static final String CLIENT_ID_PREFIX = ActiveMQDefaultConfiguration.DEFAULT_INTERNAL_NAMING_PREFIX + "balancer.client."; + + + private final String name; + + private final TargetKey targetKey; + + private final TargetKeyResolver targetKeyResolver; + + private final Target localTarget; + + private final Pattern localTargetFilter; + + private final Pool pool; + + private final Policy policy; + + private final Cache cache; + + private volatile boolean started = false; + + public String getName() { + return name; + } + + public TargetKey getTargetKey() { + return targetKey; + } + + public Target getLocalTarget() { + return localTarget; + } + + public String getLocalTargetFilter() { + return localTargetFilter != null ? localTargetFilter.pattern() : null; + } + + public Pool getPool() { + return pool; + } + + public Policy getPolicy() { + return policy; + } + + public Cache getCache() { + return cache; + } + + @Override + public boolean isStarted() { + return started; + } + + + public BrokerBalancer(final String name, final TargetKey targetKey, final String targetKeyFilter, final Target localTarget, final String localTargetFilter, final Pool pool, final Policy policy, final int cacheTimeout) { + this.name = name; + + this.targetKey = targetKey; + + this.targetKeyResolver = new TargetKeyResolver(targetKey, targetKeyFilter); + + this.localTarget = localTarget; + + this.localTargetFilter = localTargetFilter != null ? Pattern.compile(localTargetFilter) : null; + + this.pool = pool; + + this.policy = policy; + + if (cacheTimeout == -1) { + this.cache = CacheBuilder.newBuilder().build(); + } else if (cacheTimeout > 0) { + this.cache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, TimeUnit.MILLISECONDS).build(); + } else { + this.cache = null; + } + } + + @Override + public void start() throws Exception { + pool.start(); + + started = true; + } + + @Override + public void stop() throws Exception { + started = false; + + pool.stop(); + } + + public Target getTarget(Connection connection, String clientID, String username) { + if (clientID != null && clientID.startsWith(BrokerBalancer.CLIENT_ID_PREFIX)) { + if (logger.isDebugEnabled()) { + logger.debug("The clientID [" + clientID + "] starts with BrokerBalancer.CLIENT_ID_PREFIX"); + } + + return localTarget; + } + + return getTarget(targetKeyResolver.resolve(connection, clientID, username)); + } + + public Target getTarget(String key) { + + if (this.localTargetFilter != null && this.localTargetFilter.matcher(key).matches()) { + if (logger.isDebugEnabled()) { + logger.debug("The " + targetKey + "[" + key + "] matches the localTargetFilter " + localTargetFilter.pattern()); + } + + return localTarget; + } + + Target target = null; + + if (cache != null) { + target = cache.getIfPresent(key); + } + + if (target != null) { + if (pool.isTargetReady(target)) { + if (logger.isDebugEnabled()) { + logger.debug("The cache returns [" + target + "] ready for " + targetKey + "[" + key + "]"); + } + + return target; + } + + if (logger.isDebugEnabled()) { + logger.debug("The cache returns [" + target + "] not ready for " + targetKey + "[" + key + "]"); + } + } + + List targets = pool.getTargets(); + + target = policy.selectTarget(targets, key); + + if (logger.isDebugEnabled()) { + logger.debug("The policy selects [" + target + "] from " + targets + " for " + targetKey + "[" + key + "]"); + } + + if (target != null && cache != null) { + if (logger.isDebugEnabled()) { + logger.debug("Caching " + targetKey + "[" + key + "] for [" + target + "]"); + } + + cache.put(key, target); + } + + return target; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java new file mode 100644 index 0000000000..fc5fba65b9 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java @@ -0,0 +1,191 @@ +/** + * 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.balancing; + +import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.cluster.DiscoveryGroup; +import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration; +import org.apache.activemq.artemis.core.config.balancing.PolicyConfiguration; +import org.apache.activemq.artemis.core.config.balancing.PoolConfiguration; +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.server.ActiveMQComponent; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.balancing.policies.Policy; +import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactory; +import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactoryResolver; +import org.apache.activemq.artemis.core.server.balancing.pools.ClusterPool; +import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryGroupService; +import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryPool; +import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryService; +import org.apache.activemq.artemis.core.server.balancing.pools.Pool; +import org.apache.activemq.artemis.core.server.balancing.pools.StaticPool; +import org.apache.activemq.artemis.core.server.balancing.targets.ActiveMQTargetFactory; +import org.apache.activemq.artemis.core.server.balancing.targets.LocalTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; +import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; +import org.jboss.logging.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; + +public final class BrokerBalancerManager implements ActiveMQComponent { + private static final Logger logger = Logger.getLogger(BrokerBalancerManager.class); + + + private final Configuration config; + + private final ActiveMQServer server; + + private final ScheduledExecutorService scheduledExecutor; + + private volatile boolean started = false; + + private Map balancerControllers = new HashMap<>(); + + + @Override + public boolean isStarted() { + return started; + } + + + public BrokerBalancerManager(final Configuration config, final ActiveMQServer server, ScheduledExecutorService scheduledExecutor) { + this.config = config; + this.server = server; + this.scheduledExecutor = scheduledExecutor; + } + + public void deploy() throws Exception { + for (BrokerBalancerConfiguration balancerConfig : config.getBalancerConfigurations()) { + deployBrokerBalancer(balancerConfig); + } + } + + public void deployBrokerBalancer(BrokerBalancerConfiguration config) throws Exception { + if (logger.isDebugEnabled()) { + logger.debugf("Deploying BrokerBalancer " + config.getName()); + } + + Target localTarget = new LocalTarget(null, server); + + Pool pool = deployPool(config.getPoolConfiguration(), localTarget); + + Policy policy = deployPolicy(config.getPolicyConfiguration(), pool); + + BrokerBalancer balancer = new BrokerBalancer(config.getName(), config.getTargetKey(), config.getTargetKeyFilter(), + localTarget, config.getLocalTargetFilter(), pool, policy, config.getCacheTimeout()); + + balancerControllers.put(balancer.getName(), balancer); + + server.getManagementService().registerBrokerBalancer(balancer); + } + + private Pool deployPool(PoolConfiguration config, Target localTarget) throws Exception { + Pool pool; + TargetFactory targetFactory = new ActiveMQTargetFactory(); + + targetFactory.setUsername(config.getUsername()); + targetFactory.setPassword(config.getPassword()); + + if (config.getClusterConnection() != null) { + ClusterConnection clusterConnection = server.getClusterManager() + .getClusterConnection(config.getClusterConnection()); + + pool = new ClusterPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), clusterConnection); + } else if (config.getDiscoveryGroupName() != null) { + DiscoveryGroupConfiguration discoveryGroupConfiguration = server.getConfiguration(). + getDiscoveryGroupConfigurations().get(config.getDiscoveryGroupName()); + + DiscoveryService discoveryService = new DiscoveryGroupService(new DiscoveryGroup(server.getNodeID().toString(), config.getDiscoveryGroupName(), + discoveryGroupConfiguration.getRefreshTimeout(), discoveryGroupConfiguration.getBroadcastEndpointFactory(), null)); + + pool = new DiscoveryPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), discoveryService); + } else if (config.getStaticConnectors() != null) { + Map connectorConfigurations = + server.getConfiguration().getConnectorConfigurations(); + + List staticConnectors = new ArrayList<>(); + for (String staticConnector : config.getStaticConnectors()) { + TransportConfiguration connector = connectorConfigurations.get(staticConnector); + + if (connector != null) { + staticConnectors.add(connector); + } else { + logger.warn("Static connector not found: " + config.isLocalTargetEnabled()); + } + } + + pool = new StaticPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), staticConnectors); + } else { + throw new IllegalStateException("Pool configuration not valid"); + } + + pool.setUsername(config.getUsername()); + pool.setPassword(config.getPassword()); + pool.setQuorumSize(config.getQuorumSize()); + pool.setQuorumTimeout(config.getQuorumTimeout()); + + if (config.isLocalTargetEnabled()) { + pool.addTarget(localTarget); + } + + return pool; + } + + private Policy deployPolicy(PolicyConfiguration policyConfig, Pool pool) throws ClassNotFoundException { + PolicyFactory policyFactory = PolicyFactoryResolver.getInstance().resolve(policyConfig.getName()); + + Policy policy = policyFactory.createPolicy(policyConfig.getName()); + + policy.init(policyConfig.getProperties()); + + if (policy.getTargetProbe() != null) { + pool.addTargetProbe(policy.getTargetProbe()); + } + + return policy; + } + + public BrokerBalancer getBalancer(String name) { + return balancerControllers.get(name); + } + + @Override + public void start() throws Exception { + for (BrokerBalancer brokerBalancer : balancerControllers.values()) { + brokerBalancer.start(); + } + + started = true; + } + + @Override + public void stop() throws Exception { + started = false; + + for (BrokerBalancer balancer : balancerControllers.values()) { + balancer.stop(); + server.getManagementService().unregisterBrokerBalancer(balancer.getName()); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java new file mode 100644 index 0000000000..76a2a5437d --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java @@ -0,0 +1,57 @@ +/** + * 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.balancing; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; + +public class RedirectContext { + private final RemotingConnection connection; + + private final String clientID; + + private final String username; + + private Target target; + + public RemotingConnection getConnection() { + return connection; + } + + public String getClientID() { + return clientID; + } + + public String getUsername() { + return username; + } + + public Target getTarget() { + return target; + } + + public void setTarget(Target target) { + this.target = target; + } + + public RedirectContext(RemotingConnection connection, String clientID, String username) { + this.connection = connection; + this.clientID = clientID; + this.username = username; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java new file mode 100644 index 0000000000..89ace13057 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java @@ -0,0 +1,74 @@ +/** + * 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.balancing; + +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; +import org.apache.activemq.artemis.spi.core.remoting.Connection; + +public abstract class RedirectHandler { + private final ActiveMQServer server; + + + public ActiveMQServer getServer() { + return server; + } + + + protected RedirectHandler(ActiveMQServer server) { + this.server = server; + } + + protected abstract void cannotRedirect(T context) throws Exception; + + protected abstract void redirectTo(T context) throws Exception; + + protected boolean redirect(T context) throws Exception { + Connection transportConnection = context.getConnection().getTransportConnection(); + + BrokerBalancer brokerBalancer = getServer().getBalancerManager().getBalancer(transportConnection.getRedirectTo()); + + if (brokerBalancer == null) { + ActiveMQServerLogger.LOGGER.brokerBalancerNotFound(transportConnection.getRedirectTo()); + + cannotRedirect(context); + + return true; + } + + context.setTarget(brokerBalancer.getTarget(transportConnection, context.getClientID(), context.getUsername())); + + if (context.getTarget() == null) { + ActiveMQServerLogger.LOGGER.cannotRedirectClientConnection(transportConnection); + + cannotRedirect(context); + + return true; + } + + ActiveMQServerLogger.LOGGER.redirectClientConnection(transportConnection, context.getTarget()); + + if (!context.getTarget().isLocal()) { + redirectTo(context); + + return true; + } + + return false; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java new file mode 100644 index 0000000000..8cb7a92783 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java @@ -0,0 +1,52 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe; + +import java.util.Map; + +public abstract class AbstractPolicy implements Policy { + private final String name; + + private Map properties; + + @Override + public String getName() { + return name; + } + + @Override + public Map getProperties() { + return properties; + } + + @Override + public TargetProbe getTargetProbe() { + return null; + } + + @Override + public void init(Map properties) { + this.properties = properties; + } + + public AbstractPolicy(final String name) { + this.name = name; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java new file mode 100644 index 0000000000..77d4076539 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java @@ -0,0 +1,75 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; + +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +public class ConsistentHashPolicy extends AbstractPolicy { + public static final String NAME = "CONSISTENT_HASH"; + + public ConsistentHashPolicy() { + super(NAME); + } + + protected ConsistentHashPolicy(String name) { + super(name); + } + + @Override + public Target selectTarget(List targets, String key) { + if (targets.size() > 1) { + NavigableMap consistentTargets = new TreeMap<>(); + + for (Target target : targets) { + consistentTargets.put(getHash(target.getNodeID()), target); + } + + if (consistentTargets.size() > 0) { + Map.Entry consistentEntry = consistentTargets.floorEntry(getHash(key)); + + if (consistentEntry == null) { + consistentEntry = consistentTargets.firstEntry(); + } + + return consistentEntry.getValue(); + } + } else if (targets.size() > 0) { + return targets.get(0); + } + + return null; + } + + private int getHash(String str) { + final int FNV_INIT = 0x811c9dc5; + final int FNV_PRIME = 0x01000193; + + int hash = FNV_INIT; + + for (int i = 0; i < str.length(); i++) { + hash = (hash ^ str.charAt(i)) * FNV_PRIME; + } + + return hash; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java new file mode 100644 index 0000000000..aa39787456 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java @@ -0,0 +1,49 @@ +/** + * 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.balancing.policies; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class DefaultPolicyFactory extends PolicyFactory { + private static final Map> supportedPolicies = new HashMap<>(); + + static { + supportedPolicies.put(ConsistentHashPolicy.NAME, () -> new ConsistentHashPolicy()); + supportedPolicies.put(FirstElementPolicy.NAME, () -> new FirstElementPolicy()); + supportedPolicies.put(LeastConnectionsPolicy.NAME, () -> new LeastConnectionsPolicy()); + supportedPolicies.put(RoundRobinPolicy.NAME, () -> new RoundRobinPolicy()); + } + + @Override + public String[] getSupportedPolicies() { + return supportedPolicies.keySet().toArray(new String[supportedPolicies.size()]); + } + + @Override + public AbstractPolicy createPolicy(String policyName) { + Supplier policySupplier = supportedPolicies.get(policyName); + + if (policySupplier == null) { + throw new IllegalArgumentException("Policy not supported: " + policyName); + } + + return policySupplier.get(); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java new file mode 100644 index 0000000000..de8bf5b4f5 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java @@ -0,0 +1,43 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; + +import java.util.List; + +public class FirstElementPolicy extends AbstractPolicy { + public static final String NAME = "FIRST_ELEMENT"; + + public FirstElementPolicy() { + super(NAME); + } + + protected FirstElementPolicy(String name) { + super(name); + } + + @Override + public Target selectTarget(List targets, String key) { + if (targets.size() > 0) { + return targets.get(0); + } + + return null; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java new file mode 100644 index 0000000000..184cfc9d64 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java @@ -0,0 +1,133 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe; +import org.jboss.logging.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +public class LeastConnectionsPolicy extends RoundRobinPolicy { + private static final Logger logger = Logger.getLogger(LeastConnectionsPolicy.class); + + public static final String NAME = "LEAST_CONNECTIONS"; + + public static final String UPDATE_CONNECTION_COUNT_PROBE_NAME = "UPDATE_CONNECTION_COUNT_PROBE"; + + public static final String CONNECTION_COUNT_THRESHOLD = "CONNECTION_COUNT_THRESHOLD"; + + + private final Map connectionCountCache = new ConcurrentHashMap<>(); + + + private int connectionCountThreshold = 0; + + + private final TargetProbe targetProbe = new TargetProbe(UPDATE_CONNECTION_COUNT_PROBE_NAME) { + @Override + public boolean check(Target target) { + try { + Integer connectionCount = target.getAttribute("broker", "ConnectionCount", Integer.class, 3000); + + if (connectionCount < connectionCountThreshold) { + if (logger.isDebugEnabled()) { + logger.debug("Updating the connection count to 0/" + connectionCount + " for the target " + target); + } + + connectionCount = 0; + } else if (logger.isDebugEnabled()) { + logger.debug("Updating the connection count to 0/" + connectionCount + " for the target " + target); + } + + connectionCountCache.put(target, connectionCount); + + return true; + } catch (Exception e) { + logger.warn("Error on updating the connectionCount for the target " + target, e); + + return false; + } + } + }; + + @Override + public TargetProbe getTargetProbe() { + return targetProbe; + } + + public LeastConnectionsPolicy() { + super(NAME); + } + + @Override + public void init(Map properties) { + super.init(properties); + + if (properties != null) { + if (properties.containsKey(CONNECTION_COUNT_THRESHOLD)) { + connectionCountThreshold = Integer.valueOf(properties.get(CONNECTION_COUNT_THRESHOLD)); + } + } + } + + @Override + public Target selectTarget(List targets, String key) { + if (targets.size() > 1) { + NavigableMap> sortedTargets = new TreeMap<>(); + + for (Target target : targets) { + Integer connectionCount = connectionCountCache.get(target); + + if (connectionCount == null) { + connectionCount = Integer.MAX_VALUE; + } + + List leastTargets = sortedTargets.get(connectionCount); + + if (leastTargets == null) { + leastTargets = new ArrayList<>(); + sortedTargets.put(connectionCount, leastTargets); + } + + leastTargets.add(target); + } + + if (logger.isDebugEnabled()) { + logger.debug("LeastConnectionsPolicy.sortedTargets: " + sortedTargets); + } + + List selectedTargets = sortedTargets.firstEntry().getValue(); + + if (selectedTargets.size() > 1) { + return super.selectTarget(selectedTargets, key); + } else { + return selectedTargets.get(0); + } + } else if (targets.size() > 0) { + return targets.get(0); + } + + return null; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java new file mode 100644 index 0000000000..e74f2ea3d7 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java @@ -0,0 +1,36 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe; + +import java.util.List; +import java.util.Map; + +public interface Policy { + String getName(); + + TargetProbe getTargetProbe(); + + Map getProperties(); + + void init(Map properties); + + Target selectTarget(List targets, String key); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java new file mode 100644 index 0000000000..4c745ee982 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java @@ -0,0 +1,24 @@ +/** + * 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.balancing.policies; + +public abstract class PolicyFactory { + public abstract String[] getSupportedPolicies(); + + public abstract Policy createPolicy(String policyName); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java new file mode 100644 index 0000000000..dab4f9359b --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java @@ -0,0 +1,74 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +public class PolicyFactoryResolver { + private static PolicyFactoryResolver instance; + + public static PolicyFactoryResolver getInstance() { + if (instance == null) { + instance = new PolicyFactoryResolver(); + } + return instance; + } + + private final Map policyFactories = new HashMap<>(); + + private PolicyFactoryResolver() { + registerPolicyFactory(new DefaultPolicyFactory()); + + loadPolicyFactories(); + } + + public PolicyFactory resolve(String policyName) throws ClassNotFoundException { + PolicyFactory policyFactory = policyFactories.get(policyName); + + if (policyFactory == null) { + throw new ClassNotFoundException("No PolicyFactory found for the policy " + policyName); + } + + return policyFactory; + } + + private void loadPolicyFactories() { + ServiceLoader serviceLoader = ServiceLoader.load( + PolicyFactory.class, BrokerBalancer.class.getClassLoader()); + + for (PolicyFactory policyFactory : serviceLoader) { + registerPolicyFactory(policyFactory); + } + } + + public void registerPolicyFactory(PolicyFactory policyFactory) { + for (String policyName : policyFactory.getSupportedPolicies()) { + policyFactories.put(policyName, policyFactory); + } + } + + public void unregisterPolicyFactory(PolicyFactory policyFactory) { + for (String policyName : policyFactory.getSupportedPolicies()) { + policyFactories.remove(policyName, policyFactory); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java new file mode 100644 index 0000000000..7698d5e892 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java @@ -0,0 +1,47 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.utils.RandomUtil; + +import java.util.List; + +public class RoundRobinPolicy extends AbstractPolicy { + public static final String NAME = "ROUND_ROBIN"; + + private int pos = RandomUtil.randomInterval(0, Integer.MAX_VALUE); + + public RoundRobinPolicy() { + super(NAME); + } + + protected RoundRobinPolicy(String name) { + super(name); + } + + @Override + public Target selectTarget(List targets, String key) { + if (targets.size() > 0) { + pos = pos % targets.size(); + return targets.get(pos++); + } + + return null; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java new file mode 100644 index 0000000000..5ab614505c --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java @@ -0,0 +1,243 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetMonitor; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe; +import org.jboss.logging.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.stream.Collectors; + +public abstract class AbstractPool implements Pool { + private static final Logger logger = Logger.getLogger(AbstractPool.class); + + private final TargetFactory targetFactory; + + private final ScheduledExecutorService scheduledExecutor; + + private final int checkPeriod; + + private final List targetProbes = new ArrayList<>(); + + private final Map targets = new ConcurrentHashMap<>(); + + private final List targetMonitors = new CopyOnWriteArrayList<>(); + + private String username; + + private String password; + + private int quorumSize; + + private int quorumTimeout; + + private long quorumTimeoutNanos; + + private final long quorumParkNanos = TimeUnit.MILLISECONDS.toNanos(100); + + private volatile boolean started = false; + + + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public void setPassword(String password) { + this.password = password; + } + + @Override + public int getCheckPeriod() { + return checkPeriod; + } + + @Override + public int getQuorumSize() { + return quorumSize; + } + + @Override + public int getQuorumTimeout() { + return quorumTimeout; + } + + @Override + public void setQuorumTimeout(int quorumTimeout) { + this.quorumTimeout = quorumTimeout; + this.quorumTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(quorumTimeout); + } + + @Override + public void setQuorumSize(int quorumSize) { + this.quorumSize = quorumSize; + } + + @Override + public List getAllTargets() { + return targetMonitors.stream().map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList()); + } + + @Override + public List getTargets() { + List targets = targetMonitors.stream().filter(targetMonitor -> targetMonitor.isTargetReady()) + .map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList()); + + if (quorumTimeout > 0 && targets.size() < quorumSize) { + final long deadline = System.nanoTime() + quorumTimeoutNanos; + while (targets.size() < quorumSize && (System.nanoTime() - deadline) < 0) { + targets = targetMonitors.stream().filter(targetMonitor -> targetMonitor.isTargetReady()) + .map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList()); + + LockSupport.parkNanos(quorumParkNanos); + } + } + + if (logger.isDebugEnabled()) { + logger.debugf("Ready targets are " + targets + " / " + targetMonitors + " and quorumSize is " + quorumSize); + } + + return targets.size() < quorumSize ? Collections.emptyList() : targets; + } + + @Override + public List getTargetProbes() { + return targetProbes; + } + + @Override + public boolean isStarted() { + return started; + } + + + public AbstractPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor, int checkPeriod) { + this.targetFactory = targetFactory; + + this.scheduledExecutor = scheduledExecutor; + + this.checkPeriod = checkPeriod; + } + + @Override + public Target getTarget(String nodeId) { + for (TargetMonitor targetMonitor : targetMonitors) { + if (nodeId.equals(targetMonitor.getTarget().getNodeID())) { + return targetMonitor.getTarget(); + } + } + + return null; + } + + @Override + public boolean isTargetReady(Target target) { + TargetMonitor targetMonitor = targets.get(target); + + return targetMonitor != null ? targetMonitor.isTargetReady() : false; + } + + @Override + public void addTargetProbe(TargetProbe probe) { + targetProbes.add(probe); + } + + @Override + public void removeTargetProbe(TargetProbe probe) { + targetProbes.remove(probe); + } + + @Override + public void start() throws Exception { + started = true; + + for (TargetMonitor targetMonitor : targetMonitors) { + targetMonitor.start(); + } + } + + @Override + public void stop() throws Exception { + started = false; + + List targetMonitors = new ArrayList<>(this.targetMonitors); + + for (TargetMonitor targetMonitor : targetMonitors) { + removeTarget(targetMonitor.getTarget()); + } + } + + protected void addTarget(TransportConfiguration connector, String nodeID) { + addTarget(targetFactory.createTarget(connector, nodeID)); + } + + @Override + public boolean addTarget(Target target) { + TargetMonitor targetMonitor = new TargetMonitor(scheduledExecutor, checkPeriod, target, targetProbes); + + if (targets.putIfAbsent(target, targetMonitor) != null) { + return false; + } + + targetMonitors.add(targetMonitor); + + if (started) { + targetMonitor.start(); + } + + return true; + } + + @Override + public boolean removeTarget(Target target) { + TargetMonitor targetMonitor = targets.remove(target); + + if (targetMonitor == null) { + return false; + } + + targetMonitors.remove(targetMonitor); + + targetMonitor.stop(); + + return true; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java new file mode 100644 index 0000000000..e2a7a2801d --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java @@ -0,0 +1,69 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; +import org.apache.activemq.artemis.api.core.client.TopologyMember; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; +import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; + +public class ClusterPool extends AbstractPool implements ClusterTopologyListener { + private final ClusterConnection clusterConnection; + + private final Map clusterMembers = new ConcurrentHashMap<>(); + + public ClusterPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor, + int checkPeriod, ClusterConnection clusterConnection) { + super(targetFactory, scheduledExecutor, checkPeriod); + + this.clusterConnection = clusterConnection; + } + + @Override + public void start() throws Exception { + super.start(); + + clusterConnection.addClusterTopologyListener(this); + } + + @Override + public void stop() throws Exception { + clusterConnection.removeClusterTopologyListener(this); + + super.stop(); + } + + @Override + public void nodeUP(TopologyMember member, boolean last) { + if (!clusterConnection.getNodeID().equals(member.getNodeId()) && + clusterMembers.putIfAbsent(member.getNodeId(), member) == null) { + addTarget(member.getLive(), member.getNodeId()); + } + } + + @Override + public void nodeDown(long eventUID, String nodeID) { + if (clusterMembers.remove(nodeID) != null) { + removeTarget(getTarget(nodeID)); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java new file mode 100644 index 0000000000..47fbc6e551 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java @@ -0,0 +1,87 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.core.cluster.DiscoveryEntry; +import org.apache.activemq.artemis.core.cluster.DiscoveryGroup; +import org.apache.activemq.artemis.core.cluster.DiscoveryListener; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DiscoveryGroupService extends DiscoveryService implements DiscoveryListener { + private final DiscoveryGroup discoveryGroup; + + private final Map entries = new ConcurrentHashMap<>(); + + public DiscoveryGroupService(DiscoveryGroup discoveryGroup) { + this.discoveryGroup = discoveryGroup; + } + + @Override + public void start() throws Exception { + discoveryGroup.registerListener(this); + + discoveryGroup.start(); + } + + @Override + public void stop() throws Exception { + discoveryGroup.unregisterListener(this); + + discoveryGroup.stop(); + + entries.clear(); + } + + @Override + public boolean isStarted() { + return discoveryGroup.isStarted(); + } + + @Override + public void connectorsChanged(List newEntries) { + Map oldEntries = new HashMap<>(entries); + + for (DiscoveryEntry newEntry : newEntries) { + Entry oldEntry = oldEntries.remove(newEntry.getNodeID()); + + if (oldEntry == null) { + Entry addingEntry = new Entry(newEntry.getNodeID(), newEntry.getConnector()); + + entries.put(addingEntry.getNodeID(), addingEntry); + + fireEntryAddedEvent(addingEntry); + } else if (!newEntry.getConnector().equals(oldEntry.getConnector())) { + Entry updatingEntry = new Entry(newEntry.getNodeID(), newEntry.getConnector()); + + entries.put(updatingEntry.getNodeID(), updatingEntry); + + fireEntryUpdatedEvent(oldEntry, updatingEntry); + } + } + + oldEntries.forEach((nodeID, entry) -> { + entries.remove(nodeID); + + fireEntryRemovedEvent(entry); + }); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java new file mode 100644 index 0000000000..03c3a8c343 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java @@ -0,0 +1,70 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; + +import java.util.concurrent.ScheduledExecutorService; + +public class DiscoveryPool extends AbstractPool implements DiscoveryService.Listener { + private final DiscoveryService discoveryService; + + public DiscoveryPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor, + int checkPeriod, DiscoveryService discoveryService) { + super(targetFactory, scheduledExecutor, checkPeriod); + + this.discoveryService = discoveryService; + } + + @Override + public void start() throws Exception { + super.start(); + + discoveryService.setListener(this); + + discoveryService.start(); + } + + @Override + public void stop() throws Exception { + super.stop(); + + if (discoveryService != null) { + discoveryService.setListener(null); + + discoveryService.stop(); + } + } + + @Override + public void entryAdded(DiscoveryService.Entry entry) { + addTarget(entry.getConnector(), entry.getNodeID()); + } + + @Override + public void entryRemoved(DiscoveryService.Entry entry) { + removeTarget(getTarget(entry.getNodeID())); + } + + @Override + public void entryUpdated(DiscoveryService.Entry oldEntry, DiscoveryService.Entry newEntry) { + removeTarget(getTarget(oldEntry.getNodeID())); + + addTarget(newEntry.getConnector(), newEntry.getNodeID()); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java new file mode 100644 index 0000000000..a45fedec6e --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java @@ -0,0 +1,88 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.server.ActiveMQComponent; + +public abstract class DiscoveryService implements ActiveMQComponent { + + private Listener listener; + + public Listener getListener() { + return listener; + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + protected void fireEntryAddedEvent(Entry entry) { + if (listener != null) { + this.listener.entryAdded(entry); + } + } + + protected void fireEntryRemovedEvent(Entry entry) { + if (listener != null) { + this.listener.entryRemoved(entry); + } + } + + protected void fireEntryUpdatedEvent(Entry oldEntry, Entry newEntry) { + if (listener != null) { + this.listener.entryUpdated(oldEntry, newEntry); + } + } + + + public interface Listener { + void entryAdded(Entry entry); + + void entryRemoved(Entry entry); + + void entryUpdated(Entry oldEntry, Entry newEntry); + } + + public class Entry { + private final String nodeID; + private final TransportConfiguration connector; + + public String getNodeID() { + return nodeID; + } + + public TransportConfiguration getConnector() { + return connector; + } + + public Entry(String nodeID, TransportConfiguration connector) { + this.nodeID = nodeID; + this.connector = connector; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(Entry.class.getSimpleName()); + stringBuilder.append("(nodeID=" + nodeID); + stringBuilder.append(", connector=" + connector); + stringBuilder.append(") "); + return stringBuilder.toString(); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java new file mode 100644 index 0000000000..db6b733147 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java @@ -0,0 +1,65 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.core.server.ActiveMQComponent; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe; + +import java.util.List; + +public interface Pool extends ActiveMQComponent { + String getUsername(); + + void setUsername(String username); + + String getPassword(); + + void setPassword(String password); + + int getQuorumSize(); + + void setQuorumSize(int quorumSize); + + int getQuorumTimeout(); + + void setQuorumTimeout(int quorumTimeout); + + int getCheckPeriod(); + + + + Target getTarget(String nodeId); + + boolean isTargetReady(Target target); + + List getTargets(); + + List getAllTargets(); + + boolean addTarget(Target target); + + boolean removeTarget(Target target); + + + List getTargetProbes(); + + void addTargetProbe(TargetProbe probe); + + void removeTargetProbe(TargetProbe probe); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java new file mode 100644 index 0000000000..c652d4a11a --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java @@ -0,0 +1,44 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; + +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; + +public class StaticPool extends AbstractPool { + private final List staticConnectors; + + public StaticPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor, + int checkPeriod, List staticConnectors) { + super(targetFactory, scheduledExecutor, checkPeriod); + + this.staticConnectors = staticConnectors; + } + + @Override + public void start() throws Exception { + super.start(); + + for (TransportConfiguration staticConnector : staticConnectors) { + addTarget(staticConnector, null); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java new file mode 100644 index 0000000000..3f123e5a86 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java @@ -0,0 +1,112 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public abstract class AbstractTarget implements Target { + private final TransportConfiguration connector; + + private String nodeID; + + private String username; + + private String password; + + private int checkPeriod; + + private TargetListener listener; + + @Override + public String getNodeID() { + return nodeID; + } + + protected void setNodeID(String nodeID) { + this.nodeID = nodeID; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public void setPassword(String password) { + this.password = password; + } + + @Override + public int getCheckPeriod() { + return checkPeriod; + } + + @Override + public void setCheckPeriod(int checkPeriod) { + this.checkPeriod = checkPeriod; + } + + @Override + public TargetListener getListener() { + return listener; + } + + @Override + public void setListener(TargetListener listener) { + this.listener = listener; + } + + @Override + public TransportConfiguration getConnector() { + return connector; + } + + + public AbstractTarget(TransportConfiguration connector, String nodeID) { + this.connector = connector; + this.nodeID = nodeID; + } + + + protected void fireConnectedEvent() { + if (listener != null) { + listener.targetConnected(); + } + } + + protected void fireDisconnectedEvent() { + if (listener != null) { + listener.targetDisconnected(); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " [connector=" + connector + ", nodeID=" + nodeID + "]"; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java new file mode 100644 index 0000000000..9ca15f311d --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java @@ -0,0 +1,45 @@ +/** + * 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.balancing.targets; + +public abstract class AbstractTargetFactory implements TargetFactory { + + private String username; + + private String password; + + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public void setPassword(String password) { + this.password = password; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java new file mode 100644 index 0000000000..0fc1cadc64 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java @@ -0,0 +1,132 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.client.ActiveMQClient; +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.api.core.management.ActiveMQManagementProxy; +import org.apache.activemq.artemis.api.core.management.ResourceNames; +import org.apache.activemq.artemis.core.remoting.FailureListener; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; +import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; +import org.apache.activemq.artemis.utils.UUIDGenerator; +import org.jboss.logging.Logger; + +public class ActiveMQTarget extends AbstractTarget implements FailureListener { + private static final Logger logger = Logger.getLogger(ActiveMQTarget.class); + + private boolean connected = false; + + private final ServerLocator serverLocator; + + private ClientSessionFactory sessionFactory; + private RemotingConnection remotingConnection; + private ActiveMQManagementProxy managementProxy; + + @Override + public boolean isLocal() { + return false; + } + + @Override + public boolean isConnected() { + return connected; + } + + public ActiveMQTarget(TransportConfiguration connector, String nodeID) { + super(connector, nodeID); + + serverLocator = ActiveMQClient.createServerLocatorWithoutHA(connector); + } + + + @Override + public void connect() throws Exception { + sessionFactory = serverLocator.createSessionFactory(); + + remotingConnection = sessionFactory.getConnection(); + remotingConnection.addFailureListener(this); + + managementProxy = new ActiveMQManagementProxy(sessionFactory.createSession(getUsername(), getPassword(), + false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE, + BrokerBalancer.CLIENT_ID_PREFIX + UUIDGenerator.getInstance().generateStringUUID()).start()); + + connected = true; + + fireConnectedEvent(); + } + + @Override + public void disconnect() throws Exception { + if (connected) { + connected = false; + + managementProxy.close(); + + remotingConnection.removeFailureListener(this); + + sessionFactory.close(); + + fireDisconnectedEvent(); + } + } + + @Override + public boolean checkReadiness() { + try { + if (getNodeID() == null) { + setNodeID(getAttribute(ResourceNames.BROKER, "NodeID", String.class, 3000)); + } + + return getAttribute(ResourceNames.BROKER, "Active", Boolean.class, 3000); + } catch (Exception e) { + logger.warn("Error on check readiness", e); + } + + return false; + } + + @Override + public T getAttribute(String resourceName, String attributeName, Class attributeClass, int timeout) throws Exception { + return managementProxy.getAttribute(resourceName, attributeName, attributeClass, timeout); + } + + @Override + public T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class operationClass, int timeout) throws Exception { + return managementProxy.invokeOperation(resourceName, operationName, operationParams, operationClass, timeout); + } + + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver) { + connectionFailed(exception, failedOver, null); + } + + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver, String scaleDownTargetNodeID) { + try { + if (connected) { + disconnect(); + } + } catch (Exception e) { + logger.debug("Exception on disconnecting: ", e); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java new file mode 100644 index 0000000000..b7cb9f9446 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java @@ -0,0 +1,32 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public class ActiveMQTargetFactory extends AbstractTargetFactory { + @Override + public Target createTarget(TransportConfiguration connector, String nodeID) { + Target target = new ActiveMQTarget(connector, nodeID); + + target.setUsername(getUsername()); + target.setPassword(getPassword()); + + return target; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java new file mode 100644 index 0000000000..86043fefe3 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java @@ -0,0 +1,69 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.management.ManagementService; + +public class LocalTarget extends AbstractTarget { + private final ActiveMQServer server; + private final ManagementService managementService; + + public LocalTarget(TransportConfiguration connector, ActiveMQServer server) { + super(connector, server.getNodeID().toString()); + + this.server = server; + this.managementService = server.getManagementService(); + } + + @Override + public boolean isLocal() { + return true; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public void connect() throws Exception { + + } + + @Override + public void disconnect() throws Exception { + + } + + @Override + public boolean checkReadiness() { + return true; + } + + @Override + public T getAttribute(String resourceName, String attributeName, Class attributeClass, int timeout) throws Exception { + return (T)managementService.getAttribute(resourceName, attributeName); + } + + @Override + public T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class operationClass, int timeout) throws Exception { + return (T)managementService.invokeOperation(resourceName, operationName, operationParams); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java new file mode 100644 index 0000000000..f82331ba5b --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java @@ -0,0 +1,59 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public interface Target { + + boolean isLocal(); + + String getNodeID(); + + TransportConfiguration getConnector(); + + String getUsername(); + + void setUsername(String username); + + String getPassword(); + + void setPassword(String password); + + int getCheckPeriod(); + + void setCheckPeriod(int checkPeriod); + + TargetListener getListener(); + + void setListener(TargetListener listener); + + boolean isConnected(); + + void connect() throws Exception; + + void disconnect() throws Exception; + + + boolean checkReadiness(); + + + T getAttribute(String resourceName, String attributeName, Class attributeClass, int timeout) throws Exception; + + T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class operationClass, int timeout) throws Exception; +} \ No newline at end of file diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java new file mode 100644 index 0000000000..508be40200 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java @@ -0,0 +1,32 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +public interface TargetFactory { + String getUsername(); + + void setUsername(String username); + + String getPassword(); + + void setPassword(String password); + + Target createTarget(TransportConfiguration connector, String nodeID); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java new file mode 100644 index 0000000000..d01b932f63 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java @@ -0,0 +1,53 @@ +/** + * 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.balancing.targets; + +public enum TargetKey { + CLIENT_ID, SNI_HOST, SOURCE_IP, USER_NAME; + + public static final String validValues; + + static { + StringBuffer stringBuffer = new StringBuffer(); + for (TargetKey type : TargetKey.values()) { + + if (stringBuffer.length() != 0) { + stringBuffer.append(","); + } + + stringBuffer.append(type.name()); + } + + validValues = stringBuffer.toString(); + } + + public static TargetKey getType(String type) { + switch (type) { + case "CLIENT_ID": + return CLIENT_ID; + case "SNI_HOST": + return SNI_HOST; + case "SOURCE_IP": + return SOURCE_IP; + case "USER_NAME": + return USER_NAME; + default: + throw new IllegalStateException("Invalid RedirectKey:" + type + " valid Types: " + validValues); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java new file mode 100644 index 0000000000..dac82deedf --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java @@ -0,0 +1,108 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.spi.core.remoting.Connection; +import org.jboss.logging.Logger; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TargetKeyResolver { + public static final String DEFAULT_KEY_VALUE = "DEFAULT"; + + + private static final Logger logger = Logger.getLogger(TargetKeyResolver.class); + + private static final char SOCKET_ADDRESS_DELIMITER = ':'; + private static final String SOCKET_ADDRESS_PREFIX = "/"; + + + private final TargetKey key; + + private final Pattern keyFilter; + + + public TargetKey getKey() { + return key; + } + + public String getKeyFilter() { + return keyFilter != null ? keyFilter.pattern() : null; + } + + public TargetKeyResolver(TargetKey key, String keyFilter) { + this.key = key; + + this.keyFilter = keyFilter != null ? Pattern.compile(keyFilter) : null; + } + + public String resolve(Connection connection, String clientID, String username) { + String keyValue = null; + + switch (key) { + case CLIENT_ID: + keyValue = clientID; + break; + case SNI_HOST: + if (connection != null) { + keyValue = connection.getSNIHostName(); + } + break; + case SOURCE_IP: + if (connection != null && connection.getRemoteAddress() != null) { + keyValue = connection.getRemoteAddress(); + + boolean hasPrefix = keyValue.startsWith(SOCKET_ADDRESS_PREFIX); + int delimiterIndex = keyValue.lastIndexOf(SOCKET_ADDRESS_DELIMITER); + + if (hasPrefix || delimiterIndex > 0) { + keyValue = keyValue.substring(hasPrefix ? SOCKET_ADDRESS_PREFIX.length() : 0, + delimiterIndex > 0 ? delimiterIndex : keyValue.length()); + } + } + break; + case USER_NAME: + keyValue = username; + break; + default: + throw new IllegalStateException("Unexpected value: " + key); + } + + if (logger.isDebugEnabled()) { + logger.debugf("keyValue for %s: %s", key, keyValue); + } + + if (keyValue == null) { + keyValue = DEFAULT_KEY_VALUE; + } else if (keyFilter != null) { + Matcher keyMatcher = keyFilter.matcher(keyValue); + + if (keyMatcher.find()) { + keyValue = keyMatcher.group(); + + if (logger.isDebugEnabled()) { + logger.debugf("keyValue with filter %s: %s", keyFilter, keyValue); + } + } + } + + + return keyValue; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java new file mode 100644 index 0000000000..266d736989 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java @@ -0,0 +1,24 @@ +/** + * 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.balancing.targets; + +public interface TargetListener { + void targetConnected(); + + void targetDisconnected(); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java new file mode 100644 index 0000000000..b1865da476 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java @@ -0,0 +1,129 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.core.server.ActiveMQScheduledComponent; +import org.jboss.logging.Logger; + +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class TargetMonitor extends ActiveMQScheduledComponent implements TargetListener { + private static final Logger logger = Logger.getLogger(TargetMonitor.class); + + + private final Target target; + + private final List targetProbes; + + private volatile boolean targetReady = false; + + + public Target getTarget() { + return target; + } + + public boolean isTargetReady() { + return targetReady; + } + + + public TargetMonitor(ScheduledExecutorService scheduledExecutorService, int checkPeriod, Target target, List targetProbes) { + super(scheduledExecutorService, 0, checkPeriod, TimeUnit.MILLISECONDS, false); + + this.target = target; + this.targetProbes = targetProbes; + } + + @Override + public synchronized void start() { + target.setListener(this); + + super.start(); + } + + @Override + public synchronized void stop() { + super.stop(); + + targetReady = false; + + target.setListener(null); + + try { + target.disconnect(); + } catch (Exception e) { + logger.debug("Error on disconnecting target " + target, e); + } + } + + @Override + public void run() { + try { + if (!target.isConnected()) { + if (logger.isDebugEnabled()) { + logger.debug("Connecting to " + target); + } + + target.connect(); + } + + targetReady = target.checkReadiness() && checkTargetProbes(); + + if (logger.isDebugEnabled()) { + if (targetReady) { + logger.debug(target + " is ready"); + } else { + logger.debug(target + " is not ready"); + } + } + } catch (Exception e) { + logger.warn("Error monitoring " + target, e); + + targetReady = false; + } + } + + private boolean checkTargetProbes() { + for (TargetProbe targetProbe : targetProbes) { + if (!targetProbe.check(target)) { + logger.info(targetProbe.getName() + " has failed on " + target); + return false; + } + } + + return true; + } + + @Override + public void targetConnected() { + + } + + @Override + public void targetDisconnected() { + targetReady = false; + } + + + @Override + public String toString() { + return this.getClass().getSimpleName() + " [target=" + target + ", targetReady=" + targetReady + "]"; + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java new file mode 100644 index 0000000000..1d80067b12 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java @@ -0,0 +1,32 @@ +/** + * 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.balancing.targets; + +public abstract class TargetProbe { + private final String name; + + public String getName() { + return name; + } + + public TargetProbe(String name) { + this.name = name; + } + + public abstract boolean check(Target target); +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java index dac6cdee4a..841744697e 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java @@ -172,6 +172,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugi import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancerManager; import org.apache.activemq.artemis.core.server.reload.ReloadManager; import org.apache.activemq.artemis.core.server.reload.ReloadManagerImpl; import org.apache.activemq.artemis.core.server.transformer.Transformer; @@ -289,6 +290,8 @@ public class ActiveMQServerImpl implements ActiveMQServer { private volatile RemotingService remotingService; + private volatile BrokerBalancerManager balancerManager; + private final List protocolManagerFactories = new ArrayList<>(); private final List protocolServices = new ArrayList<>(); @@ -1194,6 +1197,8 @@ public class ActiveMQServerImpl implements ActiveMQServer { } } + stopComponent(balancerManager); + stopComponent(connectorsService); // we stop the groupingHandler before we stop the cluster manager so binding mappings @@ -1632,6 +1637,11 @@ public class ActiveMQServerImpl implements ActiveMQServer { return clusterManager; } + @Override + public BrokerBalancerManager getBalancerManager() { + return balancerManager; + } + public BackupManager getBackupManager() { return backupManager; } @@ -3107,6 +3117,10 @@ public class ActiveMQServerImpl implements ActiveMQServer { federationManager.deploy(); + balancerManager = new BrokerBalancerManager(configuration, this, scheduledPool); + + balancerManager.deploy(); + remotingService = new RemotingServiceImpl(clusterManager, configuration, this, managementService, scheduledPool, protocolManagerFactories, executorFactory.getExecutor(), serviceRegistry); messagingServerControl = managementService.registerServer(postOffice, securityStore, storageManager, configuration, addressSettingsRepository, securityRepository, resourceManager, remotingService, this, queueFactory, scheduledPool, pagingManager, haPolicy.isBackup()); @@ -3270,6 +3284,8 @@ public class ActiveMQServerImpl implements ActiveMQServer { federationManager.start(); } + balancerManager.start(); + startProtocolServices(); if (nodeManager.getNodeId() == null) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java index c47804ae19..a3bbe6248b 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java @@ -44,6 +44,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.Divert; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.QueueFactory; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; import org.apache.activemq.artemis.core.server.cluster.Bridge; import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; @@ -127,6 +128,10 @@ public interface ManagementService extends NotificationService, ActiveMQComponen void unregisterCluster(String name) throws Exception; + void registerBrokerBalancer(BrokerBalancer balancer) throws Exception; + + void unregisterBrokerBalancer(String name) throws Exception; + Object getResource(String resourceName); Object[] getResources(Class resourceType); @@ -136,4 +141,8 @@ public interface ManagementService extends NotificationService, ActiveMQComponen void registerHawtioSecurity(ArtemisMBeanServerGuard securityMBean) throws Exception; void unregisterHawtioSecurity() throws Exception; + + Object getAttribute(String resourceName, String attribute); + + Object invokeOperation(String resourceName, String operation, Object[] params) throws Exception; } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java index 5d9115b040..60c268cc24 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java @@ -51,6 +51,7 @@ import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; import org.apache.activemq.artemis.api.core.management.AddressControl; import org.apache.activemq.artemis.api.core.management.BaseBroadcastGroupControl; import org.apache.activemq.artemis.api.core.management.BridgeControl; +import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl; import org.apache.activemq.artemis.api.core.management.ClusterConnectionControl; import org.apache.activemq.artemis.api.core.management.DivertControl; import org.apache.activemq.artemis.api.core.management.ManagementHelper; @@ -66,6 +67,7 @@ import org.apache.activemq.artemis.core.management.impl.AddressControlImpl; import org.apache.activemq.artemis.core.management.impl.BaseBroadcastGroupControlImpl; import org.apache.activemq.artemis.core.management.impl.BridgeControlImpl; import org.apache.activemq.artemis.core.management.impl.BroadcastGroupControlImpl; +import org.apache.activemq.artemis.core.management.impl.BrokerBalancerControlImpl; import org.apache.activemq.artemis.core.management.impl.ClusterConnectionControlImpl; import org.apache.activemq.artemis.core.management.impl.DivertControlImpl; import org.apache.activemq.artemis.core.management.impl.JGroupsChannelBroadcastGroupControlImpl; @@ -88,6 +90,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.Divert; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.QueueFactory; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; import org.apache.activemq.artemis.core.server.cluster.Bridge; import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; @@ -493,6 +496,25 @@ public class ManagementServiceImpl implements ManagementService { unregisterFromRegistry(ResourceNames.CORE_CLUSTER_CONNECTION + name); } + @Override + public synchronized void registerBrokerBalancer(final BrokerBalancer balancer) throws Exception { + ObjectName objectName = objectNameBuilder.getBrokerBalancerObjectName(balancer.getName()); + BrokerBalancerControl brokerBalancerControl = new BrokerBalancerControlImpl(balancer, storageManager); + registerInJMX(objectName, brokerBalancerControl); + registerInRegistry(ResourceNames.BROKER_BALANCER + balancer.getName(), brokerBalancerControl); + + if (logger.isDebugEnabled()) { + logger.debug("registered broker balancer " + objectName); + } + } + + @Override + public synchronized void unregisterBrokerBalancer(final String name) throws Exception { + ObjectName objectName = objectNameBuilder.getBrokerBalancerObjectName(name); + unregisterFromJMX(objectName); + unregisterFromRegistry(ResourceNames.BROKER_BALANCER + name); + } + @Override public void registerHawtioSecurity(ArtemisMBeanServerGuard mBeanServerGuard) throws Exception { ObjectName objectName = objectNameBuilder.getManagementContextObjectName(); @@ -831,6 +853,7 @@ public class ManagementServiceImpl implements ManagementService { notificationsEnabled = enabled; } + @Override public Object getAttribute(final String resourceName, final String attribute) { try { Object resource = registry.get(resourceName); @@ -857,7 +880,8 @@ public class ManagementServiceImpl implements ManagementService { } } - private Object invokeOperation(final String resourceName, + @Override + public Object invokeOperation(final String resourceName, final String operation, final Object[] params) throws Exception { Object resource = registry.get(resourceName); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java index 7a4d3b175a..7d540b7df7 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java @@ -27,8 +27,9 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; -public abstract class AbstractProtocolManager, C extends RemotingConnection> implements ProtocolManager { +public abstract class AbstractProtocolManager, C extends RemotingConnection, R extends RedirectHandler> implements ProtocolManager { private final Map prefixes = new HashMap<>(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java index 770034de15..ee6ec226c6 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java @@ -25,12 +25,13 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; +import org.apache.activemq.artemis.core.server.balancing.RedirectHandler; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; /** * Info: ProtocolManager is loaded by {@link org.apache.activemq.artemis.core.remoting.server.impl.RemotingServiceImpl#loadProtocolManagerFactories(Iterable)} */ -public interface ProtocolManager

{ +public interface ProtocolManager

{ ProtocolManagerFactory

getFactory(); @@ -78,4 +79,6 @@ public interface ProtocolManager

{ void setSecurityDomain(String securityDomain); String getSecurityDomain(); + + R getRedirectHandler(); } diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index 69e06cdcc2..e35e1f33a6 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -619,6 +619,20 @@ + + + + A list of balancers + + + + + + + + + + @@ -2078,6 +2092,167 @@ + + + + + + the optional target key + + + + + + + the filter for the target key + + + + + + + the filter to get the local target + + + + + + + the time period for a cache entry to remain active + + + + + + + the policy configuration + + + + + + + the pool configuration + + + + + + + + a unique name for the broker balancer + + + + + + + + + + + + + + + + + + + + + properties to configure a policy + + + + + + + + the name of the policy + + + + + + + + + + + + the username to access the targets + + + + + + + the password to access the targets + + + + + + + the period (in milliseconds) used to check if a target is ready + + + + + + + the minimum number of ready targets + + + + + + + the timeout (in milliseconds) used to get the minimum number of ready targets + + + + + + + true means that the local target is enabled + + + + + + + + the name of a cluster connection + + + + + + + + + + + + + + + + + name of discovery group used by this bridge + + + + + + + + + + + diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java index 07f11ab558..b7027b458e 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java @@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.config.DivertConfiguration; import org.apache.activemq.artemis.core.config.FileDeploymentManager; import org.apache.activemq.artemis.core.config.HAPolicyConfiguration; import org.apache.activemq.artemis.core.config.MetricsConfiguration; +import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration; import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration; import org.apache.activemq.artemis.core.journal.impl.JournalImpl; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; @@ -54,6 +55,10 @@ import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.SecuritySettingPlugin; +import org.apache.activemq.artemis.core.server.balancing.policies.ConsistentHashPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.LeastConnectionsPolicy; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType; import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl; import org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin; @@ -260,6 +265,38 @@ public class FileConfigurationTest extends ConfigurationImplTest { } } + Assert.assertEquals(3, conf.getBalancerConfigurations().size()); + for (BrokerBalancerConfiguration bc : conf.getBalancerConfigurations()) { + if (bc.getName().equals("simple-balancer")) { + Assert.assertEquals(bc.getTargetKey(), TargetKey.USER_NAME); + Assert.assertNull(bc.getLocalTargetFilter()); + Assert.assertEquals(bc.getPolicyConfiguration().getName(), FirstElementPolicy.NAME); + Assert.assertEquals(false, bc.getPoolConfiguration().isLocalTargetEnabled()); + Assert.assertEquals("connector1", bc.getPoolConfiguration().getStaticConnectors().get(0)); + Assert.assertEquals(null, bc.getPoolConfiguration().getDiscoveryGroupName()); + } else if (bc.getName().equals("consistent-hash-balancer")) { + Assert.assertEquals(bc.getTargetKey(), TargetKey.SNI_HOST); + Assert.assertEquals(bc.getTargetKeyFilter(), "^[^.]+"); + Assert.assertEquals(bc.getLocalTargetFilter(), "DEFAULT"); + Assert.assertEquals(bc.getPolicyConfiguration().getName(), ConsistentHashPolicy.NAME); + Assert.assertEquals(1000, bc.getPoolConfiguration().getCheckPeriod()); + Assert.assertEquals(true, bc.getPoolConfiguration().isLocalTargetEnabled()); + Assert.assertEquals(Collections.emptyList(), bc.getPoolConfiguration().getStaticConnectors()); + Assert.assertEquals("dg1", bc.getPoolConfiguration().getDiscoveryGroupName()); + } else { + Assert.assertEquals(bc.getTargetKey(), TargetKey.SOURCE_IP); + Assert.assertEquals("least-connections-balancer", bc.getName()); + Assert.assertEquals(60000, bc.getCacheTimeout()); + Assert.assertEquals(bc.getPolicyConfiguration().getName(), LeastConnectionsPolicy.NAME); + Assert.assertEquals(3000, bc.getPoolConfiguration().getCheckPeriod()); + Assert.assertEquals(2, bc.getPoolConfiguration().getQuorumSize()); + Assert.assertEquals(1000, bc.getPoolConfiguration().getQuorumTimeout()); + Assert.assertEquals(false, bc.getPoolConfiguration().isLocalTargetEnabled()); + Assert.assertEquals(Collections.emptyList(), bc.getPoolConfiguration().getStaticConnectors()); + Assert.assertEquals("dg2", bc.getPoolConfiguration().getDiscoveryGroupName()); + } + } + Assert.assertEquals(4, conf.getBridgeConfigurations().size()); for (BridgeConfiguration bc : conf.getBridgeConfigurations()) { if (bc.getName().equals("bridge1")) { diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java new file mode 100644 index 0000000000..d81677ec5b --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java @@ -0,0 +1,55 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; + +public class ConsistentHashPolicyTest extends PolicyTestBase { + + @Override + protected AbstractPolicy createPolicy() { + return new ConsistentHashPolicy(); + } + + @Test + public void testPolicyWithMultipleTargets() { + AbstractPolicy policy = createPolicy(); + Target selectedTarget; + Target previousTarget; + + ArrayList targets = new ArrayList<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + targets.add(new MockTarget()); + } + + selectedTarget = policy.selectTarget(targets, "test"); + previousTarget = selectedTarget; + + selectedTarget = policy.selectTarget(targets, "test"); + Assert.assertEquals(previousTarget, selectedTarget); + + targets.remove(previousTarget); + selectedTarget = policy.selectTarget(targets, "test"); + Assert.assertNotEquals(previousTarget, selectedTarget); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java new file mode 100644 index 0000000000..b84c8e0080 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java @@ -0,0 +1,47 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; + +public class FirstElementPolicyTest extends PolicyTestBase { + + @Override + protected AbstractPolicy createPolicy() { + return new FirstElementPolicy(); + } + + @Test + public void testPolicyWithMultipleTargets() { + AbstractPolicy policy = createPolicy(); + + ArrayList targets = new ArrayList<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + targets.add(new MockTarget()); + } + + Target selectedTarget = policy.selectTarget(targets, "test"); + + Assert.assertEquals(selectedTarget, targets.get(0)); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java new file mode 100644 index 0000000000..b272f49d66 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java @@ -0,0 +1,95 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class LeastConnectionsPolicyTest extends PolicyTestBase { + + @Override + protected AbstractPolicy createPolicy() { + return new LeastConnectionsPolicy(); + } + + @Test + public void testPolicyWithMultipleTargets() { + AbstractPolicy policy = createPolicy(); + Target selectedTarget = null; + Set selectedTargets; + + + ArrayList targets = new ArrayList<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + targets.add(new MockTarget().setConnected(true).setReady(true)); + } + + + selectedTargets = new HashSet<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + selectedTarget = policy.selectTarget(targets, "test"); + selectedTargets.add(selectedTarget); + } + Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size()); + + + targets.forEach(target -> { + ((MockTarget)target).setAttributeValue("broker", "ConnectionCount", 3); + policy.getTargetProbe().check(target); + }); + + selectedTargets = new HashSet<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + selectedTarget = policy.selectTarget(targets, "test"); + selectedTargets.add(selectedTarget); + } + Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size()); + + + ((MockTarget)targets.get(0)).setAttributeValue("broker", "ConnectionCount", 2); + targets.forEach(target -> policy.getTargetProbe().check(target)); + + selectedTargets = new HashSet<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + selectedTarget = policy.selectTarget(targets, "test"); + selectedTargets.add(selectedTarget); + } + Assert.assertEquals(1, selectedTargets.size()); + Assert.assertTrue(selectedTargets.contains(targets.get(0))); + + + ((MockTarget)targets.get(1)).setAttributeValue("broker", "ConnectionCount", 1); + ((MockTarget)targets.get(2)).setAttributeValue("broker", "ConnectionCount", 1); + targets.forEach(target -> policy.getTargetProbe().check(target)); + + selectedTargets = new HashSet<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + selectedTarget = policy.selectTarget(targets, "test"); + selectedTargets.add(selectedTarget); + } + Assert.assertEquals(2, selectedTargets.size()); + Assert.assertTrue(selectedTargets.contains(targets.get(1))); + Assert.assertTrue(selectedTargets.contains(targets.get(2))); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java new file mode 100644 index 0000000000..962010bb22 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java @@ -0,0 +1,52 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; + +public abstract class PolicyTestBase { + public static final int MULTIPLE_TARGETS = 10; + + protected abstract AbstractPolicy createPolicy(); + + @Test + public void testPolicyWithNoTarget() { + AbstractPolicy policy = createPolicy(); + + Target selectedTarget = policy.selectTarget(Collections.emptyList(), "test"); + + Assert.assertNull(selectedTarget); + } + + @Test + public void testPolicyWithSingleTarget() { + AbstractPolicy policy = createPolicy(); + + ArrayList targets = new ArrayList<>(); + targets.add(new MockTarget()); + + Target selectedTarget = policy.selectTarget(targets, "test"); + Assert.assertEquals(selectedTarget, targets.get(0)); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java new file mode 100644 index 0000000000..70b7b74f04 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java @@ -0,0 +1,58 @@ +/** + * 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.balancing.policies; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class RoundRobinPolicyTest extends PolicyTestBase { + + @Override + protected AbstractPolicy createPolicy() { + return new RoundRobinPolicy(); + } + + @Test + public void testPolicyWithMultipleTargets() { + AbstractPolicy policy = createPolicy(); + Target selectedTarget = null; + Set selectedTargets = new HashSet<>(); + List previousTargets = new ArrayList<>(); + + ArrayList targets = new ArrayList<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + targets.add(new MockTarget()); + } + + selectedTargets = new HashSet<>(); + for (int i = 0; i < MULTIPLE_TARGETS; i++) { + selectedTarget = policy.selectTarget(targets, "test"); + selectedTargets.add(selectedTarget); + Assert.assertTrue("Iteration failed: " + i, !previousTargets.contains(selectedTarget)); + previousTargets.add(selectedTarget); + } + Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size()); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java new file mode 100644 index 0000000000..b637ebe808 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java @@ -0,0 +1,174 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetFactory; +import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetProbe; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; +import org.apache.activemq.artemis.utils.Wait; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.stream.Stream; + +public class DiscoveryPoolTest extends PoolTestBase { + + @Test + public void testPoolAddingRemovingAllEntries() throws Exception { + testPoolChangingEntries(5, 10, 10); + } + + @Test + public void testPoolAddingRemovingPartialEntries() throws Exception { + testPoolChangingEntries(5, 10, 5); + } + + @Test + public void testPoolAddingRemovingAllEntriesAfterStart() throws Exception { + testPoolChangingEntries(0, 10, 10); + } + + @Test + public void testPoolAddingRemovingPartialEntriesAfterStart() throws Exception { + testPoolChangingEntries(0, 10, 5); + } + + private void testPoolChangingEntries(int initialEntries, int addingEntries, int removingEntries) throws Exception { + MockTargetFactory targetFactory = new MockTargetFactory(); + MockTargetProbe targetProbe = new MockTargetProbe("TEST", true); + MockDiscoveryService discoveryService = new MockDiscoveryService(); + + targetProbe.setChecked(true); + + // Simulate initial entries. + List initialNodeIDs = new ArrayList<>(); + for (int i = 0; i < initialEntries; i++) { + initialNodeIDs.add(discoveryService.addEntry().getNodeID()); + } + + Pool pool = createDiscoveryPool(targetFactory, discoveryService); + + pool.addTargetProbe(targetProbe); + + pool.start(); + + try { + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true)); + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true)); + + Wait.assertEquals(initialEntries, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(initialEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries, targetFactory.getCreatedTargets().size()); + initialNodeIDs.forEach(nodeID -> Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)))); + + // Simulate adding entries. + List addedNodeIDs = new ArrayList<>(); + for (int i = 0; i < addingEntries; i++) { + addedNodeIDs.add(discoveryService.addEntry().getNodeID()); + } + + Assert.assertEquals(initialEntries, pool.getTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size()); + initialNodeIDs.forEach(nodeID -> { + Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0); + }); + addedNodeIDs.forEach(nodeID -> { + Assert.assertFalse(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID))); + }); + + + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true)); + + Assert.assertEquals(initialEntries, pool.getTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size()); + initialNodeIDs.forEach(nodeID -> { + Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0); + }); + addedNodeIDs.forEach(nodeID -> { + Assert.assertFalse(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID))); + }); + + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true)); + + Wait.assertEquals(initialEntries + addingEntries, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size()); + Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> { + Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0); + }); + + if (removingEntries > 0) { + // Simulate removing entries. + List removingNodeIDs = new ArrayList<>(); + for (int i = 0; i < removingEntries; i++) { + removingNodeIDs.add(discoveryService.removeEntry(targetFactory. + getCreatedTargets().get(i).getNodeID()).getNodeID()); + } + + Assert.assertEquals(initialEntries + addingEntries - removingEntries, pool.getTargets().size()); + Assert.assertEquals(initialEntries + addingEntries - removingEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size()); + Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> { + if (removingNodeIDs.contains(nodeID)) { + Assert.assertNull(pool.getTarget(nodeID)); + Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID))); + } else { + Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0); + } + }); + } else { + Assert.assertEquals(initialEntries + addingEntries, pool.getTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size()); + Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size()); + Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> { + Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))); + Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0); + }); + } + } finally { + pool.stop(); + } + } + + + @Override + protected Pool createPool(TargetFactory targetFactory, int targets) { + MockDiscoveryService discoveryService = new MockDiscoveryService(); + + for (int i = 0; i < targets; i++) { + discoveryService.addEntry(); + } + + return createDiscoveryPool(targetFactory, discoveryService); + } + + private DiscoveryPool createDiscoveryPool(TargetFactory targetFactory, DiscoveryService discoveryService) { + return new DiscoveryPool(targetFactory, new ScheduledThreadPoolExecutor(0), CHECK_PERIOD, discoveryService); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java new file mode 100644 index 0000000000..f9d19828ec --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java @@ -0,0 +1,87 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class MockDiscoveryService extends DiscoveryService { + private final Map entries = new HashMap<>(); + + private final Map pendingEntries = new HashMap<>(); + + private volatile boolean started; + + + public Map getEntries() { + return entries; + } + + public Map getPendingEntries() { + return pendingEntries; + } + + @Override + public boolean isStarted() { + return started; + } + + public Entry addEntry() { + return addEntry(new Entry(UUID.randomUUID().toString(), new TransportConfiguration())); + } + + public Entry addEntry(Entry entry) { + if (started) { + entries.put(entry.getNodeID(), entry); + fireEntryAddedEvent(entry); + } else { + pendingEntries.put(entry.getNodeID(), entry); + } + + return entry; + } + + public Entry removeEntry(String nodeID) { + if (started) { + Entry removedEntry = entries.remove(nodeID); + fireEntryRemovedEvent(removedEntry); + return removedEntry; + } else { + return pendingEntries.remove(nodeID); + } + } + + + @Override + public void start() throws Exception { + started = true; + + pendingEntries.forEach((nodeID, entry) -> { + entries.put(nodeID, entry); + fireEntryAddedEvent(entry); + }); + } + + @Override + public void stop() throws Exception { + started = false; + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java new file mode 100644 index 0000000000..1f7505ebd8 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java @@ -0,0 +1,190 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetFactory; +import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetProbe; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; +import org.apache.activemq.artemis.utils.Wait; +import org.junit.Assert; +import org.junit.Test; + +public abstract class PoolTestBase { + public static final int MULTIPLE_TARGETS = 10; + + public static final int CHECK_PERIOD = 100; + public static final int CHECK_TIMEOUT = 2 * CHECK_PERIOD; + + + protected abstract Pool createPool(TargetFactory targetFactory, int targets); + + + @Test + public void testPoolWithNoTargets() throws Exception { + testPoolTargets(0); + } + + @Test + public void testPoolWithSingleTarget() throws Exception { + testPoolTargets(1); + } + + @Test + public void testPoolWithMultipleTargets() throws Exception { + testPoolTargets(MULTIPLE_TARGETS); + } + + @Test + public void testPoolQuorumWithMultipleTargets() throws Exception { + final int targets = MULTIPLE_TARGETS; + final int quorumSize = 2; + + Assert.assertTrue(targets - quorumSize > 2); + + MockTargetFactory targetFactory = new MockTargetFactory().setConnectable(true).setReady(true); + Pool pool = createPool(targetFactory, targets); + + pool.setQuorumSize(quorumSize); + + Assert.assertEquals(0, pool.getTargets().size()); + + pool.start(); + + Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT); + + targetFactory.getCreatedTargets().stream().limit(targets - quorumSize + 1) + .forEach(mockTarget -> mockTarget.setReady(false)); + + Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT); + + targetFactory.getCreatedTargets().get(0).setReady(true); + + Wait.assertEquals(quorumSize, () -> pool.getTargets().size(), CHECK_TIMEOUT); + + pool.setQuorumSize(quorumSize + 1); + + Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT); + + targetFactory.getCreatedTargets().get(1).setReady(true); + + Wait.assertEquals(quorumSize + 1, () -> pool.getTargets().size(), CHECK_TIMEOUT); + } + + + private void testPoolTargets(int targets) throws Exception { + MockTargetFactory targetFactory = new MockTargetFactory(); + MockTargetProbe targetProbe = new MockTargetProbe("TEST", false); + Pool pool = createPool(targetFactory, targets); + + pool.addTargetProbe(targetProbe); + + Assert.assertEquals(0, pool.getTargets().size()); + Assert.assertEquals(0, pool.getAllTargets().size()); + Assert.assertEquals(0, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget)); + }); + + pool.start(); + + try { + Assert.assertEquals(0, pool.getTargets().size()); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget)); + }); + + if (targets > 0) { + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true)); + + Assert.assertEquals(0, pool.getTargets().size()); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget)); + }); + + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true)); + + Assert.assertEquals(0, pool.getTargets().size()); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Wait.assertTrue(() -> targetProbe.getTargetExecutions(mockTarget) > 0, CHECK_TIMEOUT); + }); + + targetProbe.setChecked(true); + + Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertTrue(pool.isTargetReady(mockTarget)); + Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0); + }); + + targetFactory.getCreatedTargets().forEach(mockTarget -> { + mockTarget.setConnectable(false); + try { + mockTarget.disconnect(); + } catch (Exception ignore) { + } + }); + + Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0); + }); + + targetProbe.clearTargetExecutions(); + + targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true)); + + Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertTrue(pool.isTargetReady(mockTarget)); + Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0); + }); + + targetProbe.clearTargetExecutions(); + + targetProbe.setChecked(false); + + Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT); + Assert.assertEquals(targets, pool.getAllTargets().size()); + Assert.assertEquals(targets, targetFactory.getCreatedTargets().size()); + targetFactory.getCreatedTargets().forEach(mockTarget -> { + Assert.assertFalse(pool.isTargetReady(mockTarget)); + Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0); + }); + } + } finally { + pool.stop(); + } + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java new file mode 100644 index 0000000000..6a489071ac --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java @@ -0,0 +1,39 @@ +/** + * 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.balancing.pools; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +public class StaticPoolTest extends PoolTestBase { + + @Override + protected Pool createPool(TargetFactory targetFactory, int targets) { + List staticConnectors = new ArrayList<>(); + + for (int i = 0; i < targets; i++) { + staticConnectors.add(new TransportConfiguration()); + } + + return new StaticPool(targetFactory, new ScheduledThreadPoolExecutor(0), CHECK_PERIOD, staticConnectors); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java new file mode 100644 index 0000000000..3c31d6f0a4 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java @@ -0,0 +1,156 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class MockTarget extends AbstractTarget { + private boolean local = false; + + private boolean connected = false; + + private boolean connectable = false; + + private boolean ready = false; + + private Map attributeValues = new HashMap<>(); + + private Map operationReturnValues = new HashMap<>(); + + + @Override + public boolean isLocal() { + return false; + } + + public MockTarget setLocal(boolean local) { + this.local = local; + return this; + } + + @Override + public boolean isConnected() { + return connected; + } + + public boolean isConnectable() { + return connectable; + } + + public MockTarget setConnected(boolean connected) { + this.connected = connected; + return this; + } + + public MockTarget setConnectable(boolean connectable) { + this.connectable = connectable; + return this; + } + + public boolean isReady() { + return ready; + } + + public MockTarget setReady(boolean ready) { + this.ready = ready; + return this; + } + + public Map getAttributeValues() { + return attributeValues; + } + + public void setAttributeValues(Map attributeValues) { + this.attributeValues = attributeValues; + } + + public Map getOperationReturnValues() { + return operationReturnValues; + } + + public void setOperationReturnValues(Map operationReturnValues) { + this.operationReturnValues = operationReturnValues; + } + + public MockTarget() { + this(new TransportConfiguration(), UUID.randomUUID().toString()); + } + + public MockTarget(TransportConfiguration connector, String nodeID) { + super(connector, nodeID); + } + + @Override + public void connect() throws Exception { + if (!connectable) { + throw new IllegalStateException("Target not connectable"); + } + + if (getNodeID() == null) { + setNodeID(UUID.randomUUID().toString()); + } + + connected = true; + + fireConnectedEvent(); + } + + @Override + public void disconnect() throws Exception { + connected = false; + + fireDisconnectedEvent(); + } + + @Override + public boolean checkReadiness() { + return ready; + } + + @Override + public T getAttribute(String resourceName, String attributeName, Class attributeClass, int timeout) throws Exception { + checkConnection(); + + return (T)attributeValues.get(resourceName + attributeName); + } + + @Override + public T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class operationClass, int timeout) throws Exception { + checkConnection(); + + return (T)operationReturnValues.get(resourceName + operationName); + } + + public void setAttributeValue(String resourceName, String attributeName, Object value) { + attributeValues.put(resourceName + attributeName, value); + } + + public void setOperationReturnValue(String resourceName, String attributeName, Object value) { + operationReturnValues.put(resourceName + attributeName, value); + } + + private void checkConnection() { + if (!connected) { + throw new IllegalStateException("Target not connected"); + } + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java new file mode 100644 index 0000000000..e0e0933192 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java @@ -0,0 +1,105 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MockTargetFactory extends AbstractTargetFactory { + + private final List createdTargets = new ArrayList<>(); + + private Boolean connectable = null; + + private Boolean ready = null; + + private Map attributeValues = null; + + private Map operationReturnValues = null; + + public Boolean getConnectable() { + return connectable; + } + + public MockTargetFactory setConnectable(Boolean connectable) { + this.connectable = connectable; + return this; + } + + public Boolean getReady() { + return ready; + } + + public MockTargetFactory setReady(Boolean ready) { + this.ready = ready; + return this; + } + + public Map getAttributeValues() { + return attributeValues; + } + + public MockTargetFactory setAttributeValues(Map attributeValues) { + this.attributeValues = attributeValues; + return this; + } + + public Map getOperationReturnValues() { + return operationReturnValues; + } + + public MockTargetFactory setOperationReturnValues(Map operationReturnValues) { + this.operationReturnValues = operationReturnValues; + return this; + } + + public List getCreatedTargets() { + return createdTargets; + } + + @Override + public Target createTarget(TransportConfiguration connector, String nodeID) { + MockTarget target = new MockTarget(connector, nodeID); + + target.setUsername(getUsername()); + target.setPassword(getPassword()); + + createdTargets.add(target); + + if (connectable != null) { + target.setConnectable(connectable); + } + + if (ready != null) { + target.setReady(ready); + } + + if (attributeValues != null) { + target.setAttributeValues(attributeValues); + } + + if (operationReturnValues != null) { + target.setOperationReturnValues(operationReturnValues); + } + + return target; + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java new file mode 100644 index 0000000000..d8c3aa2e48 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java @@ -0,0 +1,61 @@ +/** + * 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.balancing.targets; + +import java.util.HashMap; +import java.util.Map; + +public class MockTargetProbe extends TargetProbe { + private final Map targetExecutions = new HashMap<>(); + + private boolean checked; + + public boolean isChecked() { + return checked; + } + + public void setChecked(boolean checked) { + this.checked = checked; + } + + public MockTargetProbe(String name, boolean checked) { + super(name); + + this.checked = checked; + } + + public int getTargetExecutions(Target target) { + Integer executions = targetExecutions.get(target); + return executions != null ? executions : 0; + } + + public int setTargetExecutions(Target target, int executions) { + return targetExecutions.put(target, executions); + } + + public void clearTargetExecutions() { + targetExecutions.clear(); + } + + @Override + public boolean check(Target target) { + targetExecutions.compute(target, (t, e) -> e == null ? 1 : e++); + + return checked; + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java new file mode 100644 index 0000000000..59336969ba --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java @@ -0,0 +1,110 @@ +/** + * 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.balancing.targets; + +import org.apache.activemq.artemis.spi.core.remoting.Connection; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class TargetKeyResolverTest { + + @Test + public void testClientIDKey() { + testClientIDKey("TEST", "TEST", null); + } + + @Test + public void testClientIDKeyWithFilter() { + testClientIDKey("TEST", "TEST1234", "^.{4}"); + } + + private void testClientIDKey(String expected, String clientID, String filter) { + TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.CLIENT_ID, filter); + + Assert.assertEquals(expected, targetKeyResolver.resolve(null, clientID, null)); + + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + } + + @Test + public void testSNIHostKey() { + testSNIHostKey("TEST", "TEST", null); + } + + @Test + public void testSNIHostKeyWithFilter() { + testSNIHostKey("TEST", "TEST1234", "^.{4}"); + } + + private void testSNIHostKey(String expected, String sniHost, String filter) { + Connection connection = Mockito.mock(Connection.class); + + TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.SNI_HOST, filter); + + Mockito.when(connection.getSNIHostName()).thenReturn(sniHost); + Assert.assertEquals(expected, targetKeyResolver.resolve(connection, null, null)); + + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + + Mockito.when(connection.getSNIHostName()).thenReturn(null); + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + } + + @Test + public void testSourceIPKey() { + testSourceIPKey("10.0.0.1", "10.0.0.1:12345", null); + } + + @Test + public void testSourceIPKeyWithFilter() { + testSourceIPKey("10", "10.0.0.1:12345", "^[^.]+"); + } + + private void testSourceIPKey(String expected, String remoteAddress, String filter) { + Connection connection = Mockito.mock(Connection.class); + + TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.SOURCE_IP, filter); + + Mockito.when(connection.getRemoteAddress()).thenReturn(remoteAddress); + Assert.assertEquals(expected, targetKeyResolver.resolve(connection, null, null)); + + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + + Mockito.when(connection.getRemoteAddress()).thenReturn(null); + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + } + + @Test + public void testUserNameKey() { + testUserNameKey("TEST", "TEST", null); + } + + @Test + public void testUserNameKeyWithFilter() { + testUserNameKey("TEST", "TEST1234", "^.{4}"); + } + + private void testUserNameKey(String expected, String username, String filter) { + TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.USER_NAME, filter); + + Assert.assertEquals(expected, targetKeyResolver.resolve(null, null, username)); + + Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null)); + } +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java index 9b6fce21e4..1234a08dd6 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java @@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.Divert; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.QueueFactory; +import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer; import org.apache.activemq.artemis.core.server.cluster.Bridge; import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; @@ -325,6 +326,16 @@ public class ClusteredResetMockTest extends ActiveMQTestBase { } + @Override + public void registerBrokerBalancer(BrokerBalancer balancer) throws Exception { + + } + + @Override + public void unregisterBrokerBalancer(String name) throws Exception { + + } + @Override public Object getResource(String resourceName) { return null; @@ -350,6 +361,16 @@ public class ClusteredResetMockTest extends ActiveMQTestBase { } + @Override + public Object getAttribute(String resourceName, String attribute) { + return null; + } + + @Override + public Object invokeOperation(String resourceName, String operation, Object[] params) throws Exception { + return null; + } + @Override public void start() throws Exception { diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml index 75efb9dc29..c9e9232550 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml @@ -152,6 +152,38 @@ false + + + USER_NAME + + + + connector1 + + + + + SNI_HOST + ^[^.]+ + DEFAULT + + + 1000 + true + + + + + 60000 + + + 3000 + 2 + 1000 + + + + true diff --git a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml index 540973eed5..0a55f8462b 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml @@ -143,6 +143,38 @@ false + + + USER_NAME + + + + connector1 + + + + + SNI_HOST + ^[^.]+ + DEFAULT + + + 1000 + true + + + + + 60000 + + + 3000 + 2 + 1000 + + + + true diff --git a/docs/user-manual/en/SUMMARY.md b/docs/user-manual/en/SUMMARY.md index b5f2dd3850..d269a734a5 100644 --- a/docs/user-manual/en/SUMMARY.md +++ b/docs/user-manual/en/SUMMARY.md @@ -64,6 +64,7 @@ * [Address Federation](federation-address.md) * [Queue Federation](federation-queue.md) * [High Availability and Failover](ha.md) +* [Broker Balancers](broker-balancers.md) * [Graceful Server Shutdown](graceful-shutdown.md) * [Libaio Native Libraries](libaio.md) * [Thread management](thread-pooling.md) diff --git a/docs/user-manual/en/broker-balancers.md b/docs/user-manual/en/broker-balancers.md new file mode 100644 index 0000000000..36a7907a06 --- /dev/null +++ b/docs/user-manual/en/broker-balancers.md @@ -0,0 +1,191 @@ +# Broker Balancers +Apache ActiveMQ Artemis broker balancers allow incoming client connections to be distributed across multiple [target brokers](target-brokers). +The target brokers are grouped in [pools](#pools) and the broker balancers use a [target key](#target-key) +to select a target broker from a pool of brokers according to a [policy](#policies). + +### This feature is still **EXPERIMENTAL** and not meant to be run in production yet. Furthermore, its configuration can change until declared as **officially stable**. + +## Target Broker +Target broker is a broker that can accept incoming client connections and is local or remote. +The local target is a special target that represents the same broker hosting the broker balancer. +The remote target is another reachable broker. + +## Target Key +The broker balancer uses a target key to select a target broker. +It is a string retrieved from an incoming client connection, the supported values are: +* `CLIENT_ID` is the JMS client ID; +* `SNI_HOST` is the hostname indicated by the client in the SNI extension of the TLS protocol; +* `SOURCE_IP` is the source IP address of the client; +* `USER_NAME` is the username indicated by the client. + +## Pools +The pool is a group of target brokers and checks periodically their state. +It provides a list of ready target brokers to distribute incoming client connections only when it is active. +A pool becomes active when the minimum number of ready target brokers defined by the `quorum-size` parameter is reached. +When it is not active, it doesn't provide any target avoiding weird distribution at startup or after a restart. +Including the local broker in the target pool allows broker hosting the balancer to accept incoming client connections as well. +By default, a pool doesn't include the local broker, to include it as a target the `local-target-enabled` parameter must be `true`. +There are two pool types: [discovery pool](#discovery-pool) and [static pool](#static-pool). + +### Cluster Pool +The cluster pool uses a [cluster connection](clusters.md#configuring-cluster-connections) to get the target brokers to add. +Let's take a look at a cluster pool example from broker.xml that uses a cluster connection: +```xml + + cluster1 + +``` + +### Discovery Pool +The discovery pool uses a [discovery group](clusters.md#discovery-groups) to discover the target brokers to add. +Let's take a look at a discovery pool example from broker.xml that uses a discovery group: +```xml + + + +``` + +### Static Pool +The static pool uses a list of static connectors to define the target brokers to add. +Let's take a look at a static pool example from broker.xml that uses a list of static connectors: +```xml + + + connector1 + connector2 + connector3 + + +``` + +### Defining pools +A pool is defined by the `pool` element that includes the following items: +* the `username` element defines the username to connect to the target broker; +* the `password` element defines the password to connect to the target broker; +* the `check-period` element defines how often to check the target broker, measured in milliseconds, default is `5000`; +* the `quorum-size` element defines the minimum number of ready targets to activate the pool, default is `1`; +* the `quorum-timeout` element defines the timeout to get the minimum number of ready targets, measured in milliseconds, default is `3000`; +* the `local-target-enabled` element defines whether the pool has to include a local target, default is `false`; +* the `cluster-connection` element defines the [cluster connection](clusters.md#configuring-cluster-connections) used by the [cluster pool](#cluster-pool). +* the `static-connectors` element defines a list of static connectors used by the [static pool](#static-pool); +* the `discovery-group` element defines the [discovery group](clusters.md#discovery-groups) used by the [discovery pool](#discovery-pool). + +Let's take a look at a pool example from broker.xml: +```xml + + 2 + 1000 + true + + connector1 + connector2 + connector3 + + +``` + +## Policies +The policy define how to select a broker from a pool. The included policies are: +* `FIRST_ELEMENT` to select the first target broker from the pool which is ready. It is useful to select the ready target brokers + according to the priority defined with their sequence order, ie supposing there are 2 target brokers + this policy selects the second target broker only when the first target broker isn't ready. +* `ROUND_ROBIN` to select a target sequentially from a pool, this policy is useful to evenly distribute; +* `CONSISTENT_HASH` to select a target by a key. This policy always selects the same target broker for the same key until it is removed from the pool. +* `LEAST_CONNECTIONS` to select the targets with the fewest active connections. This policy helps you maintain an equal distribution of active connections with the target brokers. + +A policy is defined by the `policy` element. Let's take a look at a policy example from broker.xml: +```xml + +``` + +## Cache +The broker balancer provides a cache with a timeout to improve the stickiness of the target broker selected, +returning the same target broker for a target key as long as it is present in the cache and is ready. +So a broker balancer with the cache enabled doesn't strictly follow the configured policy. +By default, the cache is enabled, to disable the cache the `cache-timeout` parameter must be `0`. + +## Defining broker balancers +A broker balancer is defined by `broker-balancer` element, it includes the following items: +* the `name` attribute defines the name of the broker balancer; +* the `target-key` element defines what key to select a target broker, the supported values are: `CLIENT_ID`, `SNI_HOST`, `SOURCE_IP`, `USER_NAME`, default is `SOURCE_IP`, see [target key](#target-key) for further details; +* the `target-key-filter` element defines a regular expression to filter the resolved keys; +* the `local-target-filter` element defines a regular expression to match the keys that have to return a local target; +* the `cache-timeout` element is the time period for a target broker to remain in the cache, measured in milliseconds, setting `0` will disable the cache, default is `-1`, meaning no expiration; +* the `pool` element defines the pool to group the target brokers, see [pools](#pools). +* the `policy` element defines the policy used to select the target brokers, see [policies](#policies); + +Let's take a look at some broker balancer examples from broker.xml: +```xml + + + + + + connector1 + connector2 + connector3 + + + + + USER_NAME + admin + + + true + + + + + + CLIENT_ID + ^.{3} + + + guest + guest + + + + +``` + +## Broker Balancer Workflow +The broker balancer workflow include the following steps: +* Retrieve the target key from the incoming connection; +* Return the local target broker if the target key matches the local filter; +* Return the cached target broker if it is ready; +* Get ready target brokers from the pool; +* Select one target broker using the policy; +* Add the selected broker in the cache; +* Return the selected broker. + +Let's take a look at flowchart of the broker balancer workflow: +![Broker Balancer Workflow](images/broker_balancer_workflow.png) + + +## Redirection +Apache ActiveMQ Artemis provides a native redirection for supported clients and a new management API for other clients. +The native redirection can be enabled per acceptor and is supported only for AMQP, CORE and OPENWIRE clients. +The acceptor with the `redirect-to` url parameter will redirect the incoming connections. +The `redirect-to` url parameter specifies the name of the broker balancer to use, +ie the following acceptor will redirect the incoming CORE client connections using the broker balancer with the name `simple-balancer`: + +```xml +tcp://0.0.0.0:61616?redirect-to=simple-balancer;protocols=CORE +``` +### Native Redirect Sequence + +The clients supporting the native redirection connect to the acceptor with the redirection enabled. +The acceptor sends to the client the target broker to redirect if it is ready and closes the connection. +The client connects to the target broker if it has received one before getting disconnected +otherwise it connected again to the acceptor with the redirection enabled. + +![Native Redirect Sequence](images/native_redirect_sequence.png) + +### Management API Redirect Sequence +The clients not supporting the native redirection queries the management API of broker balancer +to get the target broker to redirect. If the API returns a target broker the client connects to it +otherwise the client queries again the API. + +![Management API Redirect Sequence](images/management_api_redirect_sequence.png) diff --git a/docs/user-manual/en/images/broker_balancer_workflow.png b/docs/user-manual/en/images/broker_balancer_workflow.png new file mode 100644 index 0000000000..97560b6bd8 Binary files /dev/null and b/docs/user-manual/en/images/broker_balancer_workflow.png differ diff --git a/docs/user-manual/en/images/management_api_redirect_sequence.png b/docs/user-manual/en/images/management_api_redirect_sequence.png new file mode 100644 index 0000000000..371baa8923 Binary files /dev/null and b/docs/user-manual/en/images/management_api_redirect_sequence.png differ diff --git a/docs/user-manual/en/images/native_redirect_sequence.png b/docs/user-manual/en/images/native_redirect_sequence.png new file mode 100644 index 0000000000..d7466da344 Binary files /dev/null and b/docs/user-manual/en/images/native_redirect_sequence.png differ diff --git a/examples/features/broker-balancer/evenly-redirect/pom.xml b/examples/features/broker-balancer/evenly-redirect/pom.xml new file mode 100644 index 0000000000..d92571bb64 --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/pom.xml @@ -0,0 +1,207 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples + broker-balancer + 2.18.0-SNAPSHOT + + + evenly-redirect + jar + evenly-redirect + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq + artemis-jms-client + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + true + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + true + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + create2 + + create + + + ${noServer} + ${basedir}/target/server2 + true + ${basedir}/target/classes/activemq/server2 + + -Djava.net.preferIPv4Stack=true + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + server1 + + + + start2 + + cli + + + ${noServer} + true + ${basedir}/target/server2 + tcp://localhost:61618 + + run + + server1 + + + + start0 + + cli + + + true + ${noServer} + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + server0 + + + + runClient + + runClient + + + + org.apache.activemq.artemis.jms.example.EvenlyRedirectExample + + + + stop0 + + cli + + + ${noServer} + ${basedir}/target/server0 + + stop + + + + + stop1 + + cli + + + ${noServer} + ${basedir}/target/server1 + + stop + + + + + stop2 + + cli + + + ${noServer} + ${basedir}/target/server2 + + stop + + + + + + + org.apache.activemq.examples + evenly-redirect + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + diff --git a/examples/features/broker-balancer/evenly-redirect/readme.md b/examples/features/broker-balancer/evenly-redirect/readme.md new file mode 100644 index 0000000000..05011d9b76 --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/readme.md @@ -0,0 +1,8 @@ +# Evenly Redirect Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to create and start the broker manually. + +This example demonstrates how incoming client connections are evenly redirected across two brokers +using a third broker with a balancer to redirect incoming client connections, +based on a least-connections policy and caching on a filtered prefix of the connection ClientID. + diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java b/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java new file mode 100644 index 0000000000..40a870641e --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java @@ -0,0 +1,106 @@ +/* + * 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.jms.example; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; + +/** + * This example demonstrates how incoming client connections are evenly redirected across two brokers + * using a third broker with a broker balancer to redirect incoming client connections. + */ +public class EvenlyRedirectExample { + + public static void main(final String[] args) throws Exception { + + /** + * Step 1. Create a connection for producer0 and producer1, and send a few messages. + * the server0 will redirect the connection of each producer to a different target broker. + */ + ConnectionFactory connectionFactoryProducer0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=FOO_PRODUCER"); + ConnectionFactory connectionFactoryProducer1 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=BAR_PRODUCER"); + + Connection connectionProducer0 = null; + Connection connectionProducer1 = null; + + try { + connectionProducer0 = connectionFactoryProducer0.createConnection(); + connectionProducer1 = connectionFactoryProducer1.createConnection(); + + for (Connection connectionProducer : new Connection[] {connectionProducer0, connectionProducer1}) { + Session session = connectionProducer.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue queue = session.createQueue("exampleQueue"); + MessageProducer sender = session.createProducer(queue); + for (int i = 0; i < 100; i++) { + sender.send(session.createTextMessage("Hello world n" + i + " - " + connectionProducer.getClientID())); + } + } + } finally { + if (connectionProducer0 != null) { + connectionProducer0.close(); + } + + if (connectionProducer1 != null) { + connectionProducer1.close(); + } + } + + /** + * Step 2. create a connection for consumer0 and consumer1, and receive a few messages. + * the server0 will redirect the connection to the same target broker of the respective producer + * because the consumer and the producer connections have the same clientID prefix, which + * the balancer configuration filters the target key on and caches the target broker the policy selects. + */ + ConnectionFactory connectionFactoryConsumer0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=BAR_CONSUMER"); + ConnectionFactory connectionFactoryConsumer1 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=FOO_CONSUMER"); + + Connection connectionConsumer0 = null; + Connection connectionConsumer1 = null; + + try { + connectionConsumer0 = connectionFactoryConsumer0.createConnection(); + connectionConsumer1 = connectionFactoryConsumer1.createConnection(); + + for (Connection connectionConsumer : new Connection[] {connectionConsumer0, connectionConsumer1}) { + connectionConsumer.start(); + Session session = connectionConsumer.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue("exampleQueue"); + MessageConsumer consumer = session.createConsumer(queue); + for (int i = 0; i < 100; i++) { + TextMessage message = (TextMessage) consumer.receive(5000); + System.out.println("Received message " + message.getText() + "/" + connectionConsumer.getClientID()); + } + } + } finally { + if (connectionConsumer0 != null) { + connectionConsumer0.close(); + } + + if (connectionConsumer1 != null) { + connectionConsumer1.close(); + } + } + } +} diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..a303f42979 --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,135 @@ + + + + + + + + 0.0.0.0 + + + false + + NIO + + + true + + 120000 + + 60000 + + HALT + + 60000 + + + + tcp://0.0.0.0:61616?redirect-to=evenly-balancer;tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true + + + + tcp://localhost:61617 + tcp://localhost:61618 + + + + + CLIENT_ID + ^.{3} + DEFAULT + + + guest + guest + + server1 + server2 + + + + + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + +

+ + + +
+
+ + + +
+ + + + + diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..a9800a8fe5 --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,113 @@ + + + + + + + + 0.0.0.0 + + + false + + NIO + + + true + + 120000 + + 60000 + + HALT + + 60000 + + + + tcp://0.0.0.0:61617?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true + + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + +
+ + + +
+
+ + + +
+ +
+ +
+
diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml new file mode 100644 index 0000000000..ed92197af3 --- /dev/null +++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml @@ -0,0 +1,113 @@ + + + + + + + + 0.0.0.0 + + + false + + NIO + + + true + + 120000 + + 60000 + + HALT + + 60000 + + + + tcp://0.0.0.0:61618?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true + + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + +
+ + + +
+
+ + + +
+ +
+ +
+
diff --git a/examples/features/broker-balancer/pom.xml b/examples/features/broker-balancer/pom.xml new file mode 100644 index 0000000000..682307b1b3 --- /dev/null +++ b/examples/features/broker-balancer/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.clustered + broker-features + 2.18.0-SNAPSHOT + + + org.apache.activemq.examples + broker-balancer + pom + ActiveMQ Artemis Broker Balancer Examples + + + + ${project.basedir}/../../.. + + + + + examples + + evenly-redirect + symmetric-redirect + + + + release + + evenly-redirect + symmetric-redirect + + + + diff --git a/examples/features/broker-balancer/symmetric-redirect/pom.xml b/examples/features/broker-balancer/symmetric-redirect/pom.xml new file mode 100644 index 0000000000..04319d8cbc --- /dev/null +++ b/examples/features/broker-balancer/symmetric-redirect/pom.xml @@ -0,0 +1,164 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples + broker-balancer + 2.18.0-SNAPSHOT + + + symmetric-redirect + jar + symmetric-redirect + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq + artemis-jms-client + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + true + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + true + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + start0 + + cli + + + true + ${noServer} + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + server0 + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + server1 + + + + runClient + + runClient + + + + org.apache.activemq.artemis.jms.example.SymmetricRedirectExample + + + + stop0 + + cli + + + ${noServer} + ${basedir}/target/server0 + + stop + + + + + stop1 + + cli + + + ${noServer} + ${basedir}/target/server1 + + stop + + + + + + + org.apache.activemq.examples + symmetric-redirect + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + diff --git a/examples/features/broker-balancer/symmetric-redirect/readme.md b/examples/features/broker-balancer/symmetric-redirect/readme.md new file mode 100644 index 0000000000..616dd8b3fa --- /dev/null +++ b/examples/features/broker-balancer/symmetric-redirect/readme.md @@ -0,0 +1,9 @@ +# Symmetric Redirect Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to create and start the broker manually. + +This example demonstrates how incoming client connections are distributed across two brokers +using a symmetric architecture. In this architecture both brokers have two roles: balancer and target. +So they can redirect or accept the incoming client connection according to the consistent hash algorithm. +Both brokers use the same consistent hash algorithm to select the target broker so for the same key +if the first broker redirects an incoming client connection the second accepts it and vice versa. \ No newline at end of file diff --git a/examples/features/broker-balancer/symmetric-redirect/src/main/java/org/apache/activemq/artemis/jms/example/SymmetricRedirectExample.java b/examples/features/broker-balancer/symmetric-redirect/src/main/java/org/apache/activemq/artemis/jms/example/SymmetricRedirectExample.java new file mode 100644 index 0000000000..ca3abe59d2 --- /dev/null +++ b/examples/features/broker-balancer/symmetric-redirect/src/main/java/org/apache/activemq/artemis/jms/example/SymmetricRedirectExample.java @@ -0,0 +1,107 @@ +/* + * 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.jms.example; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; + +/** + * This example demonstrates how incoming client connections are distributed across two brokers + * using a symmetric architecture. + */ +public class SymmetricRedirectExample { + + public static void main(final String[] args) throws Exception { + + /** + * Step 1. Create a connection for producer0 and producer1, and send a few messages. + * the server0 will redirect the connection of each producer to a different target brokers. + */ + ConnectionFactory connectionFactory0Server0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=FOO_PRODUCER"); + ConnectionFactory connectionFactory1Server0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=BAR_PRODUCER"); + + Connection connectionProducer0 = null; + Connection connectionProducer1 = null; + + try { + connectionProducer0 = connectionFactory0Server0.createConnection(); + connectionProducer1 = connectionFactory1Server0.createConnection(); + + for (Connection connectionProducer : new Connection[] {connectionProducer0, connectionProducer1}) { + Session session = connectionProducer.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue queue = session.createQueue("exampleQueue" + connectionProducer.getClientID().substring(0, 3)); + MessageProducer sender = session.createProducer(queue); + for (int i = 0; i < 100; i++) { + TextMessage message = session.createTextMessage("Hello world n" + i + " - " + connectionProducer.getClientID().substring(0, 3)); + System.out.println("Sending message " + message.getText() + "/" + connectionProducer.getClientID()); + sender.send(message); + } + } + } finally { + if (connectionProducer0 != null) { + connectionProducer0.close(); + } + + if (connectionProducer1 != null) { + connectionProducer1.close(); + } + } + + /** + * Step 2. create a connection for consumer0 and consumer1, and receive a few messages. + * the server1 will redirect the connection to the same target broker of the respective producer + * from earlier as the new consumer connection uses the same ClientID prefix. + */ + ConnectionFactory connectionFactory0Server1 = new ActiveMQConnectionFactory("tcp://localhost:61617?ha=true&reconnectAttempts=30&clientID=BAR_CONSUMER"); + ConnectionFactory connectionFactory1Server1 = new ActiveMQConnectionFactory("tcp://localhost:61617?ha=true&reconnectAttempts=30&clientID=FOO_CONSUMER"); + + Connection connectionConsumer0 = null; + Connection connectionConsumer1 = null; + + try { + connectionConsumer0 = connectionFactory0Server1.createConnection(); + connectionConsumer1 = connectionFactory1Server1.createConnection(); + + for (Connection connectionConsumer : new Connection[] {connectionConsumer0, connectionConsumer1}) { + connectionConsumer.start(); + Session session = connectionConsumer.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue("exampleQueue" + connectionConsumer.getClientID().substring(0, 3)); + MessageConsumer consumer = session.createConsumer(queue); + for (int i = 0; i < 100; i++) { + TextMessage message = (TextMessage) consumer.receive(5000); + System.out.println("Received message " + message.getText() + "/" + connectionConsumer.getClientID()); + } + } + } finally { + if (connectionConsumer0 != null) { + connectionConsumer0.close(); + } + + if (connectionConsumer1 != null) { + connectionConsumer1.close(); + } + } + } +} diff --git a/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server0/broker.xml b/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..9316bfbe1b --- /dev/null +++ b/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,150 @@ + + + + + + + + 0.0.0.0 + + + false + + NIO + + + true + + 120000 + + 60000 + + HALT + + 60000 + + + + tcp://0.0.0.0:61616?redirect-to=symmetric-balancer;tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true + + + + tcp://localhost:61616 + + + + + ${udp-address:231.7.7.7} + 9876 + 100 + netty-connector + + + + + + ${udp-address:231.7.7.7} + 9876 + 10000 + + + + + + CLIENT_ID + ^.{3} + DEFAULT + + + guest + guest + 2 + true + + + + + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + +
+ + + +
+
+ + + +
+ +
+ +
+
diff --git a/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server1/broker.xml b/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..b3fa125b8c --- /dev/null +++ b/examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,150 @@ + + + + + + + + 0.0.0.0 + + + false + + NIO + + + true + + 120000 + + 60000 + + HALT + + 60000 + + + + tcp://0.0.0.0:61617?redirect-to=symmetric-balancer;tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true + + + + tcp://localhost:61617 + + + + + ${udp-address:231.7.7.7} + 9876 + 100 + netty-connector + + + + + + ${udp-address:231.7.7.7} + 9876 + 10000 + + + + + + CLIENT_ID + ^.{3} + DEFAULT + + + guest + guest + 2 + true + + + + + + + + + + + + + + + + + + + + + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + DLQ + ExpiryQueue + 0 + + -1 + 10 + PAGE + true + true + true + true + + + + +
+ + + +
+
+ + + +
+ +
+ +
+
diff --git a/pom.xml b/pom.xml index 5e59e4999c..c6d5384f83 100644 --- a/pom.xml +++ b/pom.xml @@ -163,7 +163,7 @@ 1 0 0 - 130,129,128,127,126,125,124,123,122 + 131,130,129,128,127,126,125,124,123,122 ${project.version} ${project.version}(${activemq.version.incrementingVersion}) diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/BalancingTestBase.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/BalancingTestBase.java new file mode 100644 index 0000000000..db7a849277 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/BalancingTestBase.java @@ -0,0 +1,246 @@ +/** + * 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.balancing; + +import javax.jms.ConnectionFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration; +import org.apache.activemq.artemis.core.config.balancing.PolicyConfiguration; +import org.apache.activemq.artemis.core.config.balancing.PoolConfiguration; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.apache.activemq.artemis.tests.integration.cluster.distribution.ClusterTestBase; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.apache.qpid.jms.JmsConnectionFactory; + +public class BalancingTestBase extends ClusterTestBase { + protected static final String AMQP_PROTOCOL = "AMQP"; + protected static final String CORE_PROTOCOL = "CORE"; + protected static final String OPENWIRE_PROTOCOL = "OPENWIRE"; + + protected static final String CLUSTER_POOL = "CLUSTER"; + protected static final String DISCOVERY_POOL = "DISCOVERY"; + protected static final String STATIC_POOL = "STATIC"; + + protected static final String BROKER_BALANCER_NAME = "bb1"; + + protected static final String DEFAULT_CONNECTOR_NAME = "DEFAULT"; + + protected static final String GROUP_ADDRESS = ActiveMQTestBase.getUDPDiscoveryAddress(); + + protected static final int GROUP_PORT = ActiveMQTestBase.getUDPDiscoveryPort(); + + protected static final int MULTIPLE_TARGETS = 3; + + + protected TransportConfiguration getDefaultServerAcceptor(final int node) { + return getServer(node).getConfiguration().getAcceptorConfigurations().stream().findFirst().get(); + } + + protected TransportConfiguration getDefaultServerConnector(final int node) { + Map connectorConfigurations = getServer(node).getConfiguration().getConnectorConfigurations(); + TransportConfiguration connector = connectorConfigurations.get(DEFAULT_CONNECTOR_NAME); + return connector != null ? connector : connectorConfigurations.values().stream().findFirst().get(); + } + + protected TransportConfiguration setupDefaultServerConnector(final int node) { + TransportConfiguration defaultServerConnector = getDefaultServerConnector(node); + + if (!defaultServerConnector.getName().equals(DEFAULT_CONNECTOR_NAME)) { + defaultServerConnector = new TransportConfiguration(defaultServerConnector.getFactoryClassName(), + defaultServerConnector.getParams(), DEFAULT_CONNECTOR_NAME, defaultServerConnector.getExtraParams()); + + getServer(node).getConfiguration().getConnectorConfigurations().put(DEFAULT_CONNECTOR_NAME, defaultServerConnector); + } + + return defaultServerConnector; + } + + protected void setupBalancerServerWithCluster(final int node, final TargetKey targetKey, final String policyName, final Map properties, final boolean localTargetEnabled, final String localTargetFilter, final int quorumSize, String clusterConnection) { + Configuration configuration = getServer(node).getConfiguration(); + BrokerBalancerConfiguration brokerBalancerConfiguration = new BrokerBalancerConfiguration().setName(BROKER_BALANCER_NAME); + + setupDefaultServerConnector(node); + + brokerBalancerConfiguration.setTargetKey(targetKey).setLocalTargetFilter(localTargetFilter) + .setPoolConfiguration(new PoolConfiguration().setCheckPeriod(1000).setQuorumSize(quorumSize) + .setLocalTargetEnabled(localTargetEnabled).setClusterConnection(clusterConnection)) + .setPolicyConfiguration(new PolicyConfiguration().setName(policyName).setProperties(properties)); + + configuration.setBalancerConfigurations(Collections.singletonList(brokerBalancerConfiguration)); + + TransportConfiguration acceptor = getDefaultServerAcceptor(node); + acceptor.getParams().put("redirect-to", BROKER_BALANCER_NAME); + } + + protected void setupBalancerServerWithDiscovery(final int node, final TargetKey targetKey, final String policyName, final Map properties, final boolean localTargetEnabled, final String localTargetFilter, final int quorumSize) { + Configuration configuration = getServer(node).getConfiguration(); + BrokerBalancerConfiguration brokerBalancerConfiguration = new BrokerBalancerConfiguration().setName(BROKER_BALANCER_NAME); + + setupDefaultServerConnector(node); + + brokerBalancerConfiguration.setTargetKey(targetKey).setLocalTargetFilter(localTargetFilter) + .setPoolConfiguration(new PoolConfiguration().setCheckPeriod(1000).setQuorumSize(quorumSize) + .setLocalTargetEnabled(localTargetEnabled).setDiscoveryGroupName("dg1")) + .setPolicyConfiguration(new PolicyConfiguration().setName(policyName).setProperties(properties)); + + configuration.setBalancerConfigurations(Collections.singletonList(brokerBalancerConfiguration)); + + TransportConfiguration acceptor = getDefaultServerAcceptor(node); + acceptor.getParams().put("redirect-to", BROKER_BALANCER_NAME); + } + + protected void setupBalancerServerWithStaticConnectors(final int node, final TargetKey targetKey, final String policyName, final Map properties, final boolean localTargetEnabled, final String localTargetFilter, final int quorumSize, final int... targetNodes) { + Configuration configuration = getServer(node).getConfiguration(); + BrokerBalancerConfiguration brokerBalancerConfiguration = new BrokerBalancerConfiguration().setName(BROKER_BALANCER_NAME); + + setupDefaultServerConnector(node); + + List staticConnectors = new ArrayList<>(); + for (int targetNode : targetNodes) { + TransportConfiguration connector = getDefaultServerConnector(targetNode); + configuration.getConnectorConfigurations().put(connector.getName(), connector); + staticConnectors.add(connector.getName()); + } + + brokerBalancerConfiguration.setTargetKey(targetKey).setLocalTargetFilter(localTargetFilter) + .setPoolConfiguration(new PoolConfiguration().setCheckPeriod(1000).setQuorumSize(quorumSize) + .setLocalTargetEnabled(localTargetEnabled).setStaticConnectors(staticConnectors)) + .setPolicyConfiguration(new PolicyConfiguration().setName(policyName).setProperties(properties)); + + configuration.setBalancerConfigurations(Collections.singletonList(brokerBalancerConfiguration)); + + TransportConfiguration acceptor = getDefaultServerAcceptor(node); + acceptor.getParams().put("redirect-to", BROKER_BALANCER_NAME); + } + + protected ConnectionFactory createFactory(String protocol, boolean sslEnabled, String host, int port, String clientID, String user, String password) throws Exception { + switch (protocol) { + case CORE_PROTOCOL: { + StringBuilder urlBuilder = new StringBuilder(); + + urlBuilder.append("tcp://"); + urlBuilder.append(host); + urlBuilder.append(":"); + urlBuilder.append(port); + urlBuilder.append("?ha=true&reconnectAttempts=30"); + + urlBuilder.append("&sniHost="); + urlBuilder.append(host); + + if (clientID != null) { + urlBuilder.append("&clientID="); + urlBuilder.append(clientID); + } + + if (sslEnabled) { + urlBuilder.append("&"); + urlBuilder.append(TransportConstants.SSL_ENABLED_PROP_NAME); + urlBuilder.append("="); + urlBuilder.append(true); + + urlBuilder.append("&"); + urlBuilder.append(TransportConstants.TRUSTSTORE_PATH_PROP_NAME); + urlBuilder.append("="); + urlBuilder.append("server-ca-truststore.jks"); + + urlBuilder.append("&"); + urlBuilder.append(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME); + urlBuilder.append("="); + urlBuilder.append("securepass"); + } + + return new ActiveMQConnectionFactory(urlBuilder.toString(), user, password); + } + case AMQP_PROTOCOL: { + StringBuilder urlBuilder = new StringBuilder(); + + urlBuilder.append("failover:("); + + if (sslEnabled) { + urlBuilder.append("amqps://"); + urlBuilder.append(host); + urlBuilder.append(":"); + urlBuilder.append(port); + + urlBuilder.append("?transport.trustStoreLocation="); + urlBuilder.append(getClass().getClassLoader().getResource("server-ca-truststore.jks").getFile()); + urlBuilder.append("&transport.trustStorePassword=securepass)"); + } else { + urlBuilder.append("amqp://"); + urlBuilder.append(host); + urlBuilder.append(":"); + urlBuilder.append(port); + urlBuilder.append(")"); + } + + if (clientID != null) { + urlBuilder.append("?jms.clientID="); + urlBuilder.append(clientID); + } + + return new JmsConnectionFactory(user, password, urlBuilder.toString()); + } + case OPENWIRE_PROTOCOL: { + StringBuilder urlBuilder = new StringBuilder(); + + urlBuilder.append("failover:("); + + if (sslEnabled) { + urlBuilder.append("ssl://"); + urlBuilder.append(host); + urlBuilder.append(":"); + urlBuilder.append(port); + urlBuilder.append(")"); + } else { + urlBuilder.append("tcp://"); + urlBuilder.append(host); + urlBuilder.append(":"); + urlBuilder.append(port); + urlBuilder.append(")"); + } + + if (clientID != null) { + urlBuilder.append("?jms.clientID="); + urlBuilder.append(clientID); + } + + if (sslEnabled) { + org.apache.activemq.ActiveMQSslConnectionFactory sslConnectionFactory = new org.apache.activemq.ActiveMQSslConnectionFactory(urlBuilder.toString()); + sslConnectionFactory.setUserName(user); + sslConnectionFactory.setPassword(password); + sslConnectionFactory.setTrustStore("server-ca-truststore.jks"); + sslConnectionFactory.setTrustStorePassword("securepass"); + return sslConnectionFactory; + } else { + return new org.apache.activemq.ActiveMQConnectionFactory(user, password, urlBuilder.toString()); + } + } + default: + throw new IllegalStateException("Unexpected value: " + protocol); + } + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/MQTTRedirectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/MQTTRedirectTest.java new file mode 100644 index 0000000000..acef93ce74 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/MQTTRedirectTest.java @@ -0,0 +1,125 @@ +/** + * 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.balancing; + +import io.netty.handler.codec.mqtt.MqttConnectReturnCode; +import org.apache.activemq.artemis.api.core.QueueConfiguration; +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl; +import org.apache.activemq.artemis.api.core.management.QueueControl; +import org.apache.activemq.artemis.api.core.management.ResourceNames; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.apache.activemq.artemis.utils.Wait; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.junit.Assert; +import org.junit.Test; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class MQTTRedirectTest extends BalancingTestBase { + + private final boolean discovery = true; + + @Test + public void testSimpleRedirect() throws Exception { + final String topicName = "RedirectTestTopic"; + + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupLiveServerWithDiscovery(1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + if (discovery) { + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1); + } else { + setupBalancerServerWithStaticConnectors(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1, 1); + } + + startServers(0, 1); + + getServer(0).createQueue(new QueueConfiguration(topicName).setRoutingType(RoutingType.ANYCAST)); + getServer(1).createQueue(new QueueConfiguration(topicName).setRoutingType(RoutingType.ANYCAST)); + + QueueControl queueControl0 = (QueueControl)getServer(0).getManagementService() + .getResource(ResourceNames.QUEUE + topicName); + QueueControl queueControl1 = (QueueControl)getServer(1).getManagementService() + .getResource(ResourceNames.QUEUE + topicName); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setCleanSession(true); + connOpts.setUserName("admin"); + connOpts.setPassword("admin".toCharArray()); + + MqttClient client0 = new MqttClient("tcp://" + TransportConstants.DEFAULT_HOST + ":" + TransportConstants.DEFAULT_PORT, "TEST", new MemoryPersistence()); + try { + client0.connect(connOpts); + Assert.fail(); + } catch (MqttException e) { + Assert.assertEquals(MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER, MqttConnectReturnCode.valueOf((byte) e.getReasonCode())); + } + client0.close(); + + BrokerBalancerControl brokerBalancerControl = (BrokerBalancerControl)getServer(0).getManagementService() + .getResource(ResourceNames.BROKER_BALANCER + BROKER_BALANCER_NAME); + + CompositeData targetData = brokerBalancerControl.getTarget("admin"); + CompositeData targetConnectorData = (CompositeData)targetData.get("connector"); + TabularData targetConnectorParams = (TabularData)targetConnectorData.get("params"); + CompositeData hostData = targetConnectorParams.get(new Object[]{TransportConstants.HOST_PROP_NAME}); + CompositeData portData = targetConnectorParams.get(new Object[]{TransportConstants.PORT_PROP_NAME}); + String host = hostData != null ? (String)hostData.get("value") : TransportConstants.DEFAULT_HOST; + int port = portData != null ? Integer.valueOf((String)portData.get("value")) : TransportConstants.DEFAULT_PORT; + + CountDownLatch latch = new CountDownLatch(1); + List messages = new ArrayList<>(); + + MqttClient client1 = new MqttClient("tcp://" + host + ":" + port, "TEST", new MemoryPersistence()); + client1.connect(connOpts); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + client1.subscribe(topicName, (s, mqttMessage) -> { + messages.add(mqttMessage); + latch.countDown(); + }); + + client1.publish(topicName, new MqttMessage("TEST".getBytes())); + + Assert.assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)); + Assert.assertEquals("TEST", new String(messages.get(0).getPayload())); + + client1.disconnect(); + client1.close(); + + Assert.assertEquals(0, queueControl0.countMessages()); + Wait.assertEquals(0, () -> queueControl1.countMessages()); + } +} + diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/RedirectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/RedirectTest.java new file mode 100644 index 0000000000..185c552e1c --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/RedirectTest.java @@ -0,0 +1,399 @@ +/** + * 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.balancing; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.activemq.artemis.api.core.QueueConfiguration; +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.management.QueueControl; +import org.apache.activemq.artemis.api.core.management.ResourceNames; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.balancing.policies.ConsistentHashPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.LeastConnectionsPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.RoundRobinPolicy; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class RedirectTest extends BalancingTestBase { + + @Parameterized.Parameters(name = "protocol: {0}, pool: {1}") + public static Collection data() { + final String[] protocols = new String[] {AMQP_PROTOCOL, CORE_PROTOCOL, OPENWIRE_PROTOCOL}; + final String[] pools = new String[] {CLUSTER_POOL, DISCOVERY_POOL, STATIC_POOL}; + Collection data = new ArrayList<>(); + + for (String protocol : Arrays.asList(protocols)) { + for (String pool : Arrays.asList(pools)) { + data.add(new Object[] {protocol, pool}); + } + } + + return data; + } + + + private final String protocol; + + private final String pool; + + + public RedirectTest(String protocol, String pool) { + this.protocol = protocol; + + this.pool = pool; + } + + @Test + public void testSimpleRedirect() throws Exception { + final String queueName = "RedirectTestQueue"; + + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupLiveServerWithDiscovery(1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + if (CLUSTER_POOL.equals(pool)) { + setupDiscoveryClusterConnection("cluster0", 0, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupDiscoveryClusterConnection("cluster1", 1, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupBalancerServerWithCluster(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, "ACTIVEMQ.CLUSTER.ADMIN.USER", 1, "cluster0"); + } else if (DISCOVERY_POOL.equals(pool)) { + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1); + } else { + setupBalancerServerWithStaticConnectors(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1, 1); + } + + startServers(0, 1); + + getServer(0).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + getServer(1).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + + QueueControl queueControl0 = (QueueControl)getServer(0).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + QueueControl queueControl1 = (QueueControl)getServer(1).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + ConnectionFactory connectionFactory = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, "admin", "admin"); + + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + javax.jms.Queue queue = session.createQueue(queueName); + try (MessageProducer producer = session.createProducer(queue)) { + producer.send(session.createTextMessage("TEST")); + } + } + } + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(1, queueControl1.countMessages()); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + try (MessageConsumer consumer = session.createConsumer(session.createQueue(queueName))) { + TextMessage message = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(message); + Assert.assertEquals("TEST", message.getText()); + } + } + } + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + stopServers(0, 1); + } + + @Test + public void testRoundRobinRedirect() throws Exception { + testEvenlyRedirect(RoundRobinPolicy.NAME, null); + } + + @Test + public void testLeastConnectionsRedirect() throws Exception { + testEvenlyRedirect(LeastConnectionsPolicy.NAME, Collections.singletonMap(LeastConnectionsPolicy.CONNECTION_COUNT_THRESHOLD, String.valueOf(30))); + } + + private void testEvenlyRedirect(final String policyName, final Map properties) throws Exception { + final String queueName = "RedirectTestQueue"; + final int targets = MULTIPLE_TARGETS; + int[] nodes = new int[targets + 1]; + int[] targetNodes = new int[targets]; + QueueControl[] queueControls = new QueueControl[targets + 1]; + + nodes[0] = 0; + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + for (int i = 0; i < targets; i++) { + nodes[i + 1] = i + 1; + targetNodes[i] = i + 1; + setupLiveServerWithDiscovery(i + 1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + } + + if (CLUSTER_POOL.equals(pool)) { + for (int node : nodes) { + setupDiscoveryClusterConnection("cluster" + node, node, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + } + setupBalancerServerWithCluster(0, TargetKey.USER_NAME, policyName, properties, false, "ACTIVEMQ.CLUSTER.ADMIN.USER", targets, "cluster0"); + } else if (DISCOVERY_POOL.equals(pool)) { + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, policyName, properties, false, null, targets); + } else { + setupBalancerServerWithStaticConnectors(0, TargetKey.USER_NAME, policyName, properties, false, null, targets, 1, 2, 3); + } + + startServers(nodes); + + for (int node : nodes) { + getServer(node).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + + queueControls[node] = (QueueControl)getServer(node).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + + Assert.assertEquals(0, queueControls[node].countMessages()); + } + + + ConnectionFactory[] connectionFactories = new ConnectionFactory[targets]; + Connection[] connections = new Connection[targets]; + Session[] sessions = new Session[targets]; + + for (int i = 0; i < targets; i++) { + connectionFactories[i] = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, "user" + i, "user" + i); + + connections[i] = connectionFactories[i].createConnection(); + connections[i].start(); + + sessions[i] = connections[i].createSession(false, Session.AUTO_ACKNOWLEDGE); + } + + for (int i = 0; i < targets; i++) { + try (MessageProducer producer = sessions[i].createProducer(sessions[i].createQueue(queueName))) { + producer.send(sessions[i].createTextMessage("TEST" + i)); + } + + sessions[i].close(); + connections[i].close(); + } + + Assert.assertEquals(0, queueControls[0].countMessages()); + for (int targetNode : targetNodes) { + Assert.assertEquals("Messages of node " + targetNode, 1, queueControls[targetNode].countMessages()); + } + + for (int i = 0; i < targets; i++) { + try (Connection connection = connectionFactories[i].createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + try (MessageConsumer consumer = session.createConsumer(session.createQueue(queueName))) { + TextMessage message = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(message); + Assert.assertEquals("TEST" + i, message.getText()); + } + } + } + } + + for (int node : nodes) { + Assert.assertEquals(0, queueControls[node].countMessages()); + } + + stopServers(nodes); + } + + @Test + public void testSymmetricRedirect() throws Exception { + final String queueName = "RedirectTestQueue"; + + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupLiveServerWithDiscovery(1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + if (CLUSTER_POOL.equals(pool)) { + setupDiscoveryClusterConnection("cluster0", 0, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupDiscoveryClusterConnection("cluster1", 1, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupBalancerServerWithCluster(0, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, "ACTIVEMQ.CLUSTER.ADMIN.USER", 2, "cluster0"); + setupBalancerServerWithCluster(1, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, "ACTIVEMQ.CLUSTER.ADMIN.USER", 2, "cluster1"); + } else if (DISCOVERY_POOL.equals(pool)) { + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, null, 2); + setupBalancerServerWithDiscovery(1, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, null, 2); + } else { + setupBalancerServerWithStaticConnectors(0, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, null, 2, 1); + setupBalancerServerWithStaticConnectors(1, TargetKey.USER_NAME, ConsistentHashPolicy.NAME, null, true, null, 2, 0); + } + + startServers(0, 1); + + Assert.assertTrue(getServer(0).getNodeID() != getServer(1).getNodeID()); + + getServer(0).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + getServer(1).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + + QueueControl queueControl0 = (QueueControl)getServer(0).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + QueueControl queueControl1 = (QueueControl)getServer(1).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + ConnectionFactory connectionFactory0 = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, "admin", "admin"); + + + try (Connection connection = connectionFactory0.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + javax.jms.Queue queue = session.createQueue(queueName); + try (MessageProducer producer = session.createProducer(queue)) { + producer.send(session.createTextMessage("TEST")); + } + } + } + + Assert.assertTrue((queueControl0.countMessages() == 0 && queueControl1.countMessages() == 1) || + (queueControl0.countMessages() == 1 && queueControl1.countMessages() == 0)); + + Assert.assertTrue(getServer(0).getNodeID() != getServer(1).getNodeID()); + + ConnectionFactory connectionFactory1 = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 1, null, "admin", "admin"); + + try (Connection connection = connectionFactory1.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + try (MessageConsumer consumer = session.createConsumer(session.createQueue(queueName))) { + TextMessage message = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(message); + Assert.assertEquals("TEST", message.getText()); + } + } + } + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + + stopServers(0, 1); + } + + @Test + public void testRedirectAfterFailure() throws Exception { + final String queueName = "RedirectTestQueue"; + + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupLiveServerWithDiscovery(1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupLiveServerWithDiscovery(2, GROUP_ADDRESS, GROUP_PORT, true, true, false); + if (CLUSTER_POOL.equals(pool)) { + setupDiscoveryClusterConnection("cluster0", 0, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupDiscoveryClusterConnection("cluster1", 1, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupDiscoveryClusterConnection("cluster2", 2, "dg1", "queues", MessageLoadBalancingType.OFF, 1, true); + setupBalancerServerWithCluster(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, "ACTIVEMQ.CLUSTER.ADMIN.USER", 1, "cluster0"); + } else if (DISCOVERY_POOL.equals(pool)) { + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1); + } else { + setupBalancerServerWithStaticConnectors(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1, 1, 2); + } + + startServers(0, 1, 2); + + getServer(0).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + getServer(1).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + getServer(2).createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST)); + + QueueControl queueControl0 = (QueueControl)getServer(0).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + QueueControl queueControl1 = (QueueControl)getServer(1).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + QueueControl queueControl2 = (QueueControl)getServer(2).getManagementService() + .getResource(ResourceNames.QUEUE + queueName); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(0, queueControl1.countMessages()); + Assert.assertEquals(0, queueControl2.countMessages()); + + int failedNode; + ConnectionFactory connectionFactory = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, "admin", "admin"); + + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + javax.jms.Queue queue = session.createQueue(queueName); + try (MessageProducer producer = session.createProducer(queue)) { + producer.send(session.createTextMessage("TEST_BEFORE_FAILURE")); + + if (queueControl1.countMessages() > 0) { + failedNode = 1; + } else { + failedNode = 2; + } + + stopServers(failedNode); + + producer.send(session.createTextMessage("TEST_AFTER_FAILURE")); + } + } + } + + startServers(failedNode); + + Assert.assertEquals(0, queueControl0.countMessages()); + Assert.assertEquals(1, queueControl1.countMessages()); + Assert.assertEquals(1, queueControl2.countMessages()); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + try (MessageConsumer consumer = session.createConsumer(session.createQueue(queueName))) { + TextMessage message = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(message); + Assert.assertEquals("TEST_AFTER_FAILURE", message.getText()); + } + } + } + + Assert.assertEquals(0, queueControl0.countMessages()); + if (failedNode == 1) { + Assert.assertEquals(1, queueControl1.countMessages()); + Assert.assertEquals(0, queueControl2.countMessages()); + } else { + Assert.assertEquals(0, queueControl1.countMessages()); + Assert.assertEquals(1, queueControl2.countMessages()); + } + + stopServers(0, 1, 2); + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/TargetKeyTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/TargetKeyTest.java new file mode 100644 index 0000000000..901941b720 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/TargetKeyTest.java @@ -0,0 +1,184 @@ +/** + * 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.balancing; + +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy; +import org.apache.activemq.artemis.core.server.balancing.policies.Policy; +import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactory; +import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactoryResolver; +import org.apache.activemq.artemis.core.server.balancing.targets.Target; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +@RunWith(Parameterized.class) +public class TargetKeyTest extends BalancingTestBase { + + private static final String MOCK_POLICY_NAME = "MOCK_POLICY"; + + @Parameterized.Parameters(name = "protocol: {0}") + public static Collection data() { + Collection data = new ArrayList<>(); + + for (String protocol : Arrays.asList(new String[] {AMQP_PROTOCOL, CORE_PROTOCOL, OPENWIRE_PROTOCOL})) { + data.add(new Object[] {protocol}); + } + + return data; + } + + + private final String protocol; + + private final List keys = new ArrayList<>(); + + + public TargetKeyTest(String protocol) { + this.protocol = protocol; + } + + @Before + public void setup() throws Exception { + PolicyFactoryResolver.getInstance().registerPolicyFactory( + new PolicyFactory() { + @Override + public String[] getSupportedPolicies() { + return new String[] {MOCK_POLICY_NAME}; + } + + @Override + public Policy createPolicy(String policyName) { + return new FirstElementPolicy(MOCK_POLICY_NAME) { + @Override + public Target selectTarget(List targets, String key) { + keys.add(key); + return super.selectTarget(targets, key); + } + }; + } + }); + } + + @Test + public void testClientIDKey() throws Exception { + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupBalancerServerWithDiscovery(0, TargetKey.CLIENT_ID, MOCK_POLICY_NAME, null, true, null, 1); + startServers(0); + + ConnectionFactory connectionFactory = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, "test", null, null); + + keys.clear(); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + } + + Assert.assertEquals(1, keys.size()); + Assert.assertEquals("test", keys.get(0)); + } + + @Test + public void testSNIHostKey() throws Exception { + String localHostname = "localhost.localdomain"; + + if (!checkLocalHostname(localHostname)) { + localHostname = "artemis.localtest.me"; + + if (!checkLocalHostname(localHostname)) { + localHostname = "localhost"; + + Assume.assumeTrue(CORE_PROTOCOL.equals(protocol) && checkLocalHostname(localHostname)); + } + } + + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + getDefaultServerAcceptor(0).getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + getDefaultServerAcceptor(0).getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "server-keystore.jks"); + getDefaultServerAcceptor(0).getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "securepass"); + + setupBalancerServerWithDiscovery(0, TargetKey.SNI_HOST, MOCK_POLICY_NAME, null, true, null, 1); + startServers(0); + + ConnectionFactory connectionFactory = createFactory(protocol, true, localHostname, + TransportConstants.DEFAULT_PORT + 0, null, null, null); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + } + + Assert.assertEquals(1, keys.size()); + Assert.assertEquals(localHostname, keys.get(0)); + } + + @Test + public void testSourceIPKey() throws Exception { + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupBalancerServerWithDiscovery(0, TargetKey.SOURCE_IP, MOCK_POLICY_NAME, null, true, null, 1); + startServers(0); + + ConnectionFactory connectionFactory = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, null, null); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + } + + Assert.assertEquals(1, keys.size()); + Assert.assertEquals(InetAddress.getLoopbackAddress().getHostAddress(), keys.get(0)); + } + + @Test + public void testUserNameKey() throws Exception { + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, MOCK_POLICY_NAME, null, true, null, 1); + startServers(0); + + ConnectionFactory connectionFactory = createFactory(protocol, false, TransportConstants.DEFAULT_HOST, + TransportConstants.DEFAULT_PORT + 0, null, "admin", "admin"); + + try (Connection connection = connectionFactory.createConnection()) { + connection.start(); + } + + Assert.assertEquals(1, keys.size()); + Assert.assertEquals("admin", keys.get(0)); + } + + private boolean checkLocalHostname(String host) { + try { + return InetAddress.getByName(host).isLoopbackAddress(); + } catch (UnknownHostException ignore) { + return false; + } + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java index 43756f1b58..2cbbb3307c 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java @@ -691,7 +691,7 @@ public class FailoverTest extends FailoverTestBase { waitForBackupConfig(sf); TransportConfiguration initialLive = getFieldFromSF(sf, "currentConnectorConfig"); - TransportConfiguration initialBackup = getFieldFromSF(sf, "backupConfig"); + TransportConfiguration initialBackup = getFieldFromSF(sf, "backupConnectorConfig"); instanceLog.debug("initlive: " + initialLive); instanceLog.debug("initback: " + initialBackup); @@ -745,7 +745,7 @@ public class FailoverTest extends FailoverTestBase { assertTrue(current.isSameParams(initialLive)); //now manually corrupt the backup in sf - setSFFieldValue(sf, "backupConfig", null); + setSFFieldValue(sf, "backupConnectorConfig", null); //crash 2 crash(); @@ -759,12 +759,12 @@ public class FailoverTest extends FailoverTestBase { } protected void waitForBackupConfig(ClientSessionFactoryInternal sf) throws NoSuchFieldException, IllegalAccessException, InterruptedException { - TransportConfiguration initialBackup = getFieldFromSF(sf, "backupConfig"); + TransportConfiguration initialBackup = getFieldFromSF(sf, "backupConnectorConfig"); int cnt = 50; while (initialBackup == null && cnt > 0) { cnt--; Thread.sleep(200); - initialBackup = getFieldFromSF(sf, "backupConfig"); + initialBackup = getFieldFromSF(sf, "backupConnectorConfig"); } } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/SessionMetadataAddExceptionTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/SessionMetadataAddExceptionTest.java index 8194de2a48..39a7cc013c 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/SessionMetadataAddExceptionTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/SessionMetadataAddExceptionTest.java @@ -23,10 +23,10 @@ import javax.jms.InvalidClientIDException; import javax.jms.JMSException; import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.server.ServerSession; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerPlugin; -import org.apache.activemq.artemis.jms.client.ActiveMQConnection; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.tests.util.JMSTestBase; import org.junit.Test; @@ -49,7 +49,7 @@ public class SessionMetadataAddExceptionTest extends JMSTestBase { public void beforeSessionMetadataAdded(ServerSession session, String key, String data) throws ActiveMQException { - if (ActiveMQConnection.JMS_SESSION_CLIENT_ID_PROPERTY.equals(key)) { + if (ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY.equals(key)) { if ("invalid".equals(data)) { throw new ActiveMQException("Invalid clientId"); } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java index 9430649f4b..1e1057508f 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java @@ -100,8 +100,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.apache.activemq.artemis.jms.client.ActiveMQConnection.JMS_SESSION_CLIENT_ID_PROPERTY; - @RunWith(Parameterized.class) public class ActiveMQServerControlTest extends ManagementTestBase { @@ -2757,7 +2755,7 @@ public class ActiveMQServerControlTest extends ManagementTestBase { JsonArray array = JsonUtil.readJsonArray(jsonString); Assert.assertEquals(1 + (usingCore() ? 1 : 0), array.size()); JsonObject obj = lookupSession(array, ((ActiveMQConnection)con).getInitialSession()); - Assert.assertEquals(obj.getJsonObject("metadata").getJsonString(ActiveMQConnection.JMS_SESSION_CLIENT_ID_PROPERTY).getString(), clientID); + Assert.assertEquals(obj.getJsonObject("metadata").getJsonString(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY).getString(), clientID); Assert.assertNotNull(obj.getJsonObject("metadata").getJsonString(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY)); } @@ -3782,7 +3780,7 @@ public class ActiveMQServerControlTest extends ManagementTestBase { ClientSession session1_c1 = csf.createSession(); ClientSession session2_c1 = csf.createSession(); session1_c1.addMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY, ""); - session1_c1.addMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, "MYClientID"); + session1_c1.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, "MYClientID"); String filterString = createJsonFilter("SESSION_COUNT", "GREATER_THAN", "1"); String connectionsAsJsonString = serverControl.listConnections(filterString, 1, 50); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BrokerBalancerControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BrokerBalancerControlTest.java new file mode 100644 index 0000000000..08716ca0c5 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BrokerBalancerControlTest.java @@ -0,0 +1,186 @@ +/** + * 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.management; + +import org.apache.activemq.artemis.api.core.JsonUtil; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl; +import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy; +import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey; +import org.apache.activemq.artemis.tests.integration.balancing.BalancingTestBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.json.JsonObject; +import javax.json.JsonValue; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; +import java.util.Map; + +public class BrokerBalancerControlTest extends BalancingTestBase { + + private MBeanServer mbeanServer; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + mbeanServer = MBeanServerFactory.createMBeanServer(); + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + + MBeanServerFactory.releaseMBeanServer(mbeanServer); + } + + + @Test + public void testGetTarget() throws Exception { + BrokerBalancerControl brokerBalancerControl = getBrokerBalancerControlForTarget(); + + CompositeData targetData = brokerBalancerControl.getTarget("admin"); + Assert.assertNotNull(targetData); + + String nodeID = (String)targetData.get("nodeID"); + Assert.assertEquals(getServer(1).getNodeID().toString(), nodeID); + + Boolean local = (Boolean)targetData.get("local"); + Assert.assertEquals(false, local); + + CompositeData connectorData = (CompositeData)targetData.get("connector"); + Assert.assertNotNull(connectorData); + + TransportConfiguration connector = getDefaultServerConnector(1); + + String connectorName = (String)connectorData.get("name"); + Assert.assertEquals(connector.getName(), connectorName); + + String connectorFactoryClassName = (String)connectorData.get("factoryClassName"); + Assert.assertEquals(connector.getFactoryClassName(), connectorFactoryClassName); + + TabularData connectorParams = (TabularData)connectorData.get("params"); + Assert.assertNotNull(connectorParams); + + for (Map.Entry param : connector.getParams().entrySet()) { + CompositeData paramData = connectorParams.get(new Object[]{param.getKey()}); + Assert.assertEquals(String.valueOf(param.getValue()), paramData.get("value")); + } + } + @Test + public void testGetTargetAsJSON() throws Exception { + BrokerBalancerControl brokerBalancerControl = getBrokerBalancerControlForTarget(); + + String targetJSON = brokerBalancerControl.getTargetAsJSON("admin"); + Assert.assertNotNull(targetJSON); + + JsonObject targetData = JsonUtil.readJsonObject(targetJSON); + Assert.assertNotNull(targetData); + + String nodeID = targetData.getString("nodeID"); + Assert.assertEquals(getServer(1).getNodeID().toString(), nodeID); + + Boolean local = targetData.getBoolean("local"); + Assert.assertEquals(false, local); + + JsonObject connectorData = targetData.getJsonObject("connector"); + Assert.assertNotNull(connectorData); + + TransportConfiguration connector = getDefaultServerConnector(1); + + String connectorName = connectorData.getString("name"); + Assert.assertEquals(connector.getName(), connectorName); + + String connectorFactoryClassName = connectorData.getString("factoryClassName"); + Assert.assertEquals(connector.getFactoryClassName(), connectorFactoryClassName); + + JsonObject connectorParams = connectorData.getJsonObject("params"); + Assert.assertNotNull(connectorParams); + + for (Map.Entry param : connector.getParams().entrySet()) { + JsonValue paramData = connectorParams.get(param.getKey()); + Assert.assertEquals(String.valueOf(param.getValue()), paramData.toString()); + } + } + + + @Test + public void testGetLocalTarget() throws Exception { + BrokerBalancerControl brokerBalancerControl = getBrokerBalancerControlForLocalTarget(); + + CompositeData targetData = brokerBalancerControl.getTarget("admin"); + Assert.assertNotNull(targetData); + + String nodeID = (String)targetData.get("nodeID"); + Assert.assertEquals(getServer(0).getNodeID().toString(), nodeID); + + Boolean local = (Boolean)targetData.get("local"); + Assert.assertEquals(true, local); + + CompositeData connectorData = (CompositeData)targetData.get("connector"); + Assert.assertNull(connectorData); + } + + @Test + public void testGetLocalTargetAsJSON() throws Exception { + BrokerBalancerControl brokerBalancerControl = getBrokerBalancerControlForLocalTarget(); + + String targetJSON = brokerBalancerControl.getTargetAsJSON("admin"); + Assert.assertNotNull(targetJSON); + + JsonObject targetData = JsonUtil.readJsonObject(targetJSON); + Assert.assertNotNull(targetData); + + String nodeID = targetData.getString("nodeID"); + Assert.assertEquals(getServer(0).getNodeID().toString(), nodeID); + + Boolean local = targetData.getBoolean("local"); + Assert.assertEquals(true, local); + + Assert.assertTrue(targetData.isNull("connector")); + } + + private BrokerBalancerControl getBrokerBalancerControlForTarget() throws Exception { + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1); + getServer(0).setMBeanServer(mbeanServer); + + setupLiveServerWithDiscovery(1, GROUP_ADDRESS, GROUP_PORT, true, true, false); + + startServers(0, 1); + + return ManagementControlHelper.createBrokerBalancerControl(BROKER_BALANCER_NAME, mbeanServer); + } + + private BrokerBalancerControl getBrokerBalancerControlForLocalTarget() throws Exception { + setupLiveServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false); + setupBalancerServerWithDiscovery(0, TargetKey.USER_NAME, FirstElementPolicy.NAME, null, true, null, 1); + getServer(0).setMBeanServer(mbeanServer); + + startServers(0); + + return ManagementControlHelper.createBrokerBalancerControl(BROKER_BALANCER_NAME, mbeanServer); + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementControlHelper.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementControlHelper.java index be7aa9e219..4c6b3516c7 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementControlHelper.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementControlHelper.java @@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; import org.apache.activemq.artemis.api.core.management.AddressControl; import org.apache.activemq.artemis.api.core.management.BridgeControl; import org.apache.activemq.artemis.api.core.management.BroadcastGroupControl; +import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl; import org.apache.activemq.artemis.api.core.management.ClusterConnectionControl; import org.apache.activemq.artemis.api.core.management.DivertControl; import org.apache.activemq.artemis.api.core.management.JGroupsChannelBroadcastGroupControl; @@ -98,6 +99,12 @@ public class ManagementControlHelper { return (AddressControl) ManagementControlHelper.createProxy(ObjectNameBuilder.DEFAULT.getAddressObjectName(address), AddressControl.class, mbeanServer); } + + public static BrokerBalancerControl createBrokerBalancerControl(final String name, + final MBeanServer mbeanServer) throws Exception { + return (BrokerBalancerControl) ManagementControlHelper.createProxy(ObjectNameBuilder.DEFAULT.getBrokerBalancerObjectName(name), BrokerBalancerControl.class, mbeanServer); + } + // Constructors -------------------------------------------------- // Public -------------------------------------------------------- diff --git a/tests/security-resources/build.sh b/tests/security-resources/build.sh index 82484a4f3c..dec0ffd4dd 100755 --- a/tests/security-resources/build.sh +++ b/tests/security-resources/build.sh @@ -24,6 +24,8 @@ KEY_PASS=securepass STORE_PASS=securepass CA_VALIDITY=365000 VALIDITY=36500 +CLIENT_NAMES="san=dns:localhost,ip:127.0.0.1" +SERVER_NAMES="san=dns:localhost,dns:localhost.localdomain,dns:artemis.localtest.me,ip:127.0.0.1" # Clean up existing files # ----------------------- @@ -43,10 +45,10 @@ keytool -importkeystore -srckeystore server-ca-truststore.p12 -destkeystore serv # Create a key pair for the server, and sign it with the CA: # ---------------------------------------------------------- -keytool -storetype pkcs12 -keystore server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias server -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=sA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias server -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=sA -ext $SERVER_NAMES keytool -storetype pkcs12 -keystore server-keystore.p12 -storepass $STORE_PASS -alias server -certreq -file server.csr -keytool -storetype pkcs12 -keystore server-ca-keystore.p12 -storepass $STORE_PASS -alias server-ca -gencert -rfc -infile server.csr -outfile server.crt -validity $VALIDITY -ext bc=ca:false -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore server-ca-keystore.p12 -storepass $STORE_PASS -alias server-ca -gencert -rfc -infile server.csr -outfile server.crt -validity $VALIDITY -ext bc=ca:false -ext $SERVER_NAMES keytool -storetype pkcs12 -keystore server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias server-ca -file server-ca.crt -noprompt keytool -storetype pkcs12 -keystore server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias server -file server.crt @@ -56,10 +58,10 @@ keytool -importkeystore -srckeystore server-keystore.p12 -destkeystore server-ke # Create a key pair for the other server, and sign it with the CA: # ---------------------------------------------------------- -keytool -storetype pkcs12 -keystore other-server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias other-server -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Other Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore other-server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias other-server -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Other Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext $SERVER_NAMES keytool -storetype pkcs12 -keystore other-server-keystore.p12 -storepass $STORE_PASS -alias other-server -certreq -file other-server.csr -keytool -storetype pkcs12 -keystore server-ca-keystore.p12 -storepass $STORE_PASS -alias server-ca -gencert -rfc -infile other-server.csr -outfile other-server.crt -validity $VALIDITY -ext bc=ca:false -ext eku=sA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore server-ca-keystore.p12 -storepass $STORE_PASS -alias server-ca -gencert -rfc -infile other-server.csr -outfile other-server.crt -validity $VALIDITY -ext bc=ca:false -ext eku=sA -ext $SERVER_NAMES keytool -storetype pkcs12 -keystore other-server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias server-ca -file server-ca.crt -noprompt keytool -storetype pkcs12 -keystore other-server-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias other-server -file other-server.crt @@ -107,10 +109,10 @@ keytool -importkeystore -srckeystore client-ca-truststore.p12 -destkeystore clie # Create a key pair for the client, and sign it with the CA: # ---------------------------------------------------------- -keytool -storetype pkcs12 -keystore client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias client -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias client -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext $CLIENT_NAMES keytool -storetype pkcs12 -keystore client-keystore.p12 -storepass $STORE_PASS -alias client -certreq -file client.csr -keytool -storetype pkcs12 -keystore client-ca-keystore.p12 -storepass $STORE_PASS -alias client-ca -gencert -rfc -infile client.csr -outfile client.crt -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore client-ca-keystore.p12 -storepass $STORE_PASS -alias client-ca -gencert -rfc -infile client.csr -outfile client.crt -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext $CLIENT_NAMES keytool -storetype pkcs12 -keystore client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias client-ca -file client-ca.crt -noprompt keytool -storetype pkcs12 -keystore client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias client -file client.crt @@ -120,10 +122,10 @@ keytool -importkeystore -srckeystore client-keystore.p12 -destkeystore client-ke # Create a key pair for the other client, and sign it with the CA: # ---------------------------------------------------------- -keytool -storetype pkcs12 -keystore other-client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias other-client -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Other Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore other-client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias other-client -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ActiveMQ Artemis Other Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext $CLIENT_NAMES keytool -storetype pkcs12 -keystore other-client-keystore.p12 -storepass $STORE_PASS -alias other-client -certreq -file other-client.csr -keytool -storetype pkcs12 -keystore client-ca-keystore.p12 -storepass $STORE_PASS -alias client-ca -gencert -rfc -infile other-client.csr -outfile other-client.crt -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext san=dns:localhost,ip:127.0.0.1 +keytool -storetype pkcs12 -keystore client-ca-keystore.p12 -storepass $STORE_PASS -alias client-ca -gencert -rfc -infile other-client.csr -outfile other-client.crt -validity $VALIDITY -ext bc=ca:false -ext eku=cA -ext $CLIENT_NAMES keytool -storetype pkcs12 -keystore other-client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias client-ca -file client-ca.crt -noprompt keytool -storetype pkcs12 -keystore other-client-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -importcert -alias other-client -file other-client.crt diff --git a/tests/security-resources/client-ca-keystore.p12 b/tests/security-resources/client-ca-keystore.p12 index 7c6fae79e7..1469394515 100644 Binary files a/tests/security-resources/client-ca-keystore.p12 and b/tests/security-resources/client-ca-keystore.p12 differ diff --git a/tests/security-resources/client-ca-truststore.jceks b/tests/security-resources/client-ca-truststore.jceks index 8c7f939c6c..45501ac55f 100644 Binary files a/tests/security-resources/client-ca-truststore.jceks and b/tests/security-resources/client-ca-truststore.jceks differ diff --git a/tests/security-resources/client-ca-truststore.jks b/tests/security-resources/client-ca-truststore.jks index e2dfeff87d..49e2bc14cf 100644 Binary files a/tests/security-resources/client-ca-truststore.jks and b/tests/security-resources/client-ca-truststore.jks differ diff --git a/tests/security-resources/client-ca-truststore.p12 b/tests/security-resources/client-ca-truststore.p12 index 706407d49a..ad308a4b3d 100644 Binary files a/tests/security-resources/client-ca-truststore.p12 and b/tests/security-resources/client-ca-truststore.p12 differ diff --git a/tests/security-resources/client-ca.pem b/tests/security-resources/client-ca.pem index 6eab31a900..391859c9a3 100644 --- a/tests/security-resources/client-ca.pem +++ b/tests/security-resources/client-ca.pem @@ -1,32 +1,32 @@ Bag Attributes friendlyName: client-ca - localKeyID: 54 69 6D 65 20 31 36 32 37 39 39 35 37 37 38 32 30 33 + localKeyID: 54 69 6D 65 20 31 36 32 38 32 30 33 36 33 37 31 38 36 Key Attributes: -----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCteE6eFAkH+r0S -3xmAZW5j+sqa8RGh+/KQJ6BgQQpsDeS3RkxnN2e8tSF27eBwFZcnhUKLBFGbarjj -sHRZ5HwDPty/C8RVhvbPWi2AvW8uEvh65G+fjyf5JT8jAfvP12EFv1u9sQjI1hXH -6CjX7lOCRjxriwz86NTsgiPVO3Q89pmhgmQjO0JBtolsxCZvV0DDS7xvGpmHudlf -UHR1ydjA0+s6YDQ4UIOBwUu/CcHdIgAk1yiWQE4OA72cXTfR/Mybfpoqh6TegnSk -ONJY1/iNgmujU5nOwDKlEG5BzSd0ueE1RSoFg2OGVPuo73lS2iouCXFvFU90yDGA -bdKlRIHlAgMBAAECggEAdbXYay4fPrnnSQH4tQafFNreVqtUkr17SFSLYCViZBY9 -aBwcxkFzdDrY3XHnRUdxTVEA6YJhuft+QIrBOSpw+GbUthLPBFZT7jo7/EsPQY1/ -7SxLjlM/BbI/mIrFC7ET1imWoC6cTmPvXbps1LGVGyZ742H0yz1XFrHsjMoOQzrW -itL29T09CYfZrB+/uo2ozfAjTDKVUALhrd4qN/uiJsHTfZPOwIv/qgZTSUHDsfZP -SbUjJjWoEWJBhIewosCeyFaGOYN4JmHUQG597Xp8PS+cAvfLWMpBcSsX1ULClY2Q -PSv0PKVprZdIfeOtQHmRk56lwhW2QV7PhwstKdqjNQKBgQDvbfFlYkCq6HwMcPQJ -h2hBIUFHm7rBVflw72LKEYE5oiouSflMVRujujPUWIHkF9TRBZ0f4B+J9sUXTyPY -wAlbRTAaG5JGLjF6JxLjkw5MiPooJk8YcHPaadpOgT/vLall3mhdQG+hEshtysHP -jdagK93joWVc0aTdj2NFkJUFFwKBgQC5ebxmnkb2PyzH2oatZNfMLWnLjs8GFoWe -NHbJTzLAadl/sVTVhaWHYDjvbtZPq+0ynzLGnNQ7HPtuSqNiG2bY3/eedWdruPIO -Dcztr05YUzDX5pItoUucu19V0k0sWSOeKBD5mTVdUHgCLxd0GyZ4ODkS63ItjiBM -78m5q8MGYwKBgCed7X91DnY5Ga2FUxvwh9OfCQosPm6XJzsEoTgGRXef2ZLnMpTq -0DP7L3BHZNa1CsW7RBBuKUnOxzXgJnJK9EFh5V+siDuMkStBI+L8BjWrxJi4HgZR -NRpCwZiT0lxlFc6BSouDifUBAqEIF6GcOpMuLvznS7pcBgeTHj34em/pAoGAW8kS -ovXQyCubTYum+kfdQv12TXXunWSn2xK7dgPraaz4JWjsQn5Q3B2SD2saQ3Mhftup -lQAnRtmg04O8NuC4lLrBH3maJITxxGKv9y+55ZvFoBJKZKpdcMKI+z+HUVsLdUj+ -nYZkEjmwKeSEBsEo2HV6SRKa/lBHS8ueWHPXn2ECgYBn/WeTob0JMmoF5dIhISpP -bA/j/gj2r7aTR7/o9bpmJjj0f71zuPvJRIo5L1qs/UvsZIoU8DuZwSx8KyzS6g+J -VB5gE3JBKUhshy8TnMNIR+ZzJBFYtYc1TbB2OSsWP6sIilFN8KQKU9RMpmo6yiZZ -us6gZcNh399Hz894wYKyog== +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCf8s5tfHnara0G +c0qtw7VdaQjXxBhfH6AQ/XbTOzHMF6FtTzWWTEq6wqoUuP1Y4WLob67O3ZRQ+RMN +V+C4eFgH9e7LlZ6gU7DWEk40WkKVQ/sTW3/4ul/F7ScFtyTvZ4zvNa6Qr0np4rB+ +SJlOy5yBZlNgmU2H+J/OsxEJDkw+Q+lz1Q/d6QevnfgBu5/Ka/aYtia2fPduufIf +Dn/UbLe+AANnO8z1NQ9ocOSgCcuocOmCssSZNGFXtf8qjMhZypO2c/bmZnuki+yQ +QhnD+yrOPbJMgcfL3jIesjNCu5kapmEGJBeGFtEgilegLYpYVTvF3RURjaqy2t3j +Y36sysXZAgMBAAECggEAHIzIj/5x+biac9ZMdCvEycGf1HOqLgCFH8M+XIHqZ5Wo +OMy0sfk4NZHdrgk/H4hLkVWuDDq86J8s6WrQL907SWB6mVhBkjieDfgCgZHC5MXX +oSLp+sm5oqisGHcSXrFLFL+uQyEmvlq6SjBHPStW6joLk+iJUEXusISB++3TaiGI +2LnHu0AZR+TNGk9tf+K48mlfpRimqq+Bugh7Sgh7uis9bwXn1p1Nz5hZDbBLGVMv +wgsC6xvvZJwNrUW7mgmVuytcCivR1Q/Tt1d8cim1bt+a/ZfRD6utexcfaZ47l5+3 +WPFb7UkMlXioZzrxDjlKO8J8qBcxcd8brqp5mX3KKQKBgQDRM0I18K4QCsw1zZFa +Zu7DjmLdxsgWSmpvmHVY2iFyUDI5NVcx7HU0ACd0lRU9IFnAIDzlNPnjyaShRQxW +IrPG+zFifmwyqNVRJfZdrm1ZkLba5CgJ+vVQbZF+lEh3M/mKID1CfLk4O/xZKeho +xXn3Nika/BqHNJlFJsvytoLdrwKBgQDDuu+K0KdILWsDTnVLOY3mjXKfJjw3P5MI +/dyoWFtW4M4xQ172aSVNZ11AHaiaDQ1KRXgVcUvmMVXQXXB+01DA/DArreDmREnT +gBwenTApjT/lblRBkcei6VtM5BLQ46uXueYzTJJ419WwYc0GaXG7lnipcz+L0C2a +thEsMrG+9wKBgAjJr3FWn+k6muNztDRo+ISseYi5bfRJwfjYHa3S0+7aYZG3pOcK ++M1raDzkelTsA/knIYe7Vvfzo3/Gx8LiiEzGhoeNqfvizbsv7g53Yk6N3rCJPwlU +SnPLdn4runOPcl8UBZ7CYIF1O59/PC0ShpIU61sf1flyAzI9c/nJIuwvAoGAWG6b +T9KZ4ehzUxkdsZEdZa8+vF0gE64rloJsMbtJ+WS0hFl2DErRSbmLzi4YQRHokUf1 +y2pW6ngb13qAGy0KbUcD1JhI5oCwAlj9W2+VlRB2cAh7FOzyj85zK7hYL/zNSE37 +je3ot6R8raZiZaU6d5Cyj4y8h0TVdfMQqzF0UV0CgYEAhpNPBVxNfwX/hFKdFQKA +1yDHTQiLhC8N0xfkI4FRQUdviVmy/LLRHincjtievo7bpE0sn87q990LK4B4L4dJ +y6024cPPCxrz1p2i43B0CuS7JwCL7UxaDQnHc/zwLYjZZJhYnZmfdjzThIturKEw +pUfEMSr5rFelUKg2aZjVAqM= -----END PRIVATE KEY----- diff --git a/tests/security-resources/client-keystore.jceks b/tests/security-resources/client-keystore.jceks index a0ac1a3dad..8efa04690a 100644 Binary files a/tests/security-resources/client-keystore.jceks and b/tests/security-resources/client-keystore.jceks differ diff --git a/tests/security-resources/client-keystore.jks b/tests/security-resources/client-keystore.jks index b968a0c54d..1cbf5215f6 100644 Binary files a/tests/security-resources/client-keystore.jks and b/tests/security-resources/client-keystore.jks differ diff --git a/tests/security-resources/client-keystore.p12 b/tests/security-resources/client-keystore.p12 index 13b246823d..7fa3d66372 100644 Binary files a/tests/security-resources/client-keystore.p12 and b/tests/security-resources/client-keystore.p12 differ diff --git a/tests/security-resources/other-client-crl.pem b/tests/security-resources/other-client-crl.pem index 45f51abe29..9c6610bc81 100644 --- a/tests/security-resources/other-client-crl.pem +++ b/tests/security-resources/other-client-crl.pem @@ -1,12 +1,12 @@ -----BEGIN X509 CRL----- MIIB0zCBvAIBATANBgkqhkiG9w0BAQsFADBfMREwDwYDVQQKEwhBY3RpdmVNUTEQ MA4GA1UECxMHQXJ0ZW1pczE4MDYGA1UEAxMvQWN0aXZlTVEgQXJ0ZW1pcyBDbGll -bnQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkXDTIxMDgwMzEzMDMwN1oYDzIxMjEw -NzEwMTMwMzA3WjAXMBUCBH493qkXDTIxMDgwMzEzMDMwN1qgDjAMMAoGA1UdFAQD -AgEAMA0GCSqGSIb3DQEBCwUAA4IBAQBzM0YCos5sHRAN4pPzNWCAonqezX6FfcY+ -SuufVcxD583O2Vnuwmz9i9PhGJJbWxGuCtXwS1JNldm7/rXhpZOd539W1BJQprGb -nwooQWTBBU8qTaXmUVWiPsMlL/IcMUTB/DVgWsRuwjA7wtVAseIoa2Z/geZZAOwO -vgp7RAtWW9M1Vr7/XWNsJqIOoPnPqGhg8Nve2sFfySQmJQZP8LnnDgC6pv51TnRa -VrOmHtralj2d0U3z78nRZW26S1XMxA0wb5yTc4T8lxCZ969vwtiWOQRCoKL/EFWe -Yy2oBbRjTHEZWYyhYHCMcGP2JSGcDnSZmc+d7ydgx4Gq7nHy3FCM +bnQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkXDTIxMDgwNTIyNDcyNloYDzIxMjEw +NzEyMjI0NzI2WjAXMBUCBHRbRY4XDTIxMDgwNTIyNDcyNlqgDjAMMAoGA1UdFAQD +AgEAMA0GCSqGSIb3DQEBCwUAA4IBAQA2w9pF0q2Lvrw2VxXNVJLyZNnV46kmlVTT +ao9bMK3pIZWNALmzc2FhW7yUZXmiIOvLHqdh08sVtz9sOmxhJcqKG6jmseygapRJ +lFcujq6E2V4XGcNIBXgE+3TNB65wvRQHL//NWVFp6pFdsW0CcsIFHUQetrkVkW1r +HlL/CKoXvVrt7XeJW4PPiq5sFKqNs5cOdpqTmoaeG823uE0BvdOEJIr1aIowBA2b +qeqACPH1VjVb+/JcHZlYmYor7RzUEdMmqF/e38QAF3bveiXfsFg2n1xKlUyBz4RR +6UxlLv4txWyAO6er/90olhQWvBbQDivKVF+n5XkRJbAbbNqxvy6h -----END X509 CRL----- diff --git a/tests/security-resources/other-client-keystore.jceks b/tests/security-resources/other-client-keystore.jceks index 0cf978c275..596996ca85 100644 Binary files a/tests/security-resources/other-client-keystore.jceks and b/tests/security-resources/other-client-keystore.jceks differ diff --git a/tests/security-resources/other-client-keystore.jks b/tests/security-resources/other-client-keystore.jks index f2061bd8bc..6769890919 100644 Binary files a/tests/security-resources/other-client-keystore.jks and b/tests/security-resources/other-client-keystore.jks differ diff --git a/tests/security-resources/other-client-keystore.p12 b/tests/security-resources/other-client-keystore.p12 index 2c6d590707..b6c19aaaff 100644 Binary files a/tests/security-resources/other-client-keystore.p12 and b/tests/security-resources/other-client-keystore.p12 differ diff --git a/tests/security-resources/other-server-crl.pem b/tests/security-resources/other-server-crl.pem index 05ea5ae033..a60bfe7572 100644 --- a/tests/security-resources/other-server-crl.pem +++ b/tests/security-resources/other-server-crl.pem @@ -1,12 +1,12 @@ -----BEGIN X509 CRL----- MIIB0zCBvAIBATANBgkqhkiG9w0BAQsFADBfMREwDwYDVQQKEwhBY3RpdmVNUTEQ MA4GA1UECxMHQXJ0ZW1pczE4MDYGA1UEAxMvQWN0aXZlTVEgQXJ0ZW1pcyBTZXJ2 -ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkXDTIxMDgwMzEzMDI1NFoYDzIxMjEw -NzEwMTMwMjU0WjAXMBUCBFu18ooXDTIxMDgwMzEzMDI1NFqgDjAMMAoGA1UdFAQD -AgEAMA0GCSqGSIb3DQEBCwUAA4IBAQA8+qy2sN70qrXKuAwdIk1gF6mj+3ikFIhj -LP9hU8PBxolCzcz9SJv3xvcuGsrZtp30EU0JYQBIadfpsm6Fe6iCpXxD62n99vry -OpRF9Nt2qjkQpGVrAl4LeM53Z3CFiC9Ghg7rZftB+Glxte3+mSyxWRB3drj1xiqg -Rt6y43ipQh4F9bxMANhgEUSvC7SrGGKke2z0nHj7gpzseSYbZucfagRk9LzSFFC6 -HWXmFdWFYhEV6Gh7XFKRKVi7DNXp1jWDTAt+g4bif/N2aIES+gqJFsufnqOYNiiL -J70UOUc9D7l2GHbPaVOOHuqo+zhjTy3IJv1329uYbvMHuGJUIjVV +ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkXDTIxMDgwNTIyNDcxM1oYDzIxMjEw +NzEyMjI0NzEzWjAXMBUCBGNHJ0cXDTIxMDgwNTIyNDcxM1qgDjAMMAoGA1UdFAQD +AgEAMA0GCSqGSIb3DQEBCwUAA4IBAQCgMAVrzbQzN9HU5SlctR4od1j5lvqpMO6g +f1qtIB7FCFyu6Q/p4nPoEuipjD/0DjLZAnizDEyR4WwVz+68i39cSygFjIH8jLAN +J2fgm1e10EzRL2/Um3kCGNq1C5MQomCKBKBA5o+ww3FjVJdJfVJa8tv87J2Wz/K8 +l/UFfF0xzAvwDen5SXl5W7bsQP9IgPm2vgPxqKeoymv/nDjehb76jROnNVb2g/TJ +EFY4XzN7Hi1ISUDooOW5vLNUoqDzOl8O+di/ZKZKqsMACyXGx5egg+euH3KqbaL9 +WihLlt2P44vnIAjT3ofkMBLTt6B3fXXyRv8gQYqpoXgcV0r27LXd -----END X509 CRL----- diff --git a/tests/security-resources/other-server-keystore.jceks b/tests/security-resources/other-server-keystore.jceks index 5e3a133783..f499effeb3 100644 Binary files a/tests/security-resources/other-server-keystore.jceks and b/tests/security-resources/other-server-keystore.jceks differ diff --git a/tests/security-resources/other-server-keystore.jks b/tests/security-resources/other-server-keystore.jks index 4661445cc7..58a752e785 100644 Binary files a/tests/security-resources/other-server-keystore.jks and b/tests/security-resources/other-server-keystore.jks differ diff --git a/tests/security-resources/other-server-keystore.p12 b/tests/security-resources/other-server-keystore.p12 index e21a4e11ff..7023c405c0 100644 Binary files a/tests/security-resources/other-server-keystore.p12 and b/tests/security-resources/other-server-keystore.p12 differ diff --git a/tests/security-resources/other-server-truststore.jceks b/tests/security-resources/other-server-truststore.jceks index 58d5e0a1af..3587ef826a 100644 Binary files a/tests/security-resources/other-server-truststore.jceks and b/tests/security-resources/other-server-truststore.jceks differ diff --git a/tests/security-resources/other-server-truststore.jks b/tests/security-resources/other-server-truststore.jks index ad4a0f2c56..a8f45090c5 100644 Binary files a/tests/security-resources/other-server-truststore.jks and b/tests/security-resources/other-server-truststore.jks differ diff --git a/tests/security-resources/other-server-truststore.p12 b/tests/security-resources/other-server-truststore.p12 index 93b1d0b58f..84d5ab0983 100644 Binary files a/tests/security-resources/other-server-truststore.p12 and b/tests/security-resources/other-server-truststore.p12 differ diff --git a/tests/security-resources/server-ca-keystore.p12 b/tests/security-resources/server-ca-keystore.p12 index fb54141cf8..c9552beeb1 100644 Binary files a/tests/security-resources/server-ca-keystore.p12 and b/tests/security-resources/server-ca-keystore.p12 differ diff --git a/tests/security-resources/server-ca-truststore.jceks b/tests/security-resources/server-ca-truststore.jceks index 5c2cc80a98..992f0e4da8 100644 Binary files a/tests/security-resources/server-ca-truststore.jceks and b/tests/security-resources/server-ca-truststore.jceks differ diff --git a/tests/security-resources/server-ca-truststore.jks b/tests/security-resources/server-ca-truststore.jks index 3fe0f29ad2..bdb6985de8 100644 Binary files a/tests/security-resources/server-ca-truststore.jks and b/tests/security-resources/server-ca-truststore.jks differ diff --git a/tests/security-resources/server-ca-truststore.p12 b/tests/security-resources/server-ca-truststore.p12 index 1fa9c61b53..8993e3dc32 100644 Binary files a/tests/security-resources/server-ca-truststore.p12 and b/tests/security-resources/server-ca-truststore.p12 differ diff --git a/tests/security-resources/server-ca.pem b/tests/security-resources/server-ca.pem index 7327a779de..1df3c74395 100644 --- a/tests/security-resources/server-ca.pem +++ b/tests/security-resources/server-ca.pem @@ -1,32 +1,32 @@ Bag Attributes friendlyName: server-ca - localKeyID: 54 69 6D 65 20 31 36 32 37 39 39 35 37 36 34 38 34 34 + localKeyID: 54 69 6D 65 20 31 36 32 38 32 30 33 36 32 33 33 34 39 Key Attributes: -----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCNcCgfsx99PDN6 -+cK7pt1Z6i+6JVNVt5j2D8XsOOo2RzSZwOxPfQU7WlD2SBKF/tqRSo/qiT4Tf1UJ -nEt3HLP+CEvLWj+AVNF9V+DpzRl7PnADeZsgaPOtUnLn+4bRSnwGwsUrCeJaJCQN -drNt3sREpaQ8WizxR1nicLyN3H6RtoEV2bE+NGt+hdek/iFWtIm6L3QXbeMnBhl3 -DkXdKTnEk2zqmwtCgxHnHXKMxPB3utwKBloulHxsvUI4s+twH9cJYvFokyBVIBwJ -/Xa1IlrPdiEyv1qk7Uul3a7grR1ljEabbn9V6HS6KG8KEPLE5Kk97PNKU5LFkwPr -PC/QY8tBAgMBAAECggEARQlqvFZdV26sHimNMLU5NCtIEo8nhx5vriNy02PQhp/o -/+eYMRBwHlFuVVhGmlsUani/mJZW04OCiYddmo1LGgMIpACwID7GZm0fnl97QZnv -aPLRkldIIeCtr6gpXT0DHvWw8doIP0GGy3+WA1oJ6QwFB2RorXjLWej3UDNBIHP/ -UN/DuMvvl82ZVVpgLSAxWWDQxZpDE7Mvwcpd+yms9qhzaH6Sf3/TPxkn6tPrGSN/ -E3O7ez+ixqATQ5L+N4ZsBUWfrX2fPplZB3Zmt8QUSYDZ9IeO0Oga4N6g0PRxQILG -QxJ7MCwu0DAGx3KgKWsQg5f8tLSeHzwEHnz63+1xlQKBgQDO/cm2bJ8bqyIxERTD -s8FekrL2vlzTd+uChZEIX74nCjnG6jWK7TExqq/56khGORz5OFSRXqKR6CkEs5o8 -SzUHduc34OtFsQovyxFSxIY7O8qcbIcpav1CA5S7BtU9zleUr5Av3DsG8hZwyIhk -zDk6Vf/tLTH6PVGPfPe3E5mAxwKBgQCu7Qn7HkjLXcVzgzpp1CXYoBQTmKHZf2fF -wnTASFrRjQTwVN+p0afueTqzn4TutSosKiymtGgVonZoLWmBWSuKbEHLECHXlQcY -wjRAccB6u0Q5NZLcVmFLVjLiKw+kljSNgpQI6vYgPWp4zF6x/9ioRbz0+3wuKzsI -pUkcPg5btwKBgEyWeOFH1aNOMeuHz3AN/dl5XECR9RTFxV1ZAG3hxyD41qH0HPWX -h+FBr7U/65gYH9FS92+GXY6xISQ9NC9lAG0PoMP7M/JobEV81J8UWjpmiDRSr7wy -exzG6Gw/Pf2NcLhyMV6UFT8fqg/3EwiAzBf6pCRk2Z4mvBvkeF/EH8MXAoGAIuzm -6kGQrTIKw1Z3KjwWVlsXxxXZctCSSpTZtK59m4s5aja39XMLwXxo8QYvh22afvjo -s1wfz/oBBCnU/+Nq4xdcR4vwBdgWc6YKwrczhA2xwG5m5SFGCcGrJScN14G5+msQ -3Xr0K1m30WiUm5uGiYprAMrZb2poPgCqST5GpZ8CgYA7dc8QWQWUzaP1gjA6hspC -4qcHecNaYxaNPjhR9kBlzx9VXtVpqk0IyDkHIdJ7nz+GPa9WJTSmkgpYwz7hSWw7 -O8PbsxZ1qY4j9/yNUGcIodjgwUckwj8ULkl8mDGQCZByImZzjqHUfWuezWmhjW43 -sfD8CrHOirVMRbu49FEAVw== +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDYRlaCSUXLtzgJ +Q1+Q1CNlkBMJtsM4PIfRhwH/0C1HHRJRh7Q87k7BbUwL1ZPPcydw2OKnpw9mdDOV +rwS82vV8W/KsQWVCAzdvR/rQ4wwnUfjFl2Na6cJFdNeq8APSgBv5r81DuLK09deN +qm3wYc2I3Tw+8cbNddbG2CEFVdJ+SrUtUn8SzzI6QFNIBW+Mgo4SffbK2TmKJ3Pb +Y0SxU7vw7G5pv6r226pC5lUlLc2dJKJOUvsAKHWolm1JQbH7s1GrTXnoMAEPSFuW +rOlN4X9Vep2zn2bry+sllXf0xGybpNnwVa5uIt+muuYoyyH8NE4NbGEncMYaCaQA +RVCbfykNAgMBAAECggEBAMATV+lF4fLWubGgYFNj1DvzBLVv11kuiQERAjmLTWsG +6qn196DVlKQ8yXkJKcjn4HNca4+x0v+O2/FoCrEfVT4o+xD401EZQjZWmu7Bdp3F +M0do+BhZ2uuMpa1ulDZzCGVsOMSYWD5WyCVM427FraCLu2G7oHAA48qdUFZIrjTx +ySYmXFZqV0Rh+k68Jv2+q8Fy2DP2t6n9wbUilIM6iYi3DM+bym0n5AuG8Nc3pqfx +L4RP82/brYcZo6VX1fNnAZRRUqWP9Xv8Ry8ff/9FiLKXSMT8v9uSL0/tp1PFIe26 +zuS7Vw0xGOOH4nUtctIa9iKjZ9LRy9pYMLs/RR00TpECgYEA/8ekPk6sP5J+zty1 +iaRmL66/s+xlQIGl51Xt63r684tI6tL7Dv+r37ocCygEHNPa324iPvtau+UV3DL6 +x5AwJhLP6kn71c9l9jfjynRurtlenY6X1Vn6H29qeidQ0y/dEuclEI0+H3Sa3MmP +9rhfdy7eYYCdurvxawbl1d5Hf5sCgYEA2HX954wSj6VXFWcCLk/06v6kBbhMRDBL +u3uR16W3bm2fnoM4Hitbst+5Hcj6nspGKDBdhy/buUtF+BCmQcGlGnr5SBar0zsF +VwRKoOwJsdGzaUftj+EV/gTkx6Tloa1h7VsqMjSICuTnn6817RwdkaZ6Y43etu/N +VPwwGWAyCHcCgYAK2ZEiA3xTBgfTBpG80Ph0tVj0bOauodFDyuVYw9K5WgMx0tlL +fZTw3Jgr8PqbrnDuYWGaglcK+WDAAnmY1Yj1VH71VUYVf8K3ew9ymxXG3PmifVX7 +euGdS8CcheZrzu/1yVBNL3CfLPcUvogY0yFZkOdmA2qtbSOEgrplJBSsWQKBgQDT +GNbB/leHoR45MBjvY2id2DHLpj0ybsscjtjfLqyh0+TLqHqM6Ynm+snEY2EOhINA +5FIB6cllfiRBVLNfA2NpXK5JAFsXh8KgZv4Ey8x0juZh8RSbsU5KSSl4DbcoIjeT +S8nt5k8aGLxOfYegsj+f8HQBLLUbQOfFTp/1z1tb5wKBgQDx/1ELg0MCRXyaE7Sm +shZR//UBPldIXGr757oUOXc9hpfotws3QepMu4o342v3V3zvTBYhBf8WIyRYZdkG +gXZG/aGfmuAb7KAgzqdw/3kLbAVnWfMhRVaexauIYLIVkHsygjTvpCbaKyAzZR86 +ZeAzkZ3ivQvbKY7GDjgbJ3GjHg== -----END PRIVATE KEY----- diff --git a/tests/security-resources/server-keystore.jceks b/tests/security-resources/server-keystore.jceks index 57cc472efe..fba9d8b9e8 100644 Binary files a/tests/security-resources/server-keystore.jceks and b/tests/security-resources/server-keystore.jceks differ diff --git a/tests/security-resources/server-keystore.jks b/tests/security-resources/server-keystore.jks index 5a7e2c00bf..14168afdd3 100644 Binary files a/tests/security-resources/server-keystore.jks and b/tests/security-resources/server-keystore.jks differ diff --git a/tests/security-resources/server-keystore.p12 b/tests/security-resources/server-keystore.p12 index fe5eab7268..0a74923b7e 100644 Binary files a/tests/security-resources/server-keystore.p12 and b/tests/security-resources/server-keystore.p12 differ diff --git a/tests/security-resources/unknown-client-keystore.jceks b/tests/security-resources/unknown-client-keystore.jceks index dde733626e..41becd8da6 100644 Binary files a/tests/security-resources/unknown-client-keystore.jceks and b/tests/security-resources/unknown-client-keystore.jceks differ diff --git a/tests/security-resources/unknown-client-keystore.jks b/tests/security-resources/unknown-client-keystore.jks index 10c7c434aa..f3498bacbf 100644 Binary files a/tests/security-resources/unknown-client-keystore.jks and b/tests/security-resources/unknown-client-keystore.jks differ diff --git a/tests/security-resources/unknown-client-keystore.p12 b/tests/security-resources/unknown-client-keystore.p12 index bb53b8c446..cad538b38b 100644 Binary files a/tests/security-resources/unknown-client-keystore.p12 and b/tests/security-resources/unknown-client-keystore.p12 differ diff --git a/tests/security-resources/unknown-server-keystore.jceks b/tests/security-resources/unknown-server-keystore.jceks index ef2b078e4c..3a0e45f0f7 100644 Binary files a/tests/security-resources/unknown-server-keystore.jceks and b/tests/security-resources/unknown-server-keystore.jceks differ diff --git a/tests/security-resources/unknown-server-keystore.jks b/tests/security-resources/unknown-server-keystore.jks index 039a581d0b..be7c618935 100644 Binary files a/tests/security-resources/unknown-server-keystore.jks and b/tests/security-resources/unknown-server-keystore.jks differ diff --git a/tests/security-resources/unknown-server-keystore.p12 b/tests/security-resources/unknown-server-keystore.p12 index 8fbec3e026..2921764460 100644 Binary files a/tests/security-resources/unknown-server-keystore.p12 and b/tests/security-resources/unknown-server-keystore.p12 differ