use of io.opentracing.SpanContext in project hono by eclipse.
the class DelegatingCommandRouterAmqpEndpoint method processSetLastKnownGatewayRequest.
/**
* Processes a <em>set last known gateway</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> processSetLastKnownGatewayRequest(final Message request, final ResourceIdentifier targetAddress, final SpanContext spanContext) {
final String tenantId = targetAddress.getTenantId();
final String deviceIdAppProperty = MessageHelper.getDeviceId(request);
final String gatewayIdAppProperty = MessageHelper.getGatewayId(request);
final Span span = TracingHelper.buildServerChildSpan(tracer, spanContext, SPAN_NAME_SET_LAST_GATEWAY, getClass().getSimpleName()).start();
final Future<CommandRouterResult> resultFuture;
if (tenantId == null) {
TracingHelper.logError(span, "missing tenant");
resultFuture = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "missing tenant"));
} else {
if (deviceIdAppProperty != null && gatewayIdAppProperty != null) {
logger.debug("setting last known gateway for tenant [{}], device [{}] to {}", tenantId, deviceIdAppProperty, gatewayIdAppProperty);
TracingHelper.TAG_TENANT_ID.set(span, tenantId);
TracingHelper.TAG_DEVICE_ID.set(span, deviceIdAppProperty);
TracingHelper.TAG_GATEWAY_ID.set(span, gatewayIdAppProperty);
if (MessageHelper.getPayloadSize(request) != 0) {
logger.debug("ignoring payload in last known gateway request containing device/gateway properties");
}
resultFuture = getService().setLastKnownGatewayForDevice(tenantId, deviceIdAppProperty, gatewayIdAppProperty, span);
} else if (MessageHelper.getPayloadSize(request) != 0) {
TracingHelper.TAG_TENANT_ID.set(span, tenantId);
final Buffer payload = MessageHelper.getPayload(request);
resultFuture = parseSetLastKnownGatewayJson(payload).compose(deviceToGatewayMap -> {
logger.debug("setting {} last known gateway entries for tenant [{}]", deviceToGatewayMap.size(), tenantId);
span.log(Map.of("no_of_entries", deviceToGatewayMap.size()));
return getService().setLastKnownGatewayForDevice(tenantId, deviceToGatewayMap, span);
});
} else {
final String error = "either device_id and gateway_id application properties or alternatively a JSON payload must be set";
TracingHelper.logError(span, error);
resultFuture = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, error));
}
}
return finishSpanOnFutureCompletion(span, resultFuture.map(res -> CommandRouterConstants.getAmqpReply(CommandRouterConstants.COMMAND_ROUTER_ENDPOINT, tenantId, request, res)));
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class TracingHandler method handlerNormal.
/**
* Handles an HTTP request.
*
* @param routingContext The routing context for the request.
*/
protected void handlerNormal(final RoutingContext routingContext) {
// reroute
final Object object = routingContext.get(CURRENT_SPAN);
if (object instanceof Span) {
final Span span = (Span) object;
decorators.forEach(spanDecorator -> spanDecorator.onReroute(routingContext.request(), span));
// TODO in 3.3.3 it was sufficient to add this when creating the span
routingContext.addBodyEndHandler(finishEndHandler(routingContext, span));
routingContext.next();
return;
}
final SpanContext extractedContext = TracingHelper.extractSpanContext(tracer, routingContext.request().headers());
final Span span = tracer.buildSpan(routingContext.request().method().toString()).asChildOf(extractedContext).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER).start();
decorators.forEach(spanDecorator -> spanDecorator.onRequest(routingContext.request(), span));
routingContext.put(CURRENT_SPAN, span);
// TODO it's not guaranteed that body end handler is always called
// https://github.com/vert-x3/vertx-web/issues/662
routingContext.addBodyEndHandler(finishEndHandler(routingContext, span));
routingContext.next();
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class AbstractHonoResource method createCommandConsumer.
/**
* Creates a consumer for command messages to be sent to a device.
*
* @param ttdSecs The number of seconds the device waits for a command.
* @param tenantObject The tenant configuration object.
* @param deviceId The identifier of the device.
* @param gatewayId The identifier of the gateway that is acting on behalf of the device or {@code null} otherwise.
* @param context The device's currently executing CoAP request context.
* @param responseReady A future to complete once one of the following conditions are met:
* <ul>
* <li>the request did not include a <em>hono-ttd</em> query-parameter or</li>
* <li>a command has been received and the response ready future has not yet been completed or</li>
* <li>the ttd has expired</li>
* </ul>
* @param uploadMessageSpan The OpenTracing Span used for tracking the processing of the request.
* @return A future indicating the outcome of the operation.
* <p>
* The future will be completed with the created message consumer or {@code null}, if the response can be
* sent back to the device without waiting for a command.
* <p>
* The future will be failed with a {@code ServiceInvocationException} if the message consumer could not be
* created.
* @throws NullPointerException if any of the parameters other than TTD or gatewayId is {@code null}.
*/
protected final Future<CommandConsumer> createCommandConsumer(final Integer ttdSecs, final TenantObject tenantObject, final String deviceId, final String gatewayId, final CoapContext context, final Handler<AsyncResult<Void>> responseReady, final Span uploadMessageSpan) {
Objects.requireNonNull(tenantObject);
Objects.requireNonNull(deviceId);
Objects.requireNonNull(context);
Objects.requireNonNull(responseReady);
Objects.requireNonNull(uploadMessageSpan);
if (ttdSecs == null || ttdSecs <= 0) {
// no need to wait for a command
responseReady.handle(Future.succeededFuture());
return Future.succeededFuture();
}
final AtomicBoolean requestProcessed = new AtomicBoolean(false);
uploadMessageSpan.setTag(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttdSecs);
final Span waitForCommandSpan = TracingHelper.buildChildSpan(getTracer(), uploadMessageSpan.context(), "create consumer and wait for command", getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).start();
final Handler<CommandContext> commandHandler = commandContext -> {
final Span processCommandSpan = TracingHelper.buildFollowsFromSpan(getTracer(), waitForCommandSpan.context(), "process received command").withTag(Tags.COMPONENT.getKey(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).addReference(References.FOLLOWS_FROM, commandContext.getTracingContext()).start();
Tags.COMPONENT.set(commandContext.getTracingSpan(), getAdapter().getTypeName());
commandContext.logCommandToSpan(processCommandSpan);
final Command command = commandContext.getCommand();
final Sample commandSample = getAdapter().getMetrics().startTimer();
if (isCommandValid(command, processCommandSpan)) {
if (requestProcessed.compareAndSet(false, true)) {
waitForCommandSpan.finish();
getAdapter().checkMessageLimit(tenantObject, command.getPayloadSize(), processCommandSpan.context()).onComplete(result -> {
if (result.succeeded()) {
addMicrometerSample(commandContext, commandSample);
// put command context to routing context and notify
context.put(CommandContext.KEY_COMMAND_CONTEXT, commandContext);
} else {
commandContext.reject(result.cause());
TracingHelper.logError(processCommandSpan, "rejected command for device", result.cause());
getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.from(result.cause()), command.getPayloadSize(), commandSample);
}
cancelCommandReceptionTimer(context);
setTtdStatus(context, TtdStatus.COMMAND);
responseReady.handle(Future.succeededFuture());
processCommandSpan.finish();
});
} else {
final String errorMsg = "waiting time for command has elapsed or another command has already been processed";
LOG.debug("{} [tenantId: {}, deviceId: {}]", errorMsg, tenantObject.getTenantId(), deviceId);
getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.UNDELIVERABLE, command.getPayloadSize(), commandSample);
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, errorMsg));
TracingHelper.logError(processCommandSpan, errorMsg);
processCommandSpan.finish();
}
} else {
getAdapter().getMetrics().reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.UNPROCESSABLE, command.getPayloadSize(), commandSample);
LOG.debug("command message is invalid: {}", command);
commandContext.reject("malformed command message");
TracingHelper.logError(processCommandSpan, "malformed command message");
processCommandSpan.finish();
}
};
final Future<CommandConsumer> commandConsumerFuture;
if (gatewayId != null) {
// gateway scenario
commandConsumerFuture = getAdapter().getCommandConsumerFactory().createCommandConsumer(tenantObject.getTenantId(), deviceId, gatewayId, commandHandler, Duration.ofSeconds(ttdSecs), waitForCommandSpan.context());
} else {
commandConsumerFuture = getAdapter().getCommandConsumerFactory().createCommandConsumer(tenantObject.getTenantId(), deviceId, commandHandler, Duration.ofSeconds(ttdSecs), waitForCommandSpan.context());
}
return commandConsumerFuture.onFailure(thr -> {
TracingHelper.logError(waitForCommandSpan, thr);
waitForCommandSpan.finish();
}).map(consumer -> {
if (!requestProcessed.get()) {
// if the request was not responded already, add a timer for triggering an empty response
addCommandReceptionTimer(context, requestProcessed, responseReady, ttdSecs, waitForCommandSpan);
context.startAcceptTimer(vertx, tenantObject, getAdapter().getConfig().getTimeoutToAck());
}
// for unregistering the command consumer (which is something the parent request span doesn't wait for)
return new CommandConsumer() {
@Override
public Future<Void> close(final SpanContext ignored) {
final Span closeConsumerSpan = TracingHelper.buildFollowsFromSpan(getTracer(), waitForCommandSpan.context(), "close consumer").withTag(Tags.COMPONENT.getKey(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).start();
return consumer.close(closeConsumerSpan.context()).onFailure(thr -> TracingHelper.logError(closeConsumerSpan, thr)).onComplete(ar -> closeConsumerSpan.finish());
}
};
});
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class TracingSupportingHonoResource method handleRequest.
/**
* {@inheritDoc}
* <p>
* This implementation handles POST and PUT requests only.
* All other request codes result in a 4.05 response code.
* <p>
* For each request, a new OpenTracing {@code Span} is created. The {@code Span} context is
* associated with the {@link CoapContext} that is created by the <em>createCoapContextForXXX</em>
* method matching the request code. The {@link CoapContext} is then passed in to the corresponding
* <em>handleXXXX</em> method.
* <p>
* If the request contains the {@link CoapOptionInjectExtractAdapter#OPTION_TRACE_CONTEXT} option, its value
* is expected to be a binary encoded trace context and the {@link CoapOptionInjectExtractAdapter}
* is used to extract a {@code SpanContext} which is then used as the parent of the newly created
* {@code Span}.
*/
@Override
public void handleRequest(final Exchange exchange) {
final Span currentSpan = newSpan(exchange);
final AtomicReference<ResponseCode> responseCode = new AtomicReference<>(null);
final CoapExchange coapExchange = new CoapExchange(exchange, this) {
/**
* {@inheritDoc}
*/
@Override
public void respond(final Response response) {
super.respond(response);
responseCode.set(response.getCode());
}
};
final Future<Void> result;
switch(exchange.getRequest().getCode()) {
case POST:
result = createCoapContextForPost(coapExchange, currentSpan).compose(coapContext -> applyTraceSamplingPriority(coapContext, currentSpan)).compose(this::handlePostRequest);
break;
case PUT:
result = createCoapContextForPut(coapExchange, currentSpan).compose(coapContext -> applyTraceSamplingPriority(coapContext, currentSpan)).compose(this::handlePutRequest);
break;
default:
result = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_METHOD));
break;
}
result.map(ok -> {
if (responseCode.get() == null) {
throw new ServerErrorException(HttpURLConnection.HTTP_INTERNAL_ERROR, "no CoAP response sent");
} else {
return responseCode.get();
}
}).otherwise(t -> {
LOG.debug("error handling request", t);
TracingHelper.logError(currentSpan, t);
final Response response = CoapErrorResponse.newResponse(t, ResponseCode.INTERNAL_SERVER_ERROR);
coapExchange.respond(response);
return response.getCode();
}).onSuccess(code -> {
LOG.debug("finished processing of request [response code: {}]", code);
CoapConstants.TAG_COAP_RESPONSE_CODE.set(currentSpan, code.toString());
}).onComplete(r -> {
currentSpan.finish();
});
}
use of io.opentracing.SpanContext in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapterTest method testUploadCommandResponseSucceeds.
/**
* Verify that the AMQP adapter forwards command responses downstream.
*
* @param ctx The vert.x test context.
*/
@Test
public void testUploadCommandResponseSucceeds(final VertxTestContext ctx) {
// GIVEN an AMQP adapter
givenAnAdapter(properties);
final CommandResponseSender responseSender = givenACommandResponseSenderForAnyTenant();
when(responseSender.sendCommandResponse(any(TenantObject.class), any(RegistrationAssertion.class), any(CommandResponse.class), (SpanContext) any())).thenReturn(Future.succeededFuture());
// which is enabled for the test tenant
final TenantObject tenantObject = givenAConfiguredTenant(TEST_TENANT_ID, true);
// WHEN an unauthenticated device publishes a command response
final String replyToAddress = String.format("%s/%s/%s", getCommandResponseEndpoint(), TEST_TENANT_ID, Commands.getDeviceFacingReplyToId("test-reply-id", TEST_DEVICE, MessagingType.amqp));
final Map<String, Object> propertyMap = new HashMap<>();
propertyMap.put(MessageHelper.APP_PROPERTY_STATUS, 200);
final ApplicationProperties props = new ApplicationProperties(propertyMap);
final Buffer payload = Buffer.buffer("some payload");
final Message message = getFakeMessage(replyToAddress, payload);
message.setCorrelationId("correlation-id");
message.setApplicationProperties(props);
final ProtonDelivery delivery = mock(ProtonDelivery.class);
adapter.onMessageReceived(AmqpContext.fromMessage(delivery, message, span, null)).onComplete(ctx.succeeding(ok -> {
ctx.verify(() -> {
// THEN the adapter forwards the command response message downstream
verify(responseSender).sendCommandResponse(eq(tenantObject), any(RegistrationAssertion.class), any(CommandResponse.class), (SpanContext) any());
// and reports the forwarded message
verify(metrics).reportCommand(eq(Direction.RESPONSE), eq(TEST_TENANT_ID), eq(tenantObject), eq(ProcessingOutcome.FORWARDED), eq(payload.length()), any());
});
ctx.completeNow();
}));
}
Aggregations