ARTEMIS-4754 Structure the names used for federation internal queues
When creating internal temporary queues for the federation control links and the events link we should use a structured naming convention to ease in configuring security for the federation user where all internal names fall under a root prefix which can be used to grant read and write access for the federation user. This change allows security on the wildcarded address "$ACTIVEMQ_ARTEMIS_FEDERATION.#". This change also includes some further restrictions added to federation resources and adds support for wildcarding '$' prefixed addresses.
This commit is contained in:
parent
8b73335b46
commit
d7a7116a4c
|
@ -263,15 +263,32 @@ public class AMQPSessionCallback implements SessionCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createTemporaryQueue(SimpleString queueName, RoutingType routingType) throws Exception {
|
public void createTemporaryQueue(SimpleString queueName, RoutingType routingType) throws Exception {
|
||||||
createTemporaryQueue(queueName, queueName, routingType, null);
|
createTemporaryQueue(queueName, queueName, routingType, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTemporaryQueue(SimpleString queueName, RoutingType routingType, Integer maxConsumers) throws Exception {
|
||||||
|
createTemporaryQueue(queueName, queueName, routingType, null, maxConsumers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createTemporaryQueue(SimpleString address,
|
public void createTemporaryQueue(SimpleString address,
|
||||||
SimpleString queueName,
|
SimpleString queueName,
|
||||||
RoutingType routingType,
|
RoutingType routingType,
|
||||||
SimpleString filter) throws Exception {
|
SimpleString filter) throws Exception {
|
||||||
|
createTemporaryQueue(address, queueName, routingType, filter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTemporaryQueue(SimpleString address,
|
||||||
|
SimpleString queueName,
|
||||||
|
RoutingType routingType,
|
||||||
|
SimpleString filter,
|
||||||
|
Integer maxConsumers) throws Exception {
|
||||||
try {
|
try {
|
||||||
serverSession.createQueue(new QueueConfiguration(queueName).setAddress(address).setRoutingType(routingType).setFilterString(filter).setTemporary(true).setDurable(false));
|
serverSession.createQueue(new QueueConfiguration(queueName).setAddress(address)
|
||||||
|
.setRoutingType(routingType)
|
||||||
|
.setFilterString(filter)
|
||||||
|
.setTemporary(true)
|
||||||
|
.setDurable(false)
|
||||||
|
.setMaxConsumers(maxConsumers));
|
||||||
} catch (ActiveMQSecurityException se) {
|
} catch (ActiveMQSecurityException se) {
|
||||||
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingTempDestination(se.getMessage());
|
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingTempDestination(se.getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
package org.apache.activemq.artemis.protocol.amqp.connect.federation;
|
package org.apache.activemq.artemis.protocol.amqp.connect.federation;
|
||||||
|
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK_PREFIX;
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_BASE_VALIDATION_ADDRESS;
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENTS_LINK_PREFIX;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -170,6 +174,50 @@ public abstract class AMQPFederation implements FederationInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the prefixing for federation events queues that places the events queues into
|
||||||
|
* the name-space of federation related internal queues.
|
||||||
|
*
|
||||||
|
* @param suffix
|
||||||
|
* A suffix to append to the federation events link (normally the AMQP link name).
|
||||||
|
*
|
||||||
|
* @return the full internal queue name to use for the given suffix.
|
||||||
|
*/
|
||||||
|
String prefixEventsLinkQueueName(String suffix) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
final char delimiter = getWildcardConfiguration().getDelimiter();
|
||||||
|
|
||||||
|
builder.append(FEDERATION_BASE_VALIDATION_ADDRESS)
|
||||||
|
.append(delimiter)
|
||||||
|
.append(FEDERATION_EVENTS_LINK_PREFIX)
|
||||||
|
.append(delimiter)
|
||||||
|
.append(suffix);
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the prefixing for federation control queue name that places the queues
|
||||||
|
* into the name-space of federation related internal queues.
|
||||||
|
*
|
||||||
|
* @param suffix
|
||||||
|
* A suffix to append to the federation control link (normally the AMQP link name).
|
||||||
|
*
|
||||||
|
* @return the full internal queue name to use for the given suffix.
|
||||||
|
*/
|
||||||
|
String prefixControlLinkQueueName(String suffix) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
final char delimiter = getWildcardConfiguration().getDelimiter();
|
||||||
|
|
||||||
|
builder.append(FEDERATION_BASE_VALIDATION_ADDRESS)
|
||||||
|
.append(delimiter)
|
||||||
|
.append(FEDERATION_CONTROL_LINK_PREFIX)
|
||||||
|
.append(delimiter)
|
||||||
|
.append(suffix);
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a remote linked closed event interceptor that can intercept the closed event and
|
* Adds a remote linked closed event interceptor that can intercept the closed event and
|
||||||
* if it returns true indicate that the close has been handled and that no further action
|
* if it returns true indicate that the close has been handled and that no further action
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.activemq.artemis.protocol.amqp.connect.federation;
|
package org.apache.activemq.artemis.protocol.amqp.connect.federation;
|
||||||
|
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederation.FEDERATION_INSTANCE_RECORD;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.apache.activemq.artemis.api.core.RoutingType;
|
import org.apache.activemq.artemis.api.core.RoutingType;
|
||||||
|
@ -24,11 +26,13 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.core.server.Consumer;
|
import org.apache.activemq.artemis.core.server.Consumer;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
|
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
|
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
|
||||||
|
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPIllegalStateException;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromAddressPolicy;
|
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromAddressPolicy;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromQueuePolicy;
|
import org.apache.activemq.artemis.protocol.amqp.federation.FederationReceiveFromQueuePolicy;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
|
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext;
|
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.proton.SenderController;
|
import org.apache.activemq.artemis.protocol.amqp.proton.SenderController;
|
||||||
|
import org.apache.qpid.proton.engine.Connection;
|
||||||
import org.apache.qpid.proton.engine.Sender;
|
import org.apache.qpid.proton.engine.Sender;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +46,8 @@ public class AMQPFederationCommandDispatcher implements SenderController {
|
||||||
private final AMQPSessionCallback session;
|
private final AMQPSessionCallback session;
|
||||||
private final ActiveMQServer server;
|
private final ActiveMQServer server;
|
||||||
|
|
||||||
|
private String controlAddress;
|
||||||
|
|
||||||
AMQPFederationCommandDispatcher(Sender sender, ActiveMQServer server, AMQPSessionCallback session) {
|
AMQPFederationCommandDispatcher(Sender sender, ActiveMQServer server, AMQPSessionCallback session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
|
@ -105,33 +111,40 @@ public class AMQPFederationCommandDispatcher implements SenderController {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
|
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
|
||||||
|
final Connection protonConnection = senderContext.getSender().getSession().getConnection();
|
||||||
|
final org.apache.qpid.proton.engine.Record attachments = protonConnection.attachments();
|
||||||
|
final AMQPFederation federation = attachments.get(FEDERATION_INSTANCE_RECORD, AMQPFederation.class);
|
||||||
|
|
||||||
|
if (federation == null) {
|
||||||
|
throw new ActiveMQAMQPIllegalStateException("Cannot create a federation link from non-federation connection");
|
||||||
|
}
|
||||||
|
|
||||||
// Get the dynamically generated name to use for local creation of a matching temporary
|
// Get the dynamically generated name to use for local creation of a matching temporary
|
||||||
// queue that we will send control message to and the broker will dispatch as remote
|
// queue that we will send control message to and the broker will dispatch as remote
|
||||||
// credit is made available.
|
// credit is made available.
|
||||||
final SimpleString queueName = SimpleString.toSimpleString(sender.getRemoteTarget().getAddress());
|
controlAddress = federation.prefixControlLinkQueueName(sender.getRemoteTarget().getAddress());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.createTemporaryQueue(queueName, RoutingType.ANYCAST);
|
session.createTemporaryQueue(SimpleString.toSimpleString(getControlLinkAddress()), RoutingType.ANYCAST, 1);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
|
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Consumer) session.createSender(senderContext, queueName, null, false);
|
return (Consumer) session.createSender(senderContext, SimpleString.toSimpleString(getControlLinkAddress()), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
// Make a best effort to remove the temporary queue used for control commands on close.
|
// Make a best effort to remove the temporary queue used for control commands on close.
|
||||||
final SimpleString queueName = SimpleString.toSimpleString(sender.getRemoteTarget().getAddress());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.removeTemporaryQueue(queueName);
|
session.removeTemporaryQueue(SimpleString.toSimpleString(getControlLinkAddress()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignored as the temporary queue should be removed on connection termination.
|
// Ignored as the temporary queue should be removed on connection termination.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getControlLinkAddress() {
|
private String getControlLinkAddress() {
|
||||||
return sender.getRemoteTarget().getAddress();
|
return controlAddress;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -30,10 +30,39 @@ public final class AMQPFederationConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Address used by a remote broker instance to validate that an incoming federation connection
|
* Address used by a remote broker instance to validate that an incoming federation connection
|
||||||
* has access right to perform federation operations. The user that connects to the AMQP federation
|
* has access rights to perform federation operations. The user that connects to the AMQP federation
|
||||||
* endpoint and attempt to create the control link must have write access to this address.
|
* endpoint and attempts to create the control link must have write access to this address and any
|
||||||
|
* address prefixed by this value.
|
||||||
|
*
|
||||||
|
* When securing a federation user account the user must have read and write permissions to addresses
|
||||||
|
* under this prefix using the broker defined delimiter, this include the ability to create non-durable
|
||||||
|
* resources.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* $ACTIVEMQ_ARTEMIS_FEDERATION.#;
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public static final String FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS = "$ACTIVEMQ_ARTEMIS_FEDERATION";
|
public static final String FEDERATION_BASE_VALIDATION_ADDRESS = "$ACTIVEMQ_ARTEMIS_FEDERATION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix value added when creating a federation control link beyond the initial portion of the
|
||||||
|
* validation address prefix. Links for command and control of federation operations follow the form:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* $ACTIVEMQ_ARTEMIS_FEDERATION.control.<unique-id>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public static final String FEDERATION_CONTROL_LINK_PREFIX = "control";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix value added when creating a federation events links beyond the initial portion of the
|
||||||
|
* validation address prefix. Links for federation events follow the form:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* $ACTIVEMQ_ARTEMIS_FEDERATION.events.<unique-id>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public static final String FEDERATION_EVENTS_LINK_PREFIX = "events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A desired capability added to the federation control link that must be offered
|
* A desired capability added to the federation control link that must be offered
|
||||||
|
|
|
@ -68,6 +68,8 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
private final Set<String> addressWatches = new HashSet<>();
|
private final Set<String> addressWatches = new HashSet<>();
|
||||||
private final Set<String> queueWatches = new HashSet<>();
|
private final Set<String> queueWatches = new HashSet<>();
|
||||||
|
|
||||||
|
private String eventsAddress;
|
||||||
|
|
||||||
public AMQPFederationEventDispatcher(AMQPFederation federation, AMQPSessionCallback session, Sender sender) {
|
public AMQPFederationEventDispatcher(AMQPFederation federation, AMQPSessionCallback session, Sender sender) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
|
@ -76,7 +78,7 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getEventsLinkAddress() {
|
private String getEventsLinkAddress() {
|
||||||
return sender.getName();
|
return eventsAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,8 +102,7 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
|
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
|
||||||
final Connection protonConnection = senderContext.getSender().getSession().getConnection();
|
final Connection protonConnection = senderContext.getSender().getSession().getConnection();
|
||||||
final org.apache.qpid.proton.engine.Record attachments = protonConnection.attachments();
|
final org.apache.qpid.proton.engine.Record attachments = protonConnection.attachments();
|
||||||
|
final AMQPFederation federation = attachments.get(FEDERATION_INSTANCE_RECORD, AMQPFederation.class);
|
||||||
AMQPFederation federation = attachments.get(FEDERATION_INSTANCE_RECORD, AMQPFederation.class);
|
|
||||||
|
|
||||||
if (federation == null) {
|
if (federation == null) {
|
||||||
throw new ActiveMQAMQPIllegalStateException("Cannot create a federation link from non-federation connection");
|
throw new ActiveMQAMQPIllegalStateException("Cannot create a federation link from non-federation connection");
|
||||||
|
@ -115,7 +116,7 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
|
|
||||||
// Create a temporary queue using the unique link name which is where events will
|
// Create a temporary queue using the unique link name which is where events will
|
||||||
// be sent to so that they can be held until credit is granted by the remote.
|
// be sent to so that they can be held until credit is granted by the remote.
|
||||||
final SimpleString queueName = SimpleString.toSimpleString(sender.getName());
|
eventsAddress = federation.prefixEventsLinkQueueName(sender.getName());
|
||||||
|
|
||||||
if (sender.getLocalState() != EndpointState.ACTIVE) {
|
if (sender.getLocalState() != EndpointState.ACTIVE) {
|
||||||
// Indicate that event link capabilities is supported.
|
// Indicate that event link capabilities is supported.
|
||||||
|
@ -131,11 +132,11 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
throw new ActiveMQAMQPInternalErrorException("Remote Terminus did not arrive as dynamic node: " + remoteTerminus);
|
throw new ActiveMQAMQPInternalErrorException("Remote Terminus did not arrive as dynamic node: " + remoteTerminus);
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteTerminus.setAddress(queueName.toString());
|
remoteTerminus.setAddress(getEventsLinkAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.createTemporaryQueue(queueName, RoutingType.ANYCAST);
|
session.createTemporaryQueue(SimpleString.toSimpleString(getEventsLinkAddress()), RoutingType.ANYCAST, 1);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
|
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -145,18 +146,16 @@ public class AMQPFederationEventDispatcher implements SenderController, ActiveMQ
|
||||||
|
|
||||||
server.registerBrokerPlugin(this); // Start listening for bindings and consumer events.
|
server.registerBrokerPlugin(this); // Start listening for bindings and consumer events.
|
||||||
|
|
||||||
return (Consumer) session.createSender(senderContext, queueName, null, false);
|
return (Consumer) session.createSender(senderContext, SimpleString.toSimpleString(getEventsLinkAddress()), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
// Make a best effort to remove the temporary queue used for event messages on close.
|
// Make a best effort to remove the temporary queue used for event messages on close.
|
||||||
final SimpleString queueName = SimpleString.toSimpleString(sender.getRemoteTarget().getAddress());
|
|
||||||
|
|
||||||
server.unRegisterBrokerPlugin(this);
|
server.unRegisterBrokerPlugin(this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.removeTemporaryQueue(queueName);
|
session.removeTemporaryQueue(SimpleString.toSimpleString(getEventsLinkAddress()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignored as the temporary queue should be removed on connection termination.
|
// Ignored as the temporary queue should be removed on connection termination.
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ import java.lang.invoke.MethodHandles;
|
||||||
|
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_ADDRESS_RECEIVER;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_ADDRESS_RECEIVER;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_BASE_VALIDATION_ADDRESS;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENT_LINK;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENT_LINK;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_QUEUE_RECEIVER;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_QUEUE_RECEIVER;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.AMQP_LINK_INITIALIZER_KEY;
|
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.AMQP_LINK_INITIALIZER_KEY;
|
||||||
|
@ -472,7 +472,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
|
||||||
private void handleFederationControlLinkOpened(AMQPSessionContext protonSession, Receiver receiver) throws Exception {
|
private void handleFederationControlLinkOpened(AMQPSessionContext protonSession, Receiver receiver) throws Exception {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
protonSession.getSessionSPI().check(SimpleString.toSimpleString(FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS), CheckType.SEND, getSecurityAuth());
|
protonSession.getSessionSPI().check(SimpleString.toSimpleString(FEDERATION_BASE_VALIDATION_ADDRESS), CheckType.SEND, getSecurityAuth());
|
||||||
} catch (ActiveMQSecurityException e) {
|
} catch (ActiveMQSecurityException e) {
|
||||||
throw new ActiveMQAMQPSecurityException(
|
throw new ActiveMQAMQPSecurityException(
|
||||||
"User does not have permission to attach to the federation control address");
|
"User does not have permission to attach to the federation control address");
|
||||||
|
|
|
@ -36,6 +36,10 @@ public class Match<T> {
|
||||||
|
|
||||||
private static final String DOT_REPLACEMENT = "\\.";
|
private static final String DOT_REPLACEMENT = "\\.";
|
||||||
|
|
||||||
|
private static final String DOLLAR = "$";
|
||||||
|
|
||||||
|
private static final String DOLLAR_REPLACEMENT = "\\$";
|
||||||
|
|
||||||
private final String match;
|
private final String match;
|
||||||
|
|
||||||
private final Pattern pattern;
|
private final Pattern pattern;
|
||||||
|
@ -75,6 +79,7 @@ public class Match<T> {
|
||||||
actMatch = actMatch.replace(wildcardConfiguration.getDelimiterString() + wildcardConfiguration.getAnyWordsString(), wildcardConfiguration.getAnyWordsString());
|
actMatch = actMatch.replace(wildcardConfiguration.getDelimiterString() + wildcardConfiguration.getAnyWordsString(), wildcardConfiguration.getAnyWordsString());
|
||||||
}
|
}
|
||||||
actMatch = actMatch.replace(Match.DOT, Match.DOT_REPLACEMENT);
|
actMatch = actMatch.replace(Match.DOT, Match.DOT_REPLACEMENT);
|
||||||
|
actMatch = actMatch.replace(Match.DOLLAR, Match.DOLLAR_REPLACEMENT);
|
||||||
actMatch = actMatch.replace(wildcardConfiguration.getSingleWordString(), String.format(WORD_WILDCARD_REPLACEMENT_FORMAT, Pattern.quote(wildcardConfiguration.getDelimiterString())));
|
actMatch = actMatch.replace(wildcardConfiguration.getSingleWordString(), String.format(WORD_WILDCARD_REPLACEMENT_FORMAT, Pattern.quote(wildcardConfiguration.getDelimiterString())));
|
||||||
|
|
||||||
if (direct) {
|
if (direct) {
|
||||||
|
|
|
@ -78,6 +78,29 @@ public class MatchTest {
|
||||||
Assert.assertFalse(predicate.test("testing.A"));
|
Assert.assertFalse(predicate.test("testing.A"));
|
||||||
Assert.assertFalse(predicate.test("test"));
|
Assert.assertFalse(predicate.test("test"));
|
||||||
Assert.assertFalse(predicate.test("test.A.B"));
|
Assert.assertFalse(predicate.test("test.A.B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDollarMatchingDirectTrue() {
|
||||||
|
final Pattern pattern = Match.createPattern("$test.#", new WildcardConfiguration(), true);
|
||||||
|
final Predicate<String> predicate = pattern.asPredicate();
|
||||||
|
|
||||||
|
Assert.assertTrue(predicate.test("$test.A"));
|
||||||
|
Assert.assertTrue(predicate.test("$test.A.B"));
|
||||||
|
|
||||||
|
Assert.assertFalse(predicate.test("$testing.A"));
|
||||||
|
Assert.assertFalse(predicate.test("$test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDollarMatchingDirectFalse() {
|
||||||
|
final Pattern pattern = Match.createPattern("$test.#", new WildcardConfiguration(), false);
|
||||||
|
final Predicate<String> predicate = pattern.asPredicate();
|
||||||
|
|
||||||
|
Assert.assertTrue(predicate.test("$test"));
|
||||||
|
Assert.assertTrue(predicate.test("$test.A"));
|
||||||
|
Assert.assertTrue(predicate.test("$test.A.B"));
|
||||||
|
|
||||||
|
Assert.assertFalse(predicate.test("$testing.A"));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,9 @@ import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPF
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.ADD_QUEUE_POLICY;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.ADD_QUEUE_POLICY;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONFIGURATION;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONFIGURATION;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_CONTROL_LINK_PREFIX;
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_BASE_VALIDATION_ADDRESS;
|
||||||
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENTS_LINK_PREFIX;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENT_LINK;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.FEDERATION_EVENT_LINK;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.LARGE_MESSAGE_THRESHOLD;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.LARGE_MESSAGE_THRESHOLD;
|
||||||
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.LINK_ATTACH_TIMEOUT;
|
import static org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants.LINK_ATTACH_TIMEOUT;
|
||||||
|
@ -62,10 +64,9 @@ import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFedera
|
||||||
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederationAddressPolicyElement;
|
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederationAddressPolicyElement;
|
||||||
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederationQueuePolicyElement;
|
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPFederationQueuePolicyElement;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.protocol.amqp.connect.federation.AMQPFederationConstants;
|
|
||||||
import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
|
import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
|
||||||
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
|
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
|
||||||
import org.apache.activemq.artemis.utils.Wait;
|
import org.apache.activemq.artemis.tests.util.Wait;
|
||||||
import org.apache.qpid.proton.amqp.transport.AmqpError;
|
import org.apache.qpid.proton.amqp.transport.AmqpError;
|
||||||
import org.apache.qpid.protonj2.test.driver.ProtonTestClient;
|
import org.apache.qpid.protonj2.test.driver.ProtonTestClient;
|
||||||
import org.apache.qpid.protonj2.test.driver.ProtonTestServer;
|
import org.apache.qpid.protonj2.test.driver.ProtonTestServer;
|
||||||
|
@ -158,21 +159,23 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
federationConfiguration.put(IGNORE_QUEUE_CONSUMER_PRIORITIES, AMQP_INGNORE_CONSUMER_PRIORITIES);
|
federationConfiguration.put(IGNORE_QUEUE_CONSUMER_PRIORITIES, AMQP_INGNORE_CONSUMER_PRIORITIES);
|
||||||
federationConfiguration.put(AmqpSupport.TUNNEL_CORE_MESSAGES, AMQP_TUNNEL_CORE_MESSAGES);
|
federationConfiguration.put(AmqpSupport.TUNNEL_CORE_MESSAGES, AMQP_TUNNEL_CORE_MESSAGES);
|
||||||
|
|
||||||
|
final String controlLinkAddress = "test-control-address";
|
||||||
|
|
||||||
try (ProtonTestServer peer = new ProtonTestServer()) {
|
try (ProtonTestServer peer = new ProtonTestServer()) {
|
||||||
peer.expectSASLAnonymousConnect("PLAIN", "ANONYMOUS");
|
peer.expectSASLAnonymousConnect("PLAIN", "ANONYMOUS");
|
||||||
peer.expectOpen().respond();
|
peer.expectOpen().respond();
|
||||||
peer.expectBegin().respond();
|
peer.expectBegin().respond();
|
||||||
peer.expectAttach().ofSender()
|
peer.expectAttach().ofSender()
|
||||||
.withDesiredCapability(AMQPFederationConstants.FEDERATION_CONTROL_LINK.toString())
|
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
|
||||||
.withName(allOf(containsString("Federation"), containsString("myFederation")))
|
.withName(allOf(containsString("federation-"), containsString("myFederation")))
|
||||||
.withProperty(FEDERATION_CONFIGURATION.toString(), federationConfiguration)
|
.withProperty(FEDERATION_CONFIGURATION.toString(), federationConfiguration)
|
||||||
.withTarget().withDynamic(true)
|
.withTarget().withDynamic(true)
|
||||||
.withCapabilities("temporary-topic")
|
.withCapabilities("temporary-topic")
|
||||||
.and()
|
.and()
|
||||||
.respond()
|
.respond()
|
||||||
.withTarget().withAddress("test-control-address")
|
.withTarget().withAddress(controlLinkAddress)
|
||||||
.and()
|
.and()
|
||||||
.withOfferedCapabilities(AMQPFederationConstants.FEDERATION_CONTROL_LINK.toString());
|
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
|
||||||
peer.start();
|
peer.start();
|
||||||
|
|
||||||
final URI remoteURI = peer.getServerURI();
|
final URI remoteURI = peer.getServerURI();
|
||||||
|
@ -193,7 +196,9 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
|
|
||||||
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
Wait.assertTrue(() -> server.locateQueue("test-control-address") != null);
|
Wait.assertTrue(() -> server.locateQueue(FEDERATION_BASE_VALIDATION_ADDRESS +
|
||||||
|
"." + FEDERATION_CONTROL_LINK_PREFIX +
|
||||||
|
"." + controlLinkAddress) != null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +208,7 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
peer.expectSASLAnonymousConnect("PLAIN", "ANONYMOUS");
|
peer.expectSASLAnonymousConnect("PLAIN", "ANONYMOUS");
|
||||||
peer.expectOpen().respond();
|
peer.expectOpen().respond();
|
||||||
peer.expectBegin().respond();
|
peer.expectBegin().respond();
|
||||||
peer.expectAttach().ofSender().withDesiredCapability(AMQPFederationConstants.FEDERATION_CONTROL_LINK.toString()).respond();
|
peer.expectAttach().ofSender().withDesiredCapability(FEDERATION_CONTROL_LINK.toString()).respond();
|
||||||
peer.expectConnectionToDrop();
|
peer.expectConnectionToDrop();
|
||||||
peer.start();
|
peer.start();
|
||||||
|
|
||||||
|
@ -645,7 +650,7 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
|
|
||||||
@Test(timeout = 20000)
|
@Test(timeout = 20000)
|
||||||
public void testControlLinkPassesConnectAttemptWhenUserHasPrivledges() throws Exception {
|
public void testControlLinkPassesConnectAttemptWhenUserHasPrivledges() throws Exception {
|
||||||
enableSecurity(server, FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS);
|
enableSecurity(server, FEDERATION_BASE_VALIDATION_ADDRESS);
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
try (ProtonTestClient peer = new ProtonTestClient()) {
|
try (ProtonTestClient peer = new ProtonTestClient()) {
|
||||||
|
@ -665,9 +670,31 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 20000)
|
||||||
|
public void testControlAndEventsLinksPassesConnectAttemptWhenUserHasPrivledges() throws Exception {
|
||||||
|
enableSecurity(server, FEDERATION_BASE_VALIDATION_ADDRESS + ".#");
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
try (ProtonTestClient peer = new ProtonTestClient()) {
|
||||||
|
scriptFederationConnectToRemote(peer, getTestName(), true, fullUser, fullPass, true, true);
|
||||||
|
peer.connect("localhost", AMQP_PORT);
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.expectClose();
|
||||||
|
peer.remoteClose().now();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
peer.close();
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
|
||||||
|
logger.info("Test stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout = 20000)
|
@Test(timeout = 20000)
|
||||||
public void testControlLinkRefusesConnectAttemptWhenUseDoesNotHavePrivledgesForControlAddress() throws Exception {
|
public void testControlLinkRefusesConnectAttemptWhenUseDoesNotHavePrivledgesForControlAddress() throws Exception {
|
||||||
enableSecurity(server, FEDERATION_CONTROL_LINK_VALIDATION_ADDRESS);
|
enableSecurity(server, FEDERATION_BASE_VALIDATION_ADDRESS);
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
try (ProtonTestClient peer = new ProtonTestClient()) {
|
try (ProtonTestClient peer = new ProtonTestClient()) {
|
||||||
|
@ -773,6 +800,222 @@ public class AMQPFederationConnectTest extends AmqpClientTestSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 20000)
|
||||||
|
public void testControlLinkSenderQueueCreatedWithMaxConsumersOfOne() throws Exception {
|
||||||
|
final String controlLinkAddress = "test-control-address";
|
||||||
|
final String federationControlSenderAddress = FEDERATION_BASE_VALIDATION_ADDRESS +
|
||||||
|
"." + FEDERATION_CONTROL_LINK_PREFIX +
|
||||||
|
"." + controlLinkAddress;
|
||||||
|
|
||||||
|
try (ProtonTestServer peer = new ProtonTestServer()) {
|
||||||
|
peer.expectSASLAnonymousConnect("PLAIN", "ANONYMOUS");
|
||||||
|
peer.expectOpen().respond();
|
||||||
|
peer.expectBegin().respond();
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withDesiredCapability(FEDERATION_CONTROL_LINK.toString())
|
||||||
|
.withName(allOf(containsString("federation-"), containsString("myFederation")))
|
||||||
|
.withTarget().withDynamic(true)
|
||||||
|
.withCapabilities("temporary-topic")
|
||||||
|
.and()
|
||||||
|
.respond()
|
||||||
|
.withTarget().withAddress(controlLinkAddress)
|
||||||
|
.and()
|
||||||
|
.withOfferedCapabilities(FEDERATION_CONTROL_LINK.toString());
|
||||||
|
peer.start();
|
||||||
|
|
||||||
|
final URI remoteURI = peer.getServerURI();
|
||||||
|
logger.info("Connect test started, peer listening on: {}", remoteURI);
|
||||||
|
|
||||||
|
final AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration(
|
||||||
|
getTestName(), "tcp://" + remoteURI.getHost() + ":" + remoteURI.getPort());
|
||||||
|
amqpConnection.setReconnectAttempts(0);// No reconnects
|
||||||
|
final AMQPFederatedBrokerConnectionElement federation = new AMQPFederatedBrokerConnectionElement("myFederation");
|
||||||
|
amqpConnection.addElement(federation);
|
||||||
|
|
||||||
|
server.getConfiguration().addAMQPConnection(amqpConnection);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Wait.assertTrue(() -> server.locateQueue(federationControlSenderAddress) != null);
|
||||||
|
|
||||||
|
// Try and bind to the control address which should be rejected as the queue
|
||||||
|
// was created with max consumers of one.
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withName("test-control-link-suspect")
|
||||||
|
.withNullSource();
|
||||||
|
peer.expectDetach().withClosed(true)
|
||||||
|
.withError(AmqpError.INTERNAL_ERROR.toString());
|
||||||
|
peer.remoteAttach().ofReceiver()
|
||||||
|
.withName("test-control-link-suspect")
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withTarget().also()
|
||||||
|
.withSource().withAddress(federationControlSenderAddress)
|
||||||
|
.also()
|
||||||
|
.now();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 20000)
|
||||||
|
public void testEventSenderLinkFromTargetUsesNamespacedDynamicQueue() throws Exception {
|
||||||
|
final String federationControlLinkName = "federation-test";
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
try (ProtonTestClient peer = new ProtonTestClient()) {
|
||||||
|
peer.queueClientSaslAnonymousConnect();
|
||||||
|
peer.remoteOpen().queue();
|
||||||
|
peer.expectOpen();
|
||||||
|
peer.remoteBegin().queue();
|
||||||
|
peer.expectBegin();
|
||||||
|
peer.remoteAttach().ofSender()
|
||||||
|
.withName(federationControlLinkName)
|
||||||
|
.withDesiredCapabilities(FEDERATION_CONTROL_LINK.toString())
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withSource().also()
|
||||||
|
.withTarget().withDynamic(true)
|
||||||
|
.withDurabilityOfNone()
|
||||||
|
.withExpiryPolicyOnLinkDetach()
|
||||||
|
.withLifetimePolicyOfDeleteOnClose()
|
||||||
|
.withCapabilities("temporary-topic")
|
||||||
|
.also()
|
||||||
|
.queue();
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withName(federationControlLinkName)
|
||||||
|
.withTarget()
|
||||||
|
.withAddress(notNullValue())
|
||||||
|
.also()
|
||||||
|
.withOfferedCapability(FEDERATION_CONTROL_LINK.toString());
|
||||||
|
peer.expectFlow();
|
||||||
|
|
||||||
|
final String federationEventsSenderLinkName = "events-receiver-test";
|
||||||
|
|
||||||
|
peer.remoteAttach().ofReceiver()
|
||||||
|
.withName(federationEventsSenderLinkName)
|
||||||
|
.withDesiredCapabilities(FEDERATION_EVENT_LINK.toString())
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withTarget().also()
|
||||||
|
.withSource().withDynamic(true)
|
||||||
|
.withDurabilityOfNone()
|
||||||
|
.withExpiryPolicyOnLinkDetach()
|
||||||
|
.withLifetimePolicyOfDeleteOnClose()
|
||||||
|
.withCapabilities("temporary-topic")
|
||||||
|
.also()
|
||||||
|
.queue();
|
||||||
|
peer.remoteFlow().withLinkCredit(10).queue();
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withName(federationEventsSenderLinkName)
|
||||||
|
.withSource()
|
||||||
|
.withAddress(notNullValue())
|
||||||
|
.also()
|
||||||
|
.withOfferedCapability(FEDERATION_EVENT_LINK.toString());
|
||||||
|
|
||||||
|
peer.connect("localhost", AMQP_PORT);
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// The events receiver from the remote should trigger a temporary queue to be created on
|
||||||
|
// the server to allow sends of events beyond currently available credit.
|
||||||
|
Wait.assertTrue(() -> server.locateQueue(FEDERATION_BASE_VALIDATION_ADDRESS +
|
||||||
|
"." + FEDERATION_EVENTS_LINK_PREFIX +
|
||||||
|
"." + federationEventsSenderLinkName) != null);
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 20000)
|
||||||
|
public void testEventsLinkAtTargetIsCreatedWithMaxConsumersOfOne() throws Exception {
|
||||||
|
final String federationControlLinkName = "federation-test";
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
try (ProtonTestClient peer = new ProtonTestClient()) {
|
||||||
|
peer.queueClientSaslAnonymousConnect();
|
||||||
|
peer.remoteOpen().queue();
|
||||||
|
peer.expectOpen();
|
||||||
|
peer.remoteBegin().queue();
|
||||||
|
peer.expectBegin();
|
||||||
|
peer.remoteAttach().ofSender()
|
||||||
|
.withName(federationControlLinkName)
|
||||||
|
.withDesiredCapabilities(FEDERATION_CONTROL_LINK.toString())
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withSource().also()
|
||||||
|
.withTarget().withDynamic(true)
|
||||||
|
.withDurabilityOfNone()
|
||||||
|
.withExpiryPolicyOnLinkDetach()
|
||||||
|
.withLifetimePolicyOfDeleteOnClose()
|
||||||
|
.withCapabilities("temporary-topic")
|
||||||
|
.also()
|
||||||
|
.queue();
|
||||||
|
peer.expectAttach().ofReceiver()
|
||||||
|
.withName(federationControlLinkName)
|
||||||
|
.withTarget()
|
||||||
|
.withAddress(notNullValue())
|
||||||
|
.also()
|
||||||
|
.withOfferedCapability(FEDERATION_CONTROL_LINK.toString());
|
||||||
|
peer.expectFlow();
|
||||||
|
|
||||||
|
final String federationEventsSenderLinkName = "events-receiver-test";
|
||||||
|
final String federationEventsSenderAddress = FEDERATION_BASE_VALIDATION_ADDRESS +
|
||||||
|
"." + FEDERATION_EVENTS_LINK_PREFIX +
|
||||||
|
"." + federationEventsSenderLinkName;
|
||||||
|
|
||||||
|
peer.remoteAttach().ofReceiver()
|
||||||
|
.withName(federationEventsSenderLinkName)
|
||||||
|
.withDesiredCapabilities(FEDERATION_EVENT_LINK.toString())
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withTarget().also()
|
||||||
|
.withSource().withDynamic(true)
|
||||||
|
.withDurabilityOfNone()
|
||||||
|
.withExpiryPolicyOnLinkDetach()
|
||||||
|
.withLifetimePolicyOfDeleteOnClose()
|
||||||
|
.withCapabilities("temporary-topic")
|
||||||
|
.also()
|
||||||
|
.queue();
|
||||||
|
peer.remoteFlow().withLinkCredit(10).queue();
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withName(federationEventsSenderLinkName)
|
||||||
|
.withSource()
|
||||||
|
.withAddress(notNullValue())
|
||||||
|
.also()
|
||||||
|
.withOfferedCapability(FEDERATION_EVENT_LINK.toString());
|
||||||
|
|
||||||
|
peer.connect("localhost", AMQP_PORT);
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// The events receiver from the remote should trigger a temporary queue to be created on
|
||||||
|
// the server to allow sends of events beyond currently available credit.
|
||||||
|
Wait.assertTrue(() -> server.locateQueue(federationEventsSenderAddress) != null);
|
||||||
|
|
||||||
|
// Try and bind to the events address which should be rejected as the queue
|
||||||
|
// was created with max consumers of one.
|
||||||
|
peer.expectAttach().ofSender()
|
||||||
|
.withName("test-events-link-suspect")
|
||||||
|
.withNullSource();
|
||||||
|
peer.expectDetach().withClosed(true)
|
||||||
|
.withError(AmqpError.INTERNAL_ERROR.toString());
|
||||||
|
peer.remoteAttach().ofReceiver()
|
||||||
|
.withName("test-events-link-suspect")
|
||||||
|
.withSenderSettleModeUnsettled()
|
||||||
|
.withReceivervSettlesFirst()
|
||||||
|
.withTarget().also()
|
||||||
|
.withSource().withAddress(federationEventsSenderAddress)
|
||||||
|
.also()
|
||||||
|
.now();
|
||||||
|
|
||||||
|
peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use these methods to script the initial handshake that a broker that is establishing
|
// Use these methods to script the initial handshake that a broker that is establishing
|
||||||
// a federation connection with a remote broker instance would perform.
|
// a federation connection with a remote broker instance would perform.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue