Search in sources :

Example 56 with ErrorCondition

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);
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) Arrays(java.util.Arrays) LoggerFactory(org.slf4j.LoggerFactory) Context(io.vertx.core.Context) HonoProtonHelper(org.eclipse.hono.util.HonoProtonHelper) ConnectionFactory(org.eclipse.hono.connection.ConnectionFactory) SaslSystemException(io.vertx.proton.sasl.SaslSystemException) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) DisconnectListener(org.eclipse.hono.client.DisconnectListener) ClientConfigProperties(org.eclipse.hono.config.ClientConfigProperties) MechanismMismatchException(io.vertx.proton.sasl.MechanismMismatchException) ProtonQoS(io.vertx.proton.ProtonQoS) UUID(java.util.UUID) Future(io.vertx.core.Future) Objects(java.util.Objects) CountDownLatch(java.util.concurrent.CountDownLatch) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) ReconnectListener(org.eclipse.hono.client.ReconnectListener) List(java.util.List) SSLException(javax.net.ssl.SSLException) Optional(java.util.Optional) ProtonSender(io.vertx.proton.ProtonSender) AuthenticationException(javax.security.sasl.AuthenticationException) ProtonLink(io.vertx.proton.ProtonLink) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Constants(org.eclipse.hono.util.Constants) ArrayList(java.util.ArrayList) ProtonSession(io.vertx.proton.ProtonSession) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) Symbol(org.apache.qpid.proton.amqp.Symbol) StatusCodeMapper(org.eclipse.hono.client.StatusCodeMapper) ThreadLocalRandom(java.util.concurrent.ThreadLocalRandom) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) AsyncResult(io.vertx.core.AsyncResult) HonoConnection(org.eclipse.hono.client.HonoConnection) Logger(org.slf4j.Logger) VertxInternal(io.vertx.core.impl.VertxInternal) Iterator(java.util.Iterator) Tracer(io.opentracing.Tracer) NoopTracerFactory(io.opentracing.noop.NoopTracerFactory) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) TimeUnit(java.util.concurrent.TimeUnit) Handler(io.vertx.core.Handler) Collections(java.util.Collections) Promise(io.vertx.core.Promise) ProtonSender(io.vertx.proton.ProtonSender) DisconnectListener(org.eclipse.hono.client.DisconnectListener) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 57 with ErrorCondition

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);
    }
}
Also used : ProtonSender(io.vertx.proton.ProtonSender) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition)

Example 58 with ErrorCondition

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");
}
Also used : ProtonConnection(io.vertx.proton.ProtonConnection) InOrder(org.mockito.InOrder) Device(org.eclipse.hono.auth.Device) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) Handler(io.vertx.core.Handler) Record(org.apache.qpid.proton.engine.Record) RecordImpl(org.apache.qpid.proton.engine.impl.RecordImpl) Test(org.junit.jupiter.api.Test)

Example 59 with ErrorCondition

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);
}
Also used : ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected)

Example 60 with ErrorCondition

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);
    });
}
Also used : InternalCommandSender(org.eclipse.hono.client.command.InternalCommandSender) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) Command(org.eclipse.hono.client.command.Command) LoggerFactory(org.slf4j.LoggerFactory) NoConsumerException(org.eclipse.hono.client.NoConsumerException) Modified(org.apache.qpid.proton.amqp.messaging.Modified) StatusCodeMapper(org.eclipse.hono.client.StatusCodeMapper) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) Message(org.apache.qpid.proton.message.Message) TracingHelper(org.eclipse.hono.tracing.TracingHelper) HonoConnection(org.eclipse.hono.client.HonoConnection) CommandConstants(org.eclipse.hono.util.CommandConstants) Logger(org.slf4j.Logger) CommandContext(org.eclipse.hono.client.command.CommandContext) ProtonHelper(io.vertx.proton.ProtonHelper) MessageHelper(org.eclipse.hono.util.MessageHelper) Released(org.apache.qpid.proton.amqp.messaging.Released) Future(io.vertx.core.Future) Objects(java.util.Objects) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) SenderCachingServiceClient(org.eclipse.hono.client.amqp.SenderCachingServiceClient) Optional(java.util.Optional) Span(io.opentracing.Span) SendMessageSampler(org.eclipse.hono.client.SendMessageSampler) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted) Released(org.apache.qpid.proton.amqp.messaging.Released) Modified(org.apache.qpid.proton.amqp.messaging.Modified) Message(org.apache.qpid.proton.message.Message) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) Command(org.eclipse.hono.client.command.Command) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) NoConsumerException(org.eclipse.hono.client.NoConsumerException) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) Span(io.opentracing.Span) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted)

Aggregations

ErrorCondition (org.apache.qpid.proton.amqp.transport.ErrorCondition)67 Handler (io.vertx.core.Handler)22 ProtonConnection (io.vertx.proton.ProtonConnection)20 Symbol (org.apache.qpid.proton.amqp.Symbol)20 Rejected (org.apache.qpid.proton.amqp.messaging.Rejected)17 Message (org.apache.qpid.proton.message.Message)17 TimeUnit (java.util.concurrent.TimeUnit)14 Vertx (io.vertx.core.Vertx)13 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)13 UnsignedLong (org.apache.qpid.proton.amqp.UnsignedLong)12 DeliveryState (org.apache.qpid.proton.amqp.transport.DeliveryState)12 ProtonReceiver (io.vertx.proton.ProtonReceiver)11 CountDownLatch (java.util.concurrent.CountDownLatch)11 AtomicReference (java.util.concurrent.atomic.AtomicReference)11 Test (org.junit.Test)11 Future (io.vertx.core.Future)10 Promise (io.vertx.core.Promise)10 ProtonSender (io.vertx.proton.ProtonSender)10 AsyncResult (io.vertx.core.AsyncResult)9 Map (java.util.Map)9