Search in sources :

Example 6 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class VertxBasedAmqpProtocolAdapter method onCommandReceived.

/**
 * Invoked for every valid command that has been received from
 * an application.
 * <p>
 * This implementation simply forwards the command to the device
 * via the given link.
 *
 * @param tenantObject The tenant configuration object.
 * @param sender The link for sending the command to the device.
 * @param commandContext The context in which the adapter receives the command message.
 * @throws NullPointerException if any of the parameters is {@code null}.
 */
protected void onCommandReceived(final TenantObject tenantObject, final ProtonSender sender, final CommandContext commandContext) {
    Objects.requireNonNull(tenantObject);
    Objects.requireNonNull(sender);
    Objects.requireNonNull(commandContext);
    final Command command = commandContext.getCommand();
    final AtomicBoolean isCommandSettled = new AtomicBoolean(false);
    if (sender.sendQueueFull()) {
        log.debug("cannot send command to device: no credit available [{}]", command);
        commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "no credit available for sending command to device"));
        reportSentCommand(tenantObject, commandContext, ProcessingOutcome.UNDELIVERABLE);
    } else {
        final Message msg = ProtonHelper.message();
        msg.setAddress(String.format("%s/%s/%s", CommandConstants.COMMAND_ENDPOINT, command.getTenant(), command.getDeviceId()));
        msg.setCorrelationId(command.getCorrelationId());
        msg.setSubject(command.getName());
        MessageHelper.setPayload(msg, command.getContentType(), command.getPayload());
        if (command.isTargetedAtGateway()) {
            MessageHelper.addDeviceId(msg, command.getDeviceId());
        }
        if (!command.isOneWay()) {
            msg.setReplyTo(String.format("%s/%s/%s", CommandConstants.COMMAND_RESPONSE_ENDPOINT, command.getTenant(), Commands.getDeviceFacingReplyToId(command.getReplyToId(), command.getDeviceId(), command.getMessagingType())));
        }
        final Long timerId;
        if (getConfig().getSendMessageToDeviceTimeout() < 1) {
            timerId = null;
        } else {
            timerId = vertx.setTimer(getConfig().getSendMessageToDeviceTimeout(), tid -> {
                if (log.isDebugEnabled()) {
                    final String linkOrConnectionClosedInfo = HonoProtonHelper.isLinkOpenAndConnected(sender) ? "" : " (link or connection already closed)";
                    log.debug("waiting for delivery update timed out after {}ms{} [{}]", getConfig().getSendMessageToDeviceTimeout(), linkOrConnectionClosedInfo, command);
                }
                if (isCommandSettled.compareAndSet(false, true)) {
                    // timeout reached -> release command
                    commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "timeout waiting for delivery update from device"));
                    reportSentCommand(tenantObject, commandContext, ProcessingOutcome.UNDELIVERABLE);
                } else if (log.isTraceEnabled()) {
                    log.trace("command is already settled and downstream application was already notified [{}]", command);
                }
            });
        }
        sender.send(msg, delivery -> {
            if (timerId != null) {
                // disposition received -> cancel timer
                vertx.cancelTimer(timerId);
            }
            if (!isCommandSettled.compareAndSet(false, true)) {
                log.trace("command is already settled and downstream application was already notified [{}]", command);
            } else {
                // release the command message when the device either
                // rejects or does not settle the command request message.
                final DeliveryState remoteState = delivery.getRemoteState();
                ProcessingOutcome outcome = null;
                if (delivery.remotelySettled()) {
                    if (Accepted.class.isInstance(remoteState)) {
                        outcome = ProcessingOutcome.FORWARDED;
                        commandContext.accept();
                    } else if (Rejected.class.isInstance(remoteState)) {
                        outcome = ProcessingOutcome.UNPROCESSABLE;
                        final String cause = Optional.ofNullable(((Rejected) remoteState).getError()).map(ErrorCondition::getDescription).orElse(null);
                        commandContext.reject(cause);
                    } else if (Released.class.isInstance(remoteState)) {
                        outcome = ProcessingOutcome.UNDELIVERABLE;
                        commandContext.release();
                    } else if (Modified.class.isInstance(remoteState)) {
                        final Modified modified = (Modified) remoteState;
                        outcome = modified.getUndeliverableHere() ? ProcessingOutcome.UNPROCESSABLE : ProcessingOutcome.UNDELIVERABLE;
                        commandContext.modify(modified.getDeliveryFailed(), modified.getUndeliverableHere());
                    }
                } else {
                    log.debug("device did not settle command message [remote state: {}, {}]", remoteState, command);
                    final Map<String, Object> logItems = new HashMap<>(2);
                    logItems.put(Fields.EVENT, "device did not settle command");
                    logItems.put("remote state", remoteState);
                    commandContext.getTracingSpan().log(logItems);
                    commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "device did not settle command"));
                    outcome = ProcessingOutcome.UNDELIVERABLE;
                }
                reportSentCommand(tenantObject, commandContext, outcome);
            }
        });
        final Map<String, Object> items = new HashMap<>(4);
        items.put(Fields.EVENT, "command sent to device");
        if (sender.getRemoteTarget() != null) {
            items.put(Tags.MESSAGE_BUS_DESTINATION.getKey(), sender.getRemoteTarget().getAddress());
        }
        items.put(TracingHelper.TAG_QOS.getKey(), sender.getQoS().name());
        items.put(TracingHelper.TAG_CREDIT.getKey(), sender.getCredit());
        commandContext.getTracingSpan().log(items);
    }
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) LifecycleChange(org.eclipse.hono.notification.deviceregistry.LifecycleChange) DeviceChangeNotification(org.eclipse.hono.notification.deviceregistry.DeviceChangeNotification) Tags(io.opentracing.tag.Tags) ProtonServer(io.vertx.proton.ProtonServer) HonoProtonHelper(org.eclipse.hono.util.HonoProtonHelper) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) EndpointType(org.eclipse.hono.service.metric.MetricsTags.EndpointType) Modified(org.apache.qpid.proton.amqp.messaging.Modified) DeviceCredentials(org.eclipse.hono.adapter.auth.device.DeviceCredentials) Map(java.util.Map) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) AuthorizationException(org.eclipse.hono.adapter.AuthorizationException) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) Fields(io.opentracing.log.Fields) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) TracingHelper(org.eclipse.hono.tracing.TracingHelper) ProtonSaslAuthenticatorFactory(io.vertx.proton.sasl.ProtonSaslAuthenticatorFactory) AllDevicesOfTenantDeletedNotification(org.eclipse.hono.notification.deviceregistry.AllDevicesOfTenantDeletedNotification) TenantServiceBasedX509Authentication(org.eclipse.hono.adapter.auth.device.TenantServiceBasedX509Authentication) Predicate(java.util.function.Predicate) Collection(java.util.Collection) CommandContext(org.eclipse.hono.client.command.CommandContext) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) ProtonQoS(io.vertx.proton.ProtonQoS) MessageHelper(org.eclipse.hono.util.MessageHelper) Collectors(java.util.stream.Collectors) Future(io.vertx.core.Future) Device(org.eclipse.hono.auth.Device) Objects(java.util.Objects) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) List(java.util.List) QoS(org.eclipse.hono.service.metric.MetricsTags.QoS) TenantTraceSamplingHelper(org.eclipse.hono.tracing.TenantTraceSamplingHelper) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) ProtonSender(io.vertx.proton.ProtonSender) NotificationEventBusSupport(org.eclipse.hono.notification.NotificationEventBusSupport) ProtonLink(io.vertx.proton.ProtonLink) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted) ProtonServerOptions(io.vertx.proton.ProtonServerOptions) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) ConnectionLimitManager(org.eclipse.hono.adapter.limiting.ConnectionLimitManager) Command(org.eclipse.hono.client.command.Command) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) HashMap(java.util.HashMap) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AdapterDisabledException(org.eclipse.hono.adapter.AdapterDisabledException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) OptionalInt(java.util.OptionalInt) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) Commands(org.eclipse.hono.client.command.Commands) Constants(org.eclipse.hono.util.Constants) CompositeFuture(io.vertx.core.CompositeFuture) ProtonSession(io.vertx.proton.ProtonSession) Symbol(org.apache.qpid.proton.amqp.Symbol) AdapterConnectionsExceededException(org.eclipse.hono.adapter.AdapterConnectionsExceededException) Target(org.apache.qpid.proton.amqp.transport.Target) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) Message(org.apache.qpid.proton.message.Message) HttpUtils(org.eclipse.hono.service.http.HttpUtils) AsyncResult(io.vertx.core.AsyncResult) CommandConstants(org.eclipse.hono.util.CommandConstants) TenantChangeNotification(org.eclipse.hono.notification.deviceregistry.TenantChangeNotification) Strings(org.eclipse.hono.util.Strings) UsernamePasswordAuthProvider(org.eclipse.hono.adapter.auth.device.UsernamePasswordAuthProvider) CredentialsApiAuthProvider(org.eclipse.hono.adapter.auth.device.CredentialsApiAuthProvider) AbstractProtocolAdapterBase(org.eclipse.hono.adapter.AbstractProtocolAdapterBase) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) Sample(io.micrometer.core.instrument.Timer.Sample) Released(org.apache.qpid.proton.amqp.messaging.Released) CommandResponse(org.eclipse.hono.client.command.CommandResponse) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Source(org.apache.qpid.proton.amqp.transport.Source) ConnectionAttemptOutcome(org.eclipse.hono.service.metric.MetricsTags.ConnectionAttemptOutcome) MemoryBasedConnectionLimitStrategy(org.eclipse.hono.adapter.limiting.MemoryBasedConnectionLimitStrategy) X509AuthProvider(org.eclipse.hono.adapter.auth.device.X509AuthProvider) Handler(io.vertx.core.Handler) Collections(java.util.Collections) DefaultConnectionLimitManager(org.eclipse.hono.adapter.limiting.DefaultConnectionLimitManager) Modified(org.apache.qpid.proton.amqp.messaging.Modified) Message(org.apache.qpid.proton.message.Message) HashMap(java.util.HashMap) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) Command(org.eclipse.hono.client.command.Command) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) TenantObject(org.eclipse.hono.util.TenantObject) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 7 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class VertxBasedAmqpProtocolAdapter method createCommandConsumer.

private Future<CommandConsumer> createCommandConsumer(final ProtonSender sender, final ResourceIdentifier sourceAddress, final Device authenticatedDevice, final Span span) {
    final Handler<CommandContext> commandHandler = commandContext -> {
        final Sample timer = metrics.startTimer();
        addMicrometerSample(commandContext, timer);
        Tags.COMPONENT.set(commandContext.getTracingSpan(), getTypeName());
        final Command command = commandContext.getCommand();
        final Future<TenantObject> tenantTracker = getTenantConfiguration(sourceAddress.getTenantId(), commandContext.getTracingContext());
        tenantTracker.compose(tenantObject -> {
            if (!command.isValid()) {
                return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed command message"));
            }
            if (!HonoProtonHelper.isLinkOpenAndConnected(sender)) {
                return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "sender link is not open"));
            }
            return checkMessageLimit(tenantObject, command.getPayloadSize(), commandContext.getTracingContext());
        }).compose(success -> {
            // check the via-gateways, ensuring that the gateway may act on behalf of the device at this point in time
            if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
                return getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, commandContext.getTracingContext());
            }
            return Future.succeededFuture();
        }).compose(success -> {
            onCommandReceived(tenantTracker.result(), sender, commandContext);
            return Future.succeededFuture();
        }).otherwise(failure -> {
            if (failure instanceof ClientErrorException) {
                commandContext.reject(failure);
            } else {
                commandContext.release(failure);
            }
            metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, sourceAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.from(failure), command.getPayloadSize(), timer);
            return null;
        });
    };
    final Future<RegistrationAssertion> tokenTracker = Optional.ofNullable(authenticatedDevice).map(v -> getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, span.context())).orElseGet(Future::succeededFuture);
    if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
        // gateway scenario
        return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice.getDeviceId(), commandHandler, null, span.context()));
    } else {
        return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), commandHandler, null, span.context()));
    }
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) LifecycleChange(org.eclipse.hono.notification.deviceregistry.LifecycleChange) DeviceChangeNotification(org.eclipse.hono.notification.deviceregistry.DeviceChangeNotification) Tags(io.opentracing.tag.Tags) ProtonServer(io.vertx.proton.ProtonServer) HonoProtonHelper(org.eclipse.hono.util.HonoProtonHelper) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) EndpointType(org.eclipse.hono.service.metric.MetricsTags.EndpointType) Modified(org.apache.qpid.proton.amqp.messaging.Modified) DeviceCredentials(org.eclipse.hono.adapter.auth.device.DeviceCredentials) Map(java.util.Map) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) AuthorizationException(org.eclipse.hono.adapter.AuthorizationException) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) Fields(io.opentracing.log.Fields) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) TracingHelper(org.eclipse.hono.tracing.TracingHelper) ProtonSaslAuthenticatorFactory(io.vertx.proton.sasl.ProtonSaslAuthenticatorFactory) AllDevicesOfTenantDeletedNotification(org.eclipse.hono.notification.deviceregistry.AllDevicesOfTenantDeletedNotification) TenantServiceBasedX509Authentication(org.eclipse.hono.adapter.auth.device.TenantServiceBasedX509Authentication) Predicate(java.util.function.Predicate) Collection(java.util.Collection) CommandContext(org.eclipse.hono.client.command.CommandContext) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) ProtonQoS(io.vertx.proton.ProtonQoS) MessageHelper(org.eclipse.hono.util.MessageHelper) Collectors(java.util.stream.Collectors) Future(io.vertx.core.Future) Device(org.eclipse.hono.auth.Device) Objects(java.util.Objects) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) List(java.util.List) QoS(org.eclipse.hono.service.metric.MetricsTags.QoS) TenantTraceSamplingHelper(org.eclipse.hono.tracing.TenantTraceSamplingHelper) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) ProtonSender(io.vertx.proton.ProtonSender) NotificationEventBusSupport(org.eclipse.hono.notification.NotificationEventBusSupport) ProtonLink(io.vertx.proton.ProtonLink) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted) ProtonServerOptions(io.vertx.proton.ProtonServerOptions) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) ConnectionLimitManager(org.eclipse.hono.adapter.limiting.ConnectionLimitManager) Command(org.eclipse.hono.client.command.Command) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) HashMap(java.util.HashMap) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AdapterDisabledException(org.eclipse.hono.adapter.AdapterDisabledException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) OptionalInt(java.util.OptionalInt) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) Commands(org.eclipse.hono.client.command.Commands) Constants(org.eclipse.hono.util.Constants) CompositeFuture(io.vertx.core.CompositeFuture) ProtonSession(io.vertx.proton.ProtonSession) Symbol(org.apache.qpid.proton.amqp.Symbol) AdapterConnectionsExceededException(org.eclipse.hono.adapter.AdapterConnectionsExceededException) Target(org.apache.qpid.proton.amqp.transport.Target) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) Message(org.apache.qpid.proton.message.Message) HttpUtils(org.eclipse.hono.service.http.HttpUtils) AsyncResult(io.vertx.core.AsyncResult) CommandConstants(org.eclipse.hono.util.CommandConstants) TenantChangeNotification(org.eclipse.hono.notification.deviceregistry.TenantChangeNotification) Strings(org.eclipse.hono.util.Strings) UsernamePasswordAuthProvider(org.eclipse.hono.adapter.auth.device.UsernamePasswordAuthProvider) CredentialsApiAuthProvider(org.eclipse.hono.adapter.auth.device.CredentialsApiAuthProvider) AbstractProtocolAdapterBase(org.eclipse.hono.adapter.AbstractProtocolAdapterBase) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) Sample(io.micrometer.core.instrument.Timer.Sample) Released(org.apache.qpid.proton.amqp.messaging.Released) CommandResponse(org.eclipse.hono.client.command.CommandResponse) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Source(org.apache.qpid.proton.amqp.transport.Source) ConnectionAttemptOutcome(org.eclipse.hono.service.metric.MetricsTags.ConnectionAttemptOutcome) MemoryBasedConnectionLimitStrategy(org.eclipse.hono.adapter.limiting.MemoryBasedConnectionLimitStrategy) X509AuthProvider(org.eclipse.hono.adapter.auth.device.X509AuthProvider) Handler(io.vertx.core.Handler) Collections(java.util.Collections) DefaultConnectionLimitManager(org.eclipse.hono.adapter.limiting.DefaultConnectionLimitManager) CommandContext(org.eclipse.hono.client.command.CommandContext) Command(org.eclipse.hono.client.command.Command) Sample(io.micrometer.core.instrument.Timer.Sample) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) Future(io.vertx.core.Future) CompositeFuture(io.vertx.core.CompositeFuture) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 8 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class AbstractHonoResource method doUploadMessage.

/**
 * Forwards a message to the south bound Telemetry or Event API of the messaging infrastructure configured
 * for the tenant that the origin device belongs to.
 * <p>
 * Depending on the outcome of the attempt to upload the message, the CoAP response code is set as
 * described by the <a href="https://www.eclipse.org/hono/docs/user-guide/coap-adapter/">CoAP adapter user guide</a>
 *
 * @param context The request that contains the uploaded message.
 * @param endpoint The type of API endpoint to forward the message to.
 * @return A future indicating the outcome of the operation.
 *         The future will be succeeded if the message has been forwarded successfully.
 *         In this case one of the context's <em>respond</em> methods will have been invoked to send a CoAP response
 *         back to the device.
 *         Otherwise the future will be failed with a {@link org.eclipse.hono.client.ServiceInvocationException}.
 * @throws NullPointerException if any of the parameters are {@code null}.
 */
protected final Future<Void> doUploadMessage(final CoapContext context, final MetricsTags.EndpointType endpoint) {
    Objects.requireNonNull(context);
    Objects.requireNonNull(endpoint);
    final String contentType = context.getContentType();
    final Buffer payload = context.getPayload();
    if (contentType == null) {
        return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request message must contain content-format option"));
    } else if (payload.length() == 0 && !context.isEmptyNotification()) {
        return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request contains no body but is not marked as empty notification"));
    } else {
        final String gatewayId = context.getGatewayId();
        final String tenantId = context.getOriginDevice().getTenantId();
        final String deviceId = context.getOriginDevice().getDeviceId();
        final MetricsTags.QoS qos = context.isConfirmable() ? MetricsTags.QoS.AT_LEAST_ONCE : MetricsTags.QoS.AT_MOST_ONCE;
        final Span currentSpan = TracingHelper.buildChildSpan(getTracer(), context.getTracingContext(), "upload " + endpoint.getCanonicalName(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantId).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), context.isDeviceAuthenticated()).withTag(Constants.HEADER_QOS_LEVEL, qos.asTag().getValue()).start();
        final Promise<Void> responseReady = Promise.promise();
        final Future<RegistrationAssertion> tokenTracker = getAdapter().getRegistrationAssertion(tenantId, deviceId, context.getAuthenticatedDevice(), currentSpan.context());
        final Future<TenantObject> tenantTracker = getAdapter().getTenantClient().get(tenantId, currentSpan.context());
        final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(getAdapter().isAdapterEnabled(tenantObject), getAdapter().checkMessageLimit(tenantObject, payload.length(), currentSpan.context())).map(tenantObject));
        // we only need to consider TTD if the device and tenant are enabled and the adapter
        // is enabled for the tenant
        final Future<Integer> ttdTracker = CompositeFuture.all(tenantValidationTracker, tokenTracker).compose(ok -> {
            final Integer ttdParam = context.getTimeUntilDisconnect();
            return getAdapter().getTimeUntilDisconnect(tenantTracker.result(), ttdParam).map(effectiveTtd -> {
                if (effectiveTtd != null) {
                    currentSpan.setTag(MessageHelper.APP_PROPERTY_DEVICE_TTD, effectiveTtd);
                }
                return effectiveTtd;
            });
        });
        final Future<CommandConsumer> commandConsumerTracker = ttdTracker.compose(ttd -> createCommandConsumer(ttd, tenantTracker.result(), deviceId, gatewayId, context, responseReady, currentSpan));
        return commandConsumerTracker.compose(commandConsumer -> {
            final Map<String, Object> props = getAdapter().getDownstreamMessageProperties(context);
            Optional.ofNullable(commandConsumer).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
            customizeDownstreamMessageProperties(props, context);
            if (context.isConfirmable()) {
                context.startAcceptTimer(vertx, tenantTracker.result(), getAdapter().getConfig().getTimeoutToAck());
            }
            final Future<Void> sendResult;
            if (endpoint == EndpointType.EVENT) {
                sendResult = getAdapter().getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context());
            } else {
                sendResult = getAdapter().getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), context.getRequestedQos(), contentType, payload, props, currentSpan.context());
            }
            return CompositeFuture.all(sendResult, responseReady.future()).mapEmpty();
        }).compose(proceed -> {
            // request and the CommandConsumer from the current request has not been closed yet
            return Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).otherwise(thr -> {
                TracingHelper.logError(currentSpan, thr);
                return (Void) null;
            })).orElseGet(Future::succeededFuture);
        }).map(proceed -> {
            final CommandContext commandContext = context.get(CommandContext.KEY_COMMAND_CONTEXT);
            final Response response = new Response(ResponseCode.CHANGED);
            if (commandContext != null) {
                addCommandToResponse(response, commandContext, currentSpan);
                commandContext.accept();
                getAdapter().getMetrics().reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantId, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
            }
            LOG.trace("successfully processed message for device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName());
            getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), MetricsTags.ProcessingOutcome.FORWARDED, qos, payload.length(), getTtdStatus(context), context.getTimer());
            context.respond(response);
            currentSpan.finish();
            return (Void) null;
        }).recover(t -> {
            LOG.debug("cannot process message from device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName(), t);
            final Future<Void> commandConsumerClosedTracker = Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).onFailure(thr -> TracingHelper.logError(currentSpan, thr))).orElseGet(Future::succeededFuture);
            final CommandContext commandContext = context.get(CommandContext.KEY_COMMAND_CONTEXT);
            if (commandContext != null) {
                TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in CoAP response, CoAP request handling failed", t);
                commandContext.release(t);
                currentSpan.log("released command for device");
            }
            getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), ClientErrorException.class.isInstance(t) ? MetricsTags.ProcessingOutcome.UNPROCESSABLE : MetricsTags.ProcessingOutcome.UNDELIVERABLE, qos, payload.length(), getTtdStatus(context), context.getTimer());
            TracingHelper.logError(currentSpan, t);
            commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
            return Future.failedFuture(t);
        });
    }
}
Also used : Buffer(io.vertx.core.buffer.Buffer) HttpURLConnection(java.net.HttpURLConnection) ResponseCode(org.eclipse.californium.core.coap.CoAP.ResponseCode) CoapExchange(org.eclipse.californium.core.server.resources.CoapExchange) Response(org.eclipse.californium.core.coap.Response) Command(org.eclipse.hono.client.command.Command) LoggerFactory(org.slf4j.LoggerFactory) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ClientErrorException(org.eclipse.hono.client.ClientErrorException) Constants(org.eclipse.hono.util.Constants) Tags(io.opentracing.tag.Tags) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) CompositeFuture(io.vertx.core.CompositeFuture) EndpointType(org.eclipse.hono.service.metric.MetricsTags.EndpointType) TtdStatus(org.eclipse.hono.service.metric.MetricsTags.TtdStatus) References(io.opentracing.References) Duration(java.time.Duration) Map(java.util.Map) MediaTypeRegistry(org.eclipse.californium.core.coap.MediaTypeRegistry) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) TracingHelper(org.eclipse.hono.tracing.TracingHelper) AsyncResult(io.vertx.core.AsyncResult) CommandConstants(org.eclipse.hono.util.CommandConstants) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) Promise(io.vertx.core.Promise) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) MessageHelper(org.eclipse.hono.util.MessageHelper) Sample(io.micrometer.core.instrument.Timer.Sample) Future(io.vertx.core.Future) Device(org.eclipse.hono.auth.Device) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Objects(java.util.Objects) List(java.util.List) Principal(java.security.Principal) Buffer(io.vertx.core.buffer.Buffer) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) OptionSet(org.eclipse.californium.core.coap.OptionSet) Handler(io.vertx.core.Handler) CommandContext(org.eclipse.hono.client.command.CommandContext) Span(io.opentracing.Span) Response(org.eclipse.californium.core.coap.Response) Promise(io.vertx.core.Promise) ClientErrorException(org.eclipse.hono.client.ClientErrorException) CompositeFuture(io.vertx.core.CompositeFuture) Future(io.vertx.core.Future) TenantObject(org.eclipse.hono.util.TenantObject)

Example 9 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class LoraProtocolAdapter method handleCommand.

private void handleCommand(final CommandContext commandContext) {
    Tags.COMPONENT.set(commandContext.getTracingSpan(), getTypeName());
    final Sample timer = metrics.startTimer();
    final Command command = commandContext.getCommand();
    if (command.getGatewayId() == null) {
        final String errorMsg = "no gateway defined for command";
        LOG.debug("{} [{}]", errorMsg, command);
        commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, errorMsg));
        return;
    }
    final String tenant = command.getTenant();
    final String gatewayId = command.getGatewayId();
    final LoraProvider loraProvider = Optional.ofNullable(commandSubscriptions.get(new SubscriptionKey(tenant, gatewayId))).map(Pair::two).orElse(null);
    if (loraProvider == null) {
        LOG.debug("received command for unknown gateway [{}] for tenant [{}]", gatewayId, tenant);
        TracingHelper.logError(commandContext.getTracingSpan(), String.format("received command for unknown gateway [%s]", gatewayId));
        commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "received command for unknown gateway"));
        return;
    }
    final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, commandContext.getTracingContext());
    tenantTracker.compose(tenantObject -> {
        if (command.isValid()) {
            return checkMessageLimit(tenantObject, command.getPayloadSize(), commandContext.getTracingContext());
        } else {
            return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed command message"));
        }
    }).compose(success -> getRegistrationClient().assertRegistration(tenant, gatewayId, null, commandContext.getTracingContext())).compose(registrationAssertion -> sendCommandToGateway(commandContext, loraProvider, registrationAssertion.getCommandEndpoint())).onSuccess(aVoid -> {
        addMicrometerSample(commandContext, timer);
        commandContext.accept();
        metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), MetricsTags.ProcessingOutcome.FORWARDED, command.getPayloadSize(), timer);
    }).onFailure(t -> {
        LOG.debug("error sending command", t);
        commandContext.release(t);
        metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), MetricsTags.ProcessingOutcome.from(t), command.getPayloadSize(), timer);
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) LoraProviderMalformedPayloadException(org.eclipse.hono.adapter.lora.providers.LoraProviderMalformedPayloadException) LoggerFactory(org.slf4j.LoggerFactory) Router(io.vertx.ext.web.Router) Tag(io.opentracing.tag.Tag) RoutingContext(io.vertx.ext.web.RoutingContext) Tags(io.opentracing.tag.Tags) Map(java.util.Map) Pair(org.eclipse.hono.util.Pair) Fields(io.opentracing.log.Fields) JsonObject(io.vertx.core.json.JsonObject) TracingHelper(org.eclipse.hono.tracing.TracingHelper) TenantServiceBasedX509Authentication(org.eclipse.hono.adapter.auth.device.TenantServiceBasedX509Authentication) ChainAuthHandler(io.vertx.ext.web.handler.ChainAuthHandler) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) Message(io.vertx.core.eventbus.Message) EventConstants(org.eclipse.hono.util.EventConstants) StringTag(io.opentracing.tag.StringTag) Future(io.vertx.core.Future) HonoBasicAuthHandler(org.eclipse.hono.adapter.http.HonoBasicAuthHandler) Device(org.eclipse.hono.auth.Device) Objects(java.util.Objects) List(java.util.List) Buffer(io.vertx.core.buffer.Buffer) X509AuthHandler(org.eclipse.hono.adapter.http.X509AuthHandler) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Optional(java.util.Optional) Span(io.opentracing.Span) UsernamePasswordCredentials(org.eclipse.hono.adapter.auth.device.UsernamePasswordCredentials) CommandEndpoint(org.eclipse.hono.util.CommandEndpoint) HttpContext(org.eclipse.hono.service.http.HttpContext) Json(io.vertx.core.json.Json) LoraProvider(org.eclipse.hono.adapter.lora.providers.LoraProvider) WebClient(io.vertx.ext.web.client.WebClient) Command(org.eclipse.hono.client.command.Command) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) Constants(org.eclipse.hono.util.Constants) ArrayList(java.util.ArrayList) TracingHandler(org.eclipse.hono.service.http.TracingHandler) CompositeFuture(io.vertx.core.CompositeFuture) StatusCodeMapper(org.eclipse.hono.client.StatusCodeMapper) HttpUtils(org.eclipse.hono.service.http.HttpUtils) LinkedList(java.util.LinkedList) UsernamePasswordAuthProvider(org.eclipse.hono.adapter.auth.device.UsernamePasswordAuthProvider) Logger(org.slf4j.Logger) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) AbstractVertxBasedHttpProtocolAdapter(org.eclipse.hono.adapter.http.AbstractVertxBasedHttpProtocolAdapter) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Sample(io.micrometer.core.instrument.Timer.Sample) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) HttpRequest(io.vertx.ext.web.client.HttpRequest) DeviceCredentialsAuthProvider(org.eclipse.hono.adapter.auth.device.DeviceCredentialsAuthProvider) X509AuthProvider(org.eclipse.hono.adapter.auth.device.X509AuthProvider) HttpMethod(io.vertx.core.http.HttpMethod) HttpProtocolAdapterProperties(org.eclipse.hono.adapter.http.HttpProtocolAdapterProperties) SubjectDnCredentials(org.eclipse.hono.adapter.auth.device.SubjectDnCredentials) TenantObject(org.eclipse.hono.util.TenantObject) LoraProvider(org.eclipse.hono.adapter.lora.providers.LoraProvider) Command(org.eclipse.hono.client.command.Command) Sample(io.micrometer.core.instrument.Timer.Sample) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 10 with CommandContext

use of org.eclipse.hono.client.command.CommandContext in project hono by eclipse.

the class LoraProtocolAdapterTest method handleCommandForLNS.

/**
 * Verifies that an uplink message triggers a command subscription.
 */
@SuppressWarnings("unchecked")
@Test
public void handleCommandForLNS() {
    givenATelemetrySenderForAnyTenant();
    final LoraProvider providerMock = getLoraProviderMock();
    final HttpServerRequest request = mock(HttpServerRequest.class);
    final HttpContext httpContext = newHttpContext();
    when(httpContext.request()).thenReturn(request);
    final CommandEndpoint commandEndpoint = new CommandEndpoint();
    commandEndpoint.setHeaders(Map.of("my-header", "my-header-value"));
    commandEndpoint.setUri("https://my-server.com/commands/{{deviceId}}/send");
    setGatewayDeviceCommandEndpoint(commandEndpoint);
    final CommandConsumer commandConsumer = mock(CommandConsumer.class);
    when(commandConsumer.close(any())).thenReturn(Future.succeededFuture());
    when(commandConsumerFactory.createCommandConsumer(any(), any(), any(), any(), any())).thenReturn(Future.succeededFuture(commandConsumer));
    adapter.handleProviderRoute(httpContext, providerMock);
    final ArgumentCaptor<Handler<CommandContext>> handlerArgumentCaptor = VertxMockSupport.argumentCaptorHandler();
    verify(commandConsumerFactory).createCommandConsumer(eq(TEST_TENANT_ID), eq(TEST_GATEWAY_ID), handlerArgumentCaptor.capture(), isNull(), any());
    final Handler<CommandContext> commandHandler = handlerArgumentCaptor.getValue();
    final Command command = mock(Command.class);
    when(command.getTenant()).thenReturn(TEST_TENANT_ID);
    when(command.getDeviceId()).thenReturn(TEST_DEVICE_ID);
    when(command.getGatewayId()).thenReturn(TEST_GATEWAY_ID);
    when(command.getPayload()).thenReturn(Buffer.buffer("bumlux"));
    when(command.isValid()).thenReturn(true);
    final CommandContext commandContext = mock(CommandContext.class);
    when(commandContext.getCommand()).thenReturn(command);
    when(commandContext.getTracingSpan()).thenReturn(processMessageSpan);
    final JsonObject json = new JsonObject().put("my-payload", "bumlux");
    final LoraCommand loraCommand = new LoraCommand(json, "https://my-server.com/commands/deviceId/send");
    when(providerMock.getCommand(any(), any(), any(), any())).thenReturn(loraCommand);
    when(providerMock.getDefaultHeaders()).thenReturn(Map.of("my-provider-header", "my-provider-header-value"));
    final HttpRequest<Buffer> httpClientRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
    final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
    when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_NO_CONTENT);
    when(httpClientRequest.sendJson(any(JsonObject.class))).thenReturn(Future.succeededFuture(httpResponse));
    when(webClient.postAbs(anyString())).thenReturn(httpClientRequest);
    commandHandler.handle(commandContext);
    verify(webClient, times(1)).postAbs("https://my-server.com/commands/deviceId/send");
    verify(httpClientRequest, times(1)).putHeader("my-header", "my-header-value");
    verify(httpClientRequest, times(1)).putHeader("my-provider-header", "my-provider-header-value");
    verify(httpClientRequest, times(1)).sendJson(json);
}
Also used : Buffer(io.vertx.core.buffer.Buffer) LoraProvider(org.eclipse.hono.adapter.lora.providers.LoraProvider) CommandContext(org.eclipse.hono.client.command.CommandContext) HttpServerRequest(io.vertx.core.http.HttpServerRequest) HttpContext(org.eclipse.hono.service.http.HttpContext) TracingHandler(org.eclipse.hono.service.http.TracingHandler) Handler(io.vertx.core.Handler) JsonObject(io.vertx.core.json.JsonObject) Command(org.eclipse.hono.client.command.Command) CommandEndpoint(org.eclipse.hono.util.CommandEndpoint) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) Test(org.junit.jupiter.api.Test)

Aggregations

CommandContext (org.eclipse.hono.client.command.CommandContext)34 Buffer (io.vertx.core.buffer.Buffer)22 Future (io.vertx.core.Future)19 HttpURLConnection (java.net.HttpURLConnection)18 TenantObject (org.eclipse.hono.util.TenantObject)17 Test (org.junit.jupiter.api.Test)17 Span (io.opentracing.Span)15 Handler (io.vertx.core.Handler)15 ServerErrorException (org.eclipse.hono.client.ServerErrorException)15 Promise (io.vertx.core.Promise)14 Optional (java.util.Optional)14 ClientErrorException (org.eclipse.hono.client.ClientErrorException)14 Command (org.eclipse.hono.client.command.Command)14 List (java.util.List)13 Objects (java.util.Objects)13 SpanContext (io.opentracing.SpanContext)12 ArgumentMatchers.anyString (org.mockito.ArgumentMatchers.anyString)12 TracingHelper (org.eclipse.hono.tracing.TracingHelper)11 Tracer (io.opentracing.Tracer)10 CompositeFuture (io.vertx.core.CompositeFuture)10