ARTEMIS-4642 Fix tracked federated demand when links are rejected or blocked

Under some scenarios federation demand tracking is losing track of total demand
for a federated resource leading to teardown of federated links before all local
demand has been removed from the resource. This occurs most often if the attempts
to establish a federation link are refused because the resource hasn't yet been
created and an eventual attach succeeds, but can also occur in combination with
a plugin blocking or not blocking federation link creation in some cases.
This commit is contained in:
Timothy Bish 2024-02-15 12:02:06 -05:00 committed by Robbie Gemmell
parent 42a2e4637f
commit b8800337df
10 changed files with 772 additions and 140 deletions

View File

@ -165,7 +165,7 @@ public class AMQPFederationAddressConsumer implements FederationConsumerInternal
if (started) {
started = false;
connection.runLater(() -> {
federation.removeLinkClosedInterceptor(consumerInfo.getFqqn());
federation.removeLinkClosedInterceptor(consumerInfo.getId());
if (receiver != null) {
try {
@ -350,7 +350,7 @@ public class AMQPFederationAddressConsumer implements FederationConsumerInternal
// Intercept remote close and check for valid reasons for remote closure such as
// the remote peer not having a matching queue for this subscription or from an
// operator manually closing the link.
federation.addLinkClosedInterceptor(consumerInfo.getFqqn(), remoteCloseInterceptor);
federation.addLinkClosedInterceptor(consumerInfo.getId(), remoteCloseInterceptor);
receiver = new AMQPFederatedAddressDeliveryReceiver(session, consumerInfo, protonReceiver);

View File

@ -162,7 +162,7 @@ public class AMQPFederationQueueConsumer implements FederationConsumerInternal {
if (started) {
started = false;
connection.runLater(() -> {
federation.removeLinkClosedInterceptor(consumerInfo.getFqqn());
federation.removeLinkClosedInterceptor(consumerInfo.getId());
if (receiver != null) {
try {
@ -341,7 +341,7 @@ public class AMQPFederationQueueConsumer implements FederationConsumerInternal {
// Intercept remote close and check for valid reasons for remote closure such as
// the remote peer not having a matching queue for this subscription or from an
// operator manually closing the link.
federation.addLinkClosedInterceptor(consumerInfo.getFqqn(), remoteCloseIntercepter);
federation.addLinkClosedInterceptor(consumerInfo.getId(), remoteCloseIntercepter);
receiver = new AMQPFederatedQueueDeliveryReceiver(localQueue, protonReceiver);

View File

@ -38,6 +38,11 @@ public interface FederationConsumerInfo {
QUEUE_CONSUMER
}
/**
* @return a unique Id for the consumer being represented.
*/
String getId();
/**
* @return the type of federation consumer being represented.
*/

View File

@ -18,42 +18,59 @@
package org.apache.activemq.artemis.protocol.amqp.federation.internal;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
/**
* An entry type class used to hold a {@link FederationConsumerInternal} and
* any other state data needed by the manager that is creating them based on the
* policy configuration for the federation instance. The entry can be extended
* policy configuration for the federation instance. The entry can be extended
* by federation implementation to hold additional state data for the federation
* consumer and the managing of its lifetime.
* consumer and the managing active demand.
*
* This entry type provides a reference counter that can be used to register demand
* This entry type provides reference tracking state for current demand (bindings)
* on a federation resource such that it is not torn down until all demand has been
* removed from the local resource.
*/
public class FederationAddressEntry {
private final FederationConsumerInternal consumer;
private final AddressInfo addressInfo;
private final Set<Binding> demandBindings = new HashSet<>();
private FederationConsumerInternal consumer;
/**
* Creates a new address entry with a single reference
* Creates a new address entry for tracking demand on a federated address
*
* @param consumer
* The federation consumer that will be carried in this entry.
* @param addressInfo
* The address information object that this entry hold demand state for.
*/
public FederationAddressEntry(FederationConsumerInternal consumer) {
this.consumer = consumer;
public FederationAddressEntry(AddressInfo addressInfo) {
this.addressInfo = addressInfo;
}
/**
* @return the address information that this entry is acting to federate.
*/
public AddressInfo getAddressInfo() {
return addressInfo;
}
/**
* @return the address that this entry is acting to federate.
*/
public String getAddress() {
return consumer.getConsumerInfo().getAddress();
return addressInfo.getName().toString();
}
/**
* @return <code>true</code> if a consumer is currently set on this entry.
*/
public boolean hasConsumer() {
return consumer != null;
}
/**
@ -63,6 +80,30 @@ public class FederationAddressEntry {
return consumer;
}
/**
* Sets the consumer assigned to this entry to the given instance.
*
* @param consumer
* The federation consumer that is currently active for this entry.
*
* @return this federation address consumer entry.
*/
public FederationAddressEntry setConsumer(FederationConsumerInternal consumer) {
Objects.requireNonNull(consumer, "Cannot assign a null consumer to this entry, call clear to unset");
this.consumer = consumer;
return this;
}
/**
* Clears the currently assigned consumer from this entry.
*
* @return this federation address consumer entry.
*/
public FederationAddressEntry clearConsumer() {
this.consumer = null;
return this;
}
/**
* @return true if there are bindings that are mapped to this federation entry.
*/
@ -85,7 +126,7 @@ public class FederationAddressEntry {
*
* @return this federation address consumer entry.
*/
public FederationAddressEntry removeDenamd(Binding binding) {
public FederationAddressEntry removeDemand(Binding binding) {
demandBindings.remove(binding);
return this;
}

View File

@ -64,8 +64,8 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
protected final ActiveMQServer server;
protected final FederationInternal federation;
protected final FederationReceiveFromAddressPolicy policy;
protected final Map<String, FederationAddressEntry> remoteConsumers = new HashMap<>();
protected final Map<DivertBinding, Set<QueueBinding>> matchingDiverts = new HashMap<>();
protected final Map<String, FederationAddressEntry> demandTracking = new HashMap<>();
protected final Map<DivertBinding, Set<QueueBinding>> divertsTracking = new HashMap<>();
private volatile boolean started;
@ -101,9 +101,24 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
if (started) {
started = false;
server.unRegisterBrokerPlugin(this);
remoteConsumers.forEach((k, v) -> v.getConsumer().close()); // Cleanup and recreate if ever reconnected.
remoteConsumers.clear();
matchingDiverts.clear();
demandTracking.forEach((k, v) -> {
if (v.hasConsumer()) {
v.getConsumer().close();
}
});
demandTracking.clear();
divertsTracking.clear();
}
}
@Override
public synchronized void afterRemoveAddress(SimpleString address, AddressInfo addressInfo) throws ActiveMQException {
if (started) {
final FederationAddressEntry entry = demandTracking.remove(address.toString());
if (entry != null && entry.hasConsumer()) {
entry.getConsumer().close();
}
}
}
@ -111,7 +126,7 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
public synchronized void afterRemoveBinding(Binding binding, Transaction tx, boolean deleteData) throws ActiveMQException {
if (started) {
if (binding instanceof QueueBinding) {
final FederationAddressEntry entry = remoteConsumers.get(binding.getAddress().toString());
final FederationAddressEntry entry = demandTracking.get(binding.getAddress().toString());
if (entry != null) {
// This is QueueBinding that was mapped to a federated address so we can directly remove
@ -122,7 +137,7 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
// is bound and remove the mapping for any matches, diverts can have a composite set of address
// forwards so each divert must be checked in turn to see if it contains the address the removed
// binding was bound to.
matchingDiverts.entrySet().forEach(divertEntry -> {
divertsTracking.entrySet().forEach(divertEntry -> {
final String sourceAddress = divertEntry.getKey().getAddress().toString();
final SimpleString forwardAddress = divertEntry.getKey().getDivert().getForwardAddress();
@ -134,7 +149,7 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
divertEntry.getValue().remove(binding);
if (divertEntry.getValue().isEmpty()) {
tryRemoveDemandOnAddress(remoteConsumers.get(sourceAddress), divertEntry.getKey());
tryRemoveDemandOnAddress(demandTracking.get(sourceAddress), divertEntry.getKey());
}
}
});
@ -142,14 +157,14 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
} else if (policy.isEnableDivertBindings() && binding instanceof DivertBinding) {
final DivertBinding divert = (DivertBinding) binding;
if (matchingDiverts.remove(divert) != null) {
if (divertsTracking.remove(divert) != null) {
// The divert binding is treated as one unit of demand on a federated address and
// when the divert is removed that unit of demand is removed regardless of existing
// bindings still remaining on the divert forwards. If the divert demand was the
// only thing keeping the federated address consumer open this will result in it
// being closed.
try {
tryRemoveDemandOnAddress(remoteConsumers.get(divert.getAddress().toString()), divert);
tryRemoveDemandOnAddress(demandTracking.get(divert.getAddress().toString()), divert);
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.federationBindingsLookupError(divert.getDivert().getForwardAddress(), e);
}
@ -160,11 +175,11 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
protected final void tryRemoveDemandOnAddress(FederationAddressEntry entry, Binding binding) {
if (entry != null) {
entry.removeDenamd(binding);
entry.removeDemand(binding);
logger.trace("Reducing demand on federated address {}, remaining demand? {}", entry.getAddress(), entry.hasDemand());
if (!entry.hasDemand()) {
if (!entry.hasDemand() && entry.hasConsumer()) {
final FederationConsumerInternal federationConsuner = entry.getConsumer();
try {
@ -172,7 +187,7 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
federationConsuner.close();
signalAfterCloseFederationConsumer(federationConsuner);
} finally {
remoteConsumers.remove(entry.getAddress());
demandTracking.remove(entry.getAddress());
}
}
}
@ -200,7 +215,8 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
// match the divert.
server.getPostOffice()
.getDirectBindings(addressInfo.getName())
.stream().filter(binding -> binding instanceof DivertBinding)
.stream()
.filter(binding -> binding instanceof DivertBinding)
.forEach(this::checkBindingForMatch);
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.federationBindingsLookupError(addressInfo.getName(), e);
@ -260,9 +276,9 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
// We only need to check if we've never seen the divert before, afterwards we will
// be checking it any time a new QueueBinding is added instead.
if (matchingDiverts.get(divertBinding) == null) {
if (divertsTracking.get(divertBinding) == null) {
final Set<QueueBinding> matchingQueues = new HashSet<>();
matchingDiverts.put(divertBinding, matchingQueues);
divertsTracking.put(divertBinding, matchingQueues);
// We must account for the composite divert case by splitting the address and
// getting the bindings on each one.
@ -306,7 +322,7 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
final SimpleString queueAddress = queueBinding.getAddress();
matchingDiverts.entrySet().forEach((e) -> {
divertsTracking.entrySet().forEach((e) -> {
final SimpleString forwardAddress = e.getKey().getDivert().getForwardAddress();
final DivertBinding divertBinding = e.getKey();
@ -350,44 +366,62 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
return false;
}
protected final void createOrUpdateFederatedAddressConsumerForBinding(AddressInfo address, Binding binding) {
logger.trace("Federation Address Policy matched on for demand on address: {} : binding: {}", address, binding);
protected final void createOrUpdateFederatedAddressConsumerForBinding(AddressInfo addressInfo, Binding binding) {
logger.trace("Federation Address Policy matched on for demand on address: {} : binding: {}", addressInfo, binding);
final String addressName = addressInfo.getName().toString();
final FederationAddressEntry entry;
// Check for existing consumer add demand from a additional local consumer to ensure
// the remote consumer remains active until all local demand is withdrawn. The federation
// plugin can block creation of the federation consumer at this stage.
if (remoteConsumers.containsKey(address.getName().toString())) {
logger.trace("Federation Address Policy manager found existing demand for address: {}, adding demand", address);
remoteConsumers.get(address.getName().toString()).addDemand(binding);
} else if (!isPluginBlockingFederationConsumerCreate(address)) {
logger.trace("Federation Address Policy manager creating remote consumer for address: {}", address);
// the remote consumer remains active until all local demand is withdrawn.
if (demandTracking.containsKey(addressName)) {
entry = demandTracking.get(addressName);
} else {
entry = new FederationAddressEntry(addressInfo);
demandTracking.put(addressName, entry);
}
final FederationConsumerInfo consumerInfo = createConsumerInfo(address);
final FederationConsumerInternal queueConsumer = createFederationConsumer(consumerInfo);
final FederationAddressEntry entry = createConsumerEntry(queueConsumer);
// Demand passed all binding plugin blocking checks so we track it, plugin can still
// stop federation of the address based on some external criteria but once it does
// (if ever) allow it we will have tracked all allowed demand.
entry.addDemand(binding);
tryCreateFederationConsumerForAddress(entry);
}
private void tryCreateFederationConsumerForAddress(FederationAddressEntry addressEntry) {
final AddressInfo addressInfo = addressEntry.getAddressInfo();
if (addressEntry.hasDemand() && !addressEntry.hasConsumer() && !isPluginBlockingFederationConsumerCreate(addressInfo)) {
logger.trace("Federation Address Policy manager creating remote consumer for address: {}", addressInfo);
final FederationConsumerInfo consumerInfo = createConsumerInfo(addressInfo);
final FederationConsumerInternal addressConsumer = createFederationConsumer(consumerInfo);
signalBeforeCreateFederationConsumer(consumerInfo);
// Handle remote close with remove of consumer which means that future demand will
// attempt to create a new consumer for that demand. Ensure that thread safety is
// accounted for here as the notification can be asynchronous.
queueConsumer.setRemoteClosedHandler((closedConsumer) -> {
addressConsumer.setRemoteClosedHandler((closedConsumer) -> {
synchronized (this) {
try {
remoteConsumers.remove(closedConsumer.getConsumerInfo().getAddress());
final FederationAddressEntry tracked = demandTracking.get(closedConsumer.getConsumerInfo().getAddress());
if (tracked != null) {
tracked.clearConsumer();
}
} finally {
closedConsumer.close();
}
}
});
// Called under lock so state should stay in sync
remoteConsumers.put(entry.getAddress(), entry.addDemand(binding));
addressEntry.setConsumer(addressConsumer);
// Now that we are tracking it we can start it
queueConsumer.start();
addressConsumer.start();
signalAfterCreateFederationConsumer(queueConsumer);
signalAfterCreateFederationConsumer(addressConsumer);
}
}
@ -404,21 +438,12 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
public synchronized void afterRemoteAddressAdded(String addressName) throws Exception {
// Assume that the remote address that matched a previous federation attempt is MULTICAST
// so that we retry if current local state matches the policy and if it isn't we will once
// again record the federation attempt with the remote and but updated if the remote removes
// and adds the address again (hopefully with the correct routing type).
if (started && testIfAddressMatchesPolicy(addressName, RoutingType.MULTICAST) && !remoteConsumers.containsKey(addressName)) {
final SimpleString address = SimpleString.toSimpleString(addressName);
// Need to trigger check for all bindings that match to accumulate demand on the address
// if any and ensure an outgoing consumer is attempted.
server.getPostOffice()
.getDirectBindings(address)
.stream()
.filter(binding -> binding instanceof QueueBinding ||
(policy.isEnableDivertBindings() && binding instanceof DivertBinding))
.forEach(this::checkBindingForMatch);
// again record the federation attempt with the remote and be updated if the remote removes
// and adds the address again (hopefully with the correct routing type). We retrain all the
// current demand and don't need to re-check the server state before trying to create the
// remote address consumer.
if (started && testIfAddressMatchesPolicy(addressName, RoutingType.MULTICAST) && demandTracking.containsKey(addressName)) {
tryCreateFederationConsumerForAddress(demandTracking.get(addressName));
}
}
@ -470,13 +495,13 @@ public abstract class FederationAddressPolicyManager implements ActiveMQServerBi
* instance lifetime. A subclass can override this method to return a more customized entry type with
* additional state data.
*
* @param consumer
* The {@link FederationConsumerInternal} instance that will be housed in this entry.
* @param addressInfo
* The address information that the created entry is meant to track demand for.
*
* @return a new {@link FederationAddressEntry} that holds the given federation consumer.
* @return a new {@link FederationAddressEntry} that tracks demand on an address.
*/
protected FederationAddressEntry createConsumerEntry(FederationConsumerInternal consumer) {
return new FederationAddressEntry(consumer);
protected FederationAddressEntry createConsumerEntry(AddressInfo addressInfo) {
return new FederationAddressEntry(addressInfo);
}
/**

View File

@ -18,6 +18,7 @@
package org.apache.activemq.artemis.protocol.amqp.federation.internal;
import java.util.Objects;
import java.util.UUID;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
@ -48,6 +49,7 @@ public class FederationGenericConsumerInfo implements FederationConsumerInfo {
private final String filterString;
private final String fqqn;
private final int priority;
private final String id;
protected FederationGenericConsumerInfo(Role role, String address, String queueName, RoutingType routingType,
String filterString, String fqqn, int priority) {
@ -58,6 +60,7 @@ public class FederationGenericConsumerInfo implements FederationConsumerInfo {
this.filterString = filterString;
this.fqqn = fqqn;
this.priority = priority;
this.id = UUID.randomUUID().toString();
}
/**
@ -116,6 +119,11 @@ public class FederationGenericConsumerInfo implements FederationConsumerInfo {
ActiveMQDefaultConfiguration.getDefaultConsumerPriority());
}
@Override
public String getId() {
return id;
}
@Override
public Role getRole() {
return role;

View File

@ -18,9 +18,11 @@
package org.apache.activemq.artemis.protocol.amqp.federation.internal;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationConsumerInfo;
/**
* An entry type class used to hold a {@link FederationConsumerInternal} and
@ -29,24 +31,46 @@ import org.apache.activemq.artemis.core.server.ServerConsumer;
* by federation implementation to hold additional state data for the federation
* consumer and the managing of its lifetime.
*
* This entry type provides a reference counter that can be used to register demand
* This entry type provides reference tracking state for current demand (bindings)
* on a federation resource such that it is not torn down until all demand has been
* removed from the local resource.
*/
public class FederationQueueEntry {
private final FederationConsumerInternal consumer;
private final FederationConsumerInfo consumerInfo;
private final Set<String> consumerDemand = new HashSet<>();
private FederationConsumerInternal consumer;
/**
* Creates a new queue entry with a single reference
*
* @param consumer
* The federation consumer that will be carried in this entry.
* @param consumerInfo
* Consumer information object used to define the federation queue consumer
*/
public FederationQueueEntry(FederationConsumerInternal consumer) {
this.consumer = consumer;
public FederationQueueEntry(FederationConsumerInfo consumerInfo) {
this.consumerInfo = consumerInfo;
}
/**
* @return the name of the queue that this entry tracks demand for.
*/
public String getQueueName() {
return consumerInfo.getQueueName();
}
/**
* @return the consumer information that defines the properties of federation queue consumers
*/
public FederationConsumerInfo getConsumerInfo() {
return consumerInfo;
}
/**
* @return <code>true</code> if a consumer is currently set on this entry.
*/
public boolean hasConsumer() {
return consumer != null;
}
/**
@ -56,6 +80,30 @@ public class FederationQueueEntry {
return consumer;
}
/**
* Sets the consumer assigned to this entry to the given instance.
*
* @param consumer
* The federation consumer that is currently active for this entry.
*
* @return this federation queue consumer entry.
*/
public FederationQueueEntry setConsumer(FederationConsumerInternal consumer) {
Objects.requireNonNull(consumer, "Cannot assign a null consumer to this entry, call clear to unset");
this.consumer = consumer;
return this;
}
/**
* Clears the currently assigned consumer from this entry.
*
* @return this federation queue consumer entry.
*/
public FederationQueueEntry clearConsumer() {
this.consumer = null;
return this;
}
/**
* @return true if there are bindings that are mapped to this federation entry.
*/

View File

@ -28,13 +28,16 @@ import java.util.function.Predicate;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.core.filter.Filter;
import org.apache.activemq.artemis.core.filter.impl.FilterImpl;
import org.apache.activemq.artemis.core.postoffice.Binding;
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.server.ServerSession;
import org.apache.activemq.artemis.core.server.federation.Federation;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBindingPlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerConsumerPlugin;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationConsumer;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationConsumerInfo;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromQueuePolicy;
@ -46,14 +49,14 @@ import org.slf4j.LoggerFactory;
* monitoring broker queues for demand and creating a consumer for on the remote side
* to federate messages back to this peer.
*/
public abstract class FederationQueuePolicyManager implements ActiveMQServerConsumerPlugin {
public abstract class FederationQueuePolicyManager implements ActiveMQServerConsumerPlugin, ActiveMQServerBindingPlugin {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final ActiveMQServer server;
protected final Predicate<ServerConsumer> federationConsumerMatcher;
protected final FederationReceiveFromQueuePolicy policy;
protected final Map<String, FederationQueueEntry> remoteConsumers = new HashMap<>();
protected final Map<String, FederationQueueEntry> demandTracking = new HashMap<>();
protected final FederationInternal federation;
private volatile boolean started;
@ -93,8 +96,12 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
// broker plugin instances.
server.unRegisterBrokerPlugin(this);
started = false;
remoteConsumers.forEach((k, v) -> v.getConsumer().close()); // Cleanup and recreate if ever reconnected.
remoteConsumers.clear();
demandTracking.forEach((k, v) -> {
if (v.hasConsumer()) {
v.getConsumer().close();
}
});
demandTracking.clear();
}
}
@ -109,7 +116,7 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
public synchronized void afterCloseConsumer(ServerConsumer consumer, boolean failed) {
if (started) {
final String queueName = consumer.getQueue().getName().toString();
final FederationQueueEntry entry = remoteConsumers.get(queueName);
final FederationQueueEntry entry = demandTracking.get(queueName);
if (entry == null) {
return;
@ -119,7 +126,7 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
logger.trace("Reducing demand on federated queue {}, remaining demand? {}", queueName, entry.hasDemand());
if (!entry.hasDemand()) {
if (!entry.hasDemand() && entry.hasConsumer()) {
final FederationConsumerInternal federationConsuner = entry.getConsumer();
try {
@ -127,12 +134,24 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
federationConsuner.close();
signalAfterCloseFederationConsumer(federationConsuner);
} finally {
remoteConsumers.remove(queueName);
demandTracking.remove(queueName);
}
}
}
}
@Override
public synchronized void afterRemoveBinding(Binding binding, Transaction tx, boolean deleteData) throws ActiveMQException {
if (binding instanceof QueueBinding) {
final QueueBinding queueBinding = (QueueBinding) binding;
final FederationQueueEntry entry = demandTracking.remove(queueBinding.getQueue().getName().toString());
if (entry != null && entry.hasConsumer()) {
entry.getConsumer().close();
}
}
}
protected final void scanAllQueueBindings() {
server.getPostOffice()
.getAllBindings()
@ -150,7 +169,9 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
}
protected final void reactIfConsumerMatchesPolicy(ServerConsumer consumer) {
if (testIfQueueMatchesPolicy(consumer.getQueueAddress().toString(), consumer.getQueueName().toString())) {
final String queueName = consumer.getQueue().getName().toString();
if (testIfQueueMatchesPolicy(consumer.getQueueAddress().toString(), queueName)) {
// We should ignore federation consumers from remote peers but configuration does allow
// these to be federated again for some very specific use cases so we check before then
// moving onto any server plugin checks kick in.
@ -158,49 +179,59 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
return;
}
if (isPluginBlockingFederationConsumerCreate(consumer.getQueue())) {
return;
}
logger.trace("Federation Policy matched on consumer for binding: {}", consumer.getBinding());
final FederationConsumerInfo consumerInfo = createConsumerInfo(consumer);
final FederationQueueEntry entry;
// Check for existing consumer add demand from a additional local consumer
// to ensure the remote consumer remains active until all local demand is
// withdrawn.
if (remoteConsumers.containsKey(consumerInfo.getQueueName())) {
logger.trace("Federation Queue Policy manager found existing demand for queue: {}, adding demand", consumerInfo.getQueueName());
remoteConsumers.get(consumerInfo.getQueueName()).addDemand(consumer);
// Check for existing consumer add demand from a additional local consumer to ensure
// the remote consumer remains active until all local demand is withdrawn.
if (demandTracking.containsKey(queueName)) {
logger.trace("Federation Queue Policy manager found existing demand for queue: {}, adding demand", queueName);
entry = demandTracking.get(queueName);
} else {
logger.trace("Federation Queue Policy manager creating remote consumer for queue: {}", consumerInfo.getQueueName());
signalBeforeCreateFederationConsumer(consumerInfo);
final FederationConsumerInternal queueConsumer = createFederationConsumer(consumerInfo);
final FederationQueueEntry entry = createServerConsumerEntry(queueConsumer).addDemand(consumer);
// Handle remote close with remove of consumer which means that future demand will
// attempt to create a new consumer for that demand. Ensure that thread safety is
// accounted for here as the notification can be asynchronous.
queueConsumer.setRemoteClosedHandler((closedConsumer) -> {
synchronized (this) {
try {
remoteConsumers.remove(closedConsumer.getConsumerInfo().getQueueName());
} finally {
closedConsumer.close();
}
}
});
// Called under lock so state should stay in sync
remoteConsumers.put(consumerInfo.getQueueName(), entry);
// Now that we are tracking it we can start it
queueConsumer.start();
signalAfterCreateFederationConsumer(queueConsumer);
demandTracking.put(queueName, entry = createConsumerEntry(createConsumerInfo(consumer)));
}
// Demand passed all binding plugin blocking checks so we track it, plugin can still
// stop federation of the queue based on some external criteria but once it does
// (if ever) allow it we will have tracked all allowed demand.
entry.addDemand(consumer);
tryCreateFederationConsumerForQueue(entry, consumer.getQueue());
}
}
private void tryCreateFederationConsumerForQueue(FederationQueueEntry queueEntry, Queue queue) {
if (queueEntry.hasDemand() && !queueEntry.hasConsumer() && !isPluginBlockingFederationConsumerCreate(queue)) {
logger.trace("Federation Queue Policy manager creating remote consumer for queue: {}", queueEntry.getQueueName());
signalBeforeCreateFederationConsumer(queueEntry.getConsumerInfo());
final FederationConsumerInternal queueConsumer = createFederationConsumer(queueEntry.getConsumerInfo());
// Handle remote close with remove of consumer which means that future demand will
// attempt to create a new consumer for that demand. Ensure that thread safety is
// accounted for here as the notification can be asynchronous.
queueConsumer.setRemoteClosedHandler((closedConsumer) -> {
synchronized (this) {
try {
final FederationQueueEntry tracked = demandTracking.get(closedConsumer.getConsumerInfo().getQueueName());
if (tracked != null) {
tracked.clearConsumer();
}
} finally {
closedConsumer.close();
}
}
});
queueEntry.setConsumer(queueConsumer);
// Now that we are tracking it we can start it
queueConsumer.start();
signalAfterCreateFederationConsumer(queueConsumer);
}
}
@ -220,17 +251,15 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
// We ignore the remote address as locally the policy can be a wild card match and we can
// try to federate based on the Queue only, if the remote rejects the federation consumer
// binding again the request will once more be recorded and we will get another event if
// the queue were recreated such that a match could be made.
if (started && testIfQueueMatchesPolicy(queueName)) {
// the queue were recreated such that a match could be made. We retain all the current
// demand and don't need to re-check the server state before trying to create the
// remote queue consumer.
if (started && testIfQueueMatchesPolicy(queueName) && demandTracking.containsKey(queueName)) {
final Queue queue = server.locateQueue(queueName);
// Find a matching Queue with the given name and then check for demand based
// on the attached consumers and the current policy constraints.
server.getPostOffice()
.getAllBindings()
.filter(b -> b instanceof QueueBinding && ((QueueBinding) b).getQueue().getName().toString().equals(queueName))
.map(b -> (QueueBinding) b)
.forEach(b -> checkQueueForMatch(b.getQueue()));
if (queue != null) {
tryCreateFederationConsumerForQueue(demandTracking.get(queueName), queue);
}
}
}
@ -286,13 +315,13 @@ public abstract class FederationQueuePolicyManager implements ActiveMQServerCons
* instance. A subclass can override this method to return a more customized entry type with additional
* state data.
*
* @param consumer
* The {@link FederationConsumerInternal} instance that will be housed in this entry.
* @param consumerInfo
* The consumer information that defines characteristics of the federation queue consumer
*
* @return a new {@link FederationQueueEntry} that holds the given federation consumer.
* @return a new {@link FederationQueueEntry} that holds the given queue name.
*/
protected FederationQueueEntry createServerConsumerEntry(FederationConsumerInternal consumer) {
return new FederationQueueEntry(consumer);
protected FederationQueueEntry createConsumerEntry(FederationConsumerInfo consumerInfo) {
return new FederationQueueEntry(consumerInfo);
}
/**

View File

@ -60,6 +60,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@ -3281,6 +3282,196 @@ public class AMQPFederationAddressPolicyTest extends AmqpClientTestSupport {
}
}
@Test(timeout = 20000)
public void testFederationAddressDemandTrackedWhenRemoteRejectsInitialAttempts() throws Exception {
try (ProtonTestServer peer = new ProtonTestServer()) {
peer.expectSASLAnonymousConnect();
peer.expectOpen().respond();
peer.expectBegin().respond();
peer.expectAttach().ofSender()
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
.respond()
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(10);
peer.start();
final URI remoteURI = peer.getServerURI();
logger.info("Connect test started, peer listening on: {}", remoteURI);
final AMQPFederationAddressPolicyElement receiveFromAddress = new AMQPFederationAddressPolicyElement();
receiveFromAddress.setName("address-policy");
receiveFromAddress.addToIncludes("test");
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
element.setName("sample-federation");
element.addLocalAddressPolicy(receiveFromAddress);
final AMQPBrokerConnectConfiguration amqpConnection =
new AMQPBrokerConnectConfiguration("testSimpleConnect", "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
amqpConnection.setReconnectAttempts(0);// No reconnects
amqpConnection.addElement(element);
server.getConfiguration().addAMQPConnection(amqpConnection);
server.start();
server.addAddressInfo(new AddressInfo(SimpleString.toSimpleString("test"), RoutingType.MULTICAST));
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
try (Connection connection = factory.createConnection()) {
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
final Topic topic = session.createTopic("test");
connection.start();
// First consumer we reject the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
.respondInKind()
.withNullSource();
peer.expectFlow().withLinkCredit(1000);
peer.remoteDetach().withErrorCondition("amqp:not-found", "the requested queue was not found").queue().afterDelay(10);
peer.expectDetach();
final MessageConsumer consumer1 = session.createConsumer(topic);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Second consumer we reject the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
.respondInKind()
.withNullSource();
peer.expectFlow().withLinkCredit(1000);
peer.remoteDetach().withErrorCondition("amqp:not-found", "the requested queue was not found").queue().afterDelay(10);
peer.expectDetach();
final MessageConsumer consumer2 = session.createConsumer(topic);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Third consumer we accept the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(1000);
final MessageConsumer consumer3 = session.createConsumer(topic);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer3.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer2.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.expectDetach().respond();
// Demand should be gone now
consumer1.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.close();
}
}
}
@Test(timeout = 20000)
public void testFederationAddressDemandTrackedWhenPluginBlocksInitialAttempts() throws Exception {
try (ProtonTestServer peer = new ProtonTestServer()) {
peer.expectSASLAnonymousConnect();
peer.expectOpen().respond();
peer.expectBegin().respond();
peer.expectAttach().ofSender()
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
.respond()
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(10);
peer.start();
final URI remoteURI = peer.getServerURI();
logger.info("Connect test started, peer listening on: {}", remoteURI);
final AMQPFederationAddressPolicyElement receiveFromAddress = new AMQPFederationAddressPolicyElement();
receiveFromAddress.setName("address-policy");
receiveFromAddress.addToIncludes("test");
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
element.setName("sample-federation");
element.addLocalAddressPolicy(receiveFromAddress);
final AMQPBrokerConnectConfiguration amqpConnection =
new AMQPBrokerConnectConfiguration("testSimpleConnect", "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
amqpConnection.setReconnectAttempts(0);// No reconnects
amqpConnection.addElement(element);
final AtomicInteger blockUntilZero = new AtomicInteger(2);
final AMQPTestFederationBrokerPlugin federationPlugin = new AMQPTestFederationBrokerPlugin();
federationPlugin.shouldCreateConsumerForDivert = (d, q) -> true;
federationPlugin.shouldCreateConsumerForQueue = (q) -> true;
federationPlugin.shouldCreateConsumerForAddress = (a) -> {
return blockUntilZero.getAndDecrement() == 0;
};
server.getConfiguration().addAMQPConnection(amqpConnection);
server.registerBrokerPlugin(federationPlugin);
server.start();
server.addAddressInfo(new AddressInfo(SimpleString.toSimpleString("test"), RoutingType.MULTICAST));
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
try (Connection connection = factory.createConnection()) {
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
final Topic topic = session.createTopic("test");
connection.start();
final MessageConsumer consumer1 = session.createConsumer(topic);
final MessageConsumer consumer2 = session.createConsumer(topic);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Third consumer we expect the plugin to allow the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_ADDRESS_RECEIVER.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(1000);
final MessageConsumer consumer3 = session.createConsumer(topic);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer3.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer2.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.expectDetach().respond();
// Demand should be gone now
consumer1.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.close();
}
}
}
private static void sendAddressAddedEvent(ProtonTestPeer peer, String address, int handle, int deliveryId) {
final Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put(REQUESTED_ADDRESS_NAME, address);

View File

@ -59,6 +59,13 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.jms.BytesMessage;
import javax.jms.Connection;
@ -71,6 +78,7 @@ import javax.jms.Message;
import javax.jms.Queue;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
@ -80,10 +88,15 @@ import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBroker
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederatedBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederationQueuePolicyElement;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Divert;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.connect.federation.ActiveMQServerAMQPFederationPlugin;
import org.apache.activemq.artemis.protocol.amqp.federation.Federation;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationConsumer;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationConsumerInfo;
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromQueuePolicy;
import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
@ -3249,6 +3262,200 @@ public class AMQPFederationQueuePolicyTest extends AmqpClientTestSupport {
}
}
@Test(timeout = 20000)
public void testFederationQueueDemandTrackedWhenRemoteRejectsInitialAttempts() throws Exception {
try (ProtonTestServer peer = new ProtonTestServer()) {
peer.expectSASLAnonymousConnect();
peer.expectOpen().respond();
peer.expectBegin().respond();
peer.expectAttach().ofSender()
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
.respond()
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(10);
peer.start();
final URI remoteURI = peer.getServerURI();
logger.info("Connect test started, peer listening on: {}", remoteURI);
final AMQPFederationQueuePolicyElement receiveFromQueue = new AMQPFederationQueuePolicyElement();
receiveFromQueue.setName("queue-policy");
receiveFromQueue.addToIncludes("test", "test");
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
element.setName("sample-federation");
element.addLocalQueuePolicy(receiveFromQueue);
final AMQPBrokerConnectConfiguration amqpConnection =
new AMQPBrokerConnectConfiguration("testSimpleConnect", "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
amqpConnection.setReconnectAttempts(0);// No reconnects
amqpConnection.addElement(element);
server.getConfiguration().addAMQPConnection(amqpConnection);
server.start();
server.createQueue(new QueueConfiguration("test").setRoutingType(RoutingType.ANYCAST)
.setAddress("test")
.setAutoCreated(false));
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
try (Connection connection = factory.createConnection()) {
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
final Queue queue = session.createQueue("test");
connection.start();
// First consumer we reject the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
.respondInKind()
.withNullSource();
peer.expectFlow().withLinkCredit(1000);
peer.remoteDetach().withErrorCondition("amqp:not-found", "the requested queue was not found").queue().afterDelay(10);
peer.expectDetach();
final MessageConsumer consumer1 = session.createConsumer(queue);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Second consumer we reject the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
.respondInKind()
.withNullSource();
peer.expectFlow().withLinkCredit(1000);
peer.remoteDetach().withErrorCondition("amqp:not-found", "the requested queue was not found").queue().afterDelay(10);
peer.expectDetach();
final MessageConsumer consumer2 = session.createConsumer(queue);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Third consumer we accept the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
.respond()
.withOfferedCapabilities(FEDERATION_QUEUE_RECEIVER.toString());
peer.expectFlow().withLinkCredit(1000);
final MessageConsumer consumer3 = session.createConsumer(queue);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer3.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer2.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.expectDetach().respond();
// Demand should be gone now
consumer1.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.close();
}
}
}
@Test(timeout = 20000)
public void testFederationQueueDemandTrackedWhenPluginBlocksInitialAttempts() throws Exception {
try (ProtonTestServer peer = new ProtonTestServer()) {
peer.expectSASLAnonymousConnect();
peer.expectOpen().respond();
peer.expectBegin().respond();
peer.expectAttach().ofSender()
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
.respond()
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_EVENT_LINK.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(10);
peer.start();
final URI remoteURI = peer.getServerURI();
logger.info("Connect test started, peer listening on: {}", remoteURI);
final AMQPFederationQueuePolicyElement receiveFromQueue = new AMQPFederationQueuePolicyElement();
receiveFromQueue.setName("queue-policy");
receiveFromQueue.addToIncludes("test", "test");
final AMQPFederatedBrokerConnectionElement element = new AMQPFederatedBrokerConnectionElement();
element.setName("sample-federation");
element.addLocalQueuePolicy(receiveFromQueue);
final AMQPBrokerConnectConfiguration amqpConnection =
new AMQPBrokerConnectConfiguration("testSimpleConnect", "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
amqpConnection.setReconnectAttempts(0);// No reconnects
amqpConnection.addElement(element);
final AtomicInteger blockUntilZero = new AtomicInteger(2);
final AMQPTestFederationBrokerPlugin federationPlugin = new AMQPTestFederationBrokerPlugin();
federationPlugin.shouldCreateConsumerForDivert = (d, q) -> true;
federationPlugin.shouldCreateConsumerForQueue = (q) -> {
return blockUntilZero.getAndDecrement() == 0;
};
server.getConfiguration().addAMQPConnection(amqpConnection);
server.registerBrokerPlugin(federationPlugin);
server.start();
server.createQueue(new QueueConfiguration("test").setRoutingType(RoutingType.ANYCAST)
.setAddress("test")
.setAutoCreated(false));
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
final ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
try (Connection connection = factory.createConnection()) {
final Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
final Queue queue = session.createQueue("test");
connection.start();
final MessageConsumer consumer1 = session.createConsumer(queue);
final MessageConsumer consumer2 = session.createConsumer(queue);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Third consumer we expect the plugin to allow the federation attempt
peer.expectAttach().ofReceiver()
.withDesiredCapability(FEDERATION_QUEUE_RECEIVER.toString())
.respondInKind();
peer.expectFlow().withLinkCredit(1000);
final MessageConsumer consumer3 = session.createConsumer(queue);
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer3.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
// Demand should remain
consumer2.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.expectDetach().respond();
// Demand should be gone now
consumer1.close();
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
peer.close();
}
}
}
private static void sendQueueAddedEvent(ProtonTestPeer peer, String address, String queue, int handle, int deliveryId) {
final Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put(REQUESTED_ADDRESS_NAME, address);
@ -3442,4 +3649,82 @@ public class AMQPFederationQueuePolicyTest extends AmqpClientTestSupport {
.withOfferedCapability(FEDERATION_EVENT_LINK.toString());
}
}
private class AMQPTestFederationBrokerPlugin implements ActiveMQServerAMQPFederationPlugin {
public final AtomicBoolean started = new AtomicBoolean();
public final AtomicBoolean stopped = new AtomicBoolean();
public final AtomicReference<FederationConsumerInfo> beforeCreateConsumerCapture = new AtomicReference<>();
public final AtomicReference<FederationConsumer> afterCreateConsumerCapture = new AtomicReference<>();
public final AtomicReference<FederationConsumer> beforeCloseConsumerCapture = new AtomicReference<>();
public final AtomicReference<FederationConsumer> afterCloseConsumerCapture = new AtomicReference<>();
public Consumer<FederationConsumerInfo> beforeCreateConsumer = (c) -> beforeCreateConsumerCapture.set(c);;
public Consumer<FederationConsumer> afterCreateConsumer = (c) -> afterCreateConsumerCapture.set(c);
public Consumer<FederationConsumer> beforeCloseConsumer = (c) -> beforeCloseConsumerCapture.set(c);
public Consumer<FederationConsumer> afterCloseConsumer = (c) -> afterCloseConsumerCapture.set(c);
public BiConsumer<FederationConsumer, org.apache.activemq.artemis.api.core.Message> beforeMessageHandled = (c, m) -> { };
public BiConsumer<FederationConsumer, org.apache.activemq.artemis.api.core.Message> afterMessageHandled = (c, m) -> { };
public Function<AddressInfo, Boolean> shouldCreateConsumerForAddress = (a) -> true;
public Function<org.apache.activemq.artemis.core.server.Queue, Boolean> shouldCreateConsumerForQueue = (q) -> true;
public BiFunction<Divert, org.apache.activemq.artemis.core.server.Queue, Boolean> shouldCreateConsumerForDivert = (d, q) -> true;
@Override
public void federationStarted(final Federation federation) throws ActiveMQException {
started.set(true);
}
@Override
public void federationStopped(final Federation federation) throws ActiveMQException {
stopped.set(true);
}
@Override
public void beforeCreateFederationConsumer(final FederationConsumerInfo consumerInfo) throws ActiveMQException {
beforeCreateConsumer.accept(consumerInfo);
}
@Override
public void afterCreateFederationConsumer(final FederationConsumer consumer) throws ActiveMQException {
afterCreateConsumer.accept(consumer);
}
@Override
public void beforeCloseFederationConsumer(final FederationConsumer consumer) throws ActiveMQException {
beforeCloseConsumer.accept(consumer);
}
@Override
public void afterCloseFederationConsumer(final FederationConsumer consumer) throws ActiveMQException {
afterCloseConsumer.accept(consumer);
}
@Override
public void beforeFederationConsumerMessageHandled(final FederationConsumer consumer, org.apache.activemq.artemis.api.core.Message message) throws ActiveMQException {
beforeMessageHandled.accept(consumer, message);
}
@Override
public void afterFederationConsumerMessageHandled(final FederationConsumer consumer, org.apache.activemq.artemis.api.core.Message message) throws ActiveMQException {
afterMessageHandled.accept(consumer, message);
}
@Override
public boolean shouldCreateFederationConsumerForAddress(final AddressInfo address) throws ActiveMQException {
return shouldCreateConsumerForAddress.apply(address);
}
@Override
public boolean shouldCreateFederationConsumerForQueue(final org.apache.activemq.artemis.core.server.Queue queue) throws ActiveMQException {
return shouldCreateConsumerForQueue.apply(queue);
}
@Override
public boolean shouldCreateFederationConsumerForDivert(Divert divert, org.apache.activemq.artemis.core.server.Queue queue) throws ActiveMQException {
return shouldCreateConsumerForDivert.apply(divert, queue);
}
}
}