Search in sources :

Example 1 with NoConsumerException

use of org.eclipse.hono.client.NoConsumerException in project hono by eclipse.

the class AbstractMappingAndDelegatingCommandHandler method mapAndDelegateIncomingCommand.

/**
 * Delegates an incoming command to the protocol adapter instance that the target
 * device is connected to.
 * <p>
 * Determines the target gateway (if applicable) and protocol adapter instance for an incoming command
 * and delegates the command to the resulting protocol adapter instance.
 *
 * @param commandContext The context of the command to send.
 * @param timer The timer indicating the amount of time used for processing the command message.
 * @return A future indicating the outcome of the operation.
 * @throws NullPointerException if any of the parameters are {@code null}.
 */
protected final Future<Void> mapAndDelegateIncomingCommand(final CommandContext commandContext, final Timer.Sample timer) {
    Objects.requireNonNull(commandContext);
    Objects.requireNonNull(timer);
    final Command command = commandContext.getCommand();
    // determine last used gateway device id
    if (log.isTraceEnabled()) {
        log.trace("determine command target gateway/adapter for [{}]", command);
    }
    final Future<TenantObject> tenantObjectFuture = tenantClient.get(command.getTenant(), commandContext.getTracingContext());
    return tenantObjectFuture.compose(tenantObject -> {
        TenantTraceSamplingHelper.applyTraceSamplingPriority(tenantObject, null, commandContext.getTracingSpan());
        commandContext.put(CommandContext.KEY_TENANT_CONFIG, tenantObject);
        // check whether the handler messaging type is equal to the messaging type of the tenant (if set)
        final MessagingType tenantMessagingType = Optional.ofNullable(tenantObject.getProperty(TenantConstants.FIELD_EXT, JsonObject.class)).map(ext -> ext.getString(TenantConstants.FIELD_EXT_MESSAGING_TYPE)).map(MessagingType::valueOf).orElse(null);
        if (tenantMessagingType != null && getMessagingType() != tenantMessagingType) {
            log.info("command received via {} but tenant is configured to use {} [{}]", getMessagingType(), tenantMessagingType, commandContext.getCommand());
            commandContext.getTracingSpan().log(String.format("command received via %s but tenant is configured to use %s", getMessagingType(), tenantMessagingType));
        }
        return commandTargetMapper.getTargetGatewayAndAdapterInstance(command.getTenant(), command.getDeviceId(), commandContext.getTracingContext());
    }).recover(cause -> {
        final Throwable error;
        if (tenantObjectFuture.failed() && ServiceInvocationException.extractStatusCode(cause) == HttpURLConnection.HTTP_NOT_FOUND) {
            error = new TenantDisabledOrNotRegisteredException(command.getTenant(), HttpURLConnection.HTTP_NOT_FOUND);
        } else if (cause instanceof DeviceDisabledOrNotRegisteredException) {
            error = cause;
        } else if (ServiceInvocationException.extractStatusCode(cause) == HttpURLConnection.HTTP_NOT_FOUND) {
            log.debug("no target adapter instance found for command with device id " + command.getDeviceId(), cause);
            error = new NoConsumerException("no target adapter instance found");
        } else {
            log.debug("error getting target gateway and adapter instance for command with device id " + command.getDeviceId(), cause);
            error = new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "error getting target gateway and adapter instance", cause);
        }
        if (error instanceof ClientErrorException) {
            commandContext.reject(error);
        } else {
            commandContext.release(error);
        }
        reportCommandProcessingError(command, tenantObjectFuture.result(), error, timer);
        return Future.failedFuture(cause);
    }).compose(result -> {
        final String targetAdapterInstanceId = result.getString(DeviceConnectionConstants.FIELD_ADAPTER_INSTANCE_ID);
        final String targetDeviceId = result.getString(DeviceConnectionConstants.FIELD_PAYLOAD_DEVICE_ID);
        final String targetGatewayId = targetDeviceId.equals(command.getDeviceId()) ? null : targetDeviceId;
        if (Objects.isNull(targetGatewayId)) {
            log.trace("determined target adapter instance [{}] for [{}] (command not mapped to gateway)", targetAdapterInstanceId, command);
        } else {
            command.setGatewayId(targetGatewayId);
            log.trace("determined target gateway [{}] and adapter instance [{}] for [{}]", targetGatewayId, targetAdapterInstanceId, command);
            commandContext.getTracingSpan().log("determined target gateway [" + targetGatewayId + "]");
        }
        return sendCommand(commandContext, targetAdapterInstanceId, tenantObjectFuture.result(), timer);
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) InternalCommandSender(org.eclipse.hono.client.command.InternalCommandSender) Command(org.eclipse.hono.client.command.Command) TenantConstants(org.eclipse.hono.util.TenantConstants) Lifecycle(org.eclipse.hono.util.Lifecycle) LoggerFactory(org.slf4j.LoggerFactory) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) Tags(io.opentracing.tag.Tags) NoConsumerException(org.eclipse.hono.client.NoConsumerException) DeviceDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.DeviceDisabledOrNotRegisteredException) MessagingType(org.eclipse.hono.util.MessagingType) Timer(io.micrometer.core.instrument.Timer) JsonObject(io.vertx.core.json.JsonObject) TracingHelper(org.eclipse.hono.tracing.TracingHelper) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) DeviceConnectionConstants(org.eclipse.hono.util.DeviceConnectionConstants) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) ServerErrorException(org.eclipse.hono.client.ServerErrorException) CommandTargetMapper(org.eclipse.hono.commandrouter.CommandTargetMapper) TenantClient(org.eclipse.hono.client.registry.TenantClient) Future(io.vertx.core.Future) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) Objects(java.util.Objects) CommandRouterMetrics(org.eclipse.hono.commandrouter.CommandRouterMetrics) TenantTraceSamplingHelper(org.eclipse.hono.tracing.TenantTraceSamplingHelper) Optional(java.util.Optional) Span(io.opentracing.Span) TenantObject(org.eclipse.hono.util.TenantObject) DeviceDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.DeviceDisabledOrNotRegisteredException) Command(org.eclipse.hono.client.command.Command) NoConsumerException(org.eclipse.hono.client.NoConsumerException) MessagingType(org.eclipse.hono.util.MessagingType) ClientErrorException(org.eclipse.hono.client.ClientErrorException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 2 with NoConsumerException

use of org.eclipse.hono.client.NoConsumerException in project hono by eclipse.

the class ProtonBasedInternalCommandConsumer method handleCommandMessage.

void handleCommandMessage(final ProtonDelivery delivery, final Message msg) {
    final ProtonBasedCommand command;
    try {
        command = ProtonBasedCommand.fromRoutedCommandMessage(msg);
    } catch (final IllegalArgumentException e) {
        log.debug("address of command message is invalid: {}", msg.getAddress());
        final Rejected rejected = new Rejected();
        rejected.setError(new ErrorCondition(Constants.AMQP_BAD_REQUEST, "invalid command target address"));
        delivery.disposition(rejected, true);
        return;
    }
    final CommandHandlerWrapper commandHandler = commandHandlers.getCommandHandler(command.getTenant(), command.getGatewayOrDeviceId());
    if (commandHandler != null && commandHandler.getGatewayId() != null) {
        // Gateway information set in command handler means a gateway has subscribed for commands for a specific device.
        // This information isn't getting set in the message (by the Command Router) and therefore has to be adopted manually here.
        command.setGatewayId(commandHandler.getGatewayId());
    }
    final SpanContext spanContext = TracingHelper.extractSpanContext(tracer, msg);
    final SpanContext followsFromSpanContext = commandHandler != null ? commandHandler.getConsumerCreationSpanContext() : null;
    final Span currentSpan = CommandContext.createSpan(tracer, command, spanContext, followsFromSpanContext, getClass().getSimpleName());
    currentSpan.setTag(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
    final CommandContext commandContext = new ProtonBasedCommandContext(command, delivery, currentSpan);
    if (commandHandler != null) {
        log.trace("using [{}] for received command [{}]", commandHandler, command);
        // command.isValid() check not done here - it is to be done in the command handler
        commandHandler.handleCommand(commandContext);
    } else {
        log.info("no command handler found for command [{}]", command);
        commandContext.release(new NoConsumerException("no command handler found for command"));
    }
}
Also used : SpanContext(io.opentracing.SpanContext) CommandContext(org.eclipse.hono.client.command.CommandContext) CommandHandlerWrapper(org.eclipse.hono.client.command.CommandHandlerWrapper) 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)

Example 3 with NoConsumerException

use of org.eclipse.hono.client.NoConsumerException 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)

Example 4 with NoConsumerException

use of org.eclipse.hono.client.NoConsumerException in project hono by eclipse.

the class KafkaBasedInternalCommandConsumer method handleCommandMessage.

void handleCommandMessage(final KafkaConsumerRecord<String, Buffer> record) {
    // get partition/offset of the command record - related to the tenant-based topic the command was originally received in
    final Integer commandPartition = KafkaRecordHelper.getOriginalPartitionHeader(record.headers()).orElse(null);
    final Long commandOffset = KafkaRecordHelper.getOriginalOffsetHeader(record.headers()).orElse(null);
    if (commandPartition == null || commandOffset == null) {
        LOG.warn("command record is invalid - missing required original partition/offset headers");
        return;
    }
    final KafkaBasedCommand command;
    try {
        command = KafkaBasedCommand.fromRoutedCommandRecord(record);
    } catch (final IllegalArgumentException e) {
        LOG.warn("command record is invalid [tenant-id: {}, device-id: {}]", KafkaRecordHelper.getTenantId(record.headers()).orElse(null), KafkaRecordHelper.getDeviceId(record.headers()).orElse(null), e);
        return;
    }
    // check whether command has already been received and handled;
    // partition index and offset here are related to the *tenant-based* topic the command was originally received in
    // therefore they are stored in a map with the tenant as key
    final Map<Integer, Long> lastHandledPartitionOffsets = lastHandledPartitionOffsetsPerTenant.computeIfAbsent(command.getTenant(), k -> new HashMap<>());
    final Long lastHandledOffset = lastHandledPartitionOffsets.get(commandPartition);
    if (lastHandledOffset != null && commandOffset <= lastHandledOffset) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("ignoring command - record partition offset {} <= last handled offset {} [{}]", commandOffset, lastHandledOffset, command);
        }
    } else {
        lastHandledPartitionOffsets.put(commandPartition, commandOffset);
        final CommandHandlerWrapper commandHandler = commandHandlers.getCommandHandler(command.getTenant(), command.getGatewayOrDeviceId());
        if (commandHandler != null && commandHandler.getGatewayId() != null) {
            // Gateway information set in command handler means a gateway has subscribed for commands for a specific device.
            // This information isn't getting set in the record (by the Command Router) and therefore has to be adopted manually here.
            command.setGatewayId(commandHandler.getGatewayId());
        }
        final SpanContext spanContext = KafkaTracingHelper.extractSpanContext(tracer, record);
        final SpanContext followsFromSpanContext = commandHandler != null ? commandHandler.getConsumerCreationSpanContext() : null;
        final Span currentSpan = CommandContext.createSpan(tracer, command, spanContext, followsFromSpanContext, getClass().getSimpleName());
        currentSpan.setTag(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
        KafkaTracingHelper.TAG_OFFSET.set(currentSpan, record.offset());
        final var commandContext = new KafkaBasedCommandContext(command, commandResponseSender, currentSpan);
        tenantClient.get(command.getTenant(), spanContext).onFailure(t -> {
            if (ServiceInvocationException.extractStatusCode(t) == HttpURLConnection.HTTP_NOT_FOUND) {
                commandContext.reject(new TenantDisabledOrNotRegisteredException(command.getTenant(), HttpURLConnection.HTTP_NOT_FOUND));
            } else {
                commandContext.release(new ServerErrorException(command.getTenant(), HttpURLConnection.HTTP_UNAVAILABLE, "error retrieving tenant configuration", t));
            }
        }).onSuccess(tenantConfig -> {
            commandContext.put(CommandContext.KEY_TENANT_CONFIG, tenantConfig);
            if (commandHandler != null) {
                LOG.trace("using [{}] for received command [{}]", commandHandler, command);
                // command.isValid() check not done here - it is to be done in the command handler
                commandHandler.handleCommand(commandContext);
            } else {
                LOG.info("no command handler found for command [{}]", command);
                commandContext.release(new NoConsumerException("no command handler found for command"));
            }
        });
    }
}
Also used : HttpURLConnection(java.net.HttpURLConnection) MessagingKafkaConsumerConfigProperties(org.eclipse.hono.client.kafka.consumer.MessagingKafkaConsumerConfigProperties) LoggerFactory(org.slf4j.LoggerFactory) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) HashMap(java.util.HashMap) CommandHandlerWrapper(org.eclipse.hono.client.command.CommandHandlerWrapper) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) Supplier(java.util.function.Supplier) CommandResponseSender(org.eclipse.hono.client.command.CommandResponseSender) KafkaClientFactory(org.eclipse.hono.client.kafka.KafkaClientFactory) Context(io.vertx.core.Context) NoConsumerException(org.eclipse.hono.client.NoConsumerException) CompositeFuture(io.vertx.core.CompositeFuture) Status(io.vertx.ext.healthchecks.Status) HealthCheckHandler(io.vertx.ext.healthchecks.HealthCheckHandler) KafkaClientMetricsSupport(org.eclipse.hono.client.kafka.metrics.KafkaClientMetricsSupport) Map(java.util.Map) KafkaAdminClientConfigProperties(org.eclipse.hono.client.kafka.KafkaAdminClientConfigProperties) Admin(org.apache.kafka.clients.admin.Admin) CommandHandlers(org.eclipse.hono.client.command.CommandHandlers) KafkaTracingHelper(org.eclipse.hono.client.kafka.tracing.KafkaTracingHelper) CommonClientConfigs(org.apache.kafka.clients.CommonClientConfigs) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) Promise(io.vertx.core.Promise) NewTopic(org.apache.kafka.clients.admin.NewTopic) CommandContext(org.eclipse.hono.client.command.CommandContext) Vertx(io.vertx.core.Vertx) Set(java.util.Set) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ConsumerConfig(org.apache.kafka.clients.consumer.ConsumerConfig) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) TenantClient(org.eclipse.hono.client.registry.TenantClient) MessageHelper(org.eclipse.hono.util.MessageHelper) Future(io.vertx.core.Future) InternalCommandConsumer(org.eclipse.hono.client.command.InternalCommandConsumer) SpanContext(io.opentracing.SpanContext) TopicPartition(io.vertx.kafka.client.common.TopicPartition) Objects(java.util.Objects) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) List(java.util.List) TopicExistsException(org.apache.kafka.common.errors.TopicExistsException) Buffer(io.vertx.core.buffer.Buffer) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Optional(java.util.Optional) Span(io.opentracing.Span) KafkaConsumer(io.vertx.kafka.client.consumer.KafkaConsumer) SpanContext(io.opentracing.SpanContext) NoConsumerException(org.eclipse.hono.client.NoConsumerException) TenantDisabledOrNotRegisteredException(org.eclipse.hono.client.registry.TenantDisabledOrNotRegisteredException) Span(io.opentracing.Span) CommandHandlerWrapper(org.eclipse.hono.client.command.CommandHandlerWrapper) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Example 5 with NoConsumerException

use of org.eclipse.hono.client.NoConsumerException in project hono by eclipse.

the class GenericSenderLink method checkForCreditAndSend.

private Future<ProtonDelivery> checkForCreditAndSend(final Message message, final Span currentSpan, final Supplier<Future<ProtonDelivery>> sendOperation) {
    Objects.requireNonNull(message);
    Objects.requireNonNull(currentSpan);
    Objects.requireNonNull(sendOperation);
    Tags.MESSAGE_BUS_DESTINATION.set(currentSpan, getMessageAddress(message));
    TracingHelper.TAG_QOS.set(currentSpan, sender.getQoS().toString());
    Tags.SPAN_KIND.set(currentSpan, Tags.SPAN_KIND_PRODUCER);
    TracingHelper.setDeviceTags(currentSpan, tenantId, MessageHelper.getDeviceId(message));
    TracingHelper.injectSpanContext(connection.getTracer(), currentSpan.context(), message);
    return connection.executeOnContext(result -> {
        if (sender.sendQueueFull()) {
            final ServerErrorException e = new NoConsumerException("no credit available");
            logMessageSendingError("error sending message [ID: {}, address: {}], no credit available (drain={})", message.getMessageId(), getMessageAddress(message), sender.getDrain());
            TracingHelper.TAG_CREDIT.set(currentSpan, 0);
            logError(currentSpan, e);
            currentSpan.finish();
            result.fail(e);
            sampler.queueFull(tenantId);
        } else {
            sendOperation.get().onComplete(result);
        }
    });
}
Also used : NoConsumerException(org.eclipse.hono.client.NoConsumerException) ServerErrorException(org.eclipse.hono.client.ServerErrorException)

Aggregations

NoConsumerException (org.eclipse.hono.client.NoConsumerException)5 Span (io.opentracing.Span)4 CommandContext (org.eclipse.hono.client.command.CommandContext)4 SpanContext (io.opentracing.SpanContext)3 Future (io.vertx.core.Future)3 Objects (java.util.Objects)3 Optional (java.util.Optional)3 ServerErrorException (org.eclipse.hono.client.ServerErrorException)3 Logger (org.slf4j.Logger)3 LoggerFactory (org.slf4j.LoggerFactory)3 Tracer (io.opentracing.Tracer)2 HttpURLConnection (java.net.HttpURLConnection)2 Rejected (org.apache.qpid.proton.amqp.messaging.Rejected)2 ErrorCondition (org.apache.qpid.proton.amqp.transport.ErrorCondition)2 CommandHandlerWrapper (org.eclipse.hono.client.command.CommandHandlerWrapper)2 MessageHelper (org.eclipse.hono.util.MessageHelper)2 Timer (io.micrometer.core.instrument.Timer)1 Tags (io.opentracing.tag.Tags)1 CompositeFuture (io.vertx.core.CompositeFuture)1 Context (io.vertx.core.Context)1