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);
});
}
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"));
}
}
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);
});
}
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"));
}
});
}
}
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);
}
});
}
Aggregations