ARTEMIS-2265 Support Federated Queues and Addresses

Implement Federated Queue (builds on recent consumer priority)
Implement Federated Address (builds on recent queue level auto-delete)
Add Functional Tests
Add Failure Tests (remote and local shutdowns)
Add Documentation
This commit is contained in:
Michael André Pearce 2019-03-01 20:04:11 +00:00 committed by Clebert Suconic
parent 2cdb0670fd
commit 4a5af776d8
73 changed files with 6245 additions and 16 deletions

View File

@ -21,7 +21,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
@ -146,10 +145,20 @@ public class ByteUtil {
return groups; return groups;
} }
public static byte[] longToBytes(long x) { public static final byte[] intToBytes(int value) {
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer(8, 8); return new byte[] {
buffer.writeLong(x); (byte)(value >>> 24),
return buffer.array(); (byte)(value >>> 16),
(byte)(value >>> 8),
(byte)value
};
}
public static int bytesToInt(byte[] b) {
return ((int) b[3] & 0xff)
| ((int) b[2] & 0xff) << 8
| ((int) b[1] & 0xff) << 16
| ((int) b[0] & 0xff) << 24;
} }
public static byte[] hexToBytes(String hexStr) { public static byte[] hexToBytes(String hexStr) {

View File

@ -16,12 +16,13 @@
*/ */
package org.apache.activemq.artemis.utils; package org.apache.activemq.artemis.utils;
import java.nio.ByteBuffer;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import org.junit.Test;
public class ByteUtilTest { public class ByteUtilTest {
@ -85,4 +86,19 @@ public class ByteUtilTest {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
} }
@Test
public void testIntToByte() {
for (int i = 0; i < 1000; i++) {
int randomInt = RandomUtil.randomInt();
byte[] expected = ByteBuffer.allocate(4).putInt(randomInt).array();
byte[] actual = ByteUtil.intToBytes(randomInt);
assertArrayEquals(expected, actual);
assertEquals(randomInt, ByteUtil.bytesToInt(expected));
assertEquals(randomInt, ByteUtil.bytesToInt(actual));
}
}
} }

View File

@ -182,6 +182,12 @@ public final class ActiveMQDefaultConfiguration {
// Cluster password. It applies to all cluster configurations. // Cluster password. It applies to all cluster configurations.
private static String DEFAULT_CLUSTER_PASSWORD = "CHANGE ME!!"; private static String DEFAULT_CLUSTER_PASSWORD = "CHANGE ME!!";
// Cluster username. It applies to all cluster configurations.
private static String DEFAULT_FEDERATION_USER = "ACTIVEMQ.CLUSTER.ADMIN.USER";
// Cluster password. It applies to all cluster configurations.
private static String DEFAULT_FEDERATION_PASSWORD = "CHANGE ME!!";
// This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked. // This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked.
private static Boolean DEFAULT_MASK_PASSWORD = null; private static Boolean DEFAULT_MASK_PASSWORD = null;
@ -672,6 +678,20 @@ public final class ActiveMQDefaultConfiguration {
return DEFAULT_CLUSTER_PASSWORD; return DEFAULT_CLUSTER_PASSWORD;
} }
/**
* Federation username. It applies to all federation configurations.
*/
public static String getDefaultFederationUser() {
return DEFAULT_CLUSTER_USER;
}
/**
* Federation password. It applies to all federation configurations.
*/
public static String getDefaultFederationPassword() {
return DEFAULT_CLUSTER_PASSWORD;
}
/** /**
* This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked. * This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked.
*/ */

View File

@ -1212,4 +1212,10 @@ public interface Configuration {
* @return * @return
*/ */
List<ActiveMQServerCriticalPlugin> getBrokerCriticalPlugins(); List<ActiveMQServerCriticalPlugin> getBrokerCriticalPlugins();
/**
* @return
*/
List<FederationConfiguration> getFederationConfigurations();
} }

View File

@ -0,0 +1,142 @@
/*
* 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;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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;
public class FederationConfiguration implements Serializable {
private String name;
private Credentials credentials;
private List<FederationUpstreamConfiguration> upstreamConfigurations = new ArrayList<>();
private Map<String, FederationPolicy> federationPolicyMap = new HashMap<>();
private Map<String, FederationTransformerConfiguration> transformerConfigurationMap = new HashMap<>();
public List<FederationUpstreamConfiguration> getUpstreamConfigurations() {
return upstreamConfigurations;
}
public FederationConfiguration addUpstreamConfiguration(FederationUpstreamConfiguration federationUpstreamConfiguration) {
this.upstreamConfigurations.add(federationUpstreamConfiguration);
return this;
}
public FederationConfiguration addFederationPolicy(FederationPolicy federationPolicy) {
federationPolicyMap.put(federationPolicy.getName(), federationPolicy);
return this;
}
public Map<String, FederationPolicy> getFederationPolicyMap() {
return federationPolicyMap;
}
public FederationConfiguration addTransformerConfiguration(FederationTransformerConfiguration transformerConfiguration) {
transformerConfigurationMap.put(transformerConfiguration.getName(), transformerConfiguration);
return this;
}
public Map<String, FederationTransformerConfiguration> getTransformerConfigurationMap() {
return transformerConfigurationMap;
}
public String getName() {
return name;
}
public FederationConfiguration setName(String name) {
this.name = name;
return this;
}
public Credentials getCredentials() {
return credentials;
}
public FederationConfiguration setCredentials(Credentials credentials) {
this.credentials = credentials;
return this;
}
public static class Credentials implements Serializable {
private String user;
private String password;
public String getUser() {
return user;
}
public Credentials setUser(String user) {
this.user = user;
return this;
}
public String getPassword() {
return password;
}
public Credentials setPassword(String password) {
this.password = password;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Credentials)) return false;
Credentials that = (Credentials) o;
return Objects.equals(user, that.user) &&
Objects.equals(password, that.password);
}
@Override
public int hashCode() {
return Objects.hash(user, password);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederationConfiguration)) return false;
FederationConfiguration that = (FederationConfiguration) o;
return Objects.equals(name, that.name) &&
Objects.equals(credentials, that.credentials) &&
Objects.equals(upstreamConfigurations, that.upstreamConfigurations) &&
Objects.equals(federationPolicyMap, that.federationPolicyMap) &&
Objects.equals(transformerConfigurationMap, that.transformerConfigurationMap);
}
@Override
public int hashCode() {
return Objects.hash(name, credentials, upstreamConfigurations, federationPolicyMap, transformerConfigurationMap);
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.HashSet;
import java.util.Objects;
import java.util.Set;
public class FederationAddressPolicyConfiguration implements FederationPolicy<FederationAddressPolicyConfiguration>, Serializable {
private String name;
private Set<Matcher> includes = new HashSet<>();
private Set<Matcher> excludes = new HashSet<>();
private Boolean autoDelete;
private Long autoDeleteDelay;
private Long autoDeleteMessageCount;
private int maxHops;
private String transformerRef;
@Override
public String getName() {
return name;
}
@Override
public FederationAddressPolicyConfiguration setName(String name) {
this.name = name;
return this;
}
public Set<Matcher> getIncludes() {
return includes;
}
public Set<Matcher> getExcludes() {
return excludes;
}
public FederationAddressPolicyConfiguration addInclude(Matcher include) {
includes.add(include);
return this;
}
public FederationAddressPolicyConfiguration addExclude(Matcher exclude) {
excludes.add(exclude);
return this;
}
public int getMaxHops() {
return maxHops;
}
public FederationAddressPolicyConfiguration setMaxHops(int maxHops) {
this.maxHops = maxHops;
return this;
}
public Long getAutoDeleteMessageCount() {
return autoDeleteMessageCount;
}
public FederationAddressPolicyConfiguration setAutoDeleteMessageCount(Long autoDeleteMessageCount) {
this.autoDeleteMessageCount = autoDeleteMessageCount;
return this;
}
public Long getAutoDeleteDelay() {
return autoDeleteDelay;
}
public FederationAddressPolicyConfiguration setAutoDeleteDelay(Long autoDeleteDelay) {
this.autoDeleteDelay = autoDeleteDelay;
return this;
}
public Boolean getAutoDelete() {
return autoDelete;
}
public FederationAddressPolicyConfiguration setAutoDelete(Boolean autoDelete) {
this.autoDelete = autoDelete;
return this;
}
public String getTransformerRef() {
return transformerRef;
}
public FederationAddressPolicyConfiguration setTransformerRef(String transformerRef) {
this.transformerRef = transformerRef;
return this;
}
public static class Matcher implements Serializable {
private String addressMatch;
public String getAddressMatch() {
return addressMatch;
}
public Matcher setAddressMatch(String addressMatch) {
this.addressMatch = addressMatch;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Matcher)) return false;
Matcher matcher = (Matcher) o;
return Objects.equals(addressMatch, matcher.addressMatch);
}
@Override
public int hashCode() {
return Objects.hash(addressMatch);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederationAddressPolicyConfiguration)) return false;
FederationAddressPolicyConfiguration that = (FederationAddressPolicyConfiguration) o;
return maxHops == that.maxHops &&
Objects.equals(name, that.name) &&
Objects.equals(includes, that.includes) &&
Objects.equals(excludes, that.excludes) &&
Objects.equals(autoDelete, that.autoDelete) &&
Objects.equals(autoDeleteDelay, that.autoDeleteDelay) &&
Objects.equals(autoDeleteMessageCount, that.autoDeleteMessageCount) &&
Objects.equals(transformerRef, that.transformerRef);
}
@Override
public int hashCode() {
return Objects.hash(name, includes, excludes, autoDelete, autoDeleteDelay, autoDeleteMessageCount, maxHops, transformerRef);
}
}

View File

@ -0,0 +1,117 @@
/*
* 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<String> 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<String> getStaticConnectors() {
return staticConnectors;
}
public FederationConnectionConfiguration setStaticConnectors(List<String> 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);
}
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.config.federation;
public interface FederationPolicy<T> {
String getName();
T setName(String name);
}

View File

@ -0,0 +1,68 @@
/*
* 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.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class FederationPolicySet implements FederationPolicy<FederationPolicySet>, Serializable {
private String name;
private Set<String> policyRefs = new HashSet<>();
@Override
public String getName() {
return name;
}
@Override
public FederationPolicySet setName(String name) {
this.name = name;
return this;
}
public Set<String> getPolicyRefs() {
return policyRefs;
}
public FederationPolicySet addPolicyRef(String name) {
policyRefs.add(name);
return this;
}
public FederationPolicySet addPolicyRefs(Collection<String> name) {
policyRefs.addAll(name);
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederationPolicySet)) return false;
FederationPolicySet that = (FederationPolicySet) o;
return Objects.equals(name, that.name) &&
Objects.equals(policyRefs, that.policyRefs);
}
@Override
public int hashCode() {
return Objects.hash(name, policyRefs);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.HashSet;
import java.util.Objects;
import java.util.Set;
public class FederationQueuePolicyConfiguration implements FederationPolicy<FederationQueuePolicyConfiguration>, Serializable {
private String name;
private boolean includeFederated;
private Set<Matcher> includes = new HashSet<>();
private Set<Matcher> excludes = new HashSet<>();
private Integer priorityAdjustment;
private String transformerRef;
@Override
public String getName() {
return name;
}
@Override
public FederationQueuePolicyConfiguration setName(String name) {
this.name = name;
return this;
}
public Set<Matcher> getIncludes() {
return includes;
}
public Set<Matcher> getExcludes() {
return excludes;
}
public FederationQueuePolicyConfiguration addInclude(Matcher include) {
includes.add(include);
return this;
}
public FederationQueuePolicyConfiguration addExclude(Matcher exclude) {
excludes.add(exclude);
return this;
}
public boolean isIncludeFederated() {
return includeFederated;
}
public FederationQueuePolicyConfiguration setIncludeFederated(boolean includeFederated) {
this.includeFederated = includeFederated;
return this;
}
public Integer getPriorityAdjustment() {
return priorityAdjustment;
}
public FederationQueuePolicyConfiguration setPriorityAdjustment(Integer priorityAdjustment) {
this.priorityAdjustment = priorityAdjustment;
return this;
}
public String getTransformerRef() {
return transformerRef;
}
public FederationQueuePolicyConfiguration setTransformerRef(String transformerRef) {
this.transformerRef = transformerRef;
return this;
}
public static class Matcher implements Serializable {
private String queueMatch;
private String addressMatch;
public String getQueueMatch() {
return queueMatch;
}
public Matcher setQueueMatch(String queueMatch) {
this.queueMatch = queueMatch;
return this;
}
public String getAddressMatch() {
return addressMatch;
}
public Matcher setAddressMatch(String addressMatch) {
this.addressMatch = addressMatch;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Matcher)) return false;
Matcher matcher = (Matcher) o;
return Objects.equals(queueMatch, matcher.queueMatch) &&
Objects.equals(addressMatch, matcher.addressMatch);
}
@Override
public int hashCode() {
return Objects.hash(queueMatch, addressMatch);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederationQueuePolicyConfiguration)) return false;
FederationQueuePolicyConfiguration that = (FederationQueuePolicyConfiguration) o;
return includeFederated == that.includeFederated &&
Objects.equals(name, that.name) &&
Objects.equals(includes, that.includes) &&
Objects.equals(excludes, that.excludes) &&
Objects.equals(priorityAdjustment, that.priorityAdjustment) &&
Objects.equals(transformerRef, that.transformerRef);
}
@Override
public int hashCode() {
return Objects.hash(name, includeFederated, includes, excludes, priorityAdjustment, transformerRef);
}
}

View File

@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.config.federation;
import java.io.Serializable;
import java.util.Objects;
import org.apache.activemq.artemis.core.config.TransformerConfiguration;
public class FederationTransformerConfiguration implements Serializable {
private String name;
private TransformerConfiguration transformerConfiguration;
public FederationTransformerConfiguration(String name, TransformerConfiguration transformerConfiguration) {
this.name = name;
this.transformerConfiguration = transformerConfiguration;
}
public String getName() {
return name;
}
public TransformerConfiguration getTransformerConfiguration() {
return transformerConfiguration;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederationTransformerConfiguration)) return false;
FederationTransformerConfiguration that = (FederationTransformerConfiguration) o;
return Objects.equals(name, that.name) &&
Objects.equals(transformerConfiguration, that.transformerConfiguration);
}
@Override
public int hashCode() {
return Objects.hash(name, transformerConfiguration);
}
}

View File

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.config.federation;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class FederationUpstreamConfiguration implements Serializable {
private String name;
private FederationConnectionConfiguration connectionConfiguration = new FederationConnectionConfiguration();
private Set<String> policyRefs = new HashSet<>();
public String getName() {
return name;
}
public FederationUpstreamConfiguration setName(String name) {
this.name = name;
return this;
}
public Set<String> getPolicyRefs() {
return policyRefs;
}
public FederationUpstreamConfiguration addPolicyRef(String name) {
policyRefs.add(name);
return this;
}
public FederationUpstreamConfiguration addPolicyRefs(Collection<String> names) {
policyRefs.addAll(names);
return this;
}
public FederationConnectionConfiguration getConnectionConfiguration() {
return connectionConfiguration;
}
@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);
}
@Override
public int hashCode() {
return Objects.hash(name, connectionConfiguration, policyRefs);
}
}

View File

@ -42,6 +42,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration; import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration;
import org.apache.activemq.artemis.core.config.FederationConfiguration;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerAddressPlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerAddressPlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBasePlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBasePlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBindingPlugin; import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBindingPlugin;
@ -156,6 +157,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
protected List<ClusterConnectionConfiguration> clusterConfigurations = new ArrayList<>(); protected List<ClusterConnectionConfiguration> clusterConfigurations = new ArrayList<>();
protected List<FederationConfiguration> federationConfigurations = new ArrayList<>();
private List<CoreQueueConfiguration> queueConfigurations = new ArrayList<>(); private List<CoreQueueConfiguration> queueConfigurations = new ArrayList<>();
private List<CoreAddressConfiguration> addressConfigurations = new ArrayList<>(); private List<CoreAddressConfiguration> addressConfigurations = new ArrayList<>();
@ -1520,6 +1523,11 @@ public class ConfigurationImpl implements Configuration, Serializable {
return brokerCriticalPlugins; return brokerCriticalPlugins;
} }
@Override
public List<FederationConfiguration> getFederationConfigurations() {
return federationConfigurations;
}
@Override @Override
public File getBrokerInstance() { public File getBrokerInstance() {
if (artemisInstance != null) { if (artemisInstance != null) {

View File

@ -49,9 +49,14 @@ import org.apache.activemq.artemis.core.config.ConnectorServiceConfiguration;
import org.apache.activemq.artemis.core.config.CoreAddressConfiguration; import org.apache.activemq.artemis.core.config.CoreAddressConfiguration;
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration; import org.apache.activemq.artemis.core.config.CoreQueueConfiguration;
import org.apache.activemq.artemis.core.config.DivertConfiguration; import org.apache.activemq.artemis.core.config.DivertConfiguration;
import org.apache.activemq.artemis.core.config.FederationConfiguration;
import org.apache.activemq.artemis.core.config.ScaleDownConfiguration; import org.apache.activemq.artemis.core.config.ScaleDownConfiguration;
import org.apache.activemq.artemis.core.config.TransformerConfiguration; import org.apache.activemq.artemis.core.config.TransformerConfiguration;
import org.apache.activemq.artemis.core.config.WildcardConfiguration; 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.FederationPolicySet;
import org.apache.activemq.artemis.core.config.federation.FederationQueuePolicyConfiguration;
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.ColocatedPolicyConfiguration;
import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration; import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration;
import org.apache.activemq.artemis.core.config.ha.ReplicaPolicyConfiguration; import org.apache.activemq.artemis.core.config.ha.ReplicaPolicyConfiguration;
@ -509,6 +514,14 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
parseBridgeConfiguration(mfNode, config); parseBridgeConfiguration(mfNode, config);
} }
NodeList fedNodes = e.getElementsByTagName("federation");
for (int i = 0; i < fedNodes.getLength(); i++) {
Element fedNode = (Element) fedNodes.item(i);
parseFederationConfiguration(fedNode, config);
}
NodeList gaNodes = e.getElementsByTagName("grouping-handler"); NodeList gaNodes = e.getElementsByTagName("grouping-handler");
for (int i = 0; i < gaNodes.getLength(); i++) { for (int i = 0; i < gaNodes.getLength(); i++) {
@ -1883,6 +1896,222 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
mainConfig.getBridgeConfigurations().add(config); mainConfig.getBridgeConfigurations().add(config);
} }
private void parseFederationConfiguration(final Element fedNode, final Configuration mainConfig) throws Exception {
FederationConfiguration config = new FederationConfiguration();
String name = fedNode.getAttribute("name");
config.setName(name);
FederationConfiguration.Credentials credentials = new FederationConfiguration.Credentials();
// parsing federation password
String passwordTextFederation = fedNode.getAttribute("password");
if (passwordTextFederation != null && !passwordTextFederation.isEmpty()) {
String resolvedPassword = PasswordMaskingUtil.resolveMask(mainConfig.isMaskPassword(), passwordTextFederation, mainConfig.getPasswordCodec());
credentials.setPassword(resolvedPassword);
}
credentials.setUser(fedNode.getAttribute("user"));
config.setCredentials(credentials);
NodeList children = fedNode.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("upstream")) {
config.addUpstreamConfiguration(getUpstream((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));
}
}
mainConfig.getFederationConfigurations().add(config);
}
private FederationQueuePolicyConfiguration getQueuePolicy(Element policyNod, final Configuration mainConfig) throws Exception {
FederationQueuePolicyConfiguration config = new FederationQueuePolicyConfiguration();
config.setName(policyNod.getAttribute("name"));
NamedNodeMap attributes = policyNod.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node item = attributes.item(i);
if (item.getNodeName().equals("include-federated")) {
config.setIncludeFederated(Boolean.parseBoolean(item.getNodeValue()));
} else if (item.getNodeName().equals("priority-adjustment")) {
int priorityAdjustment = Integer.parseInt(item.getNodeValue());
config.setPriorityAdjustment(priorityAdjustment);
} else if (item.getNodeName().equals("transformer-ref")) {
String transformerRef = item.getNodeValue();
config.setTransformerRef(transformerRef);
}
}
NodeList children = policyNod.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("include")) {
config.addInclude(getQueueMatcher((Element) child));
} else if (child.getNodeName().equals("exclude")) {
config.addExclude(getQueueMatcher((Element) child));
}
}
return config;
}
private FederationQueuePolicyConfiguration.Matcher getQueueMatcher(Element child) {
FederationQueuePolicyConfiguration.Matcher matcher = new FederationQueuePolicyConfiguration.Matcher();
matcher.setAddressMatch(child.getAttribute("queue-match"));
matcher.setAddressMatch(child.getAttribute("address-match"));
return matcher;
}
private FederationAddressPolicyConfiguration getAddressPolicy(Element policyNod, final Configuration mainConfig) throws Exception {
FederationAddressPolicyConfiguration config = new FederationAddressPolicyConfiguration();
config.setName(policyNod.getAttribute("name"));
NamedNodeMap attributes = policyNod.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node item = attributes.item(i);
if (item.getNodeName().equals("max-consumers")) {
int maxConsumers = Integer.parseInt(item.getNodeValue());
Validators.MINUS_ONE_OR_GE_ZERO.validate(item.getNodeName(), maxConsumers);
config.setMaxHops(maxConsumers);
} else if (item.getNodeName().equals("auto-delete")) {
boolean autoDelete = Boolean.parseBoolean(item.getNodeValue());
config.setAutoDelete(autoDelete);
} else if (item.getNodeName().equals("auto-delete-delay")) {
long autoDeleteDelay = Long.parseLong(item.getNodeValue());
Validators.GE_ZERO.validate("auto-delete-delay", autoDeleteDelay);
config.setAutoDeleteDelay(autoDeleteDelay);
} else if (item.getNodeName().equals("auto-delete-message-count")) {
long autoDeleteMessageCount = Long.parseLong(item.getNodeValue());
Validators.MINUS_ONE_OR_GE_ZERO.validate("auto-delete-message-count", autoDeleteMessageCount);
config.setAutoDeleteMessageCount(autoDeleteMessageCount);
} else if (item.getNodeName().equals("transformer-ref")) {
String transformerRef = item.getNodeValue();
config.setTransformerRef(transformerRef);
}
}
NodeList children = policyNod.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("include")) {
config.addInclude(getAddressMatcher((Element) child));
} else if (child.getNodeName().equals("exclude")) {
config.addExclude(getAddressMatcher((Element) child));
}
}
return config;
}
private FederationAddressPolicyConfiguration.Matcher getAddressMatcher(Element child) {
FederationAddressPolicyConfiguration.Matcher matcher = new FederationAddressPolicyConfiguration.Matcher();
matcher.setAddressMatch(child.getAttribute("address-match"));
return matcher;
}
private FederationPolicySet getPolicySet(Element policySetNode, final Configuration mainConfig) throws Exception {
FederationPolicySet config = new FederationPolicySet();
config.setName(policySetNode.getAttribute("name"));
NodeList children = policySetNode.getChildNodes();
List<String> policyRefs = new ArrayList<>();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("policy")) {
policyRefs.add(((Element)child).getAttribute("ref"));
}
}
config.addPolicyRefs(policyRefs);
return config;
}
private FederationUpstreamConfiguration getUpstream(Element upstreamNode, final Configuration mainConfig) throws Exception {
FederationUpstreamConfiguration config = new FederationUpstreamConfiguration();
String name = upstreamNode.getAttribute("name");
config.setName(name);
// parsing federation password
String passwordTextFederation = upstreamNode.getAttribute("password");
if (passwordTextFederation != null && !passwordTextFederation.isEmpty()) {
String resolvedPassword = PasswordMaskingUtil.resolveMask(mainConfig.isMaskPassword(), passwordTextFederation, mainConfig.getPasswordCodec());
config.getConnectionConfiguration().setPassword(resolvedPassword);
}
config.getConnectionConfiguration().setUsername(upstreamNode.getAttribute("user"));
NamedNodeMap attributes = upstreamNode.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node item = attributes.item(i);
if (item.getNodeName().equals("priority-adjustment")) {
int priorityAdjustment = Integer.parseInt(item.getNodeValue());
config.getConnectionConfiguration().setPriorityAdjustment(priorityAdjustment);
}
}
boolean ha = getBoolean(upstreamNode, "ha", false);
long circuitBreakerTimeout = getLong(upstreamNode, "circuit-breaker-timeout", config.getConnectionConfiguration().getCircuitBreakerTimeout(), Validators.MINUS_ONE_OR_GE_ZERO);
List<String> staticConnectorNames = new ArrayList<>();
String discoveryGroupName = null;
NodeList children = upstreamNode.getChildNodes();
List<String> policyRefs = new ArrayList<>();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
if (child.getNodeName().equals("discovery-group-ref")) {
discoveryGroupName = child.getAttributes().getNamedItem("discovery-group-name").getNodeValue();
} else if (child.getNodeName().equals("static-connectors")) {
getStaticConnectors(staticConnectorNames, child);
} else if (child.getNodeName().equals("policy")) {
policyRefs.add(((Element)child).getAttribute("ref"));
}
}
config.addPolicyRefs(policyRefs);
config.getConnectionConfiguration()
.setCircuitBreakerTimeout(circuitBreakerTimeout)
.setHA(ha);
if (!staticConnectorNames.isEmpty()) {
config.getConnectionConfiguration().setStaticConnectors(staticConnectorNames);
} else {
config.getConnectionConfiguration().setDiscoveryGroupName(discoveryGroupName);
}
return config;
}
private void getStaticConnectors(List<String> staticConnectorNames, Node child) { private void getStaticConnectors(List<String> staticConnectorNames, Node child) {
NodeList children2 = ((Element) child).getElementsByTagName("connector-ref"); NodeList children2 = ((Element) child).getElementsByTagName("connector-ref");

View File

@ -16,8 +16,10 @@
*/ */
package org.apache.activemq.artemis.core.filter; package org.apache.activemq.artemis.core.filter;
import java.util.Map;
import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.selector.filter.Filterable;
public interface Filter { public interface Filter {
@ -33,5 +35,13 @@ public interface Filter {
boolean match(Message message); boolean match(Message message);
boolean match(Map<String, String> map);
boolean match(Filterable filterable);
SimpleString getFilterString(); SimpleString getFilterString();
static SimpleString toFilterString(Filter filter) {
return filter == null ? null : filter.getFilterString();
}
} }

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.artemis.core.filter.impl; package org.apache.activemq.artemis.core.filter.impl;
import java.util.Map;
import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.FilterConstants; import org.apache.activemq.artemis.api.core.FilterConstants;
import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.Message;
@ -23,12 +24,14 @@ import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.filter.Filter; import org.apache.activemq.artemis.core.filter.Filter;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.federation.address.FederatedAddress;
import org.apache.activemq.artemis.selector.filter.BooleanExpression; import org.apache.activemq.artemis.selector.filter.BooleanExpression;
import org.apache.activemq.artemis.selector.filter.FilterException; import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.filter.Filterable; import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.selector.impl.SelectorParser; import org.apache.activemq.artemis.selector.impl.SelectorParser;
import static org.apache.activemq.artemis.api.core.FilterConstants.NATIVE_MESSAGE_ID; import static org.apache.activemq.artemis.api.core.FilterConstants.NATIVE_MESSAGE_ID;
import org.apache.activemq.artemis.utils.ByteUtil;
/** /**
* This class implements an ActiveMQ Artemis filter * This class implements an ActiveMQ Artemis filter
@ -103,10 +106,20 @@ public class FilterImpl implements Filter {
} }
@Override @Override
public synchronized boolean match(final Message message) { public boolean match(final Message message) {
return match(new FilterableServerMessage(message));
}
@Override
public boolean match(final Map<String, String> map) {
return match(new FilterableMap(map));
}
@Override
public synchronized boolean match(final Filterable filterable) {
try { try {
boolean result = booleanExpression.matches(new FilterableServerMessage(message)); return booleanExpression.matches(filterable);
return result;
} catch (Exception e) { } catch (Exception e) {
ActiveMQServerLogger.LOGGER.invalidFilter(sfilterString); ActiveMQServerLogger.LOGGER.invalidFilter(sfilterString);
if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) { if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
@ -183,6 +196,30 @@ public class FilterImpl implements Filter {
} }
} }
private static class FilterableMap implements Filterable {
private final Map<String, String> map;
private FilterableMap(Map<String, String> map) {
this.map = map;
}
@Override
public <T> T getBodyAs(Class<T> type) throws FilterException {
return null;
}
@Override
public Object getProperty(SimpleString name) {
return map.get(name.toString());
}
@Override
public Object getLocalConnectionId() {
return null;
}
}
private static class FilterableServerMessage implements Filterable { private static class FilterableServerMessage implements Filterable {
private final Message message; private final Message message;
@ -197,6 +234,10 @@ public class FilterImpl implements Filter {
if (id.startsWith(FilterConstants.ACTIVEMQ_PREFIX)) { if (id.startsWith(FilterConstants.ACTIVEMQ_PREFIX)) {
result = getHeaderFieldValue(message, id); result = getHeaderFieldValue(message, id);
} }
if (id.startsWith(FederatedAddress.HDR_HOPS)) {
byte[] bytes = message.getExtraBytesProperty(FederatedAddress.HDR_HOPS);
result = bytes == null ? null : ByteUtil.bytesToInt(bytes);
}
if (result == null) { if (result == null) {
result = message.getObjectProperty(id); result = message.getObjectProperty(id);
} }

View File

@ -51,6 +51,7 @@ import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.ServerConsumer; import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.utils.JsonLoader; import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.collections.LinkedListIterator; import org.apache.activemq.artemis.utils.collections.LinkedListIterator;
@ -853,6 +854,16 @@ public class QueueControlImpl extends AbstractControl implements QueueControl {
return message.getMessageID() == messageID; return message.getMessageID() == messageID;
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
@Override @Override
public SimpleString getFilterString() { public SimpleString getFilterString() {
return new SimpleString("custom filter for MESSAGEID= messageID"); return new SimpleString("custom filter for MESSAGEID= messageID");

View File

@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.security.SecurityAuth;
import org.apache.activemq.artemis.core.security.SecurityStore; 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.ClusterManager;
import org.apache.activemq.artemis.core.server.cluster.ha.HAPolicy; 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.group.GroupingHandler; 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.Activation;
import org.apache.activemq.artemis.core.server.impl.AddressInfo; import org.apache.activemq.artemis.core.server.impl.AddressInfo;
@ -62,6 +63,7 @@ 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.ActiveMQServerMessagePlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin; 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.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.server.reload.ReloadManager;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
@ -502,6 +504,8 @@ public interface ActiveMQServer extends ServiceComponent {
ReplicationManager getReplicationManager(); ReplicationManager getReplicationManager();
FederationManager getFederationManager();
void deployDivert(DivertConfiguration config) throws Exception; void deployDivert(DivertConfiguration config) throws Exception;
void destroyDivert(SimpleString name) throws Exception; void destroyDivert(SimpleString name) throws Exception;
@ -512,6 +516,10 @@ public interface ActiveMQServer extends ServiceComponent {
void destroyBridge(String name) throws Exception; void destroyBridge(String name) throws Exception;
void deployFederation(FederationConfiguration config) throws Exception;
void undeployFederation(String name) throws Exception;
ServerSession getSessionByID(String sessionID); ServerSession getSessionByID(String sessionID);
void threadDump(); void threadDump();

View File

@ -1617,6 +1617,18 @@ public interface ActiveMQServerLogger extends BasicLogger {
@Message(id = 222278, value = "Unable to extract GroupSequence from message", format = Message.Format.MESSAGE_FORMAT) @Message(id = 222278, value = "Unable to extract GroupSequence from message", format = Message.Format.MESSAGE_FORMAT)
void unableToExtractGroupSequence(@Cause Throwable e); void unableToExtractGroupSequence(@Cause Throwable e);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 222279, value = "Federation upstream {0} policy ref {1} could not be resolved in federation configuration", format = Message.Format.MESSAGE_FORMAT)
void federationCantFindPolicyRef(String upstreamName, String policyRef);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 222280, value = "Federation upstream {0} policy ref {1} is of unknown type in federation configuration", format = Message.Format.MESSAGE_FORMAT)
void federationUnknownPolicyType(String upstreamName, String policyRef);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 222281, value = "Federation upstream {0} policy ref {1} are too self referential, avoiding stack overflow , ", format = Message.Format.MESSAGE_FORMAT)
void federationAvoidStackOverflowPolicyRef(String upstreamName, String policyRef);
@LogMessage(level = Logger.Level.ERROR) @LogMessage(level = Logger.Level.ERROR)
@Message(id = 224000, value = "Failure in initialisation", format = Message.Format.MESSAGE_FORMAT) @Message(id = 224000, value = "Failure in initialisation", format = Message.Format.MESSAGE_FORMAT)
void initializationError(@Cause Throwable e); void initializationError(@Cause Throwable e);

View File

@ -109,6 +109,17 @@ public interface ServiceRegistry {
void addBridgeTransformer(String name, Transformer transformer); void addBridgeTransformer(String name, Transformer transformer);
/**
* Get an instance of org.apache.activemq.artemis.core.server.transformer.Transformer for federation
*
* @param name the name of bridge for which the transformer will be used
* @param transformerConfiguration the transformer configuration
* @return
*/
Transformer getFederationTransformer(String name, TransformerConfiguration transformerConfiguration);
void addFederationTransformer(String name, Transformer transformer);
/** /**
* Get an instance of org.apache.activemq.artemis.spi.core.remoting.AcceptorFactory * Get an instance of org.apache.activemq.artemis.spi.core.remoting.AcceptorFactory
* *

View File

@ -0,0 +1,126 @@
/**
* 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 org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationTransformerConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.federation.FederatedQueueConsumer.ClientSessionCallback;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBasePlugin;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
public abstract class FederatedAbstract implements ActiveMQServerBasePlugin {
private static final WildcardConfiguration DEFAULT_WILDCARD_CONFIGURATION = new WildcardConfiguration();
protected final Federation federation;
protected ActiveMQServer server;
protected FederationUpstream upstream;
protected WildcardConfiguration wildcardConfiguration;
protected final Map<FederatedConsumerKey, FederatedQueueConsumer> remoteQueueConsumers = new HashMap<>();
private boolean started;
public FederatedAbstract(Federation federation, ActiveMQServer server, FederationUpstream upstream) {
this.federation = federation;
this.server = server;
this.upstream = upstream;
this.wildcardConfiguration = server.getConfiguration().getWildcardConfiguration() == null ? DEFAULT_WILDCARD_CONFIGURATION : server.getConfiguration().getWildcardConfiguration();
}
/**
* The plugin has been registered with the server
*
* @param server The ActiveMQServer the plugin has been registered to
*/
@Override
public void registered(ActiveMQServer server) {
start();
}
/**
* The plugin has been unregistered with the server
*
* @param server The ActiveMQServer the plugin has been unregistered to
*/
@Override
public void unregistered(ActiveMQServer server) {
stop();
}
public synchronized void stop() {
for (FederatedQueueConsumer remoteQueueConsumer : remoteQueueConsumers.values()) {
remoteQueueConsumer.close();
}
remoteQueueConsumers.clear();
started = false;
}
public synchronized void start() {
started = true;
}
public boolean isStarted() {
return started;
}
protected Transformer mergeTransformers(Transformer left, Transformer right) {
if (left == null) {
return right;
} else if (right == null) {
return left;
} else {
return (m) -> left.transform(right.transform(m));
}
}
protected Transformer getTransformer(String transformerRef) {
Transformer transformer = null;
if (transformerRef != null) {
FederationTransformerConfiguration federationTransformerConfiguration = federation.getConfig().getTransformerConfigurationMap().get(transformerRef);
if (federationTransformerConfiguration != null) {
transformer = server.getServiceRegistry().getFederationTransformer(federationTransformerConfiguration.getName(), federationTransformerConfiguration.getTransformerConfiguration());
}
}
return transformer;
}
public synchronized void createRemoteConsumer(FederatedConsumerKey key, Transformer transformer, ClientSessionCallback callback) {
if (started) {
FederatedQueueConsumer remoteQueueConsumer = remoteQueueConsumers.get(key);
if (remoteQueueConsumer == null) {
remoteQueueConsumer = new FederatedQueueConsumer(federation, server, transformer, key, upstream, callback);
remoteQueueConsumer.start();
remoteQueueConsumers.put(key, remoteQueueConsumer);
}
remoteQueueConsumer.incrementCount();
}
}
public synchronized void removeRemoteConsumer(FederatedConsumerKey key) {
FederatedQueueConsumer remoteQueueConsumer = remoteQueueConsumers.get(key);
if (remoteQueueConsumer != null) {
if (remoteQueueConsumer.decrementCount() <= 0) {
remoteQueueConsumer.close();
remoteQueueConsumers.remove(key);
}
}
}
}

View File

@ -0,0 +1,38 @@
/**
* 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 org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
public interface FederatedConsumerKey {
SimpleString getQueueName();
SimpleString getAddress();
SimpleString getFqqn();
RoutingType getRoutingType();
SimpleString getFilterString();
SimpleString getQueueFilterString();
int getPriority();
}

View File

@ -0,0 +1,202 @@
/**
* 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.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.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
public class FederatedQueueConsumer implements MessageHandler, SessionFailureListener {
public static final String FEDERATION_NAME = "federation-name";
public static final String FEDERATION_UPSTREAM_NAME = "federation-upstream-name";
private final ActiveMQServer server;
private final Federation federation;
private final FederatedConsumerKey key;
private final Transformer transformer;
private final FederationUpstream upstream;
private final AtomicInteger count = new AtomicInteger();
private final ScheduledExecutorService scheduledExecutorService;
private final int intialConnectDelayMultiplier = 2;
private final int intialConnectDelayMax = 30;
private final ClientSessionCallback clientSessionCallback;
private ClientSessionFactory clientSessionFactory;
private ClientSession clientSession;
private ClientConsumer clientConsumer;
public FederatedQueueConsumer(Federation federation, ActiveMQServer server, Transformer transformer, FederatedConsumerKey key, FederationUpstream upstream, ClientSessionCallback clientSessionCallback) {
this.federation = federation;
this.server = server;
this.key = key;
this.transformer = transformer;
this.upstream = upstream;
this.scheduledExecutorService = server.getScheduledPool();
this.clientSessionCallback = clientSessionCallback;
}
public int incrementCount() {
return count.incrementAndGet();
}
public int decrementCount() {
return count.decrementAndGet();
}
public void start() {
scheduleConnect(0);
}
private void scheduleConnect(int delay) {
scheduledExecutorService.schedule(() -> {
try {
connect();
} catch (Exception e) {
scheduleConnect(getNextDelay(delay, intialConnectDelayMultiplier, intialConnectDelayMax));
}
}, delay, TimeUnit.SECONDS);
}
static int getNextDelay(int delay, int delayMultiplier, int delayMax) {
int nextDelay;
if (delay == 0) {
nextDelay = 1;
} else {
nextDelay = delay * delayMultiplier;
if (nextDelay > delayMax) {
nextDelay = delayMax;
}
}
return nextDelay;
}
private void connect() throws Exception {
try {
if (clientConsumer == null) {
synchronized (this) {
this.clientSessionFactory = 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());
this.clientSession.addMetaData(FEDERATION_UPSTREAM_NAME, upstream.getName().toString());
this.clientSession.start();
if (clientSessionCallback != null) {
clientSessionCallback.callback(clientSession);
}
if (clientSession.queueQuery(key.getQueueName()).isExists()) {
this.clientConsumer = clientSession.createConsumer(key.getQueueName(), key.getFilterString(), key.getPriority(), false);
this.clientConsumer.setMessageHandler(this);
} else {
throw new ActiveMQNonExistentQueueException("Queue " + key.getQueueName() + " does not exist on remote");
}
}
}
} catch (Exception e) {
try {
if (clientSessionFactory != null) {
clientSessionFactory.cleanup();
}
disconnect();
} catch (ActiveMQException ignored) {
}
throw e;
}
}
public void close() {
scheduleDisconnect(0);
}
private void scheduleDisconnect(int delay) {
scheduledExecutorService.schedule(() -> {
try {
disconnect();
} catch (Exception ignored) {
}
}, delay, TimeUnit.SECONDS);
}
private void disconnect() throws ActiveMQException {
if (clientConsumer != null) {
clientConsumer.close();
}
if (clientSession != null) {
clientSession.close();
}
if (clientSessionFactory != null) {
clientSessionFactory.close();
}
clientConsumer = null;
clientSession = null;
clientSessionFactory = null;
}
@Override
public void onMessage(ClientMessage clientMessage) {
try {
Message message = transformer == null ? clientMessage : transformer.transform(clientMessage);
if (message != null) {
server.getPostOffice().route(message, true);
}
clientMessage.acknowledge();
} catch (Exception e) {
try {
clientSession.rollback();
} catch (ActiveMQException e1) {
}
}
}
@Override
public void connectionFailed(ActiveMQException exception, boolean failedOver) {
connectionFailed(exception, failedOver, null);
}
@Override
public void connectionFailed(ActiveMQException exception, boolean failedOver, String scaleDownTargetNodeID) {
try {
clientSessionFactory.cleanup();
clientSessionFactory.close();
clientConsumer = null;
clientSession = null;
clientSessionFactory = null;
} catch (Throwable dontCare) {
}
start();
}
@Override
public void beforeReconnect(ActiveMQException exception) {
}
public interface ClientSessionCallback {
void callback(ClientSession clientSession) throws ActiveMQException;
}
}

View File

@ -0,0 +1,159 @@
/**
* 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.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.FederationPolicy;
import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
public class Federation {
private final ActiveMQServer server;
private final SimpleString name;
private final Map<String, FederationUpstream> upstreams = new HashMap<>();
private final FederationConfiguration config;
private FederationManager.State state;
enum State {
STOPPED,
STOPPING,
/**
* Deployed means {@link FederationManager#deploy()} was called but
* {@link FederationManager#start()} was not called.
* <p>
* We need the distinction if {@link FederationManager#stop()} is called before 'start'. As
* otherwise we would leak locators.
*/
DEPLOYED, STARTED,
}
public Federation(final ActiveMQServer server, final FederationConfiguration config) {
this.server = server;
this.config = config;
Objects.requireNonNull(config.getName());
this.name = SimpleString.toSimpleString(config.getName());
}
public synchronized void start() throws ActiveMQException {
if (state == FederationManager.State.STARTED) return;
deploy();
for (FederationUpstream connection : upstreams.values()) {
connection.start();
}
state = FederationManager.State.STARTED;
}
public synchronized void stop() {
if (state == FederationManager.State.STOPPED) return;
state = FederationManager.State.STOPPING;
for (FederationUpstream connection : upstreams.values()) {
connection.stop();
}
upstreams.clear();
state = FederationManager.State.STOPPED;
}
public synchronized void deploy() throws ActiveMQException {
for (FederationUpstreamConfiguration upstreamConfiguration : config.getUpstreamConfigurations()) {
deploy(upstreamConfiguration, config.getFederationPolicyMap());
}
if (state != FederationManager.State.STARTED) {
state = FederationManager.State.DEPLOYED;
}
}
public boolean isStarted() {
return state == FederationManager.State.STARTED;
}
public synchronized boolean undeploy(String name) {
FederationUpstream federationConnection = upstreams.remove(name);
if (federationConnection != null) {
federationConnection.stop();
}
return true;
}
public synchronized boolean deploy(FederationUpstreamConfiguration upstreamConfiguration, Map<String, FederationPolicy> federationPolicyMap) throws ActiveMQException {
String name = upstreamConfiguration.getName();
FederationUpstream upstream = upstreams.get(name);
//If connection has changed we will need to do a full undeploy and redeploy.
if (upstream == null) {
undeploy(name);
upstream = deploy(name, upstreamConfiguration);
} else if (!upstream.getConnection().getConfig().equals(upstreamConfiguration.getConnectionConfiguration())) {
undeploy(name);
upstream = deploy(name, upstreamConfiguration);
}
upstream.deploy(upstreamConfiguration.getPolicyRefs(), federationPolicyMap);
return true;
}
private synchronized FederationUpstream deploy(String name, FederationUpstreamConfiguration upstreamConfiguration) {
FederationUpstream upstream = new FederationUpstream(server, this, name, upstreamConfiguration);
upstreams.put(name, upstream);
if (state == FederationManager.State.STARTED) {
upstream.start();
}
return upstream;
}
public FederationUpstream get(String name) {
return upstreams.get(name);
}
public void register(FederatedAbstract federatedAbstract) {
server.registerBrokerPlugin(federatedAbstract);
}
public void unregister(FederatedAbstract federatedAbstract) {
server.unRegisterBrokerPlugin(federatedAbstract);
}
String getFederationPassword() {
return config.getCredentials() == null ? null : config.getCredentials().getPassword();
}
String getFederationUser() {
return config.getCredentials() == null ? null : config.getCredentials().getUser();
}
public FederationConfiguration getConfig() {
return config;
}
public SimpleString getName() {
return name;
}
}

View File

@ -0,0 +1,137 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.server.federation;
import org.apache.activemq.artemis.api.core.ActiveMQSessionCreationException;
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.config.federation.FederationConnectionConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
public class FederationConnection {
private final FederationConnectionConfiguration config;
private final ServerLocator serverLocator;
private final long circuitBreakerTimeout;
private volatile ClientSessionFactory clientSessionFactory;
private volatile boolean started;
public FederationConnection(Configuration configuration, String name, FederationConnectionConfiguration config) {
this.config = config;
this.circuitBreakerTimeout = config.getCircuitBreakerTimeout();
if (config.getDiscoveryGroupName() != null) {
DiscoveryGroupConfiguration discoveryGroupConfiguration = configuration.getDiscoveryGroupConfigurations().get(config.getDiscoveryGroupName());
if (discoveryGroupConfiguration == null) {
ActiveMQServerLogger.LOGGER.bridgeNoDiscoveryGroup(config.getDiscoveryGroupName());
serverLocator = null;
return;
}
if (config.isHA()) {
serverLocator = ActiveMQClient.createServerLocatorWithHA(discoveryGroupConfiguration);
} else {
serverLocator = ActiveMQClient.createServerLocatorWithoutHA(discoveryGroupConfiguration);
}
} else {
TransportConfiguration[] tcConfigs = configuration.getTransportConfigurations(config.getStaticConnectors());
if (tcConfigs == null) {
ActiveMQServerLogger.LOGGER.bridgeCantFindConnectors(name);
serverLocator = null;
return;
}
if (config.isHA()) {
serverLocator = ActiveMQClient.createServerLocatorWithHA(tcConfigs);
} else {
serverLocator = ActiveMQClient.createServerLocatorWithoutHA(tcConfigs);
}
}
}
public synchronized void start() {
started = true;
}
public synchronized void stop() {
started = false;
ClientSessionFactory clientSessionFactory = this.clientSessionFactory;
if (clientSessionFactory != null) {
clientSessionFactory.cleanup();
clientSessionFactory.close();
this.clientSessionFactory = null;
}
}
public boolean isStarted() {
return started;
}
public final ClientSessionFactory clientSessionFactory() throws Exception {
ClientSessionFactory clientSessionFactory = this.clientSessionFactory;
if (started) {
if (clientSessionFactory != null && !clientSessionFactory.isClosed()) {
return clientSessionFactory;
} else {
return circuitBreakerCreateClientSessionFactory();
}
} else {
throw new ActiveMQSessionCreationException();
}
}
public FederationConnectionConfiguration getConfig() {
return config;
}
private Exception circuitBreakerException;
private long lastCreateClientSessionFactoryExceptionTimestamp;
private synchronized ClientSessionFactory circuitBreakerCreateClientSessionFactory() throws Exception {
if (circuitBreakerTimeout < 0 || circuitBreakerException == null || lastCreateClientSessionFactoryExceptionTimestamp < System.currentTimeMillis()) {
try {
circuitBreakerException = null;
return createClientSessionFactory();
} catch (Exception e) {
circuitBreakerException = e;
lastCreateClientSessionFactoryExceptionTimestamp = System.currentTimeMillis() + circuitBreakerTimeout;
throw e;
}
} else {
throw circuitBreakerException;
}
}
private synchronized ClientSessionFactory createClientSessionFactory() throws Exception {
ClientSessionFactory clientSessionFactory = this.clientSessionFactory;
if (clientSessionFactory != null && !clientSessionFactory.isClosed()) {
return clientSessionFactory;
} else {
clientSessionFactory = serverLocator.createSessionFactory();
this.clientSessionFactory = clientSessionFactory;
return clientSessionFactory;
}
}
}

View File

@ -0,0 +1,135 @@
/**
* 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.ActiveMQException;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.config.FederationConfiguration;
public class FederationManager implements ActiveMQComponent {
private final ActiveMQServer server;
private Map<String, Federation> federations = new HashMap<>();
private State state;
enum State {
STOPPED,
STOPPING,
/**
* Deployed means {@link FederationManager#deploy()} was called but
* {@link FederationManager#start()} was not called.
* <p>
* We need the distinction if {@link FederationManager#stop()} is called before 'start'. As
* otherwise we would leak locators.
*/
DEPLOYED, STARTED,
}
public FederationManager(final ActiveMQServer server) {
this.server = server;
}
@Override
public synchronized void start() throws ActiveMQException {
if (state == State.STARTED) return;
deploy();
for (Federation federation : federations.values()) {
federation.start();
}
state = State.STARTED;
}
@Override
public synchronized void stop() {
if (state == State.STOPPED) return;
state = State.STOPPING;
for (Federation federation : federations.values()) {
federation.stop();
}
federations.clear();
state = State.STOPPED;
}
@Override
public boolean isStarted() {
return state == State.STARTED;
}
public synchronized void deploy() throws ActiveMQException {
for (FederationConfiguration federationConfiguration : server.getConfiguration().getFederationConfigurations()) {
deploy(federationConfiguration);
}
if (state != State.STARTED) {
state = State.DEPLOYED;
}
}
public synchronized boolean undeploy(String name) {
Federation federation = federations.remove(name);
if (federation != null) {
federation.stop();
}
return true;
}
public synchronized boolean deploy(FederationConfiguration federationConfiguration) throws ActiveMQException {
Federation federation = federations.get(federationConfiguration.getName());
if (federation == null) {
federation = newFederation(federationConfiguration);
} else if (!Objects.equals(federation.getConfig().getCredentials(), federationConfiguration.getCredentials())) {
undeploy(federationConfiguration.getName());
federation = newFederation(federationConfiguration);
}
federation.deploy();
return true;
}
private synchronized Federation newFederation(FederationConfiguration federationConfiguration) throws ActiveMQException {
Federation federation = new Federation(server, federationConfiguration);
federations.put(federationConfiguration.getName(), federation);
if (state == State.STARTED) {
federation.start();
}
return federation;
}
public Federation get(String name) {
return federations.get(name);
}
public void register(FederatedAbstract federatedAbstract) {
server.registerBrokerPlugin(federatedAbstract);
}
public void unregister(FederatedAbstract federatedAbstract) {
server.unRegisterBrokerPlugin(federatedAbstract);
}
}

View File

@ -0,0 +1,196 @@
/**
* 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 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;
import org.apache.activemq.artemis.core.config.federation.FederationQueuePolicyConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
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;
private FederationUpstreamConfiguration config;
private Map<String, FederatedQueue> federatedQueueMap = new HashMap<>();
private Map<String, FederatedAddress> 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());
this.config = config;
this.connection = new FederationConnection(server.getConfiguration(), name, config.getConnectionConfiguration());
}
public synchronized void start() {
connection.start();
for (FederatedQueue federatedQueue : federatedQueueMap.values()) {
federatedQueue.start();
}
for (FederatedAddress federatedAddress : federatedAddressMap.values()) {
federatedAddress.start();
}
}
public synchronized void stop() {
for (FederatedAddress federatedAddress : federatedAddressMap.values()) {
federatedAddress.stop();
}
federatedAddressMap.clear();
for (FederatedQueue federatedQueue : federatedQueueMap.values()) {
federatedQueue.stop();
}
federatedQueueMap.clear();
connection.stop();
}
public void deploy(Set<String> policyRefsToDeploy, Map<String, FederationPolicy> policyMap) throws ActiveMQException {
deployPolicyRefs(policyRefsToDeploy, policyMap, 0);
}
private void deployPolicyRefs(Set<String> policyRefsToDeploy, Map<String, FederationPolicy> policyMap, int recursionDepth) throws ActiveMQException {
for (String policyRef : policyRefsToDeploy) {
FederationPolicy policy = policyMap.get(policyRef);
if (policy != null) {
if (policy instanceof FederationPolicySet) {
FederationPolicySet federationPolicySet = (FederationPolicySet) policy;
if (recursionDepth < 10) {
deployPolicyRefs(federationPolicySet.getPolicyRefs(), policyMap, ++recursionDepth);
} else {
ActiveMQServerLogger.LOGGER.federationAvoidStackOverflowPolicyRef(name.toString(), policyRef);
}
} else if (policy instanceof FederationQueuePolicyConfiguration) {
deploy((FederationQueuePolicyConfiguration) policy);
} else if (policy instanceof FederationAddressPolicyConfiguration) {
deploy((FederationAddressPolicyConfiguration) policy);
} else {
ActiveMQServerLogger.LOGGER.federationUnknownPolicyType(name.toString(), policyRef);
}
} else {
ActiveMQServerLogger.LOGGER.federationCantFindPolicyRef(name.toString(), policyRef);
}
}
}
public synchronized boolean deploy(FederationQueuePolicyConfiguration federatedQueueConfig) throws ActiveMQException {
String name = federatedQueueConfig.getName();
FederatedQueue existing = federatedQueueMap.get(name);
if (existing == null || !existing.getConfig().equals(federatedQueueConfig)) {
undeployQueue(name);
FederatedQueue federatedQueue = new FederatedQueue(federation, federatedQueueConfig, server, this);
federatedQueueMap.put(name, federatedQueue);
federation.register(federatedQueue);
if (connection.isStarted()) {
federatedQueue.start();
}
return true;
}
return false;
}
public synchronized boolean deploy(FederationAddressPolicyConfiguration federatedAddressConfig) throws ActiveMQException {
String name = federatedAddressConfig.getName();
FederatedAddress existing = federatedAddressMap.get(name);
if (existing == null || !existing.getConfig().equals(federatedAddressConfig)) {
undeployAddress(name);
FederatedAddress federatedAddress = new FederatedAddress(federation, federatedAddressConfig, server, this);
federatedAddressMap.put(name, federatedAddress);
federation.register(federatedAddress);
if (connection.isStarted()) {
federatedAddress.start();
}
return true;
}
return false;
}
private void undeployAddress(String name) {
FederatedAddress federatedAddress = federatedAddressMap.remove(name);
if (federatedAddress != null) {
federatedAddress.stop();
federation.unregister(federatedAddress);
}
}
private void undeployQueue(String name) {
FederatedQueue federatedQueue = federatedQueueMap.remove(name);
if (federatedQueue != null) {
federatedQueue.stop();
federation.unregister(federatedQueue);
}
}
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();
}
}

View File

@ -0,0 +1,218 @@
/**
* 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.address;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
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;
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.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;
import org.apache.activemq.artemis.core.server.federation.FederationUpstream;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
import org.apache.activemq.artemis.core.settings.impl.Match;
import org.apache.activemq.artemis.utils.ByteUtil;
/**
* Federated Address, replicate messages from the remote brokers address to itself.
*
* Only when a queue exists on the local broker do we replicate, this is to avoid un-needed replication
*
* All messages are replicated, this is on purpose so should a number queues exist with different filters
* we dont have have a consumer per queue filter.
*
*
*/
public class FederatedAddress extends FederatedAbstract implements ActiveMQServerQueuePlugin, Serializable {
public static final SimpleString HDR_HOPS = new SimpleString("_AMQ_Hops");
private final SimpleString queueNameFormat;
private final SimpleString filterString;
private final Set<Matcher> includes;
private final Set<Matcher> excludes;
private final FederationAddressPolicyConfiguration config;
public FederatedAddress(Federation federation, FederationAddressPolicyConfiguration config, ActiveMQServer server, FederationUpstream upstream) {
super(federation, server, upstream);
Objects.requireNonNull(config.getName());
this.config = config;
if (config.getMaxHops() == -1) {
this.filterString = null;
} 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}");
if (config.getIncludes().isEmpty()) {
includes = Collections.emptySet();
} else {
includes = new HashSet<>(config.getIncludes().size());
for (FederationAddressPolicyConfiguration.Matcher include : config.getIncludes()) {
includes.add(new Matcher(include, wildcardConfiguration));
}
}
if (config.getExcludes().isEmpty()) {
excludes = Collections.emptySet();
} else {
excludes = new HashSet<>(config.getExcludes().size());
for (FederationAddressPolicyConfiguration.Matcher exclude : config.getExcludes()) {
excludes.add(new Matcher(exclude, wildcardConfiguration));
}
}
}
@Override
public void start() {
super.start();
server.getPostOffice()
.getAllBindings()
.values()
.stream()
.filter(b -> b instanceof QueueBinding)
.map(b -> ((QueueBinding) b).getQueue())
.forEach(this::createRemoteConsumer);
}
/**
* After a queue has been created
*
* @param queue The newly created queue
*/
@Override
public synchronized void afterCreateQueue(Queue queue) {
createRemoteConsumer(queue);
}
public FederationAddressPolicyConfiguration getConfig() {
return config;
}
private void createRemoteConsumer(Queue queue) {
if (match(queue)) {
FederatedConsumerKey key = getKey(queue);
Transformer transformer = getTransformer(config.getTransformerRef());
Transformer addHop = FederatedAddress::addHop;
createRemoteConsumer(key, mergeTransformers(addHop, transformer), clientSession -> createRemoteQueue(clientSession, key));
}
}
private void createRemoteQueue(ClientSession clientSession, FederatedConsumerKey key) throws ActiveMQException {
if (!clientSession.queueQuery(key.getQueueName()).isExists()) {
QueueAttributes queueAttributes = new QueueAttributes()
.setRoutingType(key.getRoutingType())
.setFilterString(key.getQueueFilterString())
.setDurable(true)
.setAutoDelete(config.getAutoDelete() == null ? true : config.getAutoDelete())
.setAutoDeleteDelay(config.getAutoDeleteDelay() == null ? TimeUnit.HOURS.toMillis(1) : config.getAutoDeleteDelay())
.setAutoDeleteMessageCount(config.getAutoDeleteMessageCount() == null ? -1 : config.getAutoDeleteMessageCount())
.setMaxConsumers(-1)
.setPurgeOnNoConsumers(false);
clientSession.createQueue(key.getAddress(), key.getQueueName(), false, queueAttributes);
}
}
private boolean match(Queue queue) {
//Currently only supporting Multicast currently.
if (RoutingType.ANYCAST.equals(queue.getRoutingType())) {
return false;
}
for (Matcher exclude : excludes) {
if (exclude.test(queue)) {
return false;
}
}
if (includes.isEmpty()) {
return true;
} else {
for (Matcher include : includes) {
if (include.test(queue)) {
return true;
}
}
return false;
}
}
private static Message addHop(Message message) {
if (message != null) {
int hops = toInt(message.getExtraBytesProperty(HDR_HOPS));
hops++;
message.putExtraBytesProperty(HDR_HOPS, ByteUtil.intToBytes(hops));
}
return message;
}
private static int toInt(byte[] bytes) {
if (bytes != null && bytes.length == 4) {
return ByteUtil.bytesToInt(bytes);
} else {
return 0;
}
}
/**
* Before an address is removed
*
* @param queue The queue that will be removed
*/
@Override
public synchronized void beforeDestroyQueue(Queue queue, final SecurityAuth session, boolean checkConsumerCount,
boolean removeConsumers, boolean autoDeleteAddress) {
FederatedConsumerKey key = getKey(queue);
removeRemoteConsumer(key);
}
private FederatedConsumerKey getKey(Queue queue) {
return new FederatedAddressConsumerKey(federation.getName(), upstream.getName(), queue.getAddress(), queue.getRoutingType(), queueNameFormat, filterString);
}
public static class Matcher {
Predicate<String> addressPredicate;
Matcher(FederationAddressPolicyConfiguration.Matcher config, WildcardConfiguration wildcardConfiguration) {
if (config.getAddressMatch() != null && !config.getAddressMatch().isEmpty()) {
addressPredicate = new Match<>(config.getAddressMatch(), null, wildcardConfiguration).getPattern().asPredicate();
}
}
public boolean test(Queue queue) {
return addressPredicate == null || addressPredicate.test(queue.getAddress().toString());
}
}
}

View File

@ -0,0 +1,112 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.server.federation.address;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.federation.FederatedConsumerKey;
import org.apache.activemq.artemis.utils.CompositeAddress;
import org.apache.commons.lang3.text.StrSubstitutor;
public class FederatedAddressConsumerKey implements FederatedConsumerKey {
private final SimpleString federation;
private final SimpleString upstream;
private final SimpleString address;
private final SimpleString queueNameFormat;
private final RoutingType routingType;
private final SimpleString queueFilterString;
private SimpleString fqqn;
private SimpleString queueName;
FederatedAddressConsumerKey(SimpleString federation, SimpleString upstream, SimpleString address, RoutingType routingType, SimpleString queueNameFormat, SimpleString queueFilterString) {
this.federation = federation;
this.upstream = upstream;
this.address = address;
this.routingType = routingType;
this.queueNameFormat = queueNameFormat;
this.queueFilterString = queueFilterString;
}
@Override
public SimpleString getQueueName() {
if (queueName == null) {
Map<String, String> data = new HashMap<>();
data.put("address", address.toString());
data.put("routeType", routingType.name().toLowerCase());
data.put("upstream", upstream.toString());
data.put("federation", federation.toString());
queueName = SimpleString.toSimpleString(StrSubstitutor.replace(queueNameFormat, data));
}
return queueName;
}
@Override
public SimpleString getQueueFilterString() {
return queueFilterString;
}
@Override
public int getPriority() {
return 0;
}
@Override
public SimpleString getAddress() {
return address;
}
@Override
public SimpleString getFqqn() {
if (fqqn == null) {
fqqn = CompositeAddress.toFullyQualified(getAddress(), getQueueName());
}
return fqqn;
}
@Override
public RoutingType getRoutingType() {
return routingType;
}
@Override
public SimpleString getFilterString() {
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederatedAddressConsumerKey)) return false;
FederatedAddressConsumerKey that = (FederatedAddressConsumerKey) o;
return Objects.equals(address, that.address) &&
Objects.equals(queueNameFormat, that.queueNameFormat) &&
routingType == that.routingType &&
Objects.equals(queueFilterString, that.queueFilterString);
}
@Override
public int hashCode() {
return Objects.hash(address, queueNameFormat, routingType, queueFilterString);
}
}

View File

@ -0,0 +1,192 @@
/**
* 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.queue;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.filter.Filter;
import org.apache.activemq.artemis.core.filter.impl.FilterImpl;
import org.apache.activemq.artemis.core.postoffice.QueueBinding;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.config.federation.FederationQueuePolicyConfiguration;
import org.apache.activemq.artemis.core.server.ServerSession;
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.FederatedQueueConsumer;
import org.apache.activemq.artemis.core.server.federation.Federation;
import org.apache.activemq.artemis.core.server.federation.FederationUpstream;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerConsumerPlugin;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
import org.apache.activemq.artemis.core.settings.impl.Match;
/**
* Federated Queue, connect to upstream queues routing them to the local queue when a local consumer exist.
*
* By default we connect to -1 the current consumer priority on the remote broker, so that if consumers also exist on the remote broker they a dispatched to first.
* This though is configurable to change this behaviour.
*
*/
public class FederatedQueue extends FederatedAbstract implements ActiveMQServerConsumerPlugin, Serializable {
private final Set<Matcher> includes;
private final Set<Matcher> excludes;
private final Filter metaDataFilter;
private final int priorityAdjustment;
private final FederationQueuePolicyConfiguration config;
public FederatedQueue(Federation federation, FederationQueuePolicyConfiguration config, ActiveMQServer server, FederationUpstream federationUpstream) throws ActiveMQException {
super(federation, server, federationUpstream);
Objects.requireNonNull(config.getName());
this.config = config;
this.priorityAdjustment = federationUpstream.getPriorityAdjustment() + (config.getPriorityAdjustment() == null ? -1 : config.getPriorityAdjustment());
String metaDataFilterString = config.isIncludeFederated() ? null : FederatedQueueConsumer.FEDERATION_NAME + " IS NOT NULL";
metaDataFilter = FilterImpl.createFilter(metaDataFilterString);
if (config.getIncludes().isEmpty()) {
includes = Collections.emptySet();
} else {
includes = new HashSet<>(config.getIncludes().size());
for (FederationQueuePolicyConfiguration.Matcher include : config.getIncludes()) {
includes.add(new Matcher(include, wildcardConfiguration));
}
}
if (config.getExcludes().isEmpty()) {
excludes = Collections.emptySet();
} else {
excludes = new HashSet<>(config.getExcludes().size());
for (FederationQueuePolicyConfiguration.Matcher exclude : config.getExcludes()) {
excludes.add(new Matcher(exclude, wildcardConfiguration));
}
}
}
@Override
public void start() {
super.start();
server.getPostOffice()
.getAllBindings()
.values()
.stream()
.filter(b -> b instanceof QueueBinding)
.map(b -> (QueueBinding) b)
.forEach(b -> createRemoteConsumer(b.getQueue()));
}
/**
* After a consumer has been created
*
* @param consumer the created consumer
*/
@Override
public synchronized void afterCreateConsumer(ServerConsumer consumer) {
createRemoteConsumer(consumer);
}
public FederationQueuePolicyConfiguration getConfig() {
return config;
}
private void createRemoteConsumer(Queue queue) {
queue.getConsumers()
.stream()
.filter(consumer -> consumer instanceof ServerConsumer)
.map(c -> (ServerConsumer) c).forEach(this::createRemoteConsumer);
}
private void createRemoteConsumer(ServerConsumer consumer) {
//We check the session meta data to see if its a federation session, if so by default we ignore these.
//To not ignore these, set include-federated to true, which will mean no meta data filter.
ServerSession serverSession = server.getSessionByID(consumer.getSessionID());
if (metaDataFilter != null && serverSession != null && metaDataFilter.match(serverSession.getMetaData())) {
return;
}
if (match(consumer)) {
FederatedConsumerKey key = getKey(consumer);
Transformer transformer = getTransformer(config.getTransformerRef());
Transformer fqqnTransformer = message -> message == null ? null : message.setAddress(key.getFqqn());
createRemoteConsumer(key, mergeTransformers(fqqnTransformer, transformer), null);
}
}
private boolean match(ServerConsumer consumer) {
for (Matcher exclude : excludes) {
if (exclude.test(consumer)) {
return false;
}
}
if (includes.isEmpty()) {
return true;
} else {
for (Matcher include : includes) {
if (include.test(consumer)) {
return true;
}
}
return false;
}
}
/**
* Before a consumer is closed
*
* @param consumer
* @param failed
*/
@Override
public synchronized void beforeCloseConsumer(ServerConsumer consumer, boolean failed) {
FederatedConsumerKey key = getKey(consumer);
removeRemoteConsumer(key);
}
private FederatedConsumerKey getKey(ServerConsumer consumer) {
Queue queue = consumer.getQueue();
int priority = consumer.getPriority() + priorityAdjustment;
return new FederatedQueueConsumerKey(queue.getAddress(), queue.getRoutingType(), queue.getName(), Filter.toFilterString(queue.getFilter()), Filter.toFilterString(consumer.getFilter()), priority);
}
public static class Matcher {
Predicate<String> queuePredicate;
Predicate<String> addressPredicate;
Matcher(FederationQueuePolicyConfiguration.Matcher config, WildcardConfiguration wildcardConfiguration) {
if (config.getQueueMatch() != null && !config.getQueueMatch().isEmpty()) {
queuePredicate = new Match<>(config.getQueueMatch(), null, wildcardConfiguration).getPattern().asPredicate();
}
if (config.getAddressMatch() != null && !config.getAddressMatch().isEmpty()) {
addressPredicate = new Match<>(config.getAddressMatch(), null, wildcardConfiguration).getPattern().asPredicate();
}
}
public boolean test(ServerConsumer consumer) {
return (queuePredicate == null || queuePredicate.test(consumer.getQueueName().toString()))
&& (addressPredicate == null || addressPredicate.test(consumer.getQueueAddress().toString()));
}
}
}

View File

@ -0,0 +1,98 @@
/**
* 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.queue;
import java.util.Objects;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.federation.FederatedConsumerKey;
import org.apache.activemq.artemis.utils.CompositeAddress;
public class FederatedQueueConsumerKey implements FederatedConsumerKey {
private final SimpleString address;
private final SimpleString queueName;
private final RoutingType routingType;
private final SimpleString queueFilterString;
private final SimpleString filterString;
private final SimpleString fqqn;
private final int priority;
FederatedQueueConsumerKey(SimpleString address, RoutingType routingType, SimpleString queueName, SimpleString queueFilterString, SimpleString filterString, int priority) {
this.address = address;
this.routingType = routingType;
this.queueName = queueName;
this.fqqn = CompositeAddress.toFullyQualified(address, queueName);
this.filterString = filterString;
this.queueFilterString = queueFilterString;
this.priority = priority;
}
@Override
public SimpleString getQueueName() {
return queueName;
}
@Override
public SimpleString getQueueFilterString() {
return queueFilterString;
}
@Override
public int getPriority() {
return priority;
}
@Override
public SimpleString getAddress() {
return address;
}
@Override
public SimpleString getFqqn() {
return fqqn;
}
@Override
public RoutingType getRoutingType() {
return routingType;
}
@Override
public SimpleString getFilterString() {
return filterString;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FederatedQueueConsumerKey)) return false;
FederatedQueueConsumerKey that = (FederatedQueueConsumerKey) o;
return priority == that.priority &&
Objects.equals(address, that.address) &&
Objects.equals(queueName, that.queueName) &&
routingType == that.routingType &&
Objects.equals(queueFilterString, that.queueFilterString) &&
Objects.equals(filterString, that.filterString) &&
Objects.equals(fqqn, that.fqqn);
}
@Override
public int hashCode() {
return Objects.hash(address, queueName, routingType, queueFilterString, filterString, fqqn, priority);
}
}

View File

@ -142,6 +142,7 @@ import org.apache.activemq.artemis.core.server.ServiceRegistry;
import org.apache.activemq.artemis.core.server.cluster.BackupManager; import org.apache.activemq.artemis.core.server.cluster.BackupManager;
import org.apache.activemq.artemis.core.server.cluster.ClusterManager; 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.server.cluster.ha.HAPolicy;
import org.apache.activemq.artemis.core.config.FederationConfiguration;
import org.apache.activemq.artemis.core.server.files.FileMoveManager; import org.apache.activemq.artemis.core.server.files.FileMoveManager;
import org.apache.activemq.artemis.core.server.files.FileStoreMonitor; import org.apache.activemq.artemis.core.server.files.FileStoreMonitor;
import org.apache.activemq.artemis.core.server.group.GroupingHandler; import org.apache.activemq.artemis.core.server.group.GroupingHandler;
@ -162,6 +163,7 @@ 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.ActiveMQServerMessagePlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin; 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.plugin.ActiveMQServerSessionPlugin;
import org.apache.activemq.artemis.core.server.federation.FederationManager;
import org.apache.activemq.artemis.core.server.reload.ReloadCallback; import org.apache.activemq.artemis.core.server.reload.ReloadCallback;
import org.apache.activemq.artemis.core.server.reload.ReloadManager; import org.apache.activemq.artemis.core.server.reload.ReloadManager;
import org.apache.activemq.artemis.core.server.reload.ReloadManagerImpl; import org.apache.activemq.artemis.core.server.reload.ReloadManagerImpl;
@ -337,6 +339,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
private final ConcurrentMap<String, AtomicInteger> connectedClientIds = new ConcurrentHashMap(); private final ConcurrentMap<String, AtomicInteger> connectedClientIds = new ConcurrentHashMap();
private volatile FederationManager federationManager;
private final ActiveMQComponent networkCheckMonitor = new ActiveMQComponent() { private final ActiveMQComponent networkCheckMonitor = new ActiveMQComponent() {
@Override @Override
public void start() throws Exception { public void start() throws Exception {
@ -1027,6 +1031,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
managementService.removeNotificationListener(groupingHandler); managementService.removeNotificationListener(groupingHandler);
stopComponent(groupingHandler); stopComponent(groupingHandler);
} }
stopComponent(federationManager);
stopComponent(clusterManager); stopComponent(clusterManager);
if (remotingService != null) { if (remotingService != null) {
@ -1964,10 +1969,6 @@ public class ActiveMQServerImpl implements ActiveMQServer {
return; return;
} }
if (hasBrokerQueuePlugins()) {
callBrokerQueuePlugins(plugin -> plugin.beforeDestroyQueue(queueName, session, checkConsumerCount,
removeConsumers, autoDeleteAddress));
}
addressSettingsRepository.clearCache(); addressSettingsRepository.clearCache();
@ -1981,6 +1982,12 @@ public class ActiveMQServerImpl implements ActiveMQServer {
Queue queue = (Queue) binding.getBindable(); Queue queue = (Queue) binding.getBindable();
if (hasBrokerQueuePlugins()) {
callBrokerQueuePlugins(plugin -> plugin.beforeDestroyQueue(queueName, session, checkConsumerCount,
removeConsumers, autoDeleteAddress));
}
if (session != null) { if (session != null) {
if (queue.isDurable()) { if (queue.isDurable()) {
@ -2308,6 +2315,12 @@ public class ActiveMQServerImpl implements ActiveMQServer {
return connectorsService; return connectorsService;
} }
@Override
public FederationManager getFederationManager() {
return federationManager;
}
@Override @Override
public void deployDivert(DivertConfiguration config) throws Exception { public void deployDivert(DivertConfiguration config) throws Exception {
if (config.getName() == null) { if (config.getName() == null) {
@ -2376,6 +2389,20 @@ public class ActiveMQServerImpl implements ActiveMQServer {
} }
} }
@Override
public void deployFederation(FederationConfiguration config) throws Exception {
if (federationManager != null) {
federationManager.deploy(config);
}
}
@Override
public void undeployFederation(String name) throws Exception {
if (federationManager != null) {
federationManager.undeploy(name);
}
}
@Override @Override
public ServerSession getSessionByID(String sessionName) { public ServerSession getSessionByID(String sessionName) {
return sessions.get(sessionName); return sessions.get(sessionName);
@ -2578,10 +2605,14 @@ public class ActiveMQServerImpl implements ActiveMQServer {
// This can't be created until node id is set // This can't be created until node id is set
clusterManager = new ClusterManager(executorFactory, this, postOffice, scheduledPool, managementService, configuration, nodeManager, haPolicy.isBackup()); clusterManager = new ClusterManager(executorFactory, this, postOffice, scheduledPool, managementService, configuration, nodeManager, haPolicy.isBackup());
federationManager = new FederationManager(this);
backupManager = new BackupManager(this, executorFactory, scheduledPool, nodeManager, configuration, clusterManager); backupManager = new BackupManager(this, executorFactory, scheduledPool, nodeManager, configuration, clusterManager);
clusterManager.deploy(); clusterManager.deploy();
federationManager.deploy();
remotingService = new RemotingServiceImpl(clusterManager, configuration, this, managementService, scheduledPool, protocolManagerFactories, executorFactory.getExecutor(), serviceRegistry); remotingService = new RemotingServiceImpl(clusterManager, configuration, this, managementService, scheduledPool, protocolManagerFactories, executorFactory.getExecutor(), serviceRegistry);
messagingServerControl = managementService.registerServer(postOffice, securityStore, storageManager, configuration, addressSettingsRepository, securityRepository, resourceManager, remotingService, this, queueFactory, scheduledPool, pagingManager, haPolicy.isBackup()); messagingServerControl = managementService.registerServer(postOffice, securityStore, storageManager, configuration, addressSettingsRepository, securityRepository, resourceManager, remotingService, this, queueFactory, scheduledPool, pagingManager, haPolicy.isBackup());
@ -2699,6 +2730,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
if (groupingHandler != null && groupingHandler instanceof LocalGroupingHandler) { if (groupingHandler != null && groupingHandler instanceof LocalGroupingHandler) {
clusterManager.start(); clusterManager.start();
federationManager.start();
groupingHandler.awaitBindings(); groupingHandler.awaitBindings();
remotingService.start(); remotingService.start();
@ -2706,6 +2739,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
remotingService.start(); remotingService.start();
clusterManager.start(); clusterManager.start();
federationManager.start();
} }
if (nodeManager.getNodeId() == null) { if (nodeManager.getNodeId() == null) {

View File

@ -57,6 +57,8 @@ public class ServiceRegistryImpl implements ServiceRegistry {
private Map<String, Transformer> bridgeTransformers; private Map<String, Transformer> bridgeTransformers;
private Map<String, Transformer> federationTransformers;
private Map<String, AcceptorFactory> acceptorFactories; private Map<String, AcceptorFactory> acceptorFactories;
private Map<String, Pair<ConnectorServiceFactory, ConnectorServiceConfiguration>> connectorServices; private Map<String, Pair<ConnectorServiceFactory, ConnectorServiceConfiguration>> connectorServices;
@ -192,6 +194,23 @@ public class ServiceRegistryImpl implements ServiceRegistry {
return transformer; return transformer;
} }
@Override
public void addFederationTransformer(String name, Transformer transformer) {
federationTransformers.put(name, transformer);
}
@Override
public Transformer getFederationTransformer(String name, TransformerConfiguration transformerConfiguration) {
Transformer transformer = federationTransformers.get(name);
if (transformer == null && transformerConfiguration != null && transformerConfiguration.getClassName() != null) {
transformer = instantiateTransformer(transformerConfiguration);
addFederationTransformer(name, transformer);
}
return transformer;
}
@Override @Override
public AcceptorFactory getAcceptorFactory(String name, final String className) { public AcceptorFactory getAcceptorFactory(String name, final String className) {
AcceptorFactory factory = acceptorFactories.get(name); AcceptorFactory factory = acceptorFactories.get(name);

View File

@ -63,6 +63,21 @@ public interface ActiveMQServerQueuePlugin extends ActiveMQServerBasePlugin {
} }
/**
* Before a queue is destroyed
*
* @param queue
* @param session
* @param checkConsumerCount
* @param removeConsumers
* @param autoDeleteAddress
* @throws ActiveMQException
*/
default void beforeDestroyQueue(Queue queue, final SecurityAuth session, boolean checkConsumerCount,
boolean removeConsumers, boolean autoDeleteAddress) throws ActiveMQException {
beforeDestroyQueue(queue.getName(), session, checkConsumerCount, removeConsumers, autoDeleteAddress);
}
/** /**
* After a queue has been destroyed * After a queue has been destroyed
* *

View File

@ -546,6 +546,20 @@
</xsd:complexType> </xsd:complexType>
</xsd:element> </xsd:element>
<xsd:element name="federations" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
a list of federations to create
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="federation" type="federationType" maxOccurs="unbounded" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="ha-policy" type="haPolicyType" maxOccurs="1" minOccurs="0"> <xsd:element name="ha-policy" type="haPolicyType" maxOccurs="1" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
@ -1504,6 +1518,157 @@
<xsd:attributeGroup ref="xml:specialAttrs"/> <xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType> </xsd:complexType>
<!-- FEDERATION CONFIGURATION -->
<xsd:complexType name="federationType">
<xsd:sequence>
<xsd:element name="upstream" type="upstreamType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="policy-set" type="policySetType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="queue-policy" type="queuePolicyType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="address-policy" type="addressPolicyType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="transformer" type="federationTransformerType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attribute name="user" type="xsd:string" use="optional" />
<xsd:attribute name="password" type="xsd:string" use="optional" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="upstreamType">
<xsd:sequence>
<xsd:element name="ha" type="xsd:boolean" default="false" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
whether this connection supports fail-over
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="circuit-breaker-timeout" type="xsd:long" default="30000" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
whether this connection supports fail-over
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:choice>
<xsd:element name="static-connectors" maxOccurs="1" minOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="connector-ref" type="xsd:string" maxOccurs="unbounded" minOccurs="1"/>
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="discovery-group-ref" maxOccurs="1" minOccurs="1">
<xsd:complexType>
<xsd:attribute name="discovery-group-name" type="xsd:IDREF" use="required">
<xsd:annotation>
<xsd:documentation>
name of discovery group used by this connection
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:element name="policy" type="policyRefType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="priority-adjustment" type="xsd:int" use="optional" />
<xsd:attribute name="user" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
username, if unspecified the federated user is used
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
password, if unspecified the federated password is used
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:ID" use="required">
<xsd:annotation>
<xsd:documentation>
unique name for this upstream
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="policySetType">
<xsd:sequence>
<xsd:element name="policy" type="policyRefType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="policyRefType">
<xsd:attribute name="ref" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="queuePolicyType">
<xsd:sequence>
<xsd:element name="include" type="queueMatchType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="exclude" type="queueMatchType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="transformer-ref" type="xsd:string" use="optional" />
<xsd:attribute name="priority-adjustment" type="xsd:int" use="optional" />
<xsd:attribute name="include-federated" type="xsd:boolean" use="optional" />
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="queueMatchType">
<xsd:attribute name="queue-match" type="xsd:string" use="required" />
<xsd:attribute name="address-match" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs" />
</xsd:complexType>
<xsd:complexType name="addressPolicyType">
<xsd:sequence>
<xsd:element name="include" type="addressMatchType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="exclude" type="addressMatchType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="transformer-ref" type="xsd:string" use="optional" />
<xsd:attribute name="auto-delete" type="xsd:boolean" use="optional" />
<xsd:attribute name="auto-delete-delay" type="xsd:long" use="optional" />
<xsd:attribute name="auto-delete-message-count" type="xsd:long" use="optional" />
<xsd:attribute name="max-hops" type="xsd:int" use="optional" />
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="addressMatchType">
<xsd:attribute name="address-match" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs" />
</xsd:complexType>
<xsd:complexType name="federationTransformerType">
<xsd:complexContent>
<xsd:extension base="transformerType">
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- TRANSFORMER CONFIGURATION --> <!-- TRANSFORMER CONFIGURATION -->
<xsd:complexType name="transformerType"> <xsd:complexType name="transformerType">
<xsd:sequence> <xsd:sequence>

View File

@ -201,6 +201,84 @@
<discovery-group-ref discovery-group-name="dg1"/> <discovery-group-ref discovery-group-name="dg1"/>
</bridge> </bridge>
</bridges> </bridges>
<federations>
<federation name="federation1">
<upstream name="eu-west-1" user="westuser" password="32a10275cf4ab4e9">
<static-connectors>
<connector-ref>connector1</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="eu-east-1" user="eastuser" password="32a10275cf4ab4e9">
<ha>true</ha>
<discovery-group-ref discovery-group-name="dg1"/>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="address-federation" />
<policy ref="queue-federation" />
</policy-set>
<queue-policy name="queue-federation" >
<exclude queue-match="the_queue" address-match="#" />
</queue-policy>
<address-policy name="address-federation" >
<include address-match="the_address" />
</address-policy>
</federation>
<federation name="federation2" user="globaluser" password="32a10275cf4ab4e9">
<upstream name="usa-west-1">
<static-connectors>
<connector-ref>connector1</connector-ref>
</static-connectors>
<policy ref="address-federation-usa"/>
</upstream>
<upstream name="usa-east-1" >
<ha>true</ha>
<discovery-group-ref discovery-group-name="dg1"/>
<policy ref="queue-federation-usa"/>
</upstream>
<queue-policy name="queue-federation-usa" >
<exclude queue-match="the_queue" address-match="#" />
</queue-policy>
<address-policy name="address-federation-usa" >
<include address-match="the_address" />
</address-policy>
</federation>
<federation name="federation3" user="globaluser" password="32a10275cf4ab4e9">
<upstream name="asia-1">
<static-connectors>
<connector-ref>connector1</connector-ref>
</static-connectors>
<policy ref="queue-federation-asia"/>
<policy ref="address-federation-asia"/>
</upstream>
<upstream name="asia-2" >
<ha>true</ha>
<discovery-group-ref discovery-group-name="dg1"/>
<policy ref="queue-federation-asia"/>
<policy ref="address-federation-asia"/>
</upstream>
<queue-policy name="queue-federation-asia" transformer-ref="federation-transformer-3" >
<exclude queue-match="the_queue" address-match="#" />
</queue-policy>
<address-policy name="address-federation-asia" transformer-ref="federation-transformer-3" >
<include address-match="the_address" />
</address-policy>
<transformer name="federation-transformer-3">
<class-name>org.foo.FederationTransformer3</class-name>
<property key="federationTransformerKey1" value="federationTransformerValue1"/>
<property key="federationTransformerKey2" value="federationTransformerValue2"/>
</transformer>
</federation>
</federations>
<ha-policy> <ha-policy>
<!--only one of the following--> <!--only one of the following-->
<!--on server shutdown scale down to another live server--> <!--on server shutdown scale down to another live server-->

View File

@ -519,6 +519,20 @@
</xsd:complexType> </xsd:complexType>
</xsd:element> </xsd:element>
<xsd:element name="federations" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
a list of federations to create
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="federation" type="federationType" maxOccurs="unbounded" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="ha-policy" type="haPolicyType" maxOccurs="1" minOccurs="0"> <xsd:element name="ha-policy" type="haPolicyType" maxOccurs="1" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
@ -1294,6 +1308,168 @@
<xsd:attributeGroup ref="xml:specialAttrs"/> <xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType> </xsd:complexType>
<!-- FEDERATION CONFIGURATION -->
<xsd:complexType name="federationType">
<xsd:sequence>
<xsd:element name="upstream" type="upstreamType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="policy-set" type="policySetType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="queue-policy" type="queuePolicyType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="address-policy" type="addressPolicyType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="transformer" type="federationTransformerType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attribute name="user" type="xsd:string" use="optional" />
<xsd:attribute name="password" type="xsd:string" use="optional" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="upstreamType">
<xsd:sequence>
<xsd:element name="ha" type="xsd:boolean" default="false" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
whether this connection supports fail-over
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="circuit-breaker-timeout" type="xsd:long" default="30000" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
whether this connection supports fail-over
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:choice>
<xsd:element name="static-connectors" maxOccurs="1" minOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="connector-ref" type="xsd:string" maxOccurs="unbounded" minOccurs="1"/>
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="discovery-group-ref" maxOccurs="1" minOccurs="1">
<xsd:complexType>
<xsd:attribute name="discovery-group-name" type="xsd:IDREF" use="required">
<xsd:annotation>
<xsd:documentation>
name of discovery group used by this connection
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:element name="policy" type="policyRefType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="priority-adjustment" type="xsd:int" use="optional" />
<xsd:attribute name="user" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
username, if unspecified the federated user is used
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
password, if unspecified the federated password is used
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:ID" use="required">
<xsd:annotation>
<xsd:documentation>
unique name for this upstream
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="policySetType">
<xsd:sequence>
<xsd:element name="policy" type="policyRefType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="policyRefType">
<xsd:attribute name="ref" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="queuePolicyType">
<xsd:sequence>
<xsd:element name="include" type="queueMatchType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="exclude" type="queueMatchType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="transformer-ref" type="xsd:string" use="optional" />
<xsd:attribute name="priority-adjustment" type="xsd:int" use="optional" />
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="queueMatchType">
<xsd:attribute name="queue-match" type="xsd:string" use="required" />
<xsd:attribute name="address-match" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs" />
</xsd:complexType>
<xsd:complexType name="addressPolicyType">
<xsd:sequence>
<xsd:element name="include" type="addressMatchType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="exclude" type="addressMatchType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="transformer-ref" type="xsd:string" use="optional" />
<xsd:attribute name="auto-delete" type="xsd:boolean" use="optional" />
<xsd:attribute name="auto-delete-delay" type="xsd:long" use="optional" />
<xsd:attribute name="auto-delete-message-count" type="xsd:long" use="optional" />
<xsd:attribute name="max-hops" type="xsd:int" use="optional" />
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="addressPolicyIncludeType">
<xsd:sequence>
<xsd:element name="include" type="addressMatchType" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="addressPolicyExcludeType">
<xsd:sequence>
<xsd:element name="exclude" type="addressMatchType" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="addressMatchType">
<xsd:attribute name="address-match" type="xsd:string" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs" />
</xsd:complexType>
<xsd:complexType name="federationTransformerType">
<xsd:complexContent>
<xsd:extension base="transformerType">
<xsd:attribute name="name" type="xsd:ID" use="required" />
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- TRANSFORMER CONFIGURATION --> <!-- TRANSFORMER CONFIGURATION -->
<xsd:complexType name="transformerType"> <xsd:complexType name="transformerType">
<xsd:sequence> <xsd:sequence>

View File

@ -55,6 +55,9 @@
* [Core Bridges](core-bridges.md) * [Core Bridges](core-bridges.md)
* [Duplicate Message Detection](duplicate-detection.md) * [Duplicate Message Detection](duplicate-detection.md)
* [Clusters](clusters.md) * [Clusters](clusters.md)
* [Federation](federation.md)
* [Address Federation](federation-address.md)
* [Queue Federation](federation-queue.md)
* [High Availability and Failover](ha.md) * [High Availability and Failover](ha.md)
* [Graceful Server Shutdown](graceful-shutdown.md) * [Graceful Server Shutdown](graceful-shutdown.md)
* [Libaio Native Libraries](libaio.md) * [Libaio Native Libraries](libaio.md)

View File

@ -0,0 +1,195 @@
# Address Federation
## Introduction
Address federation is like full multicast over the connected brokers, in that every message sent to address
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,
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.
![Address Federation](images/federation-address.png)
Figure 1. Address Federation
## Topology Patterns
### Symmetric
![Address Federation](images/federation-address-symetric.png)
Figure 2. Address Federation - Symmetric
As seen above, a publisher and consumer are connected to each broker.
Queues and thus consumers on those queues, can receive messages published by either publisher.
It is important in this setup to set `max-hops=1` to so that messages are copied only one and avoid cyclic replication.
If `max-hops` is not configured correctly, consumers will get multiple copies of the same message.
### Full Mesh
![Address Federation](images/federation-address-complete-graph.png)
Figure 3. Address Federation - Full Mesh
If not already spotted, the setup is identical to symemtric but simply where all brokers are symmetrically federating each other, creating a full mesh.
As illustrated, a publisher and consumer are connected to each broker.
Queues and thus consumers on those queues, can receive messages published by either publisher.
As with symmetric setup, it is important in this setup to set `max-hops=1` to so that messages are copied only one and avoid cyclic replication.
If `max-hops` is not configured correctly, consumers will get multiple copies of the same message.
### Ring
![Address Federation](images/federation-address-ring.png)
Figure 4. Address Federation - Symmetric
In a ring of brokers each federated address is `upstream` to just one other in the ring. To avoid the cyclic issue,
it is important to set `max-hops` to `n - 1` where `n` is the number of nodes in the ring.
e.g. in the example above property is set to 5 so that every address in the ring sees the message exactly once.
Whilst this setup is cheap in regards to connections, it is brittle, in that if a single broker fails, the ring fails.
### Fan out
![Address Federation](images/federation-address-fan-out.png)
Figure 5. Address Federation - Fan Out
One master address (it would required no configuration) is linked to by a tree of downstream federated addresses,
the tree can extend to any depth, and can be extended to without needing to re-configure existing brokers.
In this case messages published to the master address can be received by any consumer connected to any broker in the tree.
## Configuring Address Federation
Federation is configured in `broker.xml`.
Sample Address Federation setup:
```
<federations>
<federation name="eu-north-1" user="federation_username" password="32a10275cf4ab4e9">
<upstream name="eu-east-1">
<static-connectors>
<connector-ref>eu-east-connector1</connector-ref>
<connector-ref>eu-east-connector1</connector-ref>
</static-connectors>
<policy ref="news-address-federation"/>
</upstream>
<upstream name="eu-west-1" >
<static-connectors>
<connector-ref>eu-west-connector1</connector-ref>
<connector-ref>eu-west-connector1</connector-ref>
</static-connectors>
<policy ref="news-address-federation"/>
</upstream>
<address-policy name="news-address-federation" max-hops="1" auto-delete="true" auto-delete-delay="300000" auto-delete-message-count="-1" transformer-ref="federation-transformer-3">
<include address-match="queue.bbc.new" />
<include address-match="queue.usatoday" />
<include address-match="queue.news.#" />
<exclude address-match="queue.news.sport.#" />
</address-policy>
<transformer name="news-transformer">
<class-name>org.foo.NewsTransformer</class-name>
<property key="key1" value="value1"/>
<property key="key2" value="value2"/>
</transformer>
</federation>
</federations>
```
In the above setup downstream broker `eu-north-1` is configured to connect to two upstream brokers `eu-east-1` and `eu-east-2`, the credentials used for both connections to both brokers in this sample are shared, you can set user and password at the upstream level should they be different per upstream.
Both upstreams are configured with the same address-policy `news-address-federation`, that is selecting addresses which match any of the include criteria, but will exclude anything that starts `queue.news.sport`.
**It is important that federation name is globally unique.**
Let's take a look at all the `address-policy` parameters in turn, in order of priority.
- `name` attribute. All address-policies must have a unique name in the server.
- `include` the address-match pattern to whitelist addresses, multiple of these can be set. If none are set all addresses are matched.
- `exclude` the address-match pattern to blacklist addresses, multiple of these can be set.
- `max-hops`. The number of hops that a message can have made for it to be federated, see [Topology Patterns](#topology-patterns) above for more details.
- `auto-delete`. For address federation, the downstream dynamically creates a durable queue on the upstream address. This is used to mark if the upstream queue should be deleted once downstream disconnects,
and the delay and message count params have been met. This is useful if you want to automate the clean up, though you may wish to disable this if you want messages to queued for the downstream when disconnect no matter what.
- `auto-delete-delay`. The amount of time in milliseconds after the downstream broker has disconnected before the upstream queue can be eligable for `auto-delete`.
- `auto-delete-message-count`. The amount number messages in the upstream queue that the message count must be equal or below before the downstream broker has disconnected before the upstream queue can be eligable for `auto-delete`.
- `transformer-ref`. The ref name for a transformer (see transformer config) that you may wish to configure to transform the message on federation transfer.
**note** `address-policy`'s and `queue-policy`'s are able to be defined in the same federation, and be linked to the same upstream.
Now look at all the `transformer` parameters in turn, in order of priority:
- `name` attribute. This must be a unique name in the server, and is used to ref the transformer in `address-policy` and `queue-policy`
- `transformer-class-name`. An optional transformer-class-name can be
specified. This is the name of a user-defined class which implements the
`org.apache.activemq.artemis.core.server.transformer.Transformer` interface.
If this is specified then the transformer's `transform()` method will be
invoked with the message before it is transferred. This gives you the opportunity
to transform the message's header or body before it is federated.
- `property` holds key, value pairs that can be used to configure the transformer.
Finally look at `upstream`, this is what defines the upstream broker connection and the policies to use against it.
- `name` attribute. This must be a unique name in the server, and is used to ref the transformer in `address-policy` and `queue-policy`
- `user`. This optional attribute determines the user name to use when creating
the upstream connection to the remote server. If it is not specified the shared
federation user and password will be used if set.
- `password`. This optional attribute determines the password to use when
creating the upstream connection to the remote server. If it is not specified the shared
federation user and password will be used if set.
- `static-connectors` or `discovery-group-ref`. Pick either of these options to
connect the bridge to the target server.
The `static-connectors` is a list of `connector-ref` elements pointing to
`connector` elements defined elsewhere. 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).
The `discovery-group-ref` element has one attribute - `discovery-group-name`.
This attribute points to a `discovery-group` defined elsewhere. For more
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,
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.

View File

@ -0,0 +1,175 @@
# Queue Federation
## Introduction
This feature provides a way of balancing the load of a single queue across remote brokers.
A federated queue links to other queues (called upstream queues). It will retrieve messages from upstream queues in order to satisfy demand for messages from local consumers.
The upstream queues do not need to be reconfigured and they do not have to be on the same broker or in the same cluster.
All of the configuration needed to establish the upstream links and the federated queue is in the downstream broker.
### Use Cases
This is not an exhaustive list of what you can do with and the benefits of federated queues, but simply some ideas.
* Higher capacity
By having a "logical" queue distributed over many brokers. Each broker would declare a federated queue with all the other federated queues upstream. (The links would form a complete bi-directional graph on n queues.)
By having this a logical distributed queue is capable of having a much higher capacity than a single queue on a single broker.
When will perform best when there is some degree of locality.
e.g. as many messages as possible are consumed from the same broker as they were published to, where federation only needs to move messages around in order to perform load balancing.
![Federated Queue](images/federated-queue-symmetric.gif)
* Supporting multi region or venue
In a multi region setup you may have producers in one region or venue and the consumer in another.
typically you want producers and consumer to keep their connections local to the region, in such as case you can deploy brokers in each region where producers and consumer are, and use federation to move messages over the WAN between regions.
![Federated Queue](images/federated-queue.gif)
* Communication between the secure enterprise lan and the DMZ.
Where a number of producer apps maybe in the DMZ and a number of consumer apps in the secure enterprise lan, it may not suitable to allow the producers to connect through to the broker in the secure enterprise lan.
In this scenario you could deploy a broker in the DMZ where the producers publish to, and then have the broker in the enterprise lan connect out to the DMZ broker and federate the queues so that messages can traverse.
This is similar to supporting multi region or venue.
* Migrating between two clusters.
Consumers and publishers can be moved in any order and the messages won't be duplicated (which is the case if you do exchange federation). Instead, messages are transferred to the new cluster when your consumers are there.
Here for such a migration with blue/green or canary moving a number of consumers on the same queue, you may want to set the `priority-adjustment` to 0, or even a positive value, so message would actively flow to the federated queue.
## Configuring Queue Federation
Federation is configured in `broker.xml`.
Sample Queue Federation setup:
```
<federations>
<federation name="eu-north-1" user="federation_username" password="32a10275cf4ab4e9">
<upstream name="eu-east-1">
<static-connectors>
<connector-ref>eu-east-connector1</connector-ref>
<connector-ref>eu-east-connector1</connector-ref>
</static-connectors>
<policy ref="news-queue-federation"/>
</upstream>
<upstream name="eu-west-1" >
<static-connectors>
<connector-ref>eu-west-connector1</connector-ref>
<connector-ref>eu-west-connector1</connector-ref>
</static-connectors>
<policy ref="news-queue-federation"/>
</upstream>
<queue-policy name="news-queue-federation" priority-adjustment="-5" include-federated="true" transformer-ref="federation-transformer-3">
<include queue-match="#" address-match="queue.bbc.new" />
<include queue-match="#" address-match="queue.usatoday" />
<include queue-match="#" address-match="queue.news.#" />
<exclude queue-match="#.local" address-match="#" />
</queue-policy>
<transformer name="news-transformer">
<class-name>org.foo.NewsTransformer</class-name>
<property key="key1" value="value1"/>
<property key="key2" value="value2"/>
</transformer>
</federation>
</federations>
```
In the above setup downstream broker `eu-north-1` is configured to connect to two upstream brokers `eu-east-1` and `eu-east-2`, the credentials used for both connections to both brokers in this sample are shared, you can set user and password at the upstream level should they be different per upstream.
Both upstreams are configured with the same queue-policy `news-queue-federation`, that is selecting addresses which match any of the include criteria, but will exclude any queues that end with `.local`, keeping these as local queues only.
**It is important that federation name is globally unique.**
Let's take a look at all the `queue-policy` parameters in turn, in order of priority.
- `name` attribute. All address-policies must have a unique name in the server.
- `include` the address-match pattern to whitelist addresses, multiple of these can be set. If none are set all addresses are matched.
- `exclude` the address-match pattern to blacklist addresses, multiple of these can be set.
- `priority-adjustment` when a consumer attaches its priority is used to make the upstream consumer,
but with an adjustment by default -1, so that local consumers get load balanced first over remote, this enables this to be configurable should it be wanted/needed.
- `include-federated` by default this is false, we dont federate a federated consumer, this is to avoid issue, where in symmetric or any closed loop setup you could end up when no "real" consumers attached with messages flowing round and round endlessly.
There is though a valid case that if you dont have a close loop setup e.g. three brokers in a chain (A->B->C) with producer at broker A and consumer at C, you would want broker B to re-federate the consumer onto A.
- `transformer-ref`. The ref name for a transformer (see transformer config) that you may wish to configure to transform the message on federation transfer.
**note** `address-policy`'s and `queue-policy`'s are able to be defined in the same federation, and be linked to the same upstream.
Now look at all the `transformer` parameters in turn, in order of priority:
- `name` attribute. This must be a unique name in the server, and is used to ref the transformer in `address-policy` and `queue-policy`
- `transformer-class-name`. An optional transformer-class-name can be
specified. This is the name of a user-defined class which implements the
`org.apache.activemq.artemis.core.server.transformer.Transformer` interface.
If this is specified then the transformer's `transform()` method will be
invoked with the message before it is transferred. This gives you the opportunity
to transform the message's header or body before it is federated.
- `property` holds key, value pairs that can be used to configure the transformer.
Finally look at `upstream`, this is what defines the upstream broker connection and the policies to use against it.
- `name` attribute. This must be a unique name in the server, and is used to ref the transformer in `address-policy` and `queue-policy`
- `user`. This optional attribute determines the user name to use when creating
the upstream connection to the remote server. If it is not specified the shared
federation user and password will be used if set.
- `password`. This optional attribute determines the password to use when
creating the upstream connection to the remote server. If it is not specified the shared
federation user and password will be used if set.
- `static-connectors` or `discovery-group-ref`. Pick either of these options to
connect the bridge to the target server.
The `static-connectors` is a list of `connector-ref` elements pointing to
`connector` elements defined elsewhere. 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).
The `discovery-group-ref` element has one attribute - `discovery-group-name`.
This attribute points to a `discovery-group` defined elsewhere. For more
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,
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.

View File

@ -0,0 +1,128 @@
# Federation
## Introduction
Federation allows transmission of messages between brokers without requiring clustering.
A federated address can replicate messages published from an upstream address to a local address.
n.b. This is only supported with multicast addresses.
A federated queue lets a local consumer receive messages from an upstream queue.
A broker can contain federated and local-only components - you don't need to federate everything if you don't want to.
### Benefits
##### WAN
The source and target servers do not have to be in the same cluster which makes
federation suitable for reliably sending messages from one cluster to another,
for instance across a WAN, between cloud regions or there internet and where the
connection may be unreliable.
Federation has built in resilience to failure so if the target server
connection is lost, e.g. due to network failure, federation will retry
connecting to the target until it comes back online. When it comes back online
it will resume operation as normal.
##### Loose Coupling of Brokers
Federation can transmit messages between brokers (or clusters) in different administrative domains:
* they may have different configuration, users and setup;
* they may run on different versions of ActiveMQ Artemis
##### Dynamic and Selective
Federation is applied by policies, that match address and queue names, and then apply.
This means that federation can dynamically be applied as queues or addresses are added and removed,
without needing to hard configure each and every one.
Like wise policies are selective, in that they apply with multiple include and exclude matches.
Mutliple policies can applied directly to multiple upstreams,
as well policies can be grouped into policy sets and then applied to upstreams to make managing easier.
## Address Federation
Address federation is like full multicast over the connected brokers, in that every message sent to address 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](images/federation-address.png)
Figure 1. Address Federation
For further details please goto [Address Federation](federation-address.md).
## Queue Federation
Effectively, all federated queues act as a single logical queue, with multiple receivers on multiple machines.
So federated queues can be used for load balancing. Typically if the brokers are in the same AZ you would look to cluster them, the advantage of queue federation is that it does not require clustering so is suitable for over WAN, cross-region, on-off prem.
![Queue Federation](images/federated-queue-symmetric.png)
Figure 2. Queue Federation
For further details please goto [Queue Federation](federation-queue.md).
## WAN Full Mesh
With federation it is possible to provide a WAN mesh of brokers, replicating with Address Federation or routing and load balancing with Queue Federation.
Linking producers and consumers distant from each other.
![WAN Full Mesh](images/federated-world-wide-mesh.png)
Figure 3. Example possible full federation mesh
## Configuring Federation
Federation is configured in `broker.xml`.
Sample:
```xml
<federations>
<federation name="eu-north-1-federation">
<upstream name="eu-west-1" user="westuser" password="32a10275cf4ab4e9">
<static-connectors>
<connector-ref>connector1</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="eu-east-1" user="eastuser" password="32a10275cf4ab4e9">
<discovery-group-ref discovery-group-name="ue-west-dg"/>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="address-federation" />
<policy ref="queue-federation" />
</policy-set>
<queue-policy name="queue-federation" >
<exclude queue-match="federated_queue" address-match="#" />
</queue-policy>
<address-policy name="address-federation" >
<include address-match="federated_address" />
</address-policy>
</federation>
</federations>
```
In the above example we have shown the basic key parameters needed to configure
federation for a queue and address to multiple upstream.
The example shows a broker `eu-north-1` connecting to two upstream brokers `eu-east-1` and `eu-west-1`,
and applying queue federation to queue `federated_queue` , and also applying address federation to `federated_address`.
**It is important that federation name is globally unique.**
There are many configuration options that you can apply these are detailed in the individual docs for [Address Federation](federation-address.md) and [Queue Federation](federation-queue.md).

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View File

@ -0,0 +1,215 @@
<?xml version='1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq.examples.federation</groupId>
<artifactId>broker-federation</artifactId>
<version>2.7.0-SNAPSHOT</version>
</parent>
<artifactId>federated-address</artifactId>
<packaging>jar</packaging>
<name>ActiveMQ Artemis Federated Address Example</name>
<properties>
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-maven-plugin</artifactId>
<executions>
<execution>
<id>create0</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server0</instance>
<configuration>${basedir}/target/classes/activemq/server0</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>create1</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server1</instance>
<configuration>${basedir}/target/classes/activemq/server1</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>create2</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server2</instance>
<configuration>${basedir}/target/classes/activemq/server2</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>start0</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server0</location>
<testURI>tcp://localhost:61616</testURI>
<args>
<param>run</param>
</args>
<name>eu-west-1</name>
</configuration>
</execution>
<execution>
<id>start1</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server1</location>
<testURI>tcp://localhost:61617</testURI>
<args>
<param>run</param>
</args>
<name>eu-east-1</name>
</configuration>
</execution>
<execution>
<id>start2</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server2</location>
<testURI>tcp://localhost:61618</testURI>
<args>
<param>run</param>
</args>
<name>us-central-1</name>
</configuration>
</execution>
<execution>
<id>runClient</id>
<goals>
<goal>runClient</goal>
</goals>
<configuration>
<clientClass>org.apache.activemq.artemis.jms.example.FederatedAddressExample</clientClass>
</configuration>
</execution>
<execution>
<id>stop0</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server0</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
<execution>
<id>stop1</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server1</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
<execution>
<id>stop2</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server2</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.activemq.examples.federation</groupId>
<artifactId>federated-address</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>markdown-page-generator-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -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.
```
<federations>
...
</federations>
```
For more information on ActiveMQ Artemis Federation please see the federation section of the user manual.

View File

@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.jms.example;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
/**
* A simple example that demonstrates multicast address replication between remote servers,
* using Address Federation feature.
*/
public class FederatedAddressExample {
public static void main(final String[] args) throws Exception {
Connection connectionEUWest = null;
Connection connectionEUEast = null;
Connection connectionUSCentral = null;
try {
// Step 1. Instantiate the Topic (multicast)
Topic topic = ActiveMQJMSClient.createTopic("exampleTopic");
// Step 2. Instantiate connection towards server EU West
ConnectionFactory cfEUWest = new ActiveMQConnectionFactory("tcp://localhost:61616");
// Step 3. Instantiate connection towards server EU East
ConnectionFactory cfEUEast = new ActiveMQConnectionFactory("tcp://localhost:61617");
// Step 4. Instantiate connection towards server US Central
ConnectionFactory cfUSCentral = new ActiveMQConnectionFactory("tcp://localhost:61618");
// Step 5. We create a JMS Connection connectionEUWest which is a connection to server EU West
connectionEUWest = cfEUWest.createConnection();
// Step 6. We create a JMS Connection connectionEUEast which is a connection to server EU East
connectionEUEast = cfEUEast.createConnection();
// Step 7. We create a JMS Connection connectionUSCentral which is a connection to server US Central
connectionUSCentral = cfUSCentral.createConnection();
// Step 8. We create a JMS Session on server EU West
Session sessionEUWest = connectionEUWest.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 9. We create a JMS Session on server EU East
Session sessionEUEast = connectionEUEast.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 10. We create a JMS Session on server US Central
Session sessionUSCentral = connectionUSCentral.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 11. We start the connections to ensure delivery occurs on them
connectionEUWest.start();
connectionEUEast.start();
connectionUSCentral.start();
// Step 12. We create a JMS MessageProducer object on each server
MessageProducer producerEUWest = sessionEUWest.createProducer(topic);
MessageProducer producerEUEast = sessionEUEast.createProducer(topic);
MessageProducer producerUSCentral = sessionUSCentral.createProducer(topic);
// Step 13. We create JMS MessageConsumer objects on each server
MessageConsumer consumerEUWest = sessionEUWest.createSharedDurableConsumer(topic, "exampleSubscription");
MessageConsumer consumerEUEast = sessionEUEast.createSharedDurableConsumer(topic, "exampleSubscription");
MessageConsumer consumerUSCentral = sessionUSCentral.createSharedDurableConsumer(topic, "exampleSubscription");
// Step 14. Let a little time for everything to start and form.
Thread.sleep(5000);
// Step 13. We send some messages to server EU West
final int numMessages = 10;
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUWest.createTextMessage("This is text sent from EU West, message " + i);
message.setStringProperty("text", "This is text sent from EU West, message " + i);
producerEUWest.send(message);
System.out.println("EU West :: Sent message: " + message.getText());
}
// Step 14. We now consume those messages on *all* servers .
// We note that every consumer, receives a message even so on seperate servers
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUWest = (TextMessage) consumerEUWest.receive(5000);
System.out.println("EU West :: Got message: " + messageEUWest.getText());
TextMessage messageUSCentral = (TextMessage) consumerUSCentral.receive(5000);
System.out.println("US Central:: Got message: " + messageUSCentral.getText());
TextMessage messageEUEast = (TextMessage) consumerEUEast.receive(5000);
System.out.println("EU East :: Got message: " + messageEUEast.getText());
}
// Step 15. Repeat same test this time sending on US Central
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionUSCentral.createTextMessage("This is text sent from US Central, message " + i);
producerUSCentral.send(message);
System.out.println("US Central:: Sent message: " + message.getText());
}
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUWest = (TextMessage) consumerEUWest.receive(5000);
System.out.println("EU West :: Got message: " + messageEUWest.getText());
TextMessage messageUSCentral = (TextMessage) consumerUSCentral.receive(5000);
System.out.println("US Central:: Got message: " + messageUSCentral.getText());
TextMessage messageEUEast = (TextMessage) consumerEUEast.receive(5000);
System.out.println("EU East :: Got message: " + messageEUEast.getText());
}
// Step 15. Repeat same test one last time, this time sending on EU East
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUEast.createTextMessage("This is text sent from EU East, message " + i);
producerEUEast.send(message);
System.out.println("EU East :: Sent message: " + message.getText());
}
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUWest = (TextMessage) consumerEUWest.receive(5000);
System.out.println("EU West :: Got message: " + messageEUWest.getText());
TextMessage messageUSCentral = (TextMessage) consumerUSCentral.receive(5000);
System.out.println("US Central:: Got message: " + messageUSCentral.getText());
TextMessage messageEUEast = (TextMessage) consumerEUEast.receive(5000);
System.out.println("EU East :: Got message: " + messageEUEast.getText());
}
} finally {
// Step 16. Be sure to close our resources!
if (connectionEUWest != null) {
connectionEUWest.stop();
connectionEUWest.close();
}
if (connectionEUEast != null) {
connectionEUEast.stop();
connectionEUEast.close();
}
if (connectionUSCentral != null) {
connectionUSCentral.stop();
connectionUSCentral.close();
}
}
}
}

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>eu-west-1-master</name>
<bindings-directory>./data/bindings</bindings-directory>
<journal-directory>./data/journal</journal-directory>
<large-messages-directory>./data/largemessages</large-messages-directory>
<paging-directory>./data/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61616</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="eu-west-1-federation">
<upstream name="eu-east-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-east-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="us-central-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>us-central-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="address-federation" />
</policy-set>
<address-policy name="address-federation" >
<include address-match="exampleTopic" />
</address-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleTopic">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleTopic">
<multicast>
<queue name="exampleSubscription"/>
</multicast>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>eu-east-1-master</name>
<bindings-directory>target/server1/data/messaging/bindings</bindings-directory>
<journal-directory>target/server1/data/messaging/journal</journal-directory>
<large-messages-directory>target/server1/data/messaging/largemessages</large-messages-directory>
<paging-directory>target/server1/data/messaging/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61617</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61617</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="eu-east-1-federation">
<upstream name="eu-west-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-west-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="us-central-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>us-central-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="address-federation" />
</policy-set>
<address-policy name="address-federation" >
<include address-match="exampleTopic" />
</address-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleTopic">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleTopic">
<multicast>
<queue name="exampleSubscription"/>
</multicast>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>us-central-1-master</name>
<bindings-directory>target/server2/data/messaging/bindings</bindings-directory>
<journal-directory>target/server2/data/messaging/journal</journal-directory>
<large-messages-directory>target/server2/data/messaging/largemessages</large-messages-directory>
<paging-directory>target/server2/data/messaging/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61618</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61618</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="us-central-1-federation">
<upstream name="eu-east-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-east-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="eu-west-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-west-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="address-federation" />
</policy-set>
<address-policy name="address-federation">
<include address-match="exampleTopic" />
</address-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleTopic">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleTopic">
<multicast>
<queue name="exampleSubscription"/>
</multicast>
</address>
</addresses>
</core>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View File

@ -0,0 +1,215 @@
<?xml version='1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq.examples.federation</groupId>
<artifactId>broker-federation</artifactId>
<version>2.7.0-SNAPSHOT</version>
</parent>
<artifactId>federated-queue</artifactId>
<packaging>jar</packaging>
<name>ActiveMQ Artemis Federated Queue Example</name>
<properties>
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-maven-plugin</artifactId>
<executions>
<execution>
<id>create0</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server0</instance>
<configuration>${basedir}/target/classes/activemq/server0</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>create1</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server1</instance>
<configuration>${basedir}/target/classes/activemq/server1</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>create2</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<instance>${basedir}/target/server2</instance>
<configuration>${basedir}/target/classes/activemq/server2</configuration>
<!-- this makes it easier in certain envs -->
<javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
</configuration>
</execution>
<execution>
<id>start0</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server0</location>
<testURI>tcp://localhost:61616</testURI>
<args>
<param>run</param>
</args>
<name>eu-west-1</name>
</configuration>
</execution>
<execution>
<id>start1</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server1</location>
<testURI>tcp://localhost:61617</testURI>
<args>
<param>run</param>
</args>
<name>eu-east-1</name>
</configuration>
</execution>
<execution>
<id>start2</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<location>${basedir}/target/server2</location>
<testURI>tcp://localhost:61618</testURI>
<args>
<param>run</param>
</args>
<name>us-central-1</name>
</configuration>
</execution>
<execution>
<id>runClient</id>
<goals>
<goal>runClient</goal>
</goals>
<configuration>
<clientClass>org.apache.activemq.artemis.jms.example.FederatedQueueExample</clientClass>
</configuration>
</execution>
<execution>
<id>stop0</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server0</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
<execution>
<id>stop1</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server1</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
<execution>
<id>stop2</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<location>${basedir}/target/server2</location>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.activemq.examples.federation</groupId>
<artifactId>federated-queue</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>markdown-page-generator-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -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.
```
<federations>
...
</federations>
```
For more information on ActiveMQ Artemis Federation please see the federation section of the user manual.

View File

@ -0,0 +1,262 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.jms.example;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
/**
* 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 FederatedQueueExample {
public static void main(final String[] args) throws Exception {
Connection connectionEUWest = null;
Connection connectionEUEast = null;
Connection connectionUSCentral = null;
try {
// Step 1. Instantiate the Queue
Queue queue = ActiveMQJMSClient.createQueue("exampleQueue");
// Step 2. Instantiate connection towards server EU West
ConnectionFactory cfEUWest = new ActiveMQConnectionFactory("tcp://localhost:61616");
// Step 3. Instantiate connection towards server EU East
ConnectionFactory cfEUEast = new ActiveMQConnectionFactory("tcp://localhost:61617");
// Step 4. Instantiate connection towards server US Central
ConnectionFactory cfUSCentral = new ActiveMQConnectionFactory("tcp://localhost:61618");
// Step 5. We create a JMS Connection connectionEUWest which is a connection to server EU West
connectionEUWest = cfEUWest.createConnection();
// Step 6. We create a JMS Connection connectionEUEast which is a connection to server EU East
connectionEUEast = cfEUEast.createConnection();
// Step 7. We create a JMS Connection connectionUSCentral which is a connection to server US Central
connectionUSCentral = cfUSCentral.createConnection();
// Step 8. We create a JMS Session on server EU West
Session sessionEUWest = connectionEUWest.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 9. We create a JMS Session on server EU East
Session sessionEUEast = connectionEUEast.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 10. We create a JMS Session on server US Central
Session sessionUSCentral = connectionUSCentral.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Step 11. We start the connections to ensure delivery occurs on them
connectionEUWest.start();
connectionEUEast.start();
connectionUSCentral.start();
// Step 12. We create a JMS MessageProducer object on each server
MessageProducer producerEUWest = sessionEUWest.createProducer(queue);
MessageProducer producerEUEast = sessionEUEast.createProducer(queue);
MessageProducer producerUSCentral = sessionUSCentral.createProducer(queue);
// Step 13. We create JMS MessageConsumer objects on each server
MessageConsumer consumerEUWest = sessionEUWest.createConsumer(queue);
MessageConsumer consumerEUEast = sessionEUEast.createConsumer(queue);
MessageConsumer consumerUSCentral = sessionUSCentral.createConsumer(queue);
// Step 14. Let a little time for everything to start and form.
Thread.sleep(5000);
// Step 15. We send some messages to server EU West
final int numMessages = 10;
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUWest.createTextMessage("This is text sent from EU West, message " + i);
producerEUWest.send(message);
System.out.println("EU West :: Sent message: " + message.getText());
}
// Step 16. we now consume those messages on EU West demonstrating that messages will deliver to local consumer by priority.
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUWest = (TextMessage) consumerEUWest.receive(5000);
System.out.println("EU West :: Got message: " + messageEUWest.getText());
}
// Step 17. We repeat the same local consumer priority check on US Central
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUWest.createTextMessage("This is text sent from US Central, message " + i);
producerUSCentral.send(message);
System.out.println("US Central:: Sent message: " + message.getText());
}
for (int i = 0; i < numMessages; i++) {
TextMessage messageUSCentral = (TextMessage) consumerUSCentral.receive(5000);
System.out.println("US Central:: Got message: " + messageUSCentral.getText());
}
// Step 18. We now close the consumer on EU West leaving it no local consumers.
consumerEUWest.close();
System.out.println("Consumer EU West now closed");
// Step 19. We now send some more messages to server EU West
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUWest.createTextMessage("This is text sent from EU West, message " + i);
producerEUWest.send(message);
System.out.println("EU West :: Sent message: " + message.getText());
}
// Step 20. we now consume those messages on EU East demonstrating that messages will re-route to the another broker based on upstream priority.
// As US Central is configured to be -1 priority compared to EU East, messages should re-route to EU East for its consumers to consume.
// If US Central and EU East were even priority then the re-direct would be loaded between the two.
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUEast = (TextMessage) consumerEUEast.receive(5000);
System.out.println("EU East :: Got message: " + messageEUEast.getText());
}
// Step 21. We now close the consumer on US Central leaving it no local consumers.
consumerUSCentral.close();
System.out.println("Consumer US Central now closed");
// Step 19. We now send some more messages to server US Central
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionUSCentral.createTextMessage("This is text sent from US Central, message " + i);
producerUSCentral.send(message);
System.out.println("US Central:: Sent message: " + message.getText());
}
// Step 20. we now consume those messages on EU East demonstrating that its messages also can re-route, if no local-consumer.
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUEast = (TextMessage) consumerEUEast.receive(5000);
System.out.println("EU East :: Got message: " + messageEUEast.getText());
}
// Step 21. Restart local consumers on EU West and US Central
consumerEUWest = sessionEUWest.createConsumer(queue);
System.out.println("Consumer EU West re-created");
consumerUSCentral = sessionUSCentral.createConsumer(queue);
System.out.println("Consumer US Central re-created");
// Step 22. we produce and consume on US Central, showing that dynamically re-adjusts now local consumers exist and messages delivery by priority to local consumer.
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionUSCentral.createTextMessage("This is text sent from US Central, message " + i);
producerUSCentral.send(message);
System.out.println("US Central:: Sent message: " + message.getText());
}
for (int i = 0; i < numMessages; i++) {
TextMessage messageUSCentral = (TextMessage) consumerUSCentral.receive(5000);
System.out.println("US Central:: Got message: " + messageUSCentral.getText());
}
// Step 23. we repeat the same test on EU West.
for (int i = 0; i < numMessages; i++) {
TextMessage message = sessionEUWest.createTextMessage("This is text sent from EU West, message " + i);
producerEUWest.send(message);
System.out.println("EU West :: Sent message: " + message.getText());
}
for (int i = 0; i < numMessages; i++) {
TextMessage messageEUWest = (TextMessage) consumerEUWest.receive(5000);
System.out.println("EU West :: Got message: " + messageEUWest.getText());
}
} finally {
// Step 24. Be sure to close our resources!
if (connectionEUWest != null) {
connectionEUWest.stop();
connectionEUWest.close();
}
if (connectionEUEast != null) {
connectionEUEast.stop();
connectionEUEast.close();
}
if (connectionUSCentral != null) {
connectionUSCentral.stop();
connectionUSCentral.close();
}
}
}
}

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>eu-west-1-master</name>
<bindings-directory>./data/bindings</bindings-directory>
<journal-directory>./data/journal</journal-directory>
<large-messages-directory>./data/largemessages</large-messages-directory>
<paging-directory>./data/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61616</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="eu-west-1-federation">
<upstream name="eu-east-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-east-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="us-central-1-upstream" priority-adjustment="-1">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>us-central-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="queue-federation" />
</policy-set>
<queue-policy name="queue-federation" >
<include queue-match="exampleQueue" address-match="#" />
</queue-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleQueue">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleQueue">
<anycast>
<queue name="exampleQueue"/>
</anycast>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>eu-east-1-master</name>
<bindings-directory>target/server1/data/messaging/bindings</bindings-directory>
<journal-directory>target/server1/data/messaging/journal</journal-directory>
<large-messages-directory>target/server1/data/messaging/largemessages</large-messages-directory>
<paging-directory>target/server1/data/messaging/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61617</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61617</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="eu-east-1-federation">
<upstream name="eu-west-1-upstream">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-west-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="us-central-1-upstream" priority-adjustment="-1">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>us-central-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="queue-federation" />
</policy-set>
<queue-policy name="queue-federation" >
<include queue-match="exampleQueue" address-match="#" />
</queue-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleQueue">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleQueue">
<anycast>
<queue name="exampleQueue"/>
</anycast>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>us-central-1-master</name>
<bindings-directory>target/server2/data/messaging/bindings</bindings-directory>
<journal-directory>target/server2/data/messaging/journal</journal-directory>
<large-messages-directory>target/server2/data/messaging/largemessages</large-messages-directory>
<paging-directory>target/server2/data/messaging/paging</paging-directory>
<!-- Connectors -->
<connectors>
<connector name="netty-connector">tcp://localhost:61618</connector>
<connector name="eu-west-1-connector">tcp://localhost:61616</connector>
<connector name="eu-east-1-connector">tcp://localhost:61617</connector>
<connector name="us-central-1-connector">tcp://localhost:61618</connector>
</connectors>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61618</acceptor>
</acceptors>
<!-- Federation -->
<federations>
<federation name="us-central-1-federation">
<upstream name="eu-east-1-upstream" priority-adjustment="-1">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-east-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<upstream name="eu-west-1-upstream" priority-adjustment="-1">
<circuit-breaker-timeout>1000</circuit-breaker-timeout>
<static-connectors>
<connector-ref>eu-west-1-connector</connector-ref>
</static-connectors>
<policy ref="policySetA"/>
</upstream>
<policy-set name="policySetA">
<policy ref="queue-federation" />
</policy-set>
<queue-policy name="queue-federation" >
<include queue-match="exampleQueue" address-match="#" />
</queue-policy>
</federation>
</federations>
<!-- Other config -->
<security-settings>
<!--security for example queue-->
<security-setting match="exampleQueue">
<permission roles="guest" type="createDurableQueue"/>
<permission roles="guest" type="deleteDurableQueue"/>
<permission roles="guest" type="createNonDurableQueue"/>
<permission roles="guest" type="deleteNonDurableQueue"/>
<permission roles="guest" type="consume"/>
<permission roles="guest" type="send"/>
</security-setting>
</security-settings>
<addresses>
<address name="exampleQueue">
<anycast>
<queue name="exampleQueue"/>
</anycast>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,62 @@
<?xml version='1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq.examples.clustered</groupId>
<artifactId>broker-features</artifactId>
<version>2.7.0-SNAPSHOT</version>
</parent>
<groupId>org.apache.activemq.examples.federation</groupId>
<artifactId>broker-federation</artifactId>
<packaging>pom</packaging>
<name>ActiveMQ Artemis Federation Examples</name>
<!-- Properties -->
<properties>
<!--
Explicitly declaring the source encoding eliminates the following
message: [WARNING] Using platform encoding (UTF-8 actually) to copy
filtered resources, i.e. build is platform dependent!
-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<activemq.basedir>${project.basedir}/../../..</activemq.basedir>
</properties>
<profiles>
<profile>
<id>examples</id>
<modules>
<module>federated-queue</module>
<module>federated-address</module>
</modules>
</profile>
<profile>
<id>release</id>
<modules>
<module>federated-queue</module>
<module>federated-address</module>
</modules>
</profile>
</profiles>
</project>

View File

@ -30,7 +30,7 @@ under the License.
<groupId>org.apache.activemq.examples.clustered</groupId> <groupId>org.apache.activemq.examples.clustered</groupId>
<artifactId>broker-features</artifactId> <artifactId>broker-features</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>ActiveMQ Artemis Clustered Examples</name> <name>ActiveMQ Artemis Features Examples</name>
<!-- Properties --> <!-- Properties -->
<properties> <properties>
@ -48,6 +48,7 @@ under the License.
<id>examples</id> <id>examples</id>
<modules> <modules>
<module>clustered</module> <module>clustered</module>
<module>federation</module>
<module>ha</module> <module>ha</module>
<module>standard</module> <module>standard</module>
<module>sub-modules</module> <module>sub-modules</module>
@ -57,6 +58,7 @@ under the License.
<profile> <profile>
<id>release</id> <id>release</id>
<modules> <modules>
<module>federation</module>
<module>clustered</module> <module>clustered</module>
<module>ha</module> <module>ha</module>
<module>standard</module> <module>standard</module>

View File

@ -0,0 +1,306 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.integration.federation;
import java.util.Collections;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.Topic;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.FederationConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.tests.util.Wait;
import org.junit.Before;
import org.junit.Test;
/**
* Federated Address Test
*/
public class FederatedAddressTest extends FederatedTestBase {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
}
protected ConnectionFactory getCF(int i) throws Exception {
return new ActiveMQConnectionFactory("vm://" + i);
}
@Test
public void testFederatedAddressReplication() throws Exception {
String address = getName();
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
FederationConfiguration federationConfiguration2 = createFederationConfiguration("server0", address);
getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration2);
getServer(1).getFederationManager().deploy();
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 producer = session1.createProducer(topic1);
producer.send(session1.createTextMessage("hello"));
Session session0 = connection0.createSession();
Topic topic0 = session0.createTopic(address);
MessageConsumer consumer0 = session0.createConsumer(topic0);
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
MessageConsumer consumer1 = session1.createConsumer(topic1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer1.receive(10000));
assertNotNull(consumer0.receive(10000));
consumer1.close();
//Groups
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
producer.send(createTextMessage(session1, "groupA"));
assertNotNull(consumer0.receive(10000));
consumer1 = session1.createConsumer(topic1);
producer.send(createTextMessage(session1, "groupA"));
assertNotNull(consumer1.receive(10000));
assertNotNull(consumer0.receive(10000));
}
}
@Test
public void testFederatedAddressDeployAfterQueuesExist() throws Exception {
String address = getName();
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 producer = session1.createProducer(topic1);
producer.send(session1.createTextMessage("hello"));
Session session0 = connection0.createSession();
Topic topic0 = session0.createTopic(address);
MessageConsumer consumer0 = session0.createConsumer(topic0);
producer.send(session1.createTextMessage("hello"));
assertNull(consumer0.receive(100));
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
}
}
@Test
public void testFederatedAddressRemoteBrokerRestart() throws Exception {
String address = getName();
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
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 producer = session1.createProducer(topic1);
producer.send(session1.createTextMessage("hello"));
Session session0 = connection0.createSession();
Topic topic0 = session0.createTopic(address);
MessageConsumer consumer0 = session0.createConsumer(topic0);
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
connection1.close();
getServer(1).stop();
Wait.waitFor(() -> !getServer(1).isStarted());
assertNull(consumer0.receive(100));
getServer(1).start();
Wait.waitFor(() -> getServer(1).isActive());
Connection c1 = cf1.createConnection();
c1.start();
Wait.waitFor(() -> getServer(1).isStarted());
session1 = c1.createSession();
topic1 = session1.createTopic(address);
producer = session1.createProducer(topic1);
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
}
}
@Test
public void testFederatedAddressLocalBrokerRestart() throws Exception {
String address = getName();
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", address);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
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 producer = session1.createProducer(topic1);
producer.send(session1.createTextMessage("hello"));
Session session0 = connection0.createSession();
Topic topic0 = session0.createTopic(address);
MessageConsumer consumer0 = session0.createConsumer(topic0);
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
connection0.close();
getServer(0).stop();
Wait.waitFor(() -> !getServer(0).isStarted());
producer.send(session1.createTextMessage("hello"));
getServer(0).start();
Wait.waitFor(() -> getServer(0).isActive());
Connection newConnection = getCF(0).createConnection();
newConnection.start();
session0 = newConnection.createSession();
topic0 = session0.createTopic(address);
consumer0 = session0.createConsumer(topic0);
Wait.waitFor(() -> getServer(1).getPostOffice().getBindingsForAddress(SimpleString.toSimpleString(address)).getBindings().size() == 1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
newConnection.close();
}
}
private FederationConfiguration createFederationConfiguration(String connector, String address) {
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.addUpstreamConfiguration(upstreamConfiguration);
federationConfiguration.addFederationPolicy(addressPolicyConfiguration);
return federationConfiguration;
}
private Message createTextMessage(Session session1, String group) throws JMSException {
Message message = session1.createTextMessage("hello");
message.setStringProperty("JMSXGroupID", group);
return message;
}
}

View File

@ -0,0 +1,399 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.integration.federation;
import java.util.Collections;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.artemis.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.federation.FederationQueuePolicyConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationUpstreamConfiguration;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.tests.util.Wait;
import org.junit.Before;
import org.junit.Test;
/**
* Federated Queue Test
*/
public class FederatedQueueTest extends FederatedTestBase {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
}
protected ConnectionFactory getCF(int i) throws Exception {
return new ActiveMQConnectionFactory("vm://" + i);
}
@Test
public void testFederatedQueueRemoteConsume() throws Exception {
String queueName = getName();
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
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 producer = session1.createProducer(queue1);
producer.send(session1.createTextMessage("hello"));
connection0.start();
Session session0 = connection0.createSession();
Queue queue0 = session0.createQueue(queueName);
MessageConsumer consumer0 = session0.createConsumer(queue0);
assertNotNull(consumer0.receive(1000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
MessageConsumer consumer1 = session1.createConsumer(queue1);
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer1.receive(1000));
assertNull(consumer0.receive(10));
consumer1.close();
//Groups
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
producer.send(createTextMessage(session1, "groupA"));
assertNotNull(consumer0.receive(1000));
consumer1 = session1.createConsumer(queue1);
producer.send(createTextMessage(session1, "groupA"));
assertNull(consumer1.receive(10));
assertNotNull(consumer0.receive(1000));
}
}
@Test
public void testFederatedQueueRemoteConsumeDeployAfterConsumersExist() throws Exception {
String queueName = getName();
ConnectionFactory cf0 = getCF(0);
ConnectionFactory cf1 = getCF(1);
try (Connection connection0 = cf0.createConnection(); Connection connection1 = cf1.createConnection()) {
connection1.start();
Session session1 = connection1.createSession();
Queue queue1 = session1.createQueue(queueName);
MessageProducer producer = session1.createProducer(queue1);
producer.send(session1.createTextMessage("hello"));
connection0.start();
Session session0 = connection0.createSession();
Queue queue0 = session0.createQueue(queueName);
MessageConsumer consumer0 = session0.createConsumer(queue0);
assertNull(consumer0.receive(100));
FederationConfiguration federationConfiguration = createFederationConfiguration("server1", queueName);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(10000));
}
}
@Test
public void testFederatedQueueBiDirectional() 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);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration0);
getServer(0).getFederationManager().deploy();
FederationConfiguration federationConfiguration1 = createFederationConfiguration("server0", queueName);
getServer(1).getConfiguration().getFederationConfigurations().add(federationConfiguration1);
getServer(1).getFederationManager().deploy();
ConnectionFactory cf1 = getCF(1);
ConnectionFactory cf0 = getCF(0);
try (Connection connection1 = cf1.createConnection(); Connection connection0 = cf0.createConnection()) {
connection0.start();
Session session0 = connection0.createSession();
Queue queue0 = session0.createQueue(queueName);
MessageProducer producer0 = session0.createProducer(queue0);
connection1.start();
Session session1 = connection1.createSession();
Queue queue1 = session1.createQueue(queueName);
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));
producer1.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
//Test consumer move from broker 0, to broker 1
consumer0.close();
Wait.waitFor(() -> ((QueueBinding) getServer(0).getPostOffice().getBinding(SimpleString.toSimpleString(queueName))).consumerCount() == 0, 1000);
MessageConsumer consumer1 = session1.createConsumer(queue1);
producer0.send(session1.createTextMessage("hello"));
assertNotNull(consumer1.receive(10000));
producer1.send(session1.createTextMessage("hello"));
assertNotNull(consumer1.receive(1000));
//Test consumers on both broker 0, and broker 1 that messages route to consumers on same broker
consumer0 = session0.createConsumer(queue0);
producer0.send(session1.createTextMessage("produce0"));
producer1.send(session1.createTextMessage("produce1"));
Message message0 = consumer0.receive(1000);
assertNotNull(message0);
assertEquals("produce0", ((TextMessage) message0).getText());
Message message1 = consumer1.receive(1000);
assertNotNull(message1);
assertEquals("produce1", ((TextMessage) message1).getText());
}
}
@Test
public void testFederatedQueueChainOfBrokers() throws Exception {
String queueName = getName();
//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 = createFederationConfiguration("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);
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();
Queue queue0 = session0.createQueue(queueName);
connection2.start();
Session session2 = connection2.createSession();
Queue queue2 = session2.createQueue(queueName);
MessageProducer producer2 = session2.createProducer(queue2);
MessageConsumer consumer0 = session0.createConsumer(queue0);
//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));
}
}
@Test
public void testFederatedQueueRemoteBrokerRestart() 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 federationConfiguration = createFederationConfiguration("server1", queueName);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
ConnectionFactory cf1 = getCF(1);
Connection connection1 = cf1.createConnection();
connection1.start();
Session session1 = connection1.createSession();
Queue queue1 = session1.createQueue(queueName);
MessageProducer producer = session1.createProducer(queue1);
producer.send(session1.createTextMessage("hello"));
ConnectionFactory cf0 = getCF(0);
Connection connection0 = cf0.createConnection();
connection0.start();
Session session0 = connection0.createSession();
Queue queue0 = session0.createQueue(queueName);
MessageConsumer consumer0 = session0.createConsumer(queue0);
assertNotNull(consumer0.receive(1000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
connection1.close();
getServer(1).stop();
assertNull(consumer0.receive(100));
getServer(1).start();
connection1 = cf1.createConnection();
connection1.start();
session1 = connection1.createSession();
queue1 = session1.createQueue(queueName);
producer = session1.createProducer(queue1);
producer.send(session1.createTextMessage("hello"));
Wait.waitFor(() -> ((QueueBinding) getServer(1).getPostOffice().getBinding(SimpleString.toSimpleString(queueName))).consumerCount() == 1);
assertNotNull(consumer0.receive(1000));
}
@Test
public void testFederatedQueueLocalBrokerRestart() 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 federationConfiguration = createFederationConfiguration("server1", queueName);
getServer(0).getConfiguration().getFederationConfigurations().add(federationConfiguration);
getServer(0).getFederationManager().deploy();
ConnectionFactory cf1 = getCF(1);
Connection connection1 = cf1.createConnection();
connection1.start();
Session session1 = connection1.createSession();
Queue queue1 = session1.createQueue(queueName);
MessageProducer producer = session1.createProducer(queue1);
producer.send(session1.createTextMessage("hello"));
ConnectionFactory cf0 = getCF(0);
Connection connection0 = cf0.createConnection();
connection0.start();
Session session0 = connection0.createSession();
Queue queue0 = session0.createQueue(queueName);
MessageConsumer consumer0 = session0.createConsumer(queue0);
assertNotNull(consumer0.receive(1000));
producer.send(session1.createTextMessage("hello"));
assertNotNull(consumer0.receive(1000));
connection0.close();
getServer(0).stop();
producer.send(session1.createTextMessage("hello"));
getServer(0).start();
Wait.waitFor(() -> getServer(0).isActive());
connection0 = getCF(0).createConnection();
connection0.start();
session0 = connection0.createSession();
queue0 = session0.createQueue(queueName);
consumer0 = session0.createConsumer(queue0);
producer.send(session1.createTextMessage("hello"));
Wait.waitFor(() -> ((QueueBinding) getServer(1)
.getPostOffice()
.getBinding(SimpleString.toSimpleString(queueName)))
.consumerCount() == 1);
assertNotNull(consumer0.receive(1000));
}
private FederationConfiguration createFederationConfiguration(String connector, String queueName) {
return createFederationConfiguration(connector, queueName, null);
}
private FederationConfiguration createFederationConfiguration(String connector, String queueName, Boolean includeFederated) {
FederationUpstreamConfiguration upstreamConfiguration = new FederationUpstreamConfiguration();
upstreamConfiguration.setName(connector);
upstreamConfiguration.getConnectionConfiguration().setStaticConnectors(Collections.singletonList(connector));
upstreamConfiguration.getConnectionConfiguration().setCircuitBreakerTimeout(-1);
upstreamConfiguration.addPolicyRef("QueuePolicy" + queueName);
FederationQueuePolicyConfiguration queuePolicyConfiguration = new FederationQueuePolicyConfiguration();
queuePolicyConfiguration.setName( "QueuePolicy" + queueName);
queuePolicyConfiguration.addInclude(new FederationQueuePolicyConfiguration.Matcher()
.setQueueMatch(queueName).setAddressMatch("#"));
if (includeFederated != null) {
queuePolicyConfiguration.setIncludeFederated(includeFederated);
}
FederationConfiguration federationConfiguration = new FederationConfiguration();
federationConfiguration.setName("default");
federationConfiguration.addUpstreamConfiguration(upstreamConfiguration);
federationConfiguration.addFederationPolicy(queuePolicyConfiguration);
return federationConfiguration;
}
private Message createTextMessage(Session session1, String group) throws JMSException {
Message message = session1.createTextMessage("hello");
message.setStringProperty("JMSXGroupID", group);
return message;
}
}

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.integration.federation;
import java.util.ArrayList;
import java.util.List;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServers;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Before;
/**
* Federation Test Base
*/
public class FederatedTestBase extends ActiveMQTestBase {
protected List<MBeanServer> mBeanServers = new ArrayList<>();
protected List<ActiveMQServer> servers = new ArrayList<>();
@Override
@Before
public void setUp() throws Exception {
super.setUp();
for (int i = 0; i < numberOfServers(); i++) {
MBeanServer mBeanServer = MBeanServerFactory.createMBeanServer();
mBeanServers.add(mBeanServer);
Configuration config = createDefaultConfig(i, false).setSecurityEnabled(false);
for (int j = 0; j < numberOfServers(); j++) {
config.addConnectorConfiguration("server" + j, "vm://" + j);
}
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(config, mBeanServer, false));
servers.add(server);
server.start();
}
}
protected int numberOfServers() {
return 3;
}
public ActiveMQServer getServer(int i) {
return servers.get(i);
}
}

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
@ -46,6 +47,7 @@ import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.core.transaction.impl.TransactionImpl; import org.apache.activemq.artemis.core.transaction.impl.TransactionImpl;
import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.tests.unit.core.postoffice.impl.FakeQueue; import org.apache.activemq.artemis.tests.unit.core.postoffice.impl.FakeQueue;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.RandomUtil; import org.apache.activemq.artemis.utils.RandomUtil;
@ -156,6 +158,16 @@ public class PageCursorStressTest extends ActiveMQTestBase {
} }
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
@Override @Override
public SimpleString getFilterString() { public SimpleString getFilterString() {
return new SimpleString("even=true"); return new SimpleString("even=true");
@ -175,6 +187,16 @@ public class PageCursorStressTest extends ActiveMQTestBase {
} }
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
@Override @Override
public SimpleString getFilterString() { public SimpleString getFilterString() {
return new SimpleString("even=true"); return new SimpleString("even=true");

View File

@ -16,11 +16,12 @@
*/ */
package org.apache.activemq.artemis.tests.unit.core.postoffice.impl; package org.apache.activemq.artemis.tests.unit.core.postoffice.impl;
import javax.transaction.xa.Xid;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import javax.transaction.xa.Xid;
import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.Message;
@ -38,6 +39,7 @@ import org.apache.activemq.artemis.core.server.impl.RefsOperation;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl; import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.core.transaction.TransactionOperation; import org.apache.activemq.artemis.core.transaction.TransactionOperation;
import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Test; import org.junit.Test;
@ -278,6 +280,17 @@ public class BindingsImplTest extends ActiveMQTestBase {
return false; return false;
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
} }
private final class FakeBinding implements Binding { private final class FakeBinding implements Binding {

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -44,6 +45,7 @@ import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.impl.QueueImpl; import org.apache.activemq.artemis.core.server.impl.QueueImpl;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakeConsumer; import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakeConsumer;
import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakeFilter; import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakeFilter;
import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakePostOffice; import org.apache.activemq.artemis.tests.unit.core.server.impl.fakes.FakePostOffice;
@ -163,6 +165,16 @@ public class QueueImplTest extends ActiveMQTestBase {
return false; return false;
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
@Override @Override
public SimpleString getFilterString() { public SimpleString getFilterString() {
return null; return null;

View File

@ -17,9 +17,11 @@
package org.apache.activemq.artemis.tests.unit.core.server.impl.fakes; package org.apache.activemq.artemis.tests.unit.core.server.impl.fakes;
import java.util.Map;
import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.filter.Filter; import org.apache.activemq.artemis.core.filter.Filter;
import org.apache.activemq.artemis.selector.filter.Filterable;
public class FakeFilter implements Filter { public class FakeFilter implements Filter {
@ -55,6 +57,16 @@ public class FakeFilter implements Filter {
return true; return true;
} }
@Override
public boolean match(Map<String, String> map) {
return false;
}
@Override
public boolean match(Filterable filterable) {
return false;
}
@Override @Override
public SimpleString getFilterString() { public SimpleString getFilterString() {
return null; return null;