use of io.opentracing.SpanContext in project hono by eclipse.
the class ProtonBasedCommandRouterClient method setLastKnownGateways.
/**
* For a given list of device and gateway combinations, sets the gateway as the last gateway that acted on behalf
* of the device.
*
* @param tenantId The tenant id.
* @param deviceToGatewayMap The map associating device identifiers with the corresponding last gateway.
* @param context The currently active OpenTracing span context or {@code null} if no span is currently active.
* An implementation should use this as the parent for any span it creates for tracing
* the execution of this operation.
* @return A future indicating the outcome of the operation.
* <p>
* The future will be succeeded if the entries were successfully set.
* Otherwise the future will be failed with a {@code org.eclipse.hono.client.ServiceInvocationException}.
* The outcome is indeterminate if any of the entries cannot be processed by the Command Router service
* implementation. In such a case, client code should assume that none of the entries have been updated.
*/
protected Future<Void> setLastKnownGateways(final String tenantId, final Map<String, String> deviceToGatewayMap, final SpanContext context) {
final Span currentSpan;
final Future<RequestResponseResult<JsonObject>> resultTracker;
if (deviceToGatewayMap.size() == 1) {
// use single entry operation so that traces with device and gateway are created
final Map.Entry<String, String> entry = deviceToGatewayMap.entrySet().iterator().next();
final String deviceId = entry.getKey();
final String gatewayId = entry.getValue();
final Map<String, Object> properties = createDeviceIdProperties(deviceId);
properties.put(MessageHelper.APP_PROPERTY_GATEWAY_ID, gatewayId);
currentSpan = newChildSpan(context, "set last known gateway for device");
TracingHelper.setDeviceTags(currentSpan, tenantId, deviceId);
currentSpan.setTag(MessageHelper.APP_PROPERTY_GATEWAY_ID, gatewayId);
resultTracker = getOrCreateClient(tenantId).compose(client -> client.createAndSendRequest(CommandRouterConstants.CommandRouterAction.SET_LAST_KNOWN_GATEWAY.getSubject(), properties, null, null, this::getRequestResponseResult, currentSpan));
} else {
currentSpan = newChildSpan(context, "set last known gateways for tenant devices");
TracingHelper.setDeviceTags(currentSpan, tenantId, null);
currentSpan.log(Map.of("no_of_entries", deviceToGatewayMap.size()));
final JsonObject payload = new JsonObject();
deviceToGatewayMap.forEach(payload::put);
resultTracker = getOrCreateClient(tenantId).compose(client -> client.createAndSendRequest(CommandRouterConstants.CommandRouterAction.SET_LAST_KNOWN_GATEWAY.getSubject(), null, payload.toBuffer(), MessageHelper.CONTENT_TYPE_APPLICATION_JSON, this::getRequestResponseResult, currentSpan));
}
return mapResultAndFinishSpan(resultTracker, result -> {
switch(result.getStatus()) {
case HttpURLConnection.HTTP_NO_CONTENT:
return null;
default:
throw StatusCodeMapper.from(result);
}
}, currentSpan).onFailure(thr -> log.debug("failed to set last known gateway(s) for tenant [{}]", tenantId, thr)).mapEmpty();
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class ProtonBasedCommandRouterClient method registerCommandConsumer.
@Override
public Future<Void> registerCommandConsumer(final String tenantId, final String deviceId, final String adapterInstanceId, final Duration lifespan, final SpanContext context) {
Objects.requireNonNull(tenantId);
Objects.requireNonNull(deviceId);
Objects.requireNonNull(adapterInstanceId);
final int lifespanSeconds = lifespan != null && lifespan.getSeconds() <= Integer.MAX_VALUE ? (int) lifespan.getSeconds() : -1;
final Map<String, Object> properties = createDeviceIdProperties(deviceId);
properties.put(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
properties.put(MessageHelper.APP_PROPERTY_LIFESPAN, lifespanSeconds);
final Span currentSpan = newChildSpan(context, "register command consumer");
TracingHelper.setDeviceTags(currentSpan, tenantId, deviceId);
currentSpan.setTag(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
currentSpan.setTag(MessageHelper.APP_PROPERTY_LIFESPAN, lifespanSeconds);
final Future<RequestResponseResult<JsonObject>> resultTracker = getOrCreateClient(tenantId).compose(client -> client.createAndSendRequest(CommandRouterConstants.CommandRouterAction.REGISTER_COMMAND_CONSUMER.getSubject(), properties, null, null, this::getRequestResponseResult, currentSpan));
return mapResultAndFinishSpan(resultTracker, result -> {
switch(result.getStatus()) {
case HttpURLConnection.HTTP_NO_CONTENT:
return null;
default:
throw StatusCodeMapper.from(result);
}
}, currentSpan).mapEmpty();
}
use of io.opentracing.SpanContext 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 io.opentracing.SpanContext in project hono by eclipse.
the class KafkaBasedCommandSender method sendCommand.
/**
* {@inheritDoc}
*
* <p>
* The replyId is not used in the Kafka based implementation. It can be set to {@code null}.
* If set it will be ignored.
* <p>
* If the timeout duration is {@code null} then the default timeout value of
* {@value DEFAULT_COMMAND_TIMEOUT_IN_MS} ms is used.
*/
@Override
public Future<DownstreamMessage<KafkaMessageContext>> sendCommand(final String tenantId, final String deviceId, final String command, final String contentType, final Buffer data, final String replyId, final Map<String, Object> properties, final Duration timeout, final SpanContext context) {
Objects.requireNonNull(tenantId);
Objects.requireNonNull(deviceId);
Objects.requireNonNull(command);
final long timeoutInMs = Optional.ofNullable(timeout).map(t -> {
if (t.isNegative()) {
throw new IllegalArgumentException("command timeout duration must be >= 0");
}
return t.toMillis();
}).orElse(DEFAULT_COMMAND_TIMEOUT_IN_MS);
final String correlationId = correlationIdSupplier.get();
final Span span = TracingHelper.buildChildSpan(tracer, context, "send command and receive response", getClass().getSimpleName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantId).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_CORRELATION_ID, correlationId).start();
final ExpiringCommandPromise expiringCommandPromise = new ExpiringCommandPromise(correlationId, timeoutInMs, // Remove the corresponding pending response entry if times out
x -> removePendingCommandResponse(tenantId, correlationId), span);
subscribeForCommandResponse(tenantId, span).compose(ok -> {
// Store the correlation id and the expiring command promise
pendingCommandResponses.computeIfAbsent(tenantId, k -> new ConcurrentHashMap<>()).put(correlationId, expiringCommandPromise);
return sendCommand(tenantId, deviceId, command, contentType, data, correlationId, properties, true, "send command", span.context()).onSuccess(sent -> {
LOGGER.debug("sent command [correlation-id: {}], waiting for response", correlationId);
span.log("sent command, waiting for response");
}).onFailure(error -> {
LOGGER.debug("error sending command", error);
// To ensure that the span is not already finished.
if (!expiringCommandPromise.future().isComplete()) {
TracingHelper.logError(span, "error sending command", error);
}
removePendingCommandResponse(tenantId, correlationId);
expiringCommandPromise.tryCompleteAndCancelTimer(Future.failedFuture(error));
});
});
return expiringCommandPromise.future().onComplete(o -> span.finish());
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class DelegatingCommandRouterAmqpEndpoint method processRegisterCommandConsumer.
/**
* Processes a <em>register command consumer</em> request message.
*
* @param request The request message.
* @param targetAddress The address the message is sent to.
* @param spanContext The span context representing the request to be processed.
* @return The response to send to the client via the event bus.
*/
protected Future<Message> processRegisterCommandConsumer(final Message request, final ResourceIdentifier targetAddress, final SpanContext spanContext) {
final String tenantId = targetAddress.getTenantId();
final String deviceId = MessageHelper.getDeviceId(request);
final String adapterInstanceId = MessageHelper.getApplicationProperty(request.getApplicationProperties(), MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, String.class);
final Integer lifespanSecondsOrNull = MessageHelper.getApplicationProperty(request.getApplicationProperties(), MessageHelper.APP_PROPERTY_LIFESPAN, Integer.class);
final Span span = TracingHelper.buildServerChildSpan(tracer, spanContext, SPAN_NAME_REGISTER_COMMAND_CONSUMER, getClass().getSimpleName()).start();
final Future<Message> resultFuture;
if (tenantId == null || deviceId == null || adapterInstanceId == null) {
TracingHelper.logError(span, "missing tenant, device and/or adapter instance id");
resultFuture = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST));
} else {
final Duration lifespan = lifespanSecondsOrNull != null ? Duration.ofSeconds(lifespanSecondsOrNull) : Duration.ofSeconds(-1);
TracingHelper.TAG_TENANT_ID.set(span, tenantId);
TracingHelper.TAG_DEVICE_ID.set(span, deviceId);
span.setTag(MessageHelper.APP_PROPERTY_ADAPTER_INSTANCE_ID, adapterInstanceId);
span.setTag(MessageHelper.APP_PROPERTY_LIFESPAN, lifespan.getSeconds());
logger.debug("register command consumer [tenant-id: {}, device-id: {}, adapter-instance-id {}, lifespan: {}s]", tenantId, deviceId, adapterInstanceId, lifespan.getSeconds());
resultFuture = getService().registerCommandConsumer(tenantId, deviceId, adapterInstanceId, lifespan, span).map(res -> CommandRouterConstants.getAmqpReply(CommandRouterConstants.COMMAND_ROUTER_ENDPOINT, tenantId, request, res));
}
return finishSpanOnFutureCompletion(span, resultFuture);
}
Aggregations