diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQBuffer.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQBuffer.java index 3a208a6980..e9cd68be18 100644 --- a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQBuffer.java +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQBuffer.java @@ -677,6 +677,15 @@ public interface ActiveMQBuffer extends DataInput { @Override int readInt(); + /** + * Gets a (potentially {@code null}) 32-bit integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @return a (potentially {@code null}) 32-bit integer at the current {@code readerIndex} + * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4} + */ + Integer readNullableInt(); + /** * Gets an unsigned 32-bit integer at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 4} in this buffer. @@ -696,6 +705,15 @@ public interface ActiveMQBuffer extends DataInput { @Override long readLong(); + /** + * Gets a (potentially {@code null}) 64-bit integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 8} in this buffer. + * + * @return a (potentially {@code null}) 64-bit integer at the current {@code readerIndex} + * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8} + */ + Long readNullableLong(); + /** * Gets a char at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 2} in this buffer. @@ -736,6 +754,15 @@ public interface ActiveMQBuffer extends DataInput { @Override boolean readBoolean(); + /** + * Gets a (potentially {@code null}) boolean at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 1} in this buffer. + * + * @return a (potentially {@code null}) boolean at the current {@code readerIndex} + * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1} + */ + Boolean readNullableBoolean(); + /** * Gets a SimpleString (potentially {@code null}) at the current {@code readerIndex} * @@ -917,6 +944,15 @@ public interface ActiveMQBuffer extends DataInput { */ void writeInt(int value); + /** + * Sets the specified (potentially {@code null}) 32-bit integer at the current {@code writerIndex} + * and increases the {@code writerIndex} by {@code 4} in this buffer. + * + * @param value The specified (potentially {@code null}) 32-bit integer + * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 4} + */ + void writeNullableInt(Integer value); + /** * Sets the specified 64-bit long integer at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} @@ -927,6 +963,16 @@ public interface ActiveMQBuffer extends DataInput { */ void writeLong(long value); + /** + * Sets the specified (potentially {@code null}) 64-bit long integer at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} + * in this buffer. + * + * @param value The specified (potentially {@code null}) 64-bit long integer + * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 8} + */ + void writeNullableLong(Long value); + /** * Sets the specified char at the current {@code writerIndex} * and increases the {@code writerIndex} by {@code 2} in this buffer. @@ -961,6 +1007,13 @@ public interface ActiveMQBuffer extends DataInput { */ void writeBoolean(boolean val); + /** + * Sets the specified (potentially {@code null}) Boolean at the current {@code writerIndex} + * + * @param val The specified boolean + */ + void writeNullableBoolean(Boolean val); + /** * Sets the specified SimpleString (potentially {@code null}) at the current {@code writerIndex} * diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ChannelBufferWrapper.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ChannelBufferWrapper.java index 92314e208e..a6a5b80f00 100644 --- a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ChannelBufferWrapper.java +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ChannelBufferWrapper.java @@ -64,6 +64,16 @@ public class ChannelBufferWrapper implements ActiveMQBuffer { return readByte() != 0; } + @Override + public Boolean readNullableBoolean() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readBoolean(); + } + } + @Override public SimpleString readNullableSimpleString() { return SimpleString.readNullableSimpleString(buffer); @@ -125,6 +135,16 @@ public class ChannelBufferWrapper implements ActiveMQBuffer { buffer.writeByte((byte) (val ? -1 : 0)); } + @Override + public void writeNullableBoolean(Boolean val) { + if (val == null) { + buffer.writeByte(DataConstants.NULL); + } else { + buffer.writeByte(DataConstants.NOT_NULL); + writeBoolean(val); + } + } + @Override public void writeNullableSimpleString(final SimpleString val) { SimpleString.writeNullableSimpleString(buffer, val); @@ -345,11 +365,31 @@ public class ChannelBufferWrapper implements ActiveMQBuffer { return buffer.readInt(); } + @Override + public Integer readNullableInt() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readInt(); + } + } + @Override public long readLong() { return buffer.readLong(); } + @Override + public Long readNullableLong() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readLong(); + } + } + @Override public short readShort() { return buffer.readShort(); @@ -565,11 +605,31 @@ public class ChannelBufferWrapper implements ActiveMQBuffer { buffer.writeInt(value); } + @Override + public void writeNullableInt(final Integer value) { + if (value == null) { + buffer.writeByte(DataConstants.NULL); + } else { + buffer.writeByte(DataConstants.NOT_NULL); + writeInt(value); + } + } + @Override public void writeLong(final long value) { buffer.writeLong(value); } + @Override + public void writeNullableLong(Long value) { + if (value == null) { + buffer.writeByte(DataConstants.NULL); + } else { + buffer.writeByte(DataConstants.NOT_NULL); + writeLong(value); + } + } + @Override public int writerIndex() { return buffer.writerIndex(); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java index 76e1557d3d..66b7289d74 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java @@ -572,6 +572,33 @@ public final class ActiveMQDefaultConfiguration { public static final CriticalAnalyzerPolicy DEFAULT_ANALYZE_CRITICAL_POLICY = CriticalAnalyzerPolicy.LOG; + // The period (in milliseconds) used to check if the federation connection has failed to receive pings from another server + private static long DEFAULT_FEDERATION_FAILURE_CHECK_PERIOD = 30000; + + // how long to keep a connection alive in the absence of any data arriving from the client + private static long DEFAULT_FEDERATION_CONNECTION_TTL = getDefaultConnectionTtl(); + + // How long to wait for a reply + private static long DEFAULT_FEDERATION_CALL_TIMEOUT = 30000; + + // period (in ms) between successive retries + private static long DEFAULT_FEDERATION_RETRY_INTERVAL = 500; + + // multiplier to apply to the retry-interval + private static double DEFAULT_FEDERATION_RETRY_INTERVAL_MULTIPLIER = getDefaultRetryIntervalMultiplier(); + + // Maximum value for retry-interval + private static long DEFAULT_FEDERATION_MAX_RETRY_INTERVAL = getDefaultMaxRetryInterval(); + + // How many attempts should be made to connect initially + private static int DEFAULT_FEDERATION_INITIAL_CONNECT_ATTEMPTS = -1; + + // How many attempts should be made to reconnect after failure + private static int DEFAULT_FEDERATION_RECONNECT_ATTEMPTS = -1; + + // How long to wait for a reply if in the middle of a fail-over. -1 means wait forever. + private static long DEFAULT_FEDERATION_CALL_FAILOVER_TIMEOUT = -1; + /** * If true then the ActiveMQ Artemis Server will make use of any Protocol Managers that are in available on the classpath. If false then only the core protocol will be available, unless in Embedded mode where users can inject their own Protocol Managers. */ @@ -1537,4 +1564,68 @@ public final class ActiveMQDefaultConfiguration { public static long getDefaultRetryReplicationWait() { return DEFAULT_RETRY_REPLICATION_WAIT; } + + /** + * The period (in milliseconds) used to check if the federation connection has failed to receive pings from another server + */ + public static long getDefaultFederationFailureCheckPeriod() { + return DEFAULT_FEDERATION_FAILURE_CHECK_PERIOD; + } + + /** + * how long to keep a connection alive in the absence of any data arriving from the client + */ + public static long getDefaultFederationConnectionTtl() { + return DEFAULT_FEDERATION_CONNECTION_TTL; + } + + /** + * How long to wait for a reply + */ + public static long getDefaultFederationCallTimeout() { + return DEFAULT_FEDERATION_CALL_TIMEOUT; + } + + /** + * period (in ms) between successive retries + */ + public static long getDefaultFederationRetryInterval() { + return DEFAULT_FEDERATION_RETRY_INTERVAL; + } + + /** + * multiplier to apply to the retry-interval + */ + public static double getDefaultFederationRetryIntervalMultiplier() { + return DEFAULT_FEDERATION_RETRY_INTERVAL_MULTIPLIER; + } + + /** + * Maximum value for retry-interval + */ + public static long getDefaultFederationMaxRetryInterval() { + return DEFAULT_FEDERATION_MAX_RETRY_INTERVAL; + } + + /** + * How many attempts should be made to connect initially + */ + public static int getDefaultFederationInitialConnectAttempts() { + return DEFAULT_FEDERATION_INITIAL_CONNECT_ATTEMPTS; + } + + /** + * How many attempts should be made to reconnect after failure + */ + public static int getDefaultFederationReconnectAttempts() { + return DEFAULT_FEDERATION_RECONNECT_ATTEMPTS; + } + + /** + * How long to wait for a reply if in the middle of a fail-over. -1 means wait forever. + */ + public static long getDefaultFederationCallFailoverTimeout() { + return DEFAULT_FEDERATION_CALL_FAILOVER_TIMEOUT; + } + } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ResetLimitWrappedActiveMQBuffer.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ResetLimitWrappedActiveMQBuffer.java index b5d5474558..7845deee7d 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ResetLimitWrappedActiveMQBuffer.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/buffers/impl/ResetLimitWrappedActiveMQBuffer.java @@ -226,6 +226,13 @@ public final class ResetLimitWrappedActiveMQBuffer extends ChannelBufferWrapper super.writeBoolean(val); } + @Override + public void writeNullableBoolean(final Boolean val) { + changed(); + + super.writeNullableBoolean(val); + } + @Override public void writeByte(final byte value) { changed(); @@ -304,6 +311,13 @@ public final class ResetLimitWrappedActiveMQBuffer extends ChannelBufferWrapper super.writeInt(value); } + @Override + public void writeNullableInt(final Integer value) { + changed(); + + super.writeNullableInt(value); + } + @Override public void writeLong(final long value) { changed(); @@ -311,6 +325,13 @@ public final class ResetLimitWrappedActiveMQBuffer extends ChannelBufferWrapper super.writeLong(value); } + @Override + public void writeNullableLong(final Long value) { + changed(); + + super.writeNullableLong(value); + } + @Override public void writeNullableSimpleString(final SimpleString val) { changed(); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/CompressedLargeMessageControllerImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/CompressedLargeMessageControllerImpl.java index ce652d20f0..5c33fafd4d 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/CompressedLargeMessageControllerImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/CompressedLargeMessageControllerImpl.java @@ -375,6 +375,16 @@ final class CompressedLargeMessageControllerImpl implements LargeMessageControll } } + @Override + public Integer readNullableInt() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readInt(); + } + } + @Override public long readUnsignedInt() { return readInt() & 0xFFFFFFFFL; @@ -389,6 +399,16 @@ final class CompressedLargeMessageControllerImpl implements LargeMessageControll } } + @Override + public Long readNullableLong() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readLong(); + } + } + @Override public void readBytes(final byte[] dst, final int dstIndex, final int length) { try { @@ -487,11 +507,21 @@ final class CompressedLargeMessageControllerImpl implements LargeMessageControll throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); } + @Override + public void writeNullableInt(final Integer value) { + throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); + } + @Override public void writeLong(final long value) { throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); } + @Override + public void writeNullableLong(final Long value) { + throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); + } + @Override public void writeBytes(final byte[] src, final int srcIndex, final int length) { throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); @@ -532,6 +562,16 @@ final class CompressedLargeMessageControllerImpl implements LargeMessageControll return readByte() != 0; } + @Override + public Boolean readNullableBoolean() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readBoolean(); + } + } + @Override public char readChar() { return (char) readShort(); @@ -624,6 +664,11 @@ final class CompressedLargeMessageControllerImpl implements LargeMessageControll throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); } + @Override + public void writeNullableBoolean(final Boolean val) { + throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); + } + @Override public void writeChar(final char val) { throw new IllegalAccessError(OPERATION_NOT_SUPPORTED); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/LargeMessageControllerImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/LargeMessageControllerImpl.java index d5d1c82242..f08e2b82d3 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/LargeMessageControllerImpl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/LargeMessageControllerImpl.java @@ -744,6 +744,16 @@ public class LargeMessageControllerImpl implements LargeMessageController { return getInt(pos); } + @Override + public Integer readNullableInt() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readInt(); + } + } + @Override public long readUnsignedInt() { return readInt() & 0xFFFFFFFFL; @@ -756,6 +766,16 @@ public class LargeMessageControllerImpl implements LargeMessageController { return v; } + @Override + public Long readNullableLong() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readLong(); + } + } + @Override public void readBytes(final byte[] dst, final int dstIndex, final int length) { getBytes(readerIndex, dst, dstIndex, length); @@ -833,11 +853,21 @@ public class LargeMessageControllerImpl implements LargeMessageController { throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); } + @Override + public void writeNullableInt(final Integer value) { + throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); + } + @Override public void writeLong(final long value) { throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); } + @Override + public void writeNullableLong(final Long value) { + throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); + } + @Override public void writeBytes(final byte[] src, final int srcIndex, final int length) { throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); @@ -915,6 +945,16 @@ public class LargeMessageControllerImpl implements LargeMessageController { return readByte() != 0; } + @Override + public Boolean readNullableBoolean() { + int b = readByte(); + if (b == DataConstants.NULL) { + return null; + } else { + return readBoolean(); + } + } + @Override public char readChar() { return (char) readShort(); @@ -1008,6 +1048,11 @@ public class LargeMessageControllerImpl implements LargeMessageController { throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); } + @Override + public void writeNullableBoolean(final Boolean val) { + throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); + } + @Override public void writeChar(final char val) { throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java similarity index 80% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java index a9973f628d..a42040adeb 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/FederationConfiguration.java @@ -22,6 +22,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationPolicy; import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; @@ -34,6 +37,8 @@ public class FederationConfiguration implements Serializable { private List upstreamConfigurations = new ArrayList<>(); + private List downstreamConfigurations = new ArrayList<>(); + private Map federationPolicyMap = new HashMap<>(); private Map transformerConfigurationMap = new HashMap<>(); @@ -47,11 +52,28 @@ public class FederationConfiguration implements Serializable { return this; } + public List getDownstreamConfigurations() { + return downstreamConfigurations; + } + + public FederationConfiguration addDownstreamConfiguration(FederationDownstreamConfiguration federationDownstreamConfiguration) { + this.downstreamConfigurations.add(federationDownstreamConfiguration); + return this; + } + public FederationConfiguration addFederationPolicy(FederationPolicy federationPolicy) { federationPolicyMap.put(federationPolicy.getName(), federationPolicy); return this; } + public void clearDownstreamConfigurations() { + this.downstreamConfigurations.clear(); + } + + public void clearUpstreamConfigurations() { + this.upstreamConfigurations.clear(); + } + public Map getFederationPolicyMap() { return federationPolicyMap; } @@ -121,6 +143,16 @@ public class FederationConfiguration implements Serializable { public int hashCode() { return Objects.hash(user, password); } + + public void encode(ActiveMQBuffer buffer) { + buffer.writeNullableString(user); + buffer.writeNullableString(password); + } + + public void decode(ActiveMQBuffer buffer) { + user = buffer.readNullableString(); + password = buffer.readNullableString(); + } } @Override diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java similarity index 100% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java similarity index 70% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java index f41e5890b5..71188e692c 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationAddressPolicyConfiguration.java @@ -21,6 +21,9 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.utils.Preconditions; + public class FederationAddressPolicyConfiguration implements FederationPolicy, Serializable { private String name; @@ -106,6 +109,55 @@ public class FederationAddressPolicyConfiguration implements FederationPolicy(); + excludes = new HashSet<>(); + decodeMatchers(buffer, includes); + decodeMatchers(buffer, excludes); + + } + + private void encodeMatchers(final ActiveMQBuffer buffer, final Set matchers) { + buffer.writeInt(matchers == null ? 0 : matchers.size()); + if (matchers != null) { + for (Matcher matcher : matchers) { + matcher.encode(buffer); + } + } + } + + private void decodeMatchers(final ActiveMQBuffer buffer, final Set matchers) { + final int size = buffer.readInt(); + + for (int i = 0; i < size; i++) { + Matcher matcher = new Matcher(); + matcher.decode(buffer); + matchers.add(matcher); + } + } + public static class Matcher implements Serializable { private String addressMatch; @@ -131,6 +183,15 @@ public class FederationAddressPolicyConfiguration implements FederationPolicy staticConnectors; + private int priorityAdjustment; + + private long circuitBreakerTimeout = DEFAULT_CIRCUIT_BREAKER_TIMEOUT; + private String username; + private String password; + private boolean shareConnection; + + private long clientFailureCheckPeriod = ActiveMQDefaultConfiguration.getDefaultFederationFailureCheckPeriod(); + private long connectionTTL = ActiveMQDefaultConfiguration.getDefaultFederationConnectionTtl(); + private long retryInterval = ActiveMQDefaultConfiguration.getDefaultFederationRetryInterval(); + private double retryIntervalMultiplier = ActiveMQDefaultConfiguration.getDefaultFederationRetryIntervalMultiplier(); + private long maxRetryInterval = ActiveMQDefaultConfiguration.getDefaultFederationMaxRetryInterval(); + private int initialConnectAttempts = ActiveMQDefaultConfiguration.getDefaultFederationInitialConnectAttempts(); + private int reconnectAttempts = ActiveMQDefaultConfiguration.getDefaultFederationReconnectAttempts(); + private long callTimeout = ActiveMQDefaultConfiguration.getDefaultFederationCallTimeout(); + private long callFailoverTimeout = ActiveMQDefaultConfiguration.getDefaultFederationCallFailoverTimeout(); + + public String getDiscoveryGroupName() { + return discoveryGroupName; + } + + public FederationConnectionConfiguration setDiscoveryGroupName(String discoveryGroupName) { + this.discoveryGroupName = discoveryGroupName; + return this; + } + + public List getStaticConnectors() { + return staticConnectors; + } + + public FederationConnectionConfiguration setStaticConnectors(List staticConnectors) { + this.staticConnectors = staticConnectors; + return this; + } + + public boolean isHA() { + return isHA; + } + + public FederationConnectionConfiguration setHA(boolean HA) { + isHA = HA; + return this; + } + + public long getCircuitBreakerTimeout() { + return circuitBreakerTimeout; + } + + public FederationConnectionConfiguration setCircuitBreakerTimeout(long circuitBreakerTimeout) { + this.circuitBreakerTimeout = circuitBreakerTimeout; + return this; + } + + public String getUsername() { + return username; + } + + public FederationConnectionConfiguration setUsername(String username) { + this.username = username; + return this; + } + + public String getPassword() { + return password; + } + + public FederationConnectionConfiguration setPassword(String password) { + this.password = password; + return this; + } + + public int getPriorityAdjustment() { + return priorityAdjustment; + } + + public FederationConnectionConfiguration setPriorityAdjustment(int priorityAdjustment) { + this.priorityAdjustment = priorityAdjustment; + return this; + } + + public boolean isShareConnection() { + return shareConnection; + } + + public FederationConnectionConfiguration setShareConnection(boolean shareConnection) { + this.shareConnection = shareConnection; + return this; + } + + public long getClientFailureCheckPeriod() { + return clientFailureCheckPeriod; + } + + public FederationConnectionConfiguration setClientFailureCheckPeriod(long clientFailureCheckPeriod) { + this.clientFailureCheckPeriod = clientFailureCheckPeriod; + return this; + } + + public long getConnectionTTL() { + return connectionTTL; + } + + public FederationConnectionConfiguration setConnectionTTL(long connectionTTL) { + this.connectionTTL = connectionTTL; + return this; + } + + public long getRetryInterval() { + return retryInterval; + } + + public FederationConnectionConfiguration setRetryInterval(long retryInterval) { + this.retryInterval = retryInterval; + return this; + } + + public double getRetryIntervalMultiplier() { + return retryIntervalMultiplier; + } + + public FederationConnectionConfiguration setRetryIntervalMultiplier(double retryIntervalMultiplier) { + this.retryIntervalMultiplier = retryIntervalMultiplier; + return this; + } + + public long getMaxRetryInterval() { + return maxRetryInterval; + } + + public FederationConnectionConfiguration setMaxRetryInterval(long maxRetryInterval) { + this.maxRetryInterval = maxRetryInterval; + return this; + } + + public int getInitialConnectAttempts() { + return initialConnectAttempts; + } + + public FederationConnectionConfiguration setInitialConnectAttempts(int initialConnectAttempts) { + this.initialConnectAttempts = initialConnectAttempts; + return this; + } + + public int getReconnectAttempts() { + return reconnectAttempts; + } + + public FederationConnectionConfiguration setReconnectAttempts(int reconnectAttempts) { + this.reconnectAttempts = reconnectAttempts; + return this; + } + + public long getCallTimeout() { + return callTimeout; + } + + public FederationConnectionConfiguration setCallTimeout(long callTimeout) { + this.callTimeout = callTimeout; + return this; + } + + public long getCallFailoverTimeout() { + return callFailoverTimeout; + } + + public FederationConnectionConfiguration setCallFailoverTimeout(long callFailoverTimeout) { + this.callFailoverTimeout = callFailoverTimeout; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FederationConnectionConfiguration that = (FederationConnectionConfiguration) o; + return clientFailureCheckPeriod == that.clientFailureCheckPeriod && + connectionTTL == that.connectionTTL && + retryInterval == that.retryInterval && + Double.compare(that.retryIntervalMultiplier, retryIntervalMultiplier) == 0 && + maxRetryInterval == that.maxRetryInterval && + initialConnectAttempts == that.initialConnectAttempts && + reconnectAttempts == that.reconnectAttempts && + callTimeout == that.callTimeout && + callFailoverTimeout == that.callFailoverTimeout && + isHA == that.isHA && + priorityAdjustment == that.priorityAdjustment && + circuitBreakerTimeout == that.circuitBreakerTimeout && + shareConnection == that.shareConnection && + Objects.equals(discoveryGroupName, that.discoveryGroupName) && + Objects.equals(staticConnectors, that.staticConnectors) && + Objects.equals(username, that.username) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects + .hash(clientFailureCheckPeriod, connectionTTL, retryInterval, retryIntervalMultiplier, + maxRetryInterval, initialConnectAttempts, reconnectAttempts, callTimeout, + callFailoverTimeout, isHA, discoveryGroupName, staticConnectors, priorityAdjustment, + circuitBreakerTimeout, username, password, shareConnection); + } + + public void encode(ActiveMQBuffer buffer) { + + buffer.writeNullableString(username); + buffer.writeNullableString(password); + buffer.writeBoolean(shareConnection); + buffer.writeInt(priorityAdjustment); + + buffer.writeLong(clientFailureCheckPeriod); + buffer.writeLong(connectionTTL); + buffer.writeLong(retryInterval); + buffer.writeDouble(retryIntervalMultiplier); + buffer.writeLong(retryInterval); + buffer.writeInt(initialConnectAttempts); + buffer.writeInt(reconnectAttempts); + buffer.writeLong(callTimeout); + buffer.writeLong(callFailoverTimeout); + } + + public void decode(ActiveMQBuffer buffer) { + username = buffer.readNullableString(); + password = buffer.readNullableString(); + shareConnection = buffer.readBoolean(); + priorityAdjustment = buffer.readInt(); + + clientFailureCheckPeriod = buffer.readLong(); + connectionTTL = buffer.readLong(); + retryInterval = buffer.readLong(); + retryIntervalMultiplier = buffer.readDouble(); + maxRetryInterval = buffer.readLong(); + initialConnectAttempts = buffer.readInt(); + reconnectAttempts = buffer.readInt(); + callTimeout = buffer.readLong(); + callFailoverTimeout = buffer.readLong(); + + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationDownstreamConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationDownstreamConfiguration.java new file mode 100644 index 0000000000..7e8c5cf7cc --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationDownstreamConfiguration.java @@ -0,0 +1,96 @@ +/* + * 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.federation; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; + +public class FederationDownstreamConfiguration extends FederationStreamConfiguration { + + private String upstreamConfigurationRef; + private TransportConfiguration upstreamConfiguration; + + public String getUpstreamConfigurationRef() { + return upstreamConfigurationRef; + } + + public void setUpstreamConfigurationRef(String upstreamConfigurationRef) { + this.upstreamConfigurationRef = upstreamConfigurationRef; + } + + public TransportConfiguration getUpstreamConfiguration() { + return upstreamConfiguration; + } + + public void setUpstreamConfiguration(TransportConfiguration transportConfiguration) { + + final Map params = new HashMap<>(transportConfiguration.getParams()); + + //clear any TLS settings as they won't apply to the federated server that uses this config + //The federated server that creates the upstream back will rely on its config from the acceptor for TLS + params.remove(TransportConstants.SSL_ENABLED_PROP_NAME); + params.remove(TransportConstants.SSL_PROVIDER); + params.remove(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME); + params.remove(TransportConstants.KEYSTORE_PATH_PROP_NAME); + params.remove(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME); + params.remove(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME); + params.remove(TransportConstants.TRUSTSTORE_PATH_PROP_NAME); + params.remove(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME); + params.remove(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME); + + this.upstreamConfiguration = new TransportConfiguration(transportConfiguration.getFactoryClassName(), params, + transportConfiguration.getName(), transportConfiguration.getExtraParams()); + } + + @Override + public void encode(ActiveMQBuffer buffer) { + super.encode(buffer); + upstreamConfiguration.encode(buffer); + } + + @Override + public void decode(ActiveMQBuffer buffer) { + super.decode(buffer); + upstreamConfiguration = new TransportConfiguration(); + upstreamConfiguration.decode(buffer); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + FederationDownstreamConfiguration that = (FederationDownstreamConfiguration) o; + return Objects.equals(upstreamConfigurationRef, that.upstreamConfigurationRef) && + Objects.equals(upstreamConfiguration, that.upstreamConfiguration); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), upstreamConfigurationRef, upstreamConfiguration); + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java similarity index 87% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java index 870fb6e120..d8d9d5eebb 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicy.java @@ -16,9 +16,16 @@ */ package org.apache.activemq.artemis.core.config.federation; +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; + public interface FederationPolicy { String getName(); T setName(String name); + + void encode(ActiveMQBuffer buffer); + + void decode(ActiveMQBuffer buffer); + } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java similarity index 72% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java index 4af0bb7cca..4e19181ed1 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationPolicySet.java @@ -22,6 +22,9 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.utils.Preconditions; + public class FederationPolicySet implements FederationPolicy, Serializable { private String name; @@ -65,4 +68,29 @@ public class FederationPolicySet implements FederationPolicy(); + + for (int i = 0; i < policyRefsSize; i++) { + policyRefs.add(buffer.readString()); + } + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java similarity index 70% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java index 34a8632629..3294165669 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationQueuePolicyConfiguration.java @@ -21,6 +21,9 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.utils.Preconditions; + public class FederationQueuePolicyConfiguration implements FederationPolicy, Serializable { private String name; @@ -86,6 +89,49 @@ public class FederationQueuePolicyConfiguration implements FederationPolicy(); + excludes = new HashSet<>(); + decodeMatchers(buffer, includes); + decodeMatchers(buffer, excludes); + } + + private void encodeMatchers(final ActiveMQBuffer buffer, final Set matchers) { + buffer.writeInt(matchers == null ? 0 : matchers.size()); + if (matchers != null) { + for (Matcher matcher : matchers) { + matcher.encode(buffer); + } + } + } + + private void decodeMatchers(final ActiveMQBuffer buffer, final Set matchers) { + final int size = buffer.readInt(); + + for (int i = 0; i < size; i++) { + Matcher matcher = new Matcher(); + matcher.decode(buffer); + matchers.add(matcher); + } + } + public static class Matcher implements Serializable { private String queueMatch; @@ -122,6 +168,16 @@ public class FederationQueuePolicyConfiguration implements FederationPolicy> implements Serializable { private String name; @@ -33,23 +35,23 @@ public class FederationUpstreamConfiguration implements Serializable { return name; } - public FederationUpstreamConfiguration setName(String name) { + public T setName(String name) { this.name = name; - return this; + return (T) this; } public Set getPolicyRefs() { return policyRefs; } - public FederationUpstreamConfiguration addPolicyRef(String name) { + public T addPolicyRef(String name) { policyRefs.add(name); - return this; + return (T) this; } - public FederationUpstreamConfiguration addPolicyRefs(Collection names) { + public T addPolicyRefs(Collection names) { policyRefs.addAll(names); - return this; + return (T) this; } public FederationConnectionConfiguration getConnectionConfiguration() { @@ -58,16 +60,35 @@ public class FederationUpstreamConfiguration implements Serializable { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof FederationUpstreamConfiguration)) return false; - FederationUpstreamConfiguration that = (FederationUpstreamConfiguration) o; - return Objects.equals(name, that.name) && - Objects.equals(connectionConfiguration, that.connectionConfiguration) && - Objects.equals(policyRefs, that.policyRefs); + if (this == o) + return true; + if (!(o instanceof FederationStreamConfiguration)) + return false; + FederationStreamConfiguration that = (FederationStreamConfiguration) o; + return Objects.equals(name, that.name) && Objects.equals(connectionConfiguration, that.connectionConfiguration) && Objects.equals(policyRefs, that.policyRefs); } @Override public int hashCode() { return Objects.hash(name, connectionConfiguration, policyRefs); } + + public void encode(ActiveMQBuffer buffer) { + buffer.writeString(name); + connectionConfiguration.encode(buffer); + + buffer.writeInt(policyRefs == null ? 0 : policyRefs.size()); + for (String policyRef : policyRefs) { + buffer.writeString(policyRef); + } + } + + public void decode(ActiveMQBuffer buffer) { + name = buffer.readString(); + connectionConfiguration.decode(buffer); + int policyRefNum = buffer.readInt(); + for (int i = 0; i < policyRefNum; i++) { + policyRefs.add(buffer.readString()); + } + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java similarity index 60% rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java index 003ed33362..2a16883fdd 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationTransformerConfiguration.java @@ -17,8 +17,12 @@ package org.apache.activemq.artemis.core.config.federation; import java.io.Serializable; +import java.util.Map; import java.util.Objects; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.core.config.TransformerConfiguration; +import org.apache.activemq.artemis.utils.Preconditions; public class FederationTransformerConfiguration implements Serializable { @@ -26,6 +30,9 @@ public class FederationTransformerConfiguration implements Serializable { private TransformerConfiguration transformerConfiguration; + public FederationTransformerConfiguration() { + } + public FederationTransformerConfiguration(String name, TransformerConfiguration transformerConfiguration) { this.name = name; this.transformerConfiguration = transformerConfiguration; @@ -52,4 +59,30 @@ public class FederationTransformerConfiguration implements Serializable { public int hashCode() { return Objects.hash(name, transformerConfiguration); } + + + public void encode(ActiveMQBuffer buffer) { + Preconditions.checkArgument(name != null, "name can not be null"); + Preconditions.checkArgument(transformerConfiguration != null, "transformerConfiguration can not be null"); + buffer.writeString(name); + + buffer.writeString(transformerConfiguration.getClassName()); + buffer.writeInt(transformerConfiguration.getProperties() == null ? 0 : transformerConfiguration.getProperties().size()); + if (transformerConfiguration.getProperties() != null) { + for (Map.Entry entry : transformerConfiguration.getProperties().entrySet()) { + buffer.writeString(entry.getKey()); + buffer.writeString(entry.getValue()); + } + } + } + + public void decode(ActiveMQBuffer buffer) { + name = buffer.readString(); + transformerConfiguration = new TransformerConfiguration(buffer.readString()); + + final int propertiesSize = buffer.readInt(); + for (int i = 0; i < propertiesSize; i++) { + transformerConfiguration.getProperties().put(buffer.readString(), buffer.readString()); + } + } } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationUpstreamConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationUpstreamConfiguration.java new file mode 100644 index 0000000000..e2c0a833cd --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationUpstreamConfiguration.java @@ -0,0 +1,21 @@ +/* + * 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.federation; + +public class FederationUpstreamConfiguration extends FederationStreamConfiguration { + +} 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 154ab8aa80..84cc135fcf 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 @@ -64,6 +64,9 @@ public final class ChannelImpl implements Channel { * cluster used for controlling nodes in a cluster remotely */ CLUSTER(3), + + FEDERATION(4), + /** * Channels [0-9] are reserved for the system, user channels must be greater than that. */ 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 f576feaf5e..9cc62b25a6 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 @@ -40,6 +40,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.FederationDownstreamConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.PacketsConfirmedMessage; @@ -109,6 +110,7 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DIS 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.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; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.PACKETS_CONFIRMED; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.PING; @@ -471,6 +473,10 @@ public abstract class PacketDecoder implements Serializable { packet = new DisconnectConsumerWithKillMessage(); break; } + case FEDERATION_DOWNSTREAM_CONNECT: { + packet = new FederationDownstreamConnectMessage(); + 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 f8f85e8a42..aea8b39e39 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 @@ -277,6 +277,8 @@ public class PacketImpl implements Packet { public static final byte SESS_BINDINGQUERY_RESP_V4 = -15; + public static final byte FEDERATION_DOWNSTREAM_CONNECT = -16; + // Static -------------------------------------------------------- diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationDownstreamConnectMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationDownstreamConnectMessage.java new file mode 100644 index 0000000000..5f74629b62 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationDownstreamConnectMessage.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.protocol.core.impl.wireformat; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; + +public class FederationDownstreamConnectMessage extends FederationStreamConnectMessage { + + public static final String UPSTREAM_SUFFIX = "-upstream"; + + public FederationDownstreamConnectMessage() { + super(FEDERATION_DOWNSTREAM_CONNECT); + } + + @Override + protected FederationDownstreamConfiguration decodeStreamConfiguration(ActiveMQBuffer buffer) { + final FederationDownstreamConfiguration downstreamConfiguration = new FederationDownstreamConfiguration(); + downstreamConfiguration.decode(buffer); + return downstreamConfiguration; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationStreamConnectMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationStreamConnectMessage.java new file mode 100644 index 0000000000..1b2a28d4f3 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/FederationStreamConnectMessage.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.protocol.core.impl.wireformat; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.activemq.artemis.api.core.ActiveMQBuffer; +import org.apache.activemq.artemis.core.config.FederationConfiguration.Credentials; +import org.apache.activemq.artemis.core.config.federation.FederationPolicy; +import org.apache.activemq.artemis.core.config.federation.FederationStreamConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration; +import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl; +import org.apache.activemq.artemis.utils.Preconditions; + +public abstract class FederationStreamConnectMessage extends PacketImpl { + + private String name; + private Credentials credentials; + private Map federationPolicyMap = new HashMap<>(); + private Map transformerConfigurationMap = new HashMap<>(); + private T streamConfiguration; + + public FederationStreamConnectMessage(final byte type) { + super(type); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Credentials getCredentials() { + return credentials; + } + + public void setCredentials( + Credentials credentials) { + this.credentials = credentials; + } + + public T getStreamConfiguration() { + return streamConfiguration; + } + + public void setStreamConfiguration(T streamConfiguration) { + this.streamConfiguration = streamConfiguration; + } + + public Map getFederationPolicyMap() { + return federationPolicyMap; + } + + public void setFederationPolicyMap( + Map federationPolicyMap) { + this.federationPolicyMap = federationPolicyMap; + } + + public Map getTransformerConfigurationMap() { + return transformerConfigurationMap; + } + + public void setTransformerConfigurationMap( + Map transformerConfigurationMap) { + this.transformerConfigurationMap = transformerConfigurationMap; + } + + @Override + public void encodeRest(ActiveMQBuffer buffer) { + Preconditions.checkNotNull(streamConfiguration); + + super.encodeRest(buffer); + buffer.writeString(name); + + if (credentials != null) { + buffer.writeBoolean(true); + credentials.encode(buffer); + } else { + buffer.writeBoolean(false); + } + + buffer.writeInt(federationPolicyMap == null ? 0 : federationPolicyMap.size()); + if (federationPolicyMap != null) { + for (FederationPolicy policy : federationPolicyMap.values()) { + buffer.writeString(policy.getClass().getName()); + policy.encode(buffer); + } + } + + buffer.writeInt(transformerConfigurationMap == null ? 0 : transformerConfigurationMap.size()); + if (transformerConfigurationMap != null) { + for (FederationTransformerConfiguration transformerConfiguration : transformerConfigurationMap.values()) { + transformerConfiguration.encode(buffer); + } + } + + streamConfiguration.encode(buffer); + } + + @Override + public void decodeRest(ActiveMQBuffer buffer) { + super.decodeRest(buffer); + + this.name = buffer.readString(); + boolean hasCredentials = buffer.readBoolean(); + if (hasCredentials) { + credentials = new Credentials(); + credentials.decode(buffer); + } + + int policySize = buffer.readInt(); + for (int i = 0; i < policySize; i++) { + String clazz = buffer.readString(); + FederationPolicy policy = getFederationPolicy(clazz); + policy.decode(buffer); + federationPolicyMap.put(policy.getName(), policy); + } + + int transformerSize = buffer.readInt(); + for (int i = 0; i < transformerSize; i++) { + FederationTransformerConfiguration transformerConfiguration + = new FederationTransformerConfiguration(); + transformerConfiguration.decode(buffer); + transformerConfigurationMap.put(transformerConfiguration.getName(), transformerConfiguration); + } + + streamConfiguration = decodeStreamConfiguration(buffer); + } + + protected abstract T decodeStreamConfiguration(ActiveMQBuffer buffer); + + private FederationPolicy getFederationPolicy(String clazz) { + try { + return (FederationPolicy) Class.forName(clazz).getConstructor(null).newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Error. Unable to instantiate FederationPolicy: " + e.getMessage(), e); + } + } +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationConnectionConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationConnectionConfiguration.java deleted file mode 100644 index 78a69553d3..0000000000 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/federation/FederationConnectionConfiguration.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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.federation; - -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -public class FederationConnectionConfiguration implements Serializable { - - public static long DEFAULT_CIRCUIT_BREAKER_TIMEOUT = 30000; - - private boolean isHA; - private String discoveryGroupName; - private List staticConnectors; - private int priorityAdjustment; - - private long circuitBreakerTimeout = DEFAULT_CIRCUIT_BREAKER_TIMEOUT; - private String username; - private String password; - - public String getDiscoveryGroupName() { - return discoveryGroupName; - } - - public FederationConnectionConfiguration setDiscoveryGroupName(String discoveryGroupName) { - this.discoveryGroupName = discoveryGroupName; - return this; - } - - public List getStaticConnectors() { - return staticConnectors; - } - - public FederationConnectionConfiguration setStaticConnectors(List staticConnectors) { - this.staticConnectors = staticConnectors; - return this; - } - - public boolean isHA() { - return isHA; - } - - public FederationConnectionConfiguration setHA(boolean HA) { - isHA = HA; - return this; - } - - public long getCircuitBreakerTimeout() { - return circuitBreakerTimeout; - } - - public FederationConnectionConfiguration setCircuitBreakerTimeout(long circuitBreakerTimeout) { - this.circuitBreakerTimeout = circuitBreakerTimeout; - return this; - } - - public String getUsername() { - return username; - } - - public FederationConnectionConfiguration setUsername(String username) { - this.username = username; - return this; - } - - public String getPassword() { - return password; - } - - public FederationConnectionConfiguration setPassword(String password) { - this.password = password; - return this; - } - - public int getPriorityAdjustment() { - return priorityAdjustment; - } - - public FederationConnectionConfiguration setPriorityAdjustment(int priorityAdjustment) { - this.priorityAdjustment = priorityAdjustment; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof FederationConnectionConfiguration)) return false; - FederationConnectionConfiguration that = (FederationConnectionConfiguration) o; - return isHA == that.isHA && - circuitBreakerTimeout == that.circuitBreakerTimeout && - Objects.equals(discoveryGroupName, that.discoveryGroupName) && - Objects.equals(staticConnectors, that.staticConnectors) && - Objects.equals(priorityAdjustment, that.priorityAdjustment) && - Objects.equals(username, that.username) && - Objects.equals(password, that.password); - } - - @Override - public int hashCode() { - return Objects.hash(isHA, discoveryGroupName, staticConnectors, priorityAdjustment, circuitBreakerTimeout, username, password); - } -} 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 648943c04e..ce4e39d1ce 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 @@ -57,8 +57,11 @@ import org.apache.activemq.artemis.core.config.ScaleDownConfiguration; import org.apache.activemq.artemis.core.config.TransformerConfiguration; import org.apache.activemq.artemis.core.config.WildcardConfiguration; 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; import org.apache.activemq.artemis.core.config.federation.FederationQueuePolicyConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationStreamConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; import org.apache.activemq.artemis.core.config.ha.ColocatedPolicyConfiguration; import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration; @@ -2002,12 +2005,18 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { if (child.getNodeName().equals("upstream")) { config.addUpstreamConfiguration(getUpstream((Element) child, mainConfig)); + } else if (child.getNodeName().equals("downstream")) { + config.addDownstreamConfiguration(getDownstream((Element) child, mainConfig)); } else if (child.getNodeName().equals("policy-set")) { config.addFederationPolicy(getPolicySet((Element)child, mainConfig)); } else if (child.getNodeName().equals("queue-policy")) { config.addFederationPolicy(getQueuePolicy((Element)child, mainConfig)); } else if (child.getNodeName().equals("address-policy")) { config.addFederationPolicy(getAddressPolicy((Element)child, mainConfig)); + } else if (child.getNodeName().equals("transformer")) { + TransformerConfiguration transformerConfiguration = getTransformerConfiguration(child); + config.addTransformerConfiguration(new FederationTransformerConfiguration( + ((Element)child).getAttribute("name"), transformerConfiguration)); } } @@ -2128,9 +2137,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { return config; } - private FederationUpstreamConfiguration getUpstream(Element upstreamNode, final Configuration mainConfig) throws Exception { - - FederationUpstreamConfiguration config = new FederationUpstreamConfiguration(); + private T getFederationStream(final T config, final Element upstreamNode, + final Configuration mainConfig) throws Exception { String name = upstreamNode.getAttribute("name"); config.setName(name); @@ -2158,6 +2166,16 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { long circuitBreakerTimeout = getLong(upstreamNode, "circuit-breaker-timeout", config.getConnectionConfiguration().getCircuitBreakerTimeout(), Validators.MINUS_ONE_OR_GE_ZERO); + long clientFailureCheckPeriod = getLong(upstreamNode, "check-period", ActiveMQDefaultConfiguration.getDefaultFederationFailureCheckPeriod(), Validators.GT_ZERO); + long connectionTTL = getLong(upstreamNode, "connection-ttl", ActiveMQDefaultConfiguration.getDefaultFederationConnectionTtl(), Validators.GT_ZERO); + long retryInterval = getLong(upstreamNode, "retry-interval", ActiveMQDefaultConfiguration.getDefaultFederationRetryInterval(), Validators.GT_ZERO); + long callTimeout = getLong(upstreamNode, "call-timeout", ActiveMQClient.DEFAULT_CALL_TIMEOUT, Validators.GT_ZERO); + long callFailoverTimeout = getLong(upstreamNode, "call-failover-timeout", ActiveMQClient.DEFAULT_CALL_FAILOVER_TIMEOUT, Validators.MINUS_ONE_OR_GT_ZERO); + double retryIntervalMultiplier = getDouble(upstreamNode, "retry-interval-multiplier", ActiveMQDefaultConfiguration.getDefaultFederationRetryIntervalMultiplier(), Validators.GT_ZERO); + long maxRetryInterval = getLong(upstreamNode, "max-retry-interval", ActiveMQDefaultConfiguration.getDefaultFederationMaxRetryInterval(), Validators.GT_ZERO); + int initialConnectAttempts = getInteger(upstreamNode, "initial-connect-attempts", ActiveMQDefaultConfiguration.getDefaultFederationInitialConnectAttempts(), Validators.MINUS_ONE_OR_GE_ZERO); + int reconnectAttempts = getInteger(upstreamNode, "reconnect-attempts", ActiveMQDefaultConfiguration.getDefaultFederationReconnectAttempts(), Validators.MINUS_ONE_OR_GE_ZERO); + List staticConnectorNames = new ArrayList<>(); String discoveryGroupName = null; @@ -2181,8 +2199,17 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { config.addPolicyRefs(policyRefs); config.getConnectionConfiguration() - .setCircuitBreakerTimeout(circuitBreakerTimeout) - .setHA(ha); + .setCircuitBreakerTimeout(circuitBreakerTimeout) + .setHA(ha) + .setClientFailureCheckPeriod(clientFailureCheckPeriod) + .setConnectionTTL(connectionTTL) + .setRetryInterval(retryInterval) + .setRetryIntervalMultiplier(retryIntervalMultiplier) + .setMaxRetryInterval(maxRetryInterval) + .setInitialConnectAttempts(initialConnectAttempts) + .setReconnectAttempts(reconnectAttempts) + .setCallTimeout(callTimeout) + .setCallFailoverTimeout(callFailoverTimeout); if (!staticConnectorNames.isEmpty()) { config.getConnectionConfiguration().setStaticConnectors(staticConnectorNames); @@ -2192,6 +2219,20 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { return config; } + private FederationUpstreamConfiguration getUpstream(final Element upstreamNode, final Configuration mainConfig) throws Exception { + return getFederationStream(new FederationUpstreamConfiguration(), upstreamNode, mainConfig); + } + + private FederationDownstreamConfiguration getDownstream(final Element downstreamNode, final Configuration mainConfig) throws Exception { + final FederationDownstreamConfiguration downstreamConfiguration = + getFederationStream(new FederationDownstreamConfiguration(), downstreamNode, mainConfig); + + final String upstreamRef = getString(downstreamNode,"upstream-connector-ref", null, Validators.NOT_NULL_OR_EMPTY); + downstreamConfiguration.setUpstreamConfigurationRef(upstreamRef); + + return downstreamConfiguration; + } + private void getStaticConnectors(List staticConnectorNames, Node child) { NodeList children2 = ((Element) child).getElementsByTagName("connector-ref"); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ServerPacketDecoder.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ServerPacketDecoder.java index 0428abebd0..311cd30e1b 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ServerPacketDecoder.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ServerPacketDecoder.java @@ -27,6 +27,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.BackupRequ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.BackupResponseMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterConnectReplyMessage; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NodeAnnounceMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.QuorumVoteMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.QuorumVoteReplyMessage; @@ -59,6 +60,7 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.BAC import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.BACKUP_REQUEST_RESPONSE; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_CONNECT; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_CONNECT_REPLY; +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.NODE_ANNOUNCE; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.QUORUM_VOTE; import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.QUORUM_VOTE_REPLY; @@ -252,6 +254,10 @@ public class ServerPacketDecoder extends ClientPacketDecoder { packet = new ScaleDownAnnounceMessage(); break; } + case FEDERATION_DOWNSTREAM_CONNECT: { + packet = new FederationDownstreamConnectMessage(); + break; + } default: { packet = super.decode(packetType, connection); } 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 5862003672..c596c6eb58 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 @@ -16,6 +16,7 @@ */ package org.apache.activemq.artemis.core.protocol.core.impl; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,6 +28,7 @@ import java.util.concurrent.RejectedExecutionException; import io.netty.channel.ChannelPipeline; 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.BaseInterceptor; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.Pair; @@ -37,6 +39,12 @@ import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.config.FederationConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationConnectionConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationPolicy; +import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; import org.apache.activemq.artemis.core.protocol.ServerPacketDecoder; import org.apache.activemq.artemis.core.protocol.core.Channel; import org.apache.activemq.artemis.core.protocol.core.ChannelHandler; @@ -47,10 +55,12 @@ 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.FederationDownstreamConnectMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2; import org.apache.activemq.artemis.core.remoting.CloseListener; +import org.apache.activemq.artemis.core.remoting.FailureListener; import org.apache.activemq.artemis.core.remoting.impl.netty.ActiveMQFrameDecoder2; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; import org.apache.activemq.artemis.core.server.ActiveMQServer; @@ -111,12 +121,15 @@ public class CoreProtocolManager implements ProtocolManager { } @Override - public ConnectionEntry createConnectionEntry(final Acceptor acceptorUsed, final Connection connection) { + public ConnectionEntry createConnectionEntry(final Acceptor acceptorUsed, + final Connection connection) { final Configuration config = server.getConfiguration(); Executor connectionExecutor = server.getExecutorFactory().getExecutor(); - final CoreRemotingConnection rc = new RemotingConnectionImpl(new ServerPacketDecoder(), connection, incomingInterceptors, outgoingInterceptors, server.getNodeID(), connectionExecutor); + final CoreRemotingConnection rc = new RemotingConnectionImpl(new ServerPacketDecoder(), + connection, incomingInterceptors, outgoingInterceptors, server.getNodeID(), + connectionExecutor); Channel channel1 = rc.getChannel(CHANNEL_ID.SESSION.id, -1); @@ -130,13 +143,19 @@ public class CoreProtocolManager implements ProtocolManager { ttl = config.getConnectionTTLOverride(); } - final ConnectionEntry entry = new ConnectionEntry(rc, connectionExecutor, System.currentTimeMillis(), ttl); + final ConnectionEntry entry = new ConnectionEntry(rc, connectionExecutor, + System.currentTimeMillis(), ttl); final Channel channel0 = rc.getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1); channel0.setHandler(new LocalChannelHandler(config, entry, channel0, acceptorUsed, rc)); - server.getClusterManager().addClusterChannelHandler(rc.getChannel(CHANNEL_ID.CLUSTER.id, -1), acceptorUsed, rc, server.getActivation()); + server.getClusterManager() + .addClusterChannelHandler(rc.getChannel(CHANNEL_ID.CLUSTER.id, -1), acceptorUsed, rc, + server.getActivation()); + + final Channel federationChannel = rc.getChannel(CHANNEL_ID.FEDERATION.id, -1); + federationChannel.setHandler(new LocalChannelHandler(config, entry, channel0, acceptorUsed, rc)); return entry; } @@ -249,18 +268,22 @@ public class CoreProtocolManager implements ProtocolManager { // Just send a ping back channel0.send(packet); - } else if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY || packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) { + } else if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY + || packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) { SubscribeClusterTopologyUpdatesMessage msg = (SubscribeClusterTopologyUpdatesMessage) packet; if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) { - channel0.getConnection().setChannelVersion(((SubscribeClusterTopologyUpdatesMessageV2) msg).getClientVersion()); + channel0.getConnection().setChannelVersion( + ((SubscribeClusterTopologyUpdatesMessageV2) msg).getClientVersion()); } final ClusterTopologyListener listener = new ClusterTopologyListener() { @Override public void nodeUP(final TopologyMember topologyMember, final boolean last) { try { - final Pair connectorPair = BackwardsCompatibilityUtils.checkTCPPairConversion(channel0.getConnection().getChannelVersion(), topologyMember); + final Pair connectorPair = BackwardsCompatibilityUtils + .checkTCPPairConversion( + channel0.getConnection().getChannelVersion(), topologyMember); final String nodeID = topologyMember.getNodeId(); // Using an executor as most of the notifications on the Topology @@ -270,11 +293,20 @@ public class CoreProtocolManager implements ProtocolManager { @Override public void run() { if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) { - channel0.send(new ClusterTopologyChangeMessage_V3(topologyMember.getUniqueEventID(), nodeID, topologyMember.getBackupGroupName(), topologyMember.getScaleDownGroupName(), connectorPair, last)); + channel0.send(new ClusterTopologyChangeMessage_V3( + topologyMember.getUniqueEventID(), nodeID, + topologyMember.getBackupGroupName(), + topologyMember.getScaleDownGroupName(), connectorPair, + last)); } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { - channel0.send(new ClusterTopologyChangeMessage_V2(topologyMember.getUniqueEventID(), nodeID, topologyMember.getBackupGroupName(), connectorPair, last)); + channel0.send(new ClusterTopologyChangeMessage_V2( + topologyMember.getUniqueEventID(), nodeID, + topologyMember.getBackupGroupName(), connectorPair, + last)); } else { - channel0.send(new ClusterTopologyChangeMessage(nodeID, connectorPair, last)); + channel0.send( + new ClusterTopologyChangeMessage(nodeID, connectorPair, + last)); } } }); @@ -295,7 +327,9 @@ public class CoreProtocolManager implements ProtocolManager { @Override public void run() { if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { - channel0.send(new ClusterTopologyChangeMessage_V2(uniqueEventID, nodeID)); + channel0.send( + new ClusterTopologyChangeMessage_V2(uniqueEventID, + nodeID)); } else { channel0.send(new ClusterTopologyChangeMessage(nodeID)); } @@ -309,7 +343,8 @@ public class CoreProtocolManager implements ProtocolManager { @Override public String toString() { - return "Remote Proxy on channel " + Integer.toHexString(System.identityHashCode(this)); + return "Remote Proxy on channel " + Integer + .toHexString(System.identityHashCode(this)); } }; @@ -319,7 +354,8 @@ public class CoreProtocolManager implements ProtocolManager { rc.addCloseListener(new CloseListener() { @Override public void connectionClosed() { - acceptorUsed.getClusterConnection().removeClusterTopologyListener(listener); + acceptorUsed.getClusterConnection() + .removeClusterTopologyListener(listener); } }); } else { @@ -329,20 +365,120 @@ public class CoreProtocolManager implements ProtocolManager { @Override public void run() { String nodeId = server.getNodeID().toString(); - Pair emptyConfig = new Pair<>(null, null); + Pair emptyConfig = new Pair<>( + null, null); if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { - channel0.send(new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(), nodeId, null, emptyConfig, true)); + channel0.send( + new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(), + nodeId, null, emptyConfig, true)); } else { - channel0.send(new ClusterTopologyChangeMessage(nodeId, emptyConfig, true)); + channel0.send( + new ClusterTopologyChangeMessage(nodeId, emptyConfig, true)); } } }); } + } else if (packet.getType() == PacketImpl.FEDERATION_DOWNSTREAM_CONNECT) { + //If we receive this packet then a remote broker is requesting us to create federated upstream connection + //back to it which simulates a downstream connection + final FederationDownstreamConnectMessage message = (FederationDownstreamConnectMessage) packet; + final FederationDownstreamConfiguration downstreamConfiguration = message.getStreamConfiguration(); + + //Create a new Upstream Federation configuration based on the received Downstream connection message + //from the remote broker + //The idea here is to set all the same configuration parameters that apply to the upstream connection + final FederationConfiguration config = new FederationConfiguration(); + config.setName(message.getName() + FederationDownstreamConnectMessage.UPSTREAM_SUFFIX); + config.setCredentials(message.getCredentials()); + + //Add the policy map configuration + for (FederationPolicy policy : message.getFederationPolicyMap().values()) { + config.addFederationPolicy(policy); + } + + //Add any transformer configurations + for (FederationTransformerConfiguration transformerConfiguration : message.getTransformerConfigurationMap().values()) { + config.addTransformerConfiguration(transformerConfiguration); + } + + //Create an upstream configuration with the same name but apply the upstream suffix so it is unique + final FederationUpstreamConfiguration upstreamConfiguration = new FederationUpstreamConfiguration() + .setName(downstreamConfiguration.getName() + FederationDownstreamConnectMessage.UPSTREAM_SUFFIX) + .addPolicyRefs(downstreamConfiguration.getPolicyRefs()); + + //Use the provided Transport Configuration information to create an upstream connection back to the broker that + //created the downstream connection + final TransportConfiguration upstreamConfig = downstreamConfiguration.getUpstreamConfiguration(); + + //Initialize the upstream transport with the config from the acceptor as this will apply + //relevant settings such as SSL, then override with settings from the downstream config + final Map params = new HashMap<>(acceptorUsed.getConfiguration()); + params.putAll(upstreamConfig.getParams()); + + //Add the new upstream configuration that was created so we can connect back to the downstream server + final TransportConfiguration upstreamConf = new TransportConfiguration( + upstreamConfig.getFactoryClassName(), params, upstreamConfig.getName() + FederationDownstreamConnectMessage.UPSTREAM_SUFFIX, + new HashMap<>()); + server.getConfiguration() + .addConnectorConfiguration(upstreamConf.getName() + FederationDownstreamConnectMessage.UPSTREAM_SUFFIX, upstreamConf); + + //Create a new upstream connection config based on the downstream configuration + FederationConnectionConfiguration downstreamConConf = downstreamConfiguration.getConnectionConfiguration(); + FederationConnectionConfiguration upstreamConConf = upstreamConfiguration.getConnectionConfiguration(); + List connectorNames = new ArrayList<>(); + connectorNames.add(upstreamConf.getName() + FederationDownstreamConnectMessage.UPSTREAM_SUFFIX); + + //Configure all of the upstream connection parameters from the downstream connection that are relevant + //Note that HA and discoveryGroupName are skipped because the downstream connection will manage that + //In this case we just want to create a connection back to the broker that sent the downstream packet. + //If this broker goes down then the original broker (if configured with HA) will re-establish a new + //connection to another broker which will then create another upstream, etc + upstreamConConf.setStaticConnectors(connectorNames); + upstreamConConf.setUsername(downstreamConConf.getUsername()); + upstreamConConf.setPassword(downstreamConConf.getPassword()); + upstreamConConf.setShareConnection(downstreamConConf.isShareConnection()); + upstreamConConf.setPriorityAdjustment(downstreamConConf.getPriorityAdjustment()); + upstreamConConf.setClientFailureCheckPeriod(downstreamConConf.getClientFailureCheckPeriod()); + upstreamConConf.setConnectionTTL(downstreamConConf.getConnectionTTL()); + upstreamConConf.setRetryInterval(downstreamConConf.getRetryInterval()); + upstreamConConf.setRetryIntervalMultiplier(downstreamConConf.getRetryIntervalMultiplier()); + upstreamConConf.setMaxRetryInterval(downstreamConConf.getMaxRetryInterval()); + upstreamConConf.setInitialConnectAttempts(downstreamConConf.getInitialConnectAttempts()); + upstreamConConf.setReconnectAttempts(downstreamConConf.getReconnectAttempts()); + upstreamConConf.setCallTimeout(downstreamConConf.getCallTimeout()); + upstreamConConf.setCallFailoverTimeout(downstreamConConf.getCallFailoverTimeout()); + config.addUpstreamConfiguration(upstreamConfiguration); + + //Register close and failure listeners, if the initial downstream connection goes down then we + //want to terminate the upstream connection + rc.addCloseListener(() -> { + server.getFederationManager().undeploy(config.getName()); + }); + + rc.addFailureListener(new FailureListener() { + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver) { + server.getFederationManager().undeploy(config.getName()); + } + + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver, + String scaleDownTargetNodeID) { + server.getFederationManager().undeploy(config.getName()); + } + }); + + try { + server.getFederationManager().deploy(config); + } catch (Exception e) { + logger.error("Error deploying federation: " + e.getMessage(), e); + } } } - private Pair getPair(TransportConfiguration conn, - boolean isBackup) { + private Pair getPair( + TransportConfiguration conn, + boolean isBackup) { if (isBackup) { return new Pair<>(null, conn); } 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 f4378204a6..d8541dd5b3 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 @@ -16,6 +16,7 @@ */ package org.apache.activemq.artemis.core.server; +import javax.management.MBeanServer; import java.util.Collection; import java.util.EnumSet; import java.util.List; @@ -25,14 +26,13 @@ import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.management.MBeanServer; - import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.DivertConfiguration; +import org.apache.activemq.artemis.core.config.FederationConfiguration; import org.apache.activemq.artemis.core.management.impl.ActiveMQServerControlImpl; import org.apache.activemq.artemis.core.paging.PagingManager; import org.apache.activemq.artemis.core.persistence.OperationContext; @@ -46,7 +46,7 @@ import org.apache.activemq.artemis.core.security.SecurityAuth; import org.apache.activemq.artemis.core.security.SecurityStore; import org.apache.activemq.artemis.core.server.cluster.ClusterManager; import org.apache.activemq.artemis.core.server.cluster.ha.HAPolicy; -import org.apache.activemq.artemis.core.config.FederationConfiguration; +import org.apache.activemq.artemis.core.server.federation.FederationManager; import org.apache.activemq.artemis.core.server.group.GroupingHandler; import org.apache.activemq.artemis.core.server.impl.Activation; import org.apache.activemq.artemis.core.server.impl.AddressInfo; @@ -64,7 +64,6 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerCriticalPlug import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin; -import org.apache.activemq.artemis.core.server.federation.FederationManager; 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; @@ -749,5 +748,4 @@ public interface ActiveMQServer extends ServiceComponent { void removeAddressInfo(SimpleString address, SecurityAuth auth, boolean force) throws Exception; String getInternalNamingPrefix(); - } 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 0d8a48ebc5..eee149eed9 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 @@ -1638,7 +1638,19 @@ public interface ActiveMQServerLogger extends BasicLogger { void federationAvoidStackOverflowPolicyRef(String upstreamName, String policyRef); @LogMessage(level = Logger.Level.WARN) - @Message(id = 222282, value = "File {0} at {1} is empty. Delete the empty file to stop this message.", + @Message(id = 222282, value = "Federation downstream {0} upstream transport configuration ref {1} could not be resolved in federation configuration", format = Message.Format.MESSAGE_FORMAT) + void federationCantFindUpstreamConnector(String downstreamName, String upstreamRef); + + @LogMessage(level = Logger.Level.INFO) + @Message(id = 222283, value = "Federation downstream {0} has been deployed", format = Message.Format.MESSAGE_FORMAT) + void federationDownstreamDeployed(String downstreamName); + + @LogMessage(level = Logger.Level.INFO) + @Message(id = 222284, value = "Federation downstream {0} has been undeployed", format = Message.Format.MESSAGE_FORMAT) + void federationDownstreamUnDeployed(String downstreamName); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 222285, value = "File {0} at {1} is empty. Delete the empty file to stop this message.", format = Message.Format.MESSAGE_FORMAT) void emptyAddressFile(String addressFile, String directory); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederatedQueueConsumer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederatedQueueConsumer.java index e7cced0f92..a1e08686df 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederatedQueueConsumer.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederatedQueueConsumer.java @@ -20,15 +20,16 @@ package org.apache.activemq.artemis.core.server.federation; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; + import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientSession; -import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.MessageHandler; import org.apache.activemq.artemis.api.core.client.SessionFailureListener; +import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.transformer.Transformer; @@ -47,7 +48,7 @@ public class FederatedQueueConsumer implements MessageHandler, SessionFailureLis private final int intialConnectDelayMax = 30; private final ClientSessionCallback clientSessionCallback; - private ClientSessionFactory clientSessionFactory; + private ClientSessionFactoryInternal clientSessionFactory; private ClientSession clientSession; private ClientConsumer clientConsumer; @@ -83,7 +84,7 @@ public class FederatedQueueConsumer implements MessageHandler, SessionFailureLis }, delay, TimeUnit.SECONDS); } - static int getNextDelay(int delay, int delayMultiplier, int delayMax) { + public static int getNextDelay(int delay, int delayMultiplier, int delayMax) { int nextDelay; if (delay == 0) { nextDelay = 1; @@ -100,7 +101,7 @@ public class FederatedQueueConsumer implements MessageHandler, SessionFailureLis try { if (clientConsumer == null) { synchronized (this) { - this.clientSessionFactory = upstream.getConnection().clientSessionFactory(); + this.clientSessionFactory = (ClientSessionFactoryInternal) upstream.getConnection().clientSessionFactory(); this.clientSession = clientSessionFactory.createSession(upstream.getUser(), upstream.getPassword(), false, true, true, clientSessionFactory.getServerLocator().isPreAcknowledge(), clientSessionFactory.getServerLocator().getAckBatchSize()); this.clientSession.addFailureListener(this); this.clientSession.addMetaData(FEDERATION_NAME, federation.getName().toString()); @@ -149,13 +150,14 @@ public class FederatedQueueConsumer implements MessageHandler, SessionFailureLis if (clientSession != null) { clientSession.close(); } - if (clientSessionFactory != null) { - clientSessionFactory.close(); - } clientConsumer = null; clientSession = null; - clientSessionFactory = null; + if (clientSessionFactory != null && (!upstream.getConnection().isSharedConnection() || + clientSessionFactory.numSessions() == 0)) { + clientSessionFactory.close(); + clientSessionFactory = null; + } } @Override diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/Federation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/Federation.java index c34123bed8..5fe14c17aa 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/Federation.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/Federation.java @@ -20,9 +20,11 @@ package org.apache.activemq.artemis.core.server.federation; import java.util.HashMap; import java.util.Map; import java.util.Objects; + import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.config.FederationConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationPolicy; import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; import org.apache.activemq.artemis.core.server.ActiveMQServer; @@ -34,9 +36,12 @@ public class Federation { private final SimpleString name; private final Map upstreams = new HashMap<>(); + private final Map downstreams = new HashMap<>(); private final FederationConfiguration config; private FederationManager.State state; + private final Map connections = new HashMap<>(); + enum State { STOPPED, STOPPING, @@ -64,6 +69,9 @@ public class Federation { for (FederationUpstream connection : upstreams.values()) { connection.start(); } + for (FederationDownstream connection : downstreams.values()) { + connection.start(); + } state = FederationManager.State.STARTED; } @@ -74,7 +82,11 @@ public class Federation { for (FederationUpstream connection : upstreams.values()) { connection.stop(); } + for (FederationDownstream connection : downstreams.values()) { + connection.stop(); + } upstreams.clear(); + downstreams.clear(); state = FederationManager.State.STOPPED; } @@ -82,6 +94,9 @@ public class Federation { for (FederationUpstreamConfiguration upstreamConfiguration : config.getUpstreamConfigurations()) { deploy(upstreamConfiguration, config.getFederationPolicyMap()); } + for (FederationDownstreamConfiguration downstreamConfiguration : config.getDownstreamConfigurations()) { + deploy(downstreamConfiguration, config.getFederationPolicyMap()); + } if (state != FederationManager.State.STARTED) { state = FederationManager.State.DEPLOYED; } @@ -96,11 +111,14 @@ public class Federation { if (federationConnection != null) { federationConnection.stop(); } + FederationDownstream federationConnectionDownstream = downstreams.remove(name); + if (federationConnectionDownstream != null) { + federationConnectionDownstream.undeploy(); + federationConnectionDownstream.stop(); + } return true; } - - public synchronized boolean deploy(FederationUpstreamConfiguration upstreamConfiguration, Map federationPolicyMap) throws ActiveMQException { String name = upstreamConfiguration.getName(); FederationUpstream upstream = upstreams.get(name); @@ -127,11 +145,52 @@ public class Federation { return upstream; } + public synchronized boolean deploy(FederationDownstreamConfiguration downstreamConfiguration, Map federationPolicyMap) throws ActiveMQException { + String name = downstreamConfiguration.getName(); + FederationDownstream downstream = downstreams.get(name); + + //If connection has changed we will need to do a full undeploy and redeploy. + if (downstream == null) { + undeploy(name); + downstream = deploy(name, downstreamConfiguration); + } else if (!downstream.getConnection().getConfig().equals(downstreamConfiguration.getConnectionConfiguration())) { + undeploy(name); + downstream = deploy(name, downstreamConfiguration); + } + + downstream.deploy(config); + return true; + } + + private synchronized FederationDownstream deploy(String name, FederationDownstreamConfiguration downstreamConfiguration) { + //If we have a matching upstream connection already configured then use it for the initiating downstream connection + FederationConnection connection = null; + if (downstreamConfiguration.getConnectionConfiguration().isShareConnection()) { + for (FederationUpstream upstream : upstreams.values()) { + if (upstream.getConfig().getConnectionConfiguration() + .equals(downstreamConfiguration.getConnectionConfiguration())) { + connection = upstream.getConnection(); + connection.setSharedConnection(true); + break; + } + } + } + + FederationDownstream downstream = new FederationDownstream(server, this, name, downstreamConfiguration, connection); + downstreams.put(name, downstream); + if (state == FederationManager.State.STARTED) { + downstream.start(); + } + return downstream; + } + public FederationUpstream get(String name) { return upstreams.get(name); } - + public FederationDownstream getDownstream(String name) { + return downstreams.get(name); + } public void register(FederatedAbstract federatedAbstract) { server.registerBrokerPlugin(federatedAbstract); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java index bd63106cec..2b00b3cfdf 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java @@ -34,6 +34,7 @@ public class FederationConnection { private final long circuitBreakerTimeout; private volatile ClientSessionFactory clientSessionFactory; private volatile boolean started; + private volatile boolean sharedConnection; public FederationConnection(Configuration configuration, String name, FederationConnectionConfiguration config) { this.config = config; @@ -67,6 +68,17 @@ public class FederationConnection { serverLocator = ActiveMQClient.createServerLocatorWithoutHA(tcConfigs); } } + + serverLocator.setConnectionTTL(config.getConnectionTTL()); + serverLocator.setClientFailureCheckPeriod(config.getClientFailureCheckPeriod()); + serverLocator.setReconnectAttempts(config.getReconnectAttempts()); + serverLocator.setInitialConnectAttempts(config.getInitialConnectAttempts()); + serverLocator.setRetryInterval(config.getRetryInterval()); + serverLocator.setRetryIntervalMultiplier(config.getRetryIntervalMultiplier()); + serverLocator.setMaxRetryInterval(config.getMaxRetryInterval()); + serverLocator.setCallTimeout(config.getCallTimeout()); + serverLocator.setCallFailoverTimeout(config.getCallFailoverTimeout()); + } public synchronized void start() { @@ -87,6 +99,14 @@ public class FederationConnection { return started; } + public boolean isSharedConnection() { + return sharedConnection; + } + + public void setSharedConnection(boolean sharedConnection) { + this.sharedConnection = sharedConnection; + } + public final ClientSessionFactory clientSessionFactory() throws Exception { ClientSessionFactory clientSessionFactory = this.clientSessionFactory; if (started) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationDownstream.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationDownstream.java new file mode 100644 index 0000000000..2e77c861d7 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationDownstream.java @@ -0,0 +1,212 @@ +/** + * 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.federation; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.client.SessionFailureListener; +import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal; +import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal; +import org.apache.activemq.artemis.core.config.FederationConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; +import org.apache.activemq.artemis.core.protocol.core.Channel; +import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection; +import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_ID; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage; +import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationStreamConnectMessage; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; +import org.jboss.logging.Logger; + +public class FederationDownstream extends FederationStream implements SessionFailureListener { + + private static final Logger logger = Logger.getLogger(FederationDownstream.class); + + private FederationDownstreamConfiguration config; + private ClientSessionFactoryInternal clientSessionFactory; + private ClientSessionInternal clientSession; + private Channel channel; + private AtomicBoolean initialized = new AtomicBoolean(); + private final ScheduledExecutorService scheduledExecutorService; + private final int intialConnectDelayMultiplier = 2; + private final int intialConnectDelayMax = 30; + + public static final String FEDERATION_DOWNSTREAM_NAME = "federation-downstream-name"; + private AtomicBoolean started = new AtomicBoolean(); + private FederationConfiguration federationConfiguration; + + public FederationDownstream(ActiveMQServer server, Federation federation, String name, FederationDownstreamConfiguration config, + final FederationConnection connection) { + super(server, federation, name, config, connection); + this.config = config; + this.scheduledExecutorService = server.getScheduledPool(); + } + + @Override + public synchronized void start() { + super.start(); + try { + deploy(federationConfiguration); + } catch (ActiveMQException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + @Override + public synchronized void stop() { + super.stop(); + } + + public void deploy(FederationConfiguration federationConfiguration) + throws ActiveMQException { + + this.federationConfiguration = federationConfiguration; + + if (connection.isStarted() && started.compareAndSet(false, true)) { + final FederationStreamConnectMessage message = new FederationDownstreamConnectMessage(); + message.setName(federationConfiguration.getName()); + message.setCredentials(federationConfiguration.getCredentials()); + message.setStreamConfiguration(config); + message.setFederationPolicyMap(federationConfiguration.getFederationPolicyMap()); + message.setTransformerConfigurationMap(federationConfiguration.getTransformerConfigurationMap()); + + if (config.getUpstreamConfigurationRef() != null + && config.getUpstreamConfiguration() == null) { + TransportConfiguration[] configs = server.getConfiguration() + .getTransportConfigurations(config.getUpstreamConfigurationRef()); + if (configs != null && configs.length > 0) { + config.setUpstreamConfiguration(configs[0]); + } else { + ActiveMQServerLogger.LOGGER.federationCantFindUpstreamConnector(config.getName(), config + .getUpstreamConfigurationRef()); + throw new ActiveMQException("Could not locate upstream transport configuration for federation downstream: " + config.getName() + + "; upstream ref: " + config.getUpstreamConfigurationRef()); + } + } + + try { + scheduleConnect(0, message); + } catch (Exception e) { + throw new ActiveMQException(e.getMessage(), e, ActiveMQExceptionType.GENERIC_EXCEPTION); + } + + ActiveMQServerLogger.LOGGER.federationDownstreamDeployed(config.getName()); + } + } + + public void undeploy() { + try { + if (started.compareAndSet(true, false)) { + disconnect(); + ActiveMQServerLogger.LOGGER.federationDownstreamUnDeployed(config.getName()); + } + } catch (ActiveMQException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + private void scheduleConnect(final int delay, final FederationStreamConnectMessage message) { + scheduledExecutorService.schedule(() -> { + try { + connect(); + if (initialized.compareAndSet(false, true)) { + channel.send(message); + } + } catch (Exception e) { + scheduleConnect(FederatedQueueConsumer.getNextDelay(delay, intialConnectDelayMultiplier, intialConnectDelayMax), + message); + } + }, delay, TimeUnit.SECONDS); + } + + private void connect() throws Exception { + try { + if (clientSession == null) { + synchronized (this) { + this.clientSessionFactory = (ClientSessionFactoryInternal) getConnection().clientSessionFactory(); + this.clientSession = (ClientSessionInternal) clientSessionFactory.createSession(getUser(), getPassword(), false, true, true, clientSessionFactory.getServerLocator().isPreAcknowledge(), clientSessionFactory.getServerLocator().getAckBatchSize()); + this.clientSession.addFailureListener(this); + this.clientSession.addMetaData(FederatedQueueConsumer.FEDERATION_NAME, federation.getName().toString()); + this.clientSession.addMetaData(FEDERATION_DOWNSTREAM_NAME, config.getName().toString()); + this.clientSession.start(); + + CoreRemotingConnection connection = (CoreRemotingConnection) clientSessionFactory.getConnection(); + channel = connection.getChannel(CHANNEL_ID.FEDERATION.id, -1); + } + } + } catch (Exception e) { + try { + if (clientSessionFactory != null) { + clientSessionFactory.cleanup(); + } + disconnect(); + } catch (ActiveMQException ignored) { + } + throw e; + } + } + + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver) { + connectionFailed(exception, failedOver, null); + } + + @Override + public void connectionFailed(ActiveMQException exception, boolean failedOver, String scaleDownTargetNodeID) { + try { + started.set(false); + initialized.set(false); + channel.close(); + clientSessionFactory.cleanup(); + clientSessionFactory.close(); + channel = null; + clientSession = null; + clientSessionFactory = null; + } catch (Throwable dontCare) { + } + start(); + } + + private void disconnect() throws ActiveMQException { + initialized.set(false); + + if (channel != null) { + channel.close(); + } + if (clientSession != null) { + clientSession.close(); + } + channel = null; + clientSession = null; + + if (clientSessionFactory != null && (!getConnection().isSharedConnection() || clientSessionFactory.numSessions() == 0)) { + clientSessionFactory.close(); + clientSessionFactory = null; + } + } + + @Override + public void beforeReconnect(ActiveMQException exception) { + } + +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationManager.java index 6aba68d1e1..9d460729fe 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationManager.java @@ -122,8 +122,6 @@ public class FederationManager implements ActiveMQComponent { return federations.get(name); } - - public void register(FederatedAbstract federatedAbstract) { server.registerBrokerPlugin(federatedAbstract); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationStream.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationStream.java new file mode 100644 index 0000000000..e983ba86dc --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationStream.java @@ -0,0 +1,109 @@ +/** + * 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.federation; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.core.config.federation.FederationStreamConfiguration; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.federation.address.FederatedAddress; +import org.apache.activemq.artemis.core.server.federation.queue.FederatedQueue; +import org.jboss.logging.Logger; + +public abstract class FederationStream { + + + private static final Logger logger = Logger.getLogger(FederationStream.class); + protected final ActiveMQServer server; + protected final Federation federation; + protected final SimpleString name; + protected final FederationConnection connection; + private FederationStreamConfiguration config; + protected Map federatedQueueMap = new HashMap<>(); + protected Map federatedAddressMap = new HashMap<>(); + + + public FederationStream(ActiveMQServer server, Federation federation, String name, FederationStreamConfiguration config) { + this(server, federation, name, config, null); + } + + public FederationStream(final ActiveMQServer server, final Federation federation, final String name, final FederationStreamConfiguration config, + final FederationConnection connection) { + this.server = server; + this.federation = federation; + Objects.requireNonNull(config.getName()); + this.name = SimpleString.toSimpleString(config.getName()); + this.config = config; + this.connection = connection != null ? connection : new FederationConnection(server.getConfiguration(), name, config.getConnectionConfiguration()); + } + + public synchronized void start() { + if (connection != null) { + connection.start(); + } + } + + public synchronized void stop() { + if (connection != null) { + connection.stop(); + } + } + + public Federation getFederation() { + return federation; + } + + public FederationStreamConfiguration getConfig() { + return config; + } + + public SimpleString getName() { + return name; + } + + public FederationConnection getConnection() { + return connection; + } + + + public String getUser() { + String user = config.getConnectionConfiguration().getUsername(); + if (user == null || user.isEmpty()) { + return federation.getFederationUser(); + } else { + return user; + } + } + + public String getPassword() { + String password = config.getConnectionConfiguration().getPassword(); + if (password == null || password.isEmpty()) { + return federation.getFederationPassword(); + } else { + return password; + } + } + + public int getPriorityAdjustment() { + return config.getConnectionConfiguration().getPriorityAdjustment(); + } + +} diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationUpstream.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationUpstream.java index ee09cfb57f..38663e457d 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationUpstream.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationUpstream.java @@ -17,12 +17,10 @@ package org.apache.activemq.artemis.core.server.federation; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; + import org.apache.activemq.artemis.api.core.ActiveMQException; -import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationPolicy; import org.apache.activemq.artemis.core.config.federation.FederationPolicySet; @@ -33,28 +31,18 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.federation.address.FederatedAddress; import org.apache.activemq.artemis.core.server.federation.queue.FederatedQueue; -public class FederationUpstream { - - private final ActiveMQServer server; - private final Federation federation; - private final SimpleString name; - private FederationConnection connection; +public class FederationUpstream extends FederationStream { private FederationUpstreamConfiguration config; - private Map federatedQueueMap = new HashMap<>(); - private Map federatedAddressMap = new HashMap<>(); public FederationUpstream(ActiveMQServer server, Federation federation, String name, FederationUpstreamConfiguration config) { - this.server = server; - this.federation = federation; - Objects.requireNonNull(config.getName()); - this.name = SimpleString.toSimpleString(config.getName()); + super(server, federation, name, config); this.config = config; - this.connection = new FederationConnection(server.getConfiguration(), name, config.getConnectionConfiguration()); - } + @Override public synchronized void start() { - connection.start(); + super.start(); + for (FederatedQueue federatedQueue : federatedQueueMap.values()) { federatedQueue.start(); } @@ -63,6 +51,7 @@ public class FederationUpstream { } } + @Override public synchronized void stop() { for (FederatedAddress federatedAddress : federatedAddressMap.values()) { federatedAddress.stop(); @@ -74,7 +63,7 @@ public class FederationUpstream { } federatedQueueMap.clear(); - connection.stop(); + super.stop(); } public void deploy(Set policyRefsToDeploy, Map policyMap) throws ActiveMQException { @@ -156,41 +145,8 @@ public class FederationUpstream { } } + @Override public FederationUpstreamConfiguration getConfig() { return config; } - - private Exception circuitBreakerException; - private long lastCreateClientSessionFactoryExceptionTimestamp; - - public SimpleString getName() { - return name; - } - - public FederationConnection getConnection() { - return connection; - } - - - public String getUser() { - String user = config.getConnectionConfiguration().getUsername(); - if (user == null || user.isEmpty()) { - return federation.getFederationUser(); - } else { - return user; - } - } - - public String getPassword() { - String password = config.getConnectionConfiguration().getPassword(); - if (password == null || password.isEmpty()) { - return federation.getFederationPassword(); - } else { - return password; - } - } - - public int getPriorityAdjustment() { - return config.getConnectionConfiguration().getPriorityAdjustment(); - } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/address/FederatedAddress.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/address/FederatedAddress.java index 788f7fa7cb..329cbdc0af 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/address/FederatedAddress.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/address/FederatedAddress.java @@ -24,6 +24,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; + import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.QueueAttributes; @@ -31,11 +32,11 @@ import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.core.config.WildcardConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration; import org.apache.activemq.artemis.core.postoffice.QueueBinding; import org.apache.activemq.artemis.core.security.SecurityAuth; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.Queue; -import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration; import org.apache.activemq.artemis.core.server.federation.FederatedAbstract; import org.apache.activemq.artemis.core.server.federation.FederatedConsumerKey; import org.apache.activemq.artemis.core.server.federation.Federation; @@ -57,6 +58,7 @@ import org.apache.activemq.artemis.utils.ByteUtil; */ public class FederatedAddress extends FederatedAbstract implements ActiveMQServerQueuePlugin, Serializable { + public static final String FEDERATED_QUEUE_PREFIX = "federated"; public static final SimpleString HDR_HOPS = new SimpleString("_AMQ_Hops"); private final SimpleString queueNameFormat; private final SimpleString filterString; @@ -74,7 +76,7 @@ public class FederatedAddress extends FederatedAbstract implements ActiveMQServe } else { this.filterString = HDR_HOPS.concat(" IS NULL OR ").concat(HDR_HOPS).concat("<").concat(Integer.toString(config.getMaxHops())); } - this.queueNameFormat = SimpleString.toSimpleString("federated.${federation}.${upstream}.${address}.${routeType}"); + this.queueNameFormat = SimpleString.toSimpleString(FEDERATED_QUEUE_PREFIX + ".${federation}.${upstream}.${address}.${routeType}"); if (config.getIncludes().isEmpty()) { includes = Collections.emptySet(); } else { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServiceRegistryImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServiceRegistryImpl.java index 49eeff1bfe..7161a68ba2 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServiceRegistryImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServiceRegistryImpl.java @@ -69,6 +69,7 @@ public class ServiceRegistryImpl implements ServiceRegistry { this.connectorServices = new ConcurrentHashMap<>(); this.divertTransformers = new ConcurrentHashMap<>(); this.bridgeTransformers = new ConcurrentHashMap<>(); + this.federationTransformers = new ConcurrentHashMap<>(); this.acceptorFactories = new ConcurrentHashMap<>(); } diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index 724485429b..e3c34cc464 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -1589,13 +1589,11 @@ - - - + @@ -1607,9 +1605,31 @@ - + + + + + + + + Name of the transport connector reference to use for the new upstream connection + back to this broker. + + + + + + + + + + + + + + @@ -1628,6 +1648,88 @@ + + + + if there is a downstream and upstream connection configured for the same broker then + the same connection will be shared as long as both stream configs set this flag to true + + + + + + + + how long to keep a connection alive in the absence of any data arriving from the client + + + + + + + + How long to wait for a reply + + + + + + + + period (in ms) between successive retries + + + + + + + + multiplier to apply to the retry-interval + + + + + + + + Maximum value for retry-interval + + + + + + + + How many attempts should be made to connect initially + + + + + + + + How many attempts should be made to reconnect after failure + + + + + + + + The period (in milliseconds) used to check if the federation connection has failed to receive pings from + another server + + + + + + + + How long to wait for a reply if in the middle of a fail-over. -1 means wait forever. + + + + diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml index 5c40d7ea02..1fd0f7c352 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml @@ -278,6 +278,36 @@ + + + + + connector1 + + + + + + true + + + + connector1 + + + + + + + + + + + org.foo.FederationTransformer4 + + + + diff --git a/docs/user-manual/en/federation-address.md b/docs/user-manual/en/federation-address.md index b7bb2d8cd3..8a77cb9f1f 100644 --- a/docs/user-manual/en/federation-address.md +++ b/docs/user-manual/en/federation-address.md @@ -6,12 +6,11 @@ Address federation is like full multicast over the connected brokers, in that ev on `Broker-A` will be delivered to every queue on that broker, but like wise will be delivered to `Broker-B` and all attached queues there. - -Address federation dynamically links to other addresses in upstream brokers. It automatically creates a queue on the remote address for itself, +Address federation dynamically links to other addresses in upstream or downstream brokers. It automatically creates a queue on the remote address for itself, to which then it consumes, copying to the local address, as though they were published directly to it. The upstream brokers do not need to be reconfigured or the address, simply permissions to the address need to be -given to the address for the downstream broker. +given to the address for the downstream broker. Similarly the same applies for downstream configurations. ![Address Federation](images/federation-address.png) @@ -182,14 +181,125 @@ Finally look at `upstream`, this is what defines the upstream broker connection information about what discovery-groups are and how to configure them, please see [Discovery Groups](clusters.md). - - `ha`. This optional parameter determines whether or not this bridge should support high availability. True means it will connect to any available server in a cluster and support failover. The default value is `false`. - `circuit-breaker-timeout` in milliseconds, When a connection issue occurs, as the single connection is shared by many federated queue and address consumers, -to avoid each one trying to reconnect and possibly causing a thrundering heard issue, +to avoid each one trying to reconnect and possibly causing a thundering heard issue, the first one will try, if unsuccessful the circuit breaker will open, returning the same exception to all, this is the timeout until the circuit can be closed and connection retried. +- `share-connection`. If there is a downstream and upstream connection configured for the same broker then + the same connection will be shared as long as both stream configs set this flag to true. + Default is false. + +- `check-period`. The period (in milliseconds) used to check if the + federation connection has failed to receive pings from another server. + Default is 30000. + +- `connection-ttl`. This is how long a federation connection should stay + alive if it stops receiving messages from the remote broker. Default is 60000. + +- `call-timeout`. When a packet is sent via a federation connection and + is a blocking call, i.e. for acknowledgements, this is how long it + will wait (in milliseconds) for the reply before throwing an + exception. Default is 30000. + +- `call-failover-timeout`. Similar to `call-timeout` but used when a + call is made during a failover attempt. Default is -1 (no timeout). + +- `retry-interval`. This optional parameter determines the period in + milliseconds between subsequent reconnection attempts, if the connection to + the target server has failed. The default value is `500` milliseconds. + +- `retry-interval-multiplier`. This is a multiplier used to increase + the `retry-interval` after each reconnect attempt, default is 1. + +- `max-retry-interval`. The maximum delay (in milliseconds) for + retries. Default is 2000. + +- `initial-connect-attempts`. The number of times the system will try + to connect to the remote broker in the federation. If the max-retry is + achieved this broker will be considered permanently down and the + system will not route messages to this broker. Default is -1 (infinite + retries). + +- `reconnect-attempts`. The number of times the system will try to + reconnect to the remote broker in the federation. If the max-retry is achieved + this broker will be considered permanently down and the system will + stop routing messages to this broker. Default is -1 (infinite + retries). + +## Configuring Downstream Federation + +Similarly to `upstream` configuration, a downstream configuration can be configured. This works by sending a command +to the `downstream` broker to have it create an `upstream` connection back to the downstream broker. The benefit of +this is being able to configure everything for federation on one broker in some cases to make it easier, such +as a hub and spoke topology + +All of the same configuration options apply to to `downstream` as does `upstream` with the exception of one +extra configuration flag that needs to be set: + + The `transport-connector-ref` is an element pointing to a + `connector` elements defined elsewhere. This ref is used to tell the downstream broker + what connector to use to create a new upstream connection back to the downstream broker. + + A *connector* encapsulates knowledge of what transport to use (TCP, SSL, HTTP etc) as well as + the server connection parameters (host, port etc). For more information about what connectors are and + how to configure them, please see [Configuring the + Transport](configuring-transports.md). + +Sample Downstream Address Federation setup: + +```xml + + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + + + + tcp://localhost:61616 + + + + + + + + + eu-east-connector1 + + netty-connector + + + + + eu-west-connector1 + + netty-connector + + + + + + + + + + + + + org.foo.NewsTransformer + + + + + + +``` \ No newline at end of file diff --git a/docs/user-manual/en/federation-queue.md b/docs/user-manual/en/federation-queue.md index 171fc76aa3..8f2b0f968c 100644 --- a/docs/user-manual/en/federation-queue.md +++ b/docs/user-manual/en/federation-queue.md @@ -162,7 +162,6 @@ Finally look at `upstream`, this is what defines the upstream broker connection information about what discovery-groups are and how to configure them, please see [Discovery Groups](clusters.md). - - `ha`. This optional parameter determines whether or not this bridge should support high availability. True means it will connect to any available server in a cluster and support failover. The default value is `false`. @@ -173,3 +172,116 @@ to avoid each one trying to reconnect and possibly causing a thrundering heard i the first one will try, if unsuccessful the circuit breaker will open, returning the same exception to all, this is the timeout until the circuit can be closed and connection retried. +- `share-connection`. If there is a downstream and upstream connection configured for the same broker then + the same connection will be shared as long as both stream configs set this flag to true. + Default is false. + +- `check-period`. The period (in milliseconds) used to check if the + federation connection has failed to receive pings from another server. + Default is 30000. + +- `connection-ttl`. This is how long a federation connection should stay + alive if it stops receiving messages from the remote broker. Default is 60000. + +- `call-timeout`. When a packet is sent via a federation connection and + is a blocking call, i.e. for acknowledgements, this is how long it + will wait (in milliseconds) for the reply before throwing an + exception. Default is 30000. + +- `call-failover-timeout`. Similar to `call-timeout` but used when a + call is made during a failover attempt. Default is -1 (no timeout). + +- `retry-interval`. This optional parameter determines the period in + milliseconds between subsequent reconnection attempts, if the connection to + the target server has failed. The default value is `500` milliseconds. + +- `retry-interval-multiplier`. This is a multiplier used to increase + the `retry-interval` after each reconnect attempt, default is 1. + +- `max-retry-interval`. The maximum delay (in milliseconds) for + retries. Default is 2000. + +- `initial-connect-attempts`. The number of times the system will try + to connect to the remote broker in the federation. If the max-retry is + achieved this broker will be considered permanently down and the + system will not route messages to this broker. Default is -1 (infinite + retries). + +- `reconnect-attempts`. The number of times the system will try to + reconnect to the remote broker in the federation. If the max-retry is achieved + this broker will be considered permanently down and the system will + stop routing messages to this broker. Default is -1 (infinite + retries). + +## Configuring Downstream Federation + +Similarly to `upstream` configuration, a downstream configuration can be configured. This works by sending a command +to the `downstream` broker to have it create an `upstream` connection back to the downstream broker. The benefit of +this is being able to configure everything for federation on one broker in some cases to make it easier, such +as a hub and spoke topology. + +All of the same configuration options apply to to `downstream` as does `upstream` with the exception of one +extra configuration flag that needs to be set: + + The `transport-connector-ref` is an element pointing to a + `connector` elements defined elsewhere. This ref is used to tell the downstream broker + what connector to use to create a new upstream connection back to the downstream broker. + + A *connector* encapsulates knowledge of what transport to use (TCP, SSL, HTTP etc) as well as + the server connection parameters (host, port etc). For more information about what connectors are and + how to configure them, please see [Configuring the + Transport](configuring-transports.md). + + + Sample Downstream Address Federation setup: + +```xml + + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + + + + tcp://localhost:61616 + + + + + + + + + eu-east-connector1 + + netty-connector + + + + + eu-west-connector1 + + netty-connector + + + + + + + + + + + + + org.foo.NewsTransformer + + + + + + + ``` \ No newline at end of file diff --git a/examples/features/federation/federated-address-downstream-upstream/eu-west-east-us-central.png b/examples/features/federation/federated-address-downstream-upstream/eu-west-east-us-central.png new file mode 100644 index 0000000000..0ba844733e Binary files /dev/null and b/examples/features/federation/federated-address-downstream-upstream/eu-west-east-us-central.png differ diff --git a/examples/features/federation/federated-address-downstream-upstream/pom.xml b/examples/features/federation/federated-address-downstream-upstream/pom.xml new file mode 100644 index 0000000000..f60f43fde0 --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/pom.xml @@ -0,0 +1,215 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.federation + broker-federation + 2.11.0-SNAPSHOT + + + federated-address-downstream-upstream + jar + ActiveMQ Artemis Downstream/Upstream Federated Address Example + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq.examples.federation + federated-address + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + create2 + + create + + + ${noServer} + ${basedir}/target/server2 + ${basedir}/target/classes/activemq/server2 + + -Djava.net.preferIPv4Stack=true + + + + start0 + + cli + + + ${noServer} + true + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + eu-west-1 + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + eu-east-1 + + + + start2 + + cli + + + ${noServer} + true + ${basedir}/target/server2 + tcp://localhost:61618 + + run + + us-central-1 + + + + runClient + + runClient + + + org.apache.activemq.artemis.jms.example.FederatedAddressDownstreamUpstreamExample + + + + 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.federation + federated-address-downstream-upstream + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + + + release + + + + com.vladsch.flexmark + markdown-page-generator-plugin + + + + + + diff --git a/examples/features/federation/federated-address-downstream-upstream/readme.md b/examples/features/federation/federated-address-downstream-upstream/readme.md new file mode 100644 index 0000000000..d89db44af8 --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/readme.md @@ -0,0 +1,34 @@ +# Federated Address Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the broker manually. + +This example demonstrates a core multicast address deployed on three different brokers. The three brokers are configured to form a federated address mesh. + +In the example we name the brokers, eu-west, eu-east and us-central to give an idea of the use case. + +![EU West, EU East and US Central Diagram](eu-west-east-us-central.png) + +The following is then carried out: + +1. create a consumer on the queue on each node, and we create a producer on only one of the nodes. + +2. send some messages via the producer on EU West, and we verify that **all** the consumers receive the sent messages, in essence multicasting the messages within and accross brokers. + +3. Next then verify the same on US Central. + +4. Next then verify the same on EU East. + + + +In other words, we are showing how with Federated Address, ActiveMQ Artemis **replicates** sent messages to all addresses and subsequently delivered to all all consumers, regardless if the consumer is local or is on a distant broker. Decoupling the location where producers and consumers need to be. + +The config that defines the federation you can see in the broker.xml for each broker is within the following tags. You will note upstreams are different in each as well as the federation name, which has to be globally unique. + +``` + + ... + +``` + + +For more information on ActiveMQ Artemis Federation please see the federation section of the user manual. \ No newline at end of file diff --git a/examples/features/federation/federated-address-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamUpstreamExample.java b/examples/features/federation/federated-address-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamUpstreamExample.java new file mode 100644 index 0000000000..08e958e665 --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamUpstreamExample.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * A simple example that demonstrates multicast address replication between remote servers, + * using Address Federation downstream and upstream feature combined. + */ +public class FederatedAddressDownstreamUpstreamExample { + + public static void main(final String[] args) throws Exception { + //Re-use the same Federated address test for upstream + FederatedAddressExample.main(args); + } +} diff --git a/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server0/broker.xml b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..e3eb6df0bd --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,118 @@ + + + + + + eu-west-1-master + + ./data/bindings + + ./data/journal + + ./data/largemessages + + ./data/paging + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61616 + + + + + + + + 1000 + true + + eu-east-1-connector + + + + + 1000 + true + + us-central-1-connector + + + + + 1000 + true + + eu-east-1-connector + + + netty-connector + + + 1000 + true + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server1/broker.xml b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..626cd9582a --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-east-1-master + + target/server1/data/messaging/bindings + + target/server1/data/messaging/journal + + target/server1/data/messaging/largemessages + + target/server1/data/messaging/paging + + + + tcp://localhost:61617 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61617 + + + + + + + + 1000 + true + + us-central-1-connector + + + + + 1000 + true + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server2/broker.xml b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server2/broker.xml new file mode 100644 index 0000000000..8cb6973fed --- /dev/null +++ b/examples/features/federation/federated-address-downstream-upstream/src/main/resources/activemq/server2/broker.xml @@ -0,0 +1,70 @@ + + + + + + us-central-1-master + + target/server2/data/messaging/bindings + + target/server2/data/messaging/journal + + target/server2/data/messaging/largemessages + + target/server2/data/messaging/paging + + + + tcp://localhost:61618 + + + + + tcp://localhost:61618 + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-address-downstream/eu-west-east-us-central.png b/examples/features/federation/federated-address-downstream/eu-west-east-us-central.png new file mode 100644 index 0000000000..0ba844733e Binary files /dev/null and b/examples/features/federation/federated-address-downstream/eu-west-east-us-central.png differ diff --git a/examples/features/federation/federated-address-downstream/pom.xml b/examples/features/federation/federated-address-downstream/pom.xml new file mode 100644 index 0000000000..d642bef5c1 --- /dev/null +++ b/examples/features/federation/federated-address-downstream/pom.xml @@ -0,0 +1,215 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.federation + broker-federation + 2.11.0-SNAPSHOT + + + federated-address-downstream + jar + ActiveMQ Artemis Downstream Federated Address Example + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq.examples.federation + federated-address + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + create2 + + create + + + ${noServer} + ${basedir}/target/server2 + ${basedir}/target/classes/activemq/server2 + + -Djava.net.preferIPv4Stack=true + + + + start0 + + cli + + + ${noServer} + true + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + eu-west-1 + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + eu-east-1 + + + + start2 + + cli + + + ${noServer} + true + ${basedir}/target/server2 + tcp://localhost:61618 + + run + + us-central-1 + + + + runClient + + runClient + + + org.apache.activemq.artemis.jms.example.FederatedAddressDownstreamExample + + + + 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.federation + federated-address-downstream + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + + + release + + + + com.vladsch.flexmark + markdown-page-generator-plugin + + + + + + diff --git a/examples/features/federation/federated-address-downstream/readme.md b/examples/features/federation/federated-address-downstream/readme.md new file mode 100644 index 0000000000..d89db44af8 --- /dev/null +++ b/examples/features/federation/federated-address-downstream/readme.md @@ -0,0 +1,34 @@ +# Federated Address Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the broker manually. + +This example demonstrates a core multicast address deployed on three different brokers. The three brokers are configured to form a federated address mesh. + +In the example we name the brokers, eu-west, eu-east and us-central to give an idea of the use case. + +![EU West, EU East and US Central Diagram](eu-west-east-us-central.png) + +The following is then carried out: + +1. create a consumer on the queue on each node, and we create a producer on only one of the nodes. + +2. send some messages via the producer on EU West, and we verify that **all** the consumers receive the sent messages, in essence multicasting the messages within and accross brokers. + +3. Next then verify the same on US Central. + +4. Next then verify the same on EU East. + + + +In other words, we are showing how with Federated Address, ActiveMQ Artemis **replicates** sent messages to all addresses and subsequently delivered to all all consumers, regardless if the consumer is local or is on a distant broker. Decoupling the location where producers and consumers need to be. + +The config that defines the federation you can see in the broker.xml for each broker is within the following tags. You will note upstreams are different in each as well as the federation name, which has to be globally unique. + +``` + + ... + +``` + + +For more information on ActiveMQ Artemis Federation please see the federation section of the user manual. \ No newline at end of file diff --git a/examples/features/federation/federated-address-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamExample.java b/examples/features/federation/federated-address-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamExample.java new file mode 100644 index 0000000000..505b862398 --- /dev/null +++ b/examples/features/federation/federated-address-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedAddressDownstreamExample.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * A simple example that demonstrates multicast address replication between remote servers, + * using Address Federation downstream feature. + */ +public class FederatedAddressDownstreamExample { + + public static void main(final String[] args) throws Exception { + //Re-use the same Federated address test for upstream + FederatedAddressExample.main(args); + } +} diff --git a/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server0/broker.xml b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..73c774c398 --- /dev/null +++ b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-west-1-master + + ./data/bindings + + ./data/journal + + ./data/largemessages + + ./data/paging + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61616 + + + + + + + + 1000 + + eu-east-1-connector + + + netty-connector + + + 1000 + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server1/broker.xml b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..0bc2579fb7 --- /dev/null +++ b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-east-1-master + + target/server1/data/messaging/bindings + + target/server1/data/messaging/journal + + target/server1/data/messaging/largemessages + + target/server1/data/messaging/paging + + + + tcp://localhost:61617 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61617 + + + + + + + + 1000 + + eu-west-1-connector + + + netty-connector + + + 1000 + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server2/broker.xml b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server2/broker.xml new file mode 100644 index 0000000000..a80ea60b9f --- /dev/null +++ b/examples/features/federation/federated-address-downstream/src/main/resources/activemq/server2/broker.xml @@ -0,0 +1,100 @@ + + + + + + us-central-1-master + + target/server2/data/messaging/bindings + + target/server2/data/messaging/journal + + target/server2/data/messaging/largemessages + + target/server2/data/messaging/paging + + + + tcp://localhost:61618 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61618 + + + + + + + + + 1000 + + eu-east-1-connector + + + netty-connector + + + 1000 + + eu-west-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream-upstream/eu-west-east-us-central.png b/examples/features/federation/federated-queue-downstream-upstream/eu-west-east-us-central.png new file mode 100644 index 0000000000..0ba844733e Binary files /dev/null and b/examples/features/federation/federated-queue-downstream-upstream/eu-west-east-us-central.png differ diff --git a/examples/features/federation/federated-queue-downstream-upstream/pom.xml b/examples/features/federation/federated-queue-downstream-upstream/pom.xml new file mode 100644 index 0000000000..2a7a1e1d61 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/pom.xml @@ -0,0 +1,215 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.federation + broker-federation + 2.11.0-SNAPSHOT + + + federated-queue-downstream-upstream + jar + ActiveMQ Artemis Downstream/Upstream Federated Queue Example + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq.examples.federation + federated-queue + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + create2 + + create + + + ${noServer} + ${basedir}/target/server2 + ${basedir}/target/classes/activemq/server2 + + -Djava.net.preferIPv4Stack=true + + + + start0 + + cli + + + ${noServer} + true + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + eu-west-1 + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + eu-east-1 + + + + start2 + + cli + + + ${noServer} + true + ${basedir}/target/server2 + tcp://localhost:61618 + + run + + us-central-1 + + + + runClient + + runClient + + + org.apache.activemq.artemis.jms.example.FederatedQueueDownstreamUpstreamExample + + + + 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.federation + federated-queue-downstream-upstream + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + + + release + + + + com.vladsch.flexmark + markdown-page-generator-plugin + + + + + + \ No newline at end of file diff --git a/examples/features/federation/federated-queue-downstream-upstream/readme.md b/examples/features/federation/federated-queue-downstream-upstream/readme.md new file mode 100644 index 0000000000..347c60d810 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/readme.md @@ -0,0 +1,51 @@ +# Federated Queue Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the broker manually. + +This example demonstrates a core queue deployed on three different brokers. The three brokers are configured to form a federated queue mesh. + +In the example we name the brokers, eu-west, eu-east and us-central to give an idea of the use case. + +![EU West, EU East and US Central Diagram](eu-west-east-us-central.png) + +The following is then carried out: + +1. create a consumer on the queue on each node, and we create a producer on only one of the nodes. + +2. send some messages via the producer on EU West, and we verify that **only the local** consumer receives the sent messages. + +3. Next then verify the same on US Central. + +4. Now the consumer on EU West is closed leaving it no local consumers. + +5. Send some more messages to server EU West + +6. We now consume those messages on EU East demonstrating that messages will **re-route** to the another broker based on upstream priority. You will note, US Central is configured to be -1 priority compared to EU East, +there for messages should re-route to EU East as it will have a slightly higher priority for its consumers to consume. +If US Central and EU East were even priority then the re-direct would be loaded between the two. + +7. Next the consumer on US Central is closed leaving it no local consumers. And we send some more messages to US Cental + +8. Again we consume on EU East demonstrating that US Central messages also can **re-route**, if no local-consumer. + +9. Now we restart EU West and US Centrals consumers. + +10. We produce and consume on US Central, showing that dynamically re-adjusts now local consumers exist and messages delivery by priority to local consumer. + +11. And repeat the same on EU West. + + +In other words, we are showing how with Federated Queues, ActiveMQ Artemis **routes** sent messages to local consumers as priority, but is able to re-route the sent messages to other distant brokers if consumers are attached to those brokers. Decoupling the location where producers and consumers need to be. + +Here's the relevant snippet from the broker configuration, which tells the broker to form a cluster between the two nodes and to load balance the messages between the nodes. + +The config that defines the federation you can see in the broker.xml for each broker is within the following tags. You will note upstreams are different in each as well as the federation name, which has to be globally unique. + +``` + + ... + +``` + + +For more information on ActiveMQ Artemis Federation please see the federation section of the user manual. \ No newline at end of file diff --git a/examples/features/federation/federated-queue-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamUpstreamExample.java b/examples/features/federation/federated-queue-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamUpstreamExample.java new file mode 100644 index 0000000000..cc12ee96b2 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamUpstreamExample.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * A simple example that demonstrates dynamic queue messaging routing between remote servers, + * as consumers come and go, routing based on priorities. + * using Queue Federation feature. + */ +public class FederatedQueueDownstreamUpstreamExample { + + public static void main(final String[] args) throws Exception { + FederatedQueueExample.main(args); + } +} diff --git a/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server0/broker.xml b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..5c06f6229d --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,118 @@ + + + + + + eu-west-1-master + + ./data/bindings + + ./data/journal + + ./data/largemessages + + ./data/paging + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61616 + + + + + + + + 1000 + true + + eu-east-1-connector + + + + + 1000 + true + + us-central-1-connector + + + + + 1000 + true + + eu-east-1-connector + + + netty-connector + + + 1000 + true + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server1/broker.xml b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..1e9a497713 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-east-1-master + + target/server1/data/messaging/bindings + + target/server1/data/messaging/journal + + target/server1/data/messaging/largemessages + + target/server1/data/messaging/paging + + + + tcp://localhost:61617 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61617 + + + + + + + + 1000 + true + + us-central-1-connector + + + + + 1000 + true + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server2/broker.xml b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server2/broker.xml new file mode 100644 index 0000000000..f674e9f329 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream-upstream/src/main/resources/activemq/server2/broker.xml @@ -0,0 +1,73 @@ + + + + + + us-central-1-master + + target/server2/data/messaging/bindings + + target/server2/data/messaging/journal + + target/server2/data/messaging/largemessages + + target/server2/data/messaging/paging + + + + tcp://localhost:61618 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61618 + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream/eu-west-east-us-central.png b/examples/features/federation/federated-queue-downstream/eu-west-east-us-central.png new file mode 100644 index 0000000000..0ba844733e Binary files /dev/null and b/examples/features/federation/federated-queue-downstream/eu-west-east-us-central.png differ diff --git a/examples/features/federation/federated-queue-downstream/pom.xml b/examples/features/federation/federated-queue-downstream/pom.xml new file mode 100644 index 0000000000..4ef530e77f --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/pom.xml @@ -0,0 +1,215 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.federation + broker-federation + 2.11.0-SNAPSHOT + + + federated-queue-downstream + jar + ActiveMQ Artemis Downstream Federated Queue Example + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq.examples.federation + federated-queue + ${project.version} + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create0 + + create + + + ${noServer} + ${basedir}/target/server0 + ${basedir}/target/classes/activemq/server0 + + -Djava.net.preferIPv4Stack=true + + + + create1 + + create + + + ${noServer} + ${basedir}/target/server1 + ${basedir}/target/classes/activemq/server1 + + -Djava.net.preferIPv4Stack=true + + + + create2 + + create + + + ${noServer} + ${basedir}/target/server2 + ${basedir}/target/classes/activemq/server2 + + -Djava.net.preferIPv4Stack=true + + + + start0 + + cli + + + ${noServer} + true + ${basedir}/target/server0 + tcp://localhost:61616 + + run + + eu-west-1 + + + + start1 + + cli + + + ${noServer} + true + ${basedir}/target/server1 + tcp://localhost:61617 + + run + + eu-east-1 + + + + start2 + + cli + + + ${noServer} + true + ${basedir}/target/server2 + tcp://localhost:61618 + + run + + us-central-1 + + + + runClient + + runClient + + + org.apache.activemq.artemis.jms.example.FederatedQueueDownstreamExample + + + + 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.federation + federated-queue-downstream + ${project.version} + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + + + release + + + + com.vladsch.flexmark + markdown-page-generator-plugin + + + + + + \ No newline at end of file diff --git a/examples/features/federation/federated-queue-downstream/readme.md b/examples/features/federation/federated-queue-downstream/readme.md new file mode 100644 index 0000000000..347c60d810 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/readme.md @@ -0,0 +1,51 @@ +# Federated Queue Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the broker manually. + +This example demonstrates a core queue deployed on three different brokers. The three brokers are configured to form a federated queue mesh. + +In the example we name the brokers, eu-west, eu-east and us-central to give an idea of the use case. + +![EU West, EU East and US Central Diagram](eu-west-east-us-central.png) + +The following is then carried out: + +1. create a consumer on the queue on each node, and we create a producer on only one of the nodes. + +2. send some messages via the producer on EU West, and we verify that **only the local** consumer receives the sent messages. + +3. Next then verify the same on US Central. + +4. Now the consumer on EU West is closed leaving it no local consumers. + +5. Send some more messages to server EU West + +6. We now consume those messages on EU East demonstrating that messages will **re-route** to the another broker based on upstream priority. You will note, US Central is configured to be -1 priority compared to EU East, +there for messages should re-route to EU East as it will have a slightly higher priority for its consumers to consume. +If US Central and EU East were even priority then the re-direct would be loaded between the two. + +7. Next the consumer on US Central is closed leaving it no local consumers. And we send some more messages to US Cental + +8. Again we consume on EU East demonstrating that US Central messages also can **re-route**, if no local-consumer. + +9. Now we restart EU West and US Centrals consumers. + +10. We produce and consume on US Central, showing that dynamically re-adjusts now local consumers exist and messages delivery by priority to local consumer. + +11. And repeat the same on EU West. + + +In other words, we are showing how with Federated Queues, ActiveMQ Artemis **routes** sent messages to local consumers as priority, but is able to re-route the sent messages to other distant brokers if consumers are attached to those brokers. Decoupling the location where producers and consumers need to be. + +Here's the relevant snippet from the broker configuration, which tells the broker to form a cluster between the two nodes and to load balance the messages between the nodes. + +The config that defines the federation you can see in the broker.xml for each broker is within the following tags. You will note upstreams are different in each as well as the federation name, which has to be globally unique. + +``` + + ... + +``` + + +For more information on ActiveMQ Artemis Federation please see the federation section of the user manual. \ No newline at end of file diff --git a/examples/features/federation/federated-queue-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamExample.java b/examples/features/federation/federated-queue-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamExample.java new file mode 100644 index 0000000000..935f09d459 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueDownstreamExample.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * A simple example that demonstrates dynamic queue messaging routing between remote servers, + * as consumers come and go, routing based on priorities. + * using Queue Federation feature. + */ +public class FederatedQueueDownstreamExample { + + public static void main(final String[] args) throws Exception { + FederatedQueueExample.main(args); + } +} diff --git a/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server0/broker.xml b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..4cb986a9f3 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-west-1-master + + ./data/bindings + + ./data/journal + + ./data/largemessages + + ./data/paging + + + + tcp://localhost:61616 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61616 + + + + + + + + 1000 + + eu-east-1-connector + + + netty-connector + + + 1000 + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server1/broker.xml b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server1/broker.xml new file mode 100644 index 0000000000..fd862ccf74 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server1/broker.xml @@ -0,0 +1,100 @@ + + + + + + eu-east-1-master + + target/server1/data/messaging/bindings + + target/server1/data/messaging/journal + + target/server1/data/messaging/largemessages + + target/server1/data/messaging/paging + + + + tcp://localhost:61617 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61617 + + + + + + + + 1000 + + eu-west-1-connector + + + netty-connector + + + 1000 + + us-central-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server2/broker.xml b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server2/broker.xml new file mode 100644 index 0000000000..be8a8f1350 --- /dev/null +++ b/examples/features/federation/federated-queue-downstream/src/main/resources/activemq/server2/broker.xml @@ -0,0 +1,100 @@ + + + + + + us-central-1-master + + target/server2/data/messaging/bindings + + target/server2/data/messaging/journal + + target/server2/data/messaging/largemessages + + target/server2/data/messaging/paging + + + + tcp://localhost:61618 + tcp://localhost:61616 + tcp://localhost:61617 + tcp://localhost:61618 + + + + + tcp://localhost:61618 + + + + + + + + + 1000 + + eu-east-1-connector + + + netty-connector + + + 1000 + + eu-west-1-connector + + + netty-connector + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
diff --git a/examples/features/federation/federated-queue/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueExample.java b/examples/features/federation/federated-queue/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueExample.java index 48aaa8c0a3..e594b96151 100644 --- a/examples/features/federation/federated-queue/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueExample.java +++ b/examples/features/federation/federated-queue/src/main/java/org/apache/activemq/artemis/jms/example/FederatedQueueExample.java @@ -23,6 +23,7 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; + import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; diff --git a/examples/features/federation/pom.xml b/examples/features/federation/pom.xml index 30a89d49e3..25e02809f9 100644 --- a/examples/features/federation/pom.xml +++ b/examples/features/federation/pom.xml @@ -48,14 +48,21 @@ under the License. examples federated-queue + federated-queue-downstream + federated-queue-downstream-upstream federated-address + federated-address-downstream + federated-address-downstream-upstream release federated-queue + federated-queue-downstream + federated-queue-downstream-upstream federated-address + federated-address-downstream-upstream diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedAddressTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedAddressTest.java index bdd12e5095..c0f9e1c6df 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedAddressTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedAddressTest.java @@ -16,7 +16,6 @@ */ package org.apache.activemq.artemis.tests.integration.federation; -import java.util.Collections; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; @@ -25,10 +24,17 @@ import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.Topic; +import java.util.Collections; + import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.core.config.FederationConfiguration; -import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; +import org.apache.activemq.artemis.core.config.TransformerConfiguration; 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.FederationTransformerConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; +import org.apache.activemq.artemis.core.server.transformer.Transformer; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.tests.util.Wait; import org.junit.Before; @@ -46,25 +52,133 @@ public class FederatedAddressTest extends FederatedTestBase { super.setUp(); } - protected ConnectionFactory getCF(int i) throws Exception { return new ActiveMQConnectionFactory("vm://" + i); } - - @Test - public void testFederatedAddressReplication() throws Exception { + public void testDownstreamFederatedAddressReplication() throws Exception { String address = getName(); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address); + FederationConfiguration federationConfiguration = createDownstreamFederationConfiguration("server1", address, + getServer(0).getConfiguration().getTransportConfigurations("server0")[0]); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); - FederationConfiguration federationConfiguration2 = createFederationConfiguration("server0", address); + FederationConfiguration federationConfiguration2 = createDownstreamFederationConfiguration("server0", address, + getServer(1).getConfiguration().getTransportConfigurations("server1")[0]); getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2); getServer(1).getFederationManager().deploy(); + testFederatedAddressReplication(address); + } + + @Test + public void testDownstreamFederatedAddressReplicationRef() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration = createDownstreamFederationConfiguration("server1", address, + "server0"); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + FederationConfiguration federationConfiguration2 = createDownstreamFederationConfiguration("server0", address, + "server1"); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2); + getServer(1).getFederationManager().deploy(); + + testFederatedAddressReplication(address); + } + + @Test + public void testDownstreamFederatedAddressReplicationRefOneWay() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration2 = createDownstreamFederationConfiguration("server0", address, + "server1"); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2); + getServer(1).getFederationManager().deploy(); + + testFederatedAddressReplication(address); + } + + @Test + public void testUpstreamFederatedAddressReplication() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + FederationConfiguration federationConfiguration2 = createUpstreamFederationConfiguration("server0", address); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2); + getServer(1).getFederationManager().deploy(); + + testFederatedAddressReplication(address); + } + + @Test + public void testDownstreamFederatedAddressReplicationRefOneWayTransformer() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration2 = createDownstreamFederationConfiguration("server0", address, "server1"); + addTransformerConfiguration(federationConfiguration2, address); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2); + getServer(1).getFederationManager().deploy(); + + verifyTransformer(address); + } + + private void verifyTransformer(String address) throws Exception { + ConnectionFactory cf1 = getCF(1); + ConnectionFactory cf0 = getCF(0); + try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { + connection1.start(); + connection0.start(); + + Session session1 = connection1.createSession(); + Topic topic1 = session1.createTopic(address); + MessageProducer producer1 = session1.createProducer(topic1); + + Session session0 = connection0.createSession(); + Topic topic0 = session0.createTopic(address); + MessageConsumer consumer0 = session0.createConsumer(topic0); + + assertTrue(Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress( + SimpleString.toSimpleString(address)).getBindings().size() == 1)); + + producer1.send(session1.createTextMessage("hello")); + Message message = consumer0.receive(1000); + assertNotNull(message); + assertEquals(message.getBooleanProperty(FederatedQueueTest.TestTransformer.TEST_PROPERTY), true); + } + } + + @Test + public void testUpstreamFederatedAddressReplicationOneWay() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + testFederatedAddressReplication(address); + } + + @Test + public void testUpstreamFederatedAddressReplicationOneWayTransformer() throws Exception { + String address = getName(); + + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); + addTransformerConfiguration(federationConfiguration, address); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + verifyTransformer(address); + } + + private void testFederatedAddressReplication(String address) throws Exception { + ConnectionFactory cf1 = getCF(1); ConnectionFactory cf0 = getCF(0); try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { @@ -81,7 +195,7 @@ public class FederatedAddressTest extends FederatedTestBase { Topic topic0 = session0.createTopic(address); MessageConsumer consumer0 = session0.createConsumer(topic0); - Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1); + assertTrue(Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1)); producer.send(session1.createTextMessage("hello")); @@ -143,7 +257,7 @@ public class FederatedAddressTest extends FederatedTestBase { assertNull(consumer0.receive(100)); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -161,7 +275,7 @@ public class FederatedAddressTest extends FederatedTestBase { public void testFederatedAddressRemoteBrokerRestart() throws Exception { String address = getName(); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -219,7 +333,7 @@ public class FederatedAddressTest extends FederatedTestBase { public void testFederatedAddressLocalBrokerRestart() throws Exception { String address = getName(); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", address); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -274,27 +388,125 @@ public class FederatedAddressTest extends FederatedTestBase { } } + @Test + public void testFederatedAddressChainOfBrokers() throws Exception { + String address = getName(); - private FederationConfiguration createFederationConfiguration(String connector, String address) { + //Set queue up on all three brokers +// for (int i = 0; i < 3; i++) { +// getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); +// } + + //Connect broker 0 (consumer will be here at end of chain) to broker 1 + FederationConfiguration federationConfiguration0 = createUpstreamFederationConfiguration("server1", address, 2); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + //Connect broker 1 (middle of chain) to broker 2 + FederationConfiguration federationConfiguration1 = createUpstreamFederationConfiguration("server2", address, 2); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration1); + getServer(1).getFederationManager().deploy(); + //Broker 2 we dont setup any federation as he is the upstream (head of the chain) + + //Now the test. + + + ConnectionFactory cf2 = getCF(2); + ConnectionFactory cf0 = getCF(0); + try (Connection connection2 = cf2.createConnection(); Connection connection0 = cf0.createConnection()) { + connection0.start(); + Session session0 = connection0.createSession(); + Topic topic0 = session0.createTopic(address); + + connection2.start(); + Session session2 = connection2.createSession(); + Topic topic2 = session2.createTopic(address); + + MessageProducer producer2 = session2.createProducer(topic2); + MessageConsumer consumer0 = session0.createConsumer(topic0); + + assertTrue(Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1)); + assertTrue(Wait.waitFor(() -> getServer(2).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1)); + + //Test producers being on broker 2 and consumer on broker 0, with broker 2 being in the middle of the chain. + producer2.send(session2.createTextMessage("hello")); + assertNotNull(consumer0.receive(1000)); + } + } + + private FederationConfiguration createFederationConfiguration(String address, int hops) { + FederationAddressPolicyConfiguration addressPolicyConfiguration = new FederationAddressPolicyConfiguration(); + addressPolicyConfiguration.setName( "AddressPolicy" + address); + addressPolicyConfiguration.addInclude(new FederationAddressPolicyConfiguration.Matcher().setAddressMatch(address)); + addressPolicyConfiguration.setMaxHops(hops); + + FederationConfiguration federationConfiguration = new FederationConfiguration(); + federationConfiguration.setName("default"); + federationConfiguration.addFederationPolicy(addressPolicyConfiguration); + + return federationConfiguration; + } + + private FederationConfiguration createUpstreamFederationConfiguration(String connector, String address, int hops) { FederationUpstreamConfiguration upstreamConfiguration = new FederationUpstreamConfiguration(); upstreamConfiguration.setName(connector); upstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector)); upstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1); upstreamConfiguration.addPolicyRef("AddressPolicy" + address); - - FederationAddressPolicyConfiguration addressPolicyConfiguration = new FederationAddressPolicyConfiguration(); - addressPolicyConfiguration.setName( "AddressPolicy" + address); - addressPolicyConfiguration.addInclude(new FederationAddressPolicyConfiguration.Matcher().setAddressMatch(address)); - addressPolicyConfiguration.setMaxHops(1); - - FederationConfiguration federationConfiguration = new FederationConfiguration(); - federationConfiguration.setName("default"); + FederationConfiguration federationConfiguration = createFederationConfiguration(address, hops); federationConfiguration.addUpstreamConfiguration(upstreamConfiguration); - federationConfiguration.addFederationPolicy(addressPolicyConfiguration); return federationConfiguration; } + private FederationConfiguration createUpstreamFederationConfiguration(String connector, String address) { + return createUpstreamFederationConfiguration(connector, address, 1); + } + + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String address, TransportConfiguration transportConfiguration) { + return createDownstreamFederationConfiguration(connector, address, transportConfiguration, 1); + } + + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String address, TransportConfiguration transportConfiguration, + int hops) { + FederationDownstreamConfiguration downstreamConfiguration = new FederationDownstreamConfiguration(); + downstreamConfiguration.setName(connector); + downstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector)); + downstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1); + downstreamConfiguration.addPolicyRef("AddressPolicy" + address); + downstreamConfiguration.setUpstreamConfiguration(transportConfiguration); + + FederationConfiguration federationConfiguration = createFederationConfiguration(address, hops); + federationConfiguration.addDownstreamConfiguration(downstreamConfiguration); + + return federationConfiguration; + } + + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String address, String transportConfigurationRef, + int hops) { + FederationDownstreamConfiguration downstreamConfiguration = new FederationDownstreamConfiguration(); + downstreamConfiguration.setName(connector); + downstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector)); + downstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1); + downstreamConfiguration.addPolicyRef("AddressPolicy" + address); + downstreamConfiguration.setUpstreamConfigurationRef(transportConfigurationRef); + + FederationConfiguration federationConfiguration = createFederationConfiguration(address, hops); + federationConfiguration.addDownstreamConfiguration(downstreamConfiguration); + + return federationConfiguration; + } + + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String address, String transportConfigurationRef) { + return createDownstreamFederationConfiguration(connector, address, transportConfigurationRef, 1); + } + + private void addTransformerConfiguration(final FederationConfiguration federationConfiguration, final String address) { + federationConfiguration.addTransformerConfiguration( + new FederationTransformerConfiguration("transformer", new TransformerConfiguration(TestTransformer.class.getName()))); + FederationAddressPolicyConfiguration policy = (FederationAddressPolicyConfiguration) federationConfiguration.getFederationPolicyMap().get("AddressPolicy" + address); + policy.setTransformerRef("transformer"); + } private Message createTextMessage(Session session1, String group) throws JMSException { Message message = session1.createTextMessage("hello"); @@ -302,5 +514,15 @@ public class FederatedAddressTest extends FederatedTestBase { return message; } + public static class TestTransformer implements Transformer { + + static String TEST_PROPERTY = "transformed"; + + @Override + public org.apache.activemq.artemis.api.core.Message transform(org.apache.activemq.artemis.api.core.Message message) { + message.putBooleanProperty(TEST_PROPERTY, true); + return message; + } + } } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedQueueTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedQueueTest.java index 514778c1e0..871a8b1ed2 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedQueueTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/federation/FederatedQueueTest.java @@ -16,7 +16,6 @@ */ package org.apache.activemq.artemis.tests.integration.federation; -import java.util.Collections; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; @@ -26,12 +25,18 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; +import java.util.Collections; + import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; -import org.apache.activemq.artemis.core.postoffice.QueueBinding; import org.apache.activemq.artemis.core.config.FederationConfiguration; +import org.apache.activemq.artemis.core.config.TransformerConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationQueuePolicyConfiguration; +import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration; import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration; +import org.apache.activemq.artemis.core.postoffice.QueueBinding; +import org.apache.activemq.artemis.core.server.transformer.Transformer; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.tests.util.Wait; import org.junit.Before; @@ -54,16 +59,136 @@ public class FederatedQueueTest extends FederatedTestBase { return new ActiveMQConnectionFactory("vm://" + i); } - - @Test - public void testFederatedQueueRemoteConsume() throws Exception { + public void testFederatedQueueRemoteConsumeUpstream() throws Exception { String queueName = getName(); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); + testFederatedQueueRemoteConsume(queueName); + } + + @Test + public void testFederatedQueueRemoteConsumeUpstreamPriorityAdjustment() throws Exception { + String queueName = getName(); + + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); + FederationQueuePolicyConfiguration policy = (FederationQueuePolicyConfiguration) federationConfiguration.getFederationPolicyMap().get("QueuePolicy" + queueName); + //Favor federated broker over local consumers + policy.setPriorityAdjustment(1); + + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + testFederatedQueueRemoteConsumeUpstreamPriorityAdjustment(queueName); + } + + @Test + public void testFederatedQueueRemoteConsumeDownstreamPriorityAdjustment() throws Exception { + String queueName = getName(); + + FederationConfiguration federationConfiguration = createDownstreamFederationConfiguration("server0", queueName, "server1"); + FederationQueuePolicyConfiguration policy = (FederationQueuePolicyConfiguration) federationConfiguration.getFederationPolicyMap().get("QueuePolicy" + queueName); + //Favor federated broker over local consumers + policy.setPriorityAdjustment(1); + + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(1).getFederationManager().deploy(); + + testFederatedQueueRemoteConsumeUpstreamPriorityAdjustment(queueName); + } + + private void testFederatedQueueRemoteConsumeUpstreamPriorityAdjustment(final String queueName) throws Exception { + ConnectionFactory cf1 = getCF(1); + ConnectionFactory cf0 = getCF(0); + try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { + connection0.start(); + connection1.start(); + Session session0 = connection0.createSession(); + Session session1 = connection1.createSession(); + Queue queue0 = session0.createQueue(queueName); + Queue queue1 = session1.createQueue(queueName); + + MessageConsumer consumer0 = session0.createConsumer(queue0); + MessageConsumer consumer1 = session1.createConsumer(queue1); + + //Wait for local and federated consumer to be established on Server 1 + assertTrue(Wait.waitFor(() -> getServer(1).locateQueue(SimpleString.toSimpleString(queueName)).getConsumerCount() == 2, + 5000, 100)); + + MessageProducer producer1 = session1.createProducer(queue1); + producer1.send(session1.createTextMessage("hello")); + + //Consumer 0 should receive the message over consumer because of adjusted priority + //to favor the federated broker + assertNull(consumer1.receive(500)); + assertNotNull(consumer0.receive(1000)); + + consumer0.close(); + consumer1.close(); + } + } + + private void verifyTransformer(String queueName) throws Exception { + ConnectionFactory cf1 = getCF(1); + ConnectionFactory cf0 = getCF(0); + try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { + connection1.start(); + Session session1 = connection1.createSession(); + Queue queue1 = session1.createQueue(queueName); + MessageProducer producer1 = session1.createProducer(queue1); + producer1.send(session1.createTextMessage("hello")); + + connection0.start(); + Session session0 = connection0.createSession(); + Queue queue0 = session0.createQueue(queueName); + MessageConsumer consumer0 = session0.createConsumer(queue0); + + Message message = consumer0.receive(1000); + assertNotNull(message); + assertEquals(message.getBooleanProperty(TestTransformer.TEST_PROPERTY), true); + } + } + + @Test + public void testFederatedQueueRemoteConsumeUpstreamTransformer() throws Exception { + String queueName = getName(); + + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); + addTransformerConfiguration(federationConfiguration, queueName); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(0).getFederationManager().deploy(); + + verifyTransformer(queueName); + } + + @Test + public void testFederatedQueueRemoteConsumeDownstream() throws Exception { + String queueName = getName(); + + FederationConfiguration federationConfiguration = createDownstreamFederationConfiguration("server0", queueName, "server1"); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(1).getFederationManager().deploy(); + + testFederatedQueueRemoteConsume(queueName); + } + + @Test + public void testFederatedQueueRemoteConsumeDownstreamTransformer() throws Exception { + String queueName = getName(); + + FederationConfiguration federationConfiguration = createDownstreamFederationConfiguration("server0", queueName, "server1"); + addTransformerConfiguration(federationConfiguration, queueName); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration); + getServer(1).getFederationManager().deploy(); + + verifyTransformer(queueName); + } + + private void testFederatedQueueRemoteConsume(final String queueName) throws Exception { + ConnectionFactory cf1 = getCF(1); ConnectionFactory cf0 = getCF(0); try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { @@ -130,7 +255,7 @@ public class FederatedQueueTest extends FederatedTestBase { assertNull(consumer0.receive(100)); - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -141,20 +266,134 @@ public class FederatedQueueTest extends FederatedTestBase { } @Test - public void testFederatedQueueBiDirectional() throws Exception { + public void testFederatedQueueBiDirectionalUpstream() throws Exception { String queueName = getName(); //Set queue up on both brokers for (int i = 0; i < 2; i++) { getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); } - FederationConfiguration federationConfiguration0 = createFederationConfiguration("server1", queueName); + FederationConfiguration federationConfiguration0 = createUpstreamFederationConfiguration("server1", queueName); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); getServer(0).getFederationManager().deploy(); - FederationConfiguration federationConfiguration1 = createFederationConfiguration("server0", queueName); + FederationConfiguration federationConfiguration1 = createUpstreamFederationConfiguration("server0", queueName); getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration1); getServer(1).getFederationManager().deploy(); + testFederatedQueueBiDirectional(queueName, false); + } + + @Test + public void testFederatedQueueBiDirectionalDownstream() throws Exception { + String queueName = getName(); + //Set queue up on both brokers + for (int i = 0; i < 2; i++) { + getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); + } + FederationConfiguration federationConfiguration0 = createDownstreamFederationConfiguration("server1", queueName, "server0"); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + FederationConfiguration federationConfiguration1 = createDownstreamFederationConfiguration("server0", queueName, "server1"); + getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration1); + getServer(1).getFederationManager().deploy(); + + testFederatedQueueBiDirectional(queueName, false); + } + + @Test + public void testFederatedQueueBiDirectionalDownstreamUpstream() throws Exception { + String queueName = getName(); + //Set queue up on both brokers + for (int i = 0; i < 2; i++) { + getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); + } + + FederationConfiguration federationConfiguration0 = createDownstreamFederationConfiguration("server1-downstream", + "server1", queueName, null, false, "server0"); + FederationUpstreamConfiguration upstreamConfig = createFederationUpstream("server1", queueName); + federationConfiguration0.addUpstreamConfiguration(upstreamConfig); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + testFederatedQueueBiDirectional(queueName, false); + } + + @Test + public void testFederatedQueueBiDirectionalDownstreamUpstreamSharedConnection() throws Exception { + String queueName = getName(); + //Set queue up on both brokers + for (int i = 0; i < 2; i++) { + getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); + } + + FederationConfiguration federationConfiguration0 = createDownstreamFederationConfiguration("server1-downstream", + "server1", queueName, null, true, "server0"); + FederationUpstreamConfiguration upstreamConfig = createFederationUpstream("server1", queueName); + upstreamConfig.getConnectionConfiguration().setShareConnection(true); + federationConfiguration0.addUpstreamConfiguration(upstreamConfig); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + testFederatedQueueBiDirectional(queueName, true); + } + + @Test + public void testFederatedQueueShareUpstreamConnectionFalse() throws Exception { + String queueName = getName(); + //Set queue up on both brokers + for (int i = 0; i < 2; i++) { + getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); + } + + FederationConfiguration federationConfiguration0 = createDownstreamFederationConfiguration("server1-downstream", + "server1", queueName, null, false, "server0"); + federationConfiguration0.addUpstreamConfiguration(createFederationUpstream("server1", queueName)); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + testFederatedQueueShareUpstreamConnection(queueName, 2, 3); + } + + @Test + public void testFederatedQueueShareUpstreamConnectionTrue() throws Exception { + String queueName = getName(); + //Set queue up on both brokers + for (int i = 0; i < 2; i++) { + getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); + } + + FederationConfiguration federationConfiguration0 = createDownstreamFederationConfiguration("server1-downstream", + "server1", queueName, null, true, "server0"); + FederationUpstreamConfiguration upstreamConfiguration = createFederationUpstream("server1", queueName); + upstreamConfiguration.getConnectionConfiguration().setShareConnection(true); + federationConfiguration0.addUpstreamConfiguration(upstreamConfiguration); + getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); + getServer(0).getFederationManager().deploy(); + + testFederatedQueueShareUpstreamConnection(queueName, 2, 2); + } + + private void testFederatedQueueShareUpstreamConnection(String queueName, int server0Connections, int server1Connections) throws Exception { + ConnectionFactory cf1 = getCF(1); + ConnectionFactory cf0 = getCF(0); + try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { + connection0.start(); + connection1.start(); + Session session0 = connection0.createSession(); + Session session1 = connection1.createSession(); + + MessageConsumer consumer0 = session0.createConsumer(session0.createQueue(queueName)); + MessageConsumer consumer1 = session1.createConsumer(session1.createQueue(queueName)); + + assertTrue(Wait.waitFor(() -> getServer(0).getConnectionCount() == server0Connections, 500, 100)); + assertTrue(Wait.waitFor(() -> getServer(1).getConnectionCount() == server1Connections, 500, 100)); + assertFalse(Wait.waitFor(() -> getServer(0).getConnectionCount() > server0Connections, 500, 100)); + assertFalse(Wait.waitFor(() -> getServer(1).getConnectionCount() > server1Connections, 500, 100)); + } + } + + private void testFederatedQueueBiDirectional(String queueName, boolean shared) throws Exception { ConnectionFactory cf1 = getCF(1); ConnectionFactory cf0 = getCF(0); try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) { @@ -169,7 +408,6 @@ public class FederatedQueueTest extends FederatedTestBase { MessageProducer producer1 = session1.createProducer(queue1); MessageConsumer consumer0 = session0.createConsumer(queue0); - //Test producers being on broker 0 and broker 1 and consumer on broker 0. producer0.send(session1.createTextMessage("hello")); assertNotNull(consumer0.receive(1000)); @@ -183,13 +421,21 @@ public class FederatedQueueTest extends FederatedTestBase { assertFalse(Wait.waitFor(() -> getServer(1).locateQueue(SimpleString.toSimpleString(queueName)).getConsumerCount() > 1, 500, 100)); //Test consumer move from broker 0, to broker 1 + final int server1ConsumerCount = getServer(1).getConnectionCount(); consumer0.close(); Wait.waitFor(() -> ((QueueBinding) getServer(0).getPostOffice().getBinding(SimpleString.toSimpleString(queueName))).consumerCount() == 0, 1000); + //Make sure we don't drop connection if shared + if (shared) { + assertFalse(Wait.waitFor(() -> getServer(1).getConnectionCount() == server1ConsumerCount - 1, + 500, 100)); + assertTrue(server1ConsumerCount == getServer(1).getConnectionCount()); + } + MessageConsumer consumer1 = session1.createConsumer(queue1); producer0.send(session1.createTextMessage("hello")); - assertNotNull(consumer1.receive(10000)); + assertNotNull(consumer1.receive(1000)); producer1.send(session1.createTextMessage("hello")); assertNotNull(consumer1.receive(1000)); @@ -210,7 +456,6 @@ public class FederatedQueueTest extends FederatedTestBase { } } - @Test public void testFederatedQueueChainOfBrokers() throws Exception { String queueName = getName(); @@ -221,12 +466,12 @@ public class FederatedQueueTest extends FederatedTestBase { } //Connect broker 0 (consumer will be here at end of chain) to broker 1 - FederationConfiguration federationConfiguration0 = createFederationConfiguration("server1", queueName, true); + FederationConfiguration federationConfiguration0 = createUpstreamFederationConfiguration("server1", queueName, true); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0); getServer(0).getFederationManager().deploy(); //Connect broker 1 (middle of chain) to broker 2 - FederationConfiguration federationConfiguration1 = createFederationConfiguration("server2", queueName, true); + FederationConfiguration federationConfiguration1 = createUpstreamFederationConfiguration("server2", queueName, true); getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration1); getServer(1).getFederationManager().deploy(); //Broker 2 we dont setup any federation as he is the upstream (head of the chain) @@ -263,7 +508,7 @@ public class FederatedQueueTest extends FederatedTestBase { getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); } - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -307,7 +552,6 @@ public class FederatedQueueTest extends FederatedTestBase { assertNotNull(consumer0.receive(1000)); } - @Test public void testFederatedQueueLocalBrokerRestart() throws Exception { String queueName = getName(); @@ -317,7 +561,7 @@ public class FederatedQueueTest extends FederatedTestBase { getServer(i).createQueue(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST, SimpleString.toSimpleString(queueName), null, true, false); } - FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName); + FederationConfiguration federationConfiguration = createUpstreamFederationConfiguration("server1", queueName); getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration); getServer(0).getFederationManager().deploy(); @@ -365,17 +609,56 @@ public class FederatedQueueTest extends FederatedTestBase { assertNotNull(consumer0.receive(1000)); } - private FederationConfiguration createFederationConfiguration(String connector, String queueName) { - return createFederationConfiguration(connector, queueName, null); + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String queueName, Boolean includeFederated, + String transportConfigurationRef) { + return createDownstreamFederationConfiguration(null, connector, queueName, includeFederated, false, transportConfigurationRef); } - private FederationConfiguration createFederationConfiguration(String connector, String queueName, Boolean includeFederated) { + private FederationConfiguration createDownstreamFederationConfiguration(String name, String connector, String queueName, Boolean includeFederated, + boolean shareConnection, String transportConfigurationRef) { + FederationDownstreamConfiguration downstreamConfiguration = new FederationDownstreamConfiguration(); + downstreamConfiguration.setName(name != null ? name : connector); + downstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector)); + downstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1); + downstreamConfiguration.getConnectionConfiguration().setShareConnection(shareConnection); + downstreamConfiguration.addPolicyRef("QueuePolicy" + queueName); + downstreamConfiguration.setUpstreamConfigurationRef(transportConfigurationRef); + + FederationConfiguration federationConfiguration = createFederationConfiguration(connector, queueName, includeFederated); + federationConfiguration.addDownstreamConfiguration(downstreamConfiguration); + + return federationConfiguration; + } + + private FederationConfiguration createDownstreamFederationConfiguration(String connector, String queueName, String transportConfigurationRef) { + return createDownstreamFederationConfiguration(null, connector, queueName, null, false, transportConfigurationRef); + } + + private FederationConfiguration createUpstreamFederationConfiguration(String connector, String queueName, Boolean includeFederated) { + FederationUpstreamConfiguration upstreamConfiguration = createFederationUpstream(connector, queueName); + + FederationConfiguration federationConfiguration = createFederationConfiguration(connector, queueName, includeFederated); + federationConfiguration.addUpstreamConfiguration(upstreamConfiguration); + + return federationConfiguration; + } + + private FederationUpstreamConfiguration createFederationUpstream(String connector, String queueName) { + FederationUpstreamConfiguration upstreamConfiguration = new FederationUpstreamConfiguration(); - upstreamConfiguration.setName(connector); + upstreamConfiguration.setName("server1-upstream"); upstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector)); upstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1); upstreamConfiguration.addPolicyRef("QueuePolicy" + queueName); + return upstreamConfiguration; + } + + private FederationConfiguration createUpstreamFederationConfiguration(String connector, String queueName) { + return createUpstreamFederationConfiguration(connector, queueName, null); + } + + private FederationConfiguration createFederationConfiguration(String connector, String queueName, Boolean includeFederated) { FederationQueuePolicyConfiguration queuePolicyConfiguration = new FederationQueuePolicyConfiguration(); queuePolicyConfiguration.setName( "QueuePolicy" + queueName); @@ -387,17 +670,33 @@ public class FederatedQueueTest extends FederatedTestBase { FederationConfiguration federationConfiguration = new FederationConfiguration(); federationConfiguration.setName("default"); - federationConfiguration.addUpstreamConfiguration(upstreamConfiguration); federationConfiguration.addFederationPolicy(queuePolicyConfiguration); return federationConfiguration; } + private void addTransformerConfiguration(final FederationConfiguration federationConfiguration, final String queueName) { + federationConfiguration.addTransformerConfiguration( + new FederationTransformerConfiguration("transformer", new TransformerConfiguration(TestTransformer.class.getName()))); + FederationQueuePolicyConfiguration policy = (FederationQueuePolicyConfiguration) federationConfiguration.getFederationPolicyMap().get("QueuePolicy" + queueName); + policy.setTransformerRef("transformer"); + } + private Message createTextMessage(Session session1, String group) throws JMSException { Message message = session1.createTextMessage("hello"); message.setStringProperty("JMSXGroupID", group); return message; } + public static class TestTransformer implements Transformer { + + static String TEST_PROPERTY = "transformed"; + + @Override + public org.apache.activemq.artemis.api.core.Message transform(org.apache.activemq.artemis.api.core.Message message) { + message.putBooleanProperty(TEST_PROPERTY, true); + return message; + } + } } diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/ActiveMQBufferTestBase.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/ActiveMQBufferTestBase.java index f4611405e9..57014b38ce 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/ActiveMQBufferTestBase.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/ActiveMQBufferTestBase.java @@ -158,6 +158,27 @@ public abstract class ActiveMQBufferTestBase extends ActiveMQTestBase { Assert.assertFalse(wrapper.readBoolean()); } + @Test + public void testPutNullableTrueBoolean() throws Exception { + wrapper.writeNullableBoolean(true); + + Assert.assertTrue(wrapper.readNullableBoolean()); + } + + @Test + public void testPutNullableFalseBoolean() throws Exception { + wrapper.writeNullableBoolean(false); + + Assert.assertFalse(wrapper.readNullableBoolean()); + } + + @Test + public void testNullBoolean() throws Exception { + wrapper.writeNullableBoolean(null); + + Assert.assertNull(wrapper.readNullableBoolean()); + } + @Test public void testChar() throws Exception { wrapper.writeChar('a'); @@ -195,6 +216,21 @@ public abstract class ActiveMQBufferTestBase extends ActiveMQTestBase { Assert.assertEquals(l, wrapper.readLong()); } + @Test + public void testNullableLong() throws Exception { + Long l = RandomUtil.randomLong(); + wrapper.writeNullableLong(l); + + Assert.assertEquals(l, wrapper.readNullableLong()); + } + + @Test + public void testNullLong() throws Exception { + wrapper.writeNullableLong(null); + + Assert.assertNull(wrapper.readNullableLong()); + } + @Test public void testUnsignedShort() throws Exception { short s1 = Short.MAX_VALUE;