use of org.apache.qpid.proton.amqp.transport.ErrorCondition in project hono by eclipse.
the class HonoConnectionImpl method createSender.
/**
* Creates a sender link.
*
* @param targetAddress The target address of the link. If the address is {@code null}, the
* sender link will be established to the 'anonymous relay' and each
* message must specify its destination address.
* @param qos The quality of service to use for the link.
* @param closeHook The handler to invoke when the link is closed by the peer (may be {@code null}).
* @return A future for the created link. The future will be completed once the link is open.
* The future will fail with a {@link ServiceInvocationException} if the link cannot be opened.
* @throws NullPointerException if qos is {@code null}.
*/
@Override
public final Future<ProtonSender> createSender(final String targetAddress, final ProtonQoS qos, final Handler<String> closeHook) {
Objects.requireNonNull(qos);
return executeOnContext(result -> {
checkConnected().compose(v -> {
if (targetAddress == null && !supportsCapability(Constants.CAP_ANONYMOUS_RELAY)) {
// before a client can use anonymous terminus
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, "server does not support anonymous terminus"));
}
final Promise<ProtonSender> senderPromise = Promise.promise();
final ProtonSender sender = session.createSender(targetAddress);
sender.setQoS(qos);
sender.setAutoSettle(true);
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening sender [{}] failed: got disconnected", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
sender.openHandler(senderOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (senderPromise.future().isComplete()) {
log.debug("ignoring server response for opening sender [{}]: sender creation already timed out", targetAddress);
} else if (senderOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = sender.getRemoteCondition();
if (error == null) {
log.debug("opening sender [{}] failed", targetAddress, senderOpen.cause());
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open sender", senderOpen.cause()));
} else {
log.debug("opening sender [{}] failed: {} - {}", targetAddress, error.getCondition(), error.getDescription());
senderPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(sender)) {
log.debug("sender open [target: {}, sendQueueFull: {}, remote max-message-size: {}]", targetAddress, sender.sendQueueFull(), sender.getRemoteMaxMessageSize());
final long remoteMaxMessageSize = Optional.ofNullable(sender.getRemoteMaxMessageSize()).map(UnsignedLong::longValue).orElse(0L);
if (remoteMaxMessageSize > 0 && remoteMaxMessageSize < clientConfigProperties.getMinMaxMessageSize()) {
// peer won't accept our (biggest) messages
sender.close();
final String msg = String.format("peer does not support minimum max-message-size [required: %d, supported: %d", clientConfigProperties.getMinMaxMessageSize(), remoteMaxMessageSize);
log.debug(msg);
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_PRECON_FAILED, msg));
} else if (sender.getCredit() <= 0) {
// wait on credits a little time, if not already given
final long waitOnCreditsTimerId = vertx.setTimer(clientConfigProperties.getFlowLatency(), timerID -> {
log.debug("sender [target: {}] has {} credits after grace period of {}ms", targetAddress, sender.getCredit(), clientConfigProperties.getFlowLatency());
sender.sendQueueDrainHandler(null);
senderPromise.tryComplete(sender);
});
sender.sendQueueDrainHandler(replenishedSender -> {
log.debug("sender [target: {}] has received {} initial credits", targetAddress, replenishedSender.getCredit());
if (vertx.cancelTimer(waitOnCreditsTimerId)) {
result.tryComplete(replenishedSender);
replenishedSender.sendQueueDrainHandler(null);
}
// otherwise the timer has already completed the future and cleaned up
// sendQueueDrainHandler
});
} else {
senderPromise.tryComplete(sender);
}
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for target [{}] and will detach the link", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(sender, remoteDetached -> onRemoteDetach(sender, connection.getRemoteContainer(), false, closeHook));
HonoProtonHelper.setCloseHandler(sender, remoteClosed -> onRemoteDetach(sender, connection.getRemoteContainer(), true, closeHook));
sender.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(sender, clientConfigProperties, senderPromise);
}
});
return senderPromise.future();
}).onComplete(result);
});
}
use of org.apache.qpid.proton.amqp.transport.ErrorCondition in project hono by eclipse.
the class HonoConnectionImpl method onRemoteDetach.
private void onRemoteDetach(final ProtonLink<?> link, final String remoteContainer, final boolean closed, final Handler<String> closeHook) {
final ErrorCondition error = link.getRemoteCondition();
final String type = link instanceof ProtonSender ? "sender" : "receiver";
final String address = link instanceof ProtonSender ? link.getTarget().getAddress() : link.getSource().getAddress();
if (error == null) {
log.debug("{} [{}] detached (with closed={}) by peer [{}]", type, address, closed, remoteContainer);
} else {
log.debug("{} [{}] detached (with closed={}) by peer [{}]: {} - {}", type, address, closed, remoteContainer, error.getCondition(), error.getDescription());
}
link.close();
if (HonoProtonHelper.isLinkEstablished(link) && closeHook != null) {
closeHook.handle(address);
}
}
use of org.apache.qpid.proton.amqp.transport.ErrorCondition in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapterTest method testConnectionFailsForAuthenticatedDeviceIfAdapterLevelConnectionLimitIsExceeded.
/**
* Verifies that an authenticated device's attempt to establish a connection fails if
* the adapter's connection limit is exceeded.
*/
@Test
public void testConnectionFailsForAuthenticatedDeviceIfAdapterLevelConnectionLimitIsExceeded() {
// GIVEN an AMQP adapter that requires devices to authenticate
properties.setAuthenticationRequired(true);
givenAnAdapter(properties);
// WHEN the adapter's connection limit exceeds
when(connectionLimitManager.isLimitExceeded()).thenReturn(true);
// WHEN a device connects
final Device authenticatedDevice = new Device(TEST_TENANT_ID, TEST_DEVICE);
final Record record = new RecordImpl();
record.set(AmqpAdapterConstants.KEY_CLIENT_DEVICE, Device.class, authenticatedDevice);
record.set(AmqpAdapterConstants.KEY_TLS_CIPHER_SUITE, String.class, "BUMLUX_CIPHER");
final ProtonConnection deviceConnection = mock(ProtonConnection.class);
when(deviceConnection.attachments()).thenReturn(record);
adapter.onConnectRequest(deviceConnection);
@SuppressWarnings("unchecked") final ArgumentCaptor<Handler<AsyncResult<ProtonConnection>>> openHandler = ArgumentCaptor.forClass(Handler.class);
verify(deviceConnection).openHandler(openHandler.capture());
openHandler.getValue().handle(Future.succeededFuture(deviceConnection));
// THEN the connection count should be incremented when the connection is opened
final InOrder metricsInOrderVerifier = inOrder(metrics);
metricsInOrderVerifier.verify(metrics).incrementConnections(TEST_TENANT_ID);
// AND the adapter should close the connection right after it opened it
final ArgumentCaptor<Handler<AsyncResult<ProtonConnection>>> closeHandler = VertxMockSupport.argumentCaptorHandler();
verify(deviceConnection).closeHandler(closeHandler.capture());
closeHandler.getValue().handle(Future.succeededFuture());
final ArgumentCaptor<ErrorCondition> errorConditionCaptor = ArgumentCaptor.forClass(ErrorCondition.class);
verify(deviceConnection).setCondition(errorConditionCaptor.capture());
assertEquals(AmqpError.UNAUTHORIZED_ACCESS, errorConditionCaptor.getValue().getCondition());
// AND the connection count should be decremented accordingly when the connection is closed
metricsInOrderVerifier.verify(metrics).decrementConnections(TEST_TENANT_ID);
verify(metrics).reportConnectionAttempt(ConnectionAttemptOutcome.ADAPTER_CONNECTIONS_EXCEEDED, TEST_TENANT_ID, "BUMLUX_CIPHER");
}
use of org.apache.qpid.proton.amqp.transport.ErrorCondition in project hono by eclipse.
the class ProtonBasedCommandContext method reject.
@Override
public void reject(final String error) {
TracingHelper.logError(getTracingSpan(), "client error trying to deliver or process command: " + error);
Tags.HTTP_STATUS.set(getTracingSpan(), HttpURLConnection.HTTP_BAD_REQUEST);
final ErrorCondition errorCondition = ProtonHelper.condition(Constants.AMQP_BAD_REQUEST, error);
final Rejected rejected = new Rejected();
rejected.setError(errorCondition);
updateDelivery(rejected);
}
use of org.apache.qpid.proton.amqp.transport.ErrorCondition in project hono by eclipse.
the class ProtonBasedInternalCommandSender method sendCommand.
@Override
public Future<Void> sendCommand(final CommandContext commandContext, final String adapterInstanceId) {
Objects.requireNonNull(commandContext);
Objects.requireNonNull(adapterInstanceId);
return getOrCreateSenderLink(getTargetAddress(adapterInstanceId)).recover(thr -> Future.failedFuture(StatusCodeMapper.toServerError(thr))).compose(sender -> {
final Span span = newChildSpan(commandContext.getTracingContext(), "delegate Command request");
final Command command = commandContext.getCommand();
final Message message = adoptOrCreateMessage(command);
TracingHelper.setDeviceTags(span, command.getTenant(), command.getDeviceId());
if (command.isTargetedAtGateway()) {
MessageHelper.addProperty(message, MessageHelper.APP_PROPERTY_CMD_VIA, command.getGatewayId());
TracingHelper.TAG_GATEWAY_ID.set(span, command.getGatewayId());
}
return sender.sendAndWaitForRawOutcome(message, span);
}).map(delivery -> {
final DeliveryState remoteState = delivery.getRemoteState();
LOG.trace("command [{}] sent to downstream peer; remote state of delivery: {}", commandContext.getCommand(), remoteState);
if (Accepted.class.isInstance(remoteState)) {
commandContext.accept();
} else if (Rejected.class.isInstance(remoteState)) {
final Rejected rejected = (Rejected) remoteState;
commandContext.reject(Optional.ofNullable(rejected.getError()).map(ErrorCondition::getDescription).orElse(null));
} else if (Released.class.isInstance(remoteState)) {
commandContext.release();
} else if (Modified.class.isInstance(remoteState)) {
final Modified modified = (Modified) remoteState;
commandContext.modify(modified.getDeliveryFailed(), modified.getUndeliverableHere());
}
return (Void) null;
}).onFailure(thr -> {
LOG.debug("failed to send command [{}] to downstream peer", commandContext.getCommand(), thr);
if (thr instanceof NoConsumerException) {
TracingHelper.logError(commandContext.getTracingSpan(), "no credit - target adapter instance '" + adapterInstanceId + "' may be offline in which case the device hasn't subscribed again yet");
}
commandContext.release(thr);
});
}
Aggregations