use of org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome in project hono by eclipse.
the class AbstractVertxBasedHttpProtocolAdapter method doUploadMessage.
private void doUploadMessage(final HttpContext ctx, final String tenant, final String deviceId, final Buffer payload, final String contentType, final MetricsTags.EndpointType endpoint) {
if (!ctx.hasValidQoS()) {
HttpUtils.badRequest(ctx.getRoutingContext(), "unsupported QoS-Level header value");
return;
}
if (!isPayloadOfIndicatedType(payload, contentType)) {
HttpUtils.badRequest(ctx.getRoutingContext(), String.format("content type [%s] does not match payload", contentType));
return;
}
final MetricsTags.QoS qos = getQoSLevel(endpoint, ctx.getRequestedQos());
final Device authenticatedDevice = ctx.getAuthenticatedDevice();
final String gatewayId = authenticatedDevice != null && !deviceId.equals(authenticatedDevice.getDeviceId()) ? authenticatedDevice.getDeviceId() : null;
final Span currentSpan = TracingHelper.buildChildSpan(tracer, TracingHandler.serverSpanContext(ctx.getRoutingContext()), "upload " + endpoint.getCanonicalName(), getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenant).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), authenticatedDevice != null).withTag(TracingHelper.TAG_QOS, qos.name()).start();
final Promise<Void> responseReady = Promise.promise();
final Future<RegistrationAssertion> tokenTracker = getRegistrationAssertion(tenant, deviceId, authenticatedDevice, currentSpan.context());
final int payloadSize = Optional.ofNullable(payload).map(ok -> payload.length()).orElse(0);
final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, currentSpan.context());
final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(isAdapterEnabled(tenantObject), checkMessageLimit(tenantObject, payloadSize, currentSpan.context())).map(success -> 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 = getTimeUntilDisconnectFromRequest(ctx);
return 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, ctx.getRoutingContext(), responseReady, currentSpan));
commandConsumerTracker.compose(ok -> {
final Map<String, Object> props = getDownstreamMessageProperties(ctx);
Optional.ofNullable(commandConsumerTracker.result()).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
props.put(MessageHelper.APP_PROPERTY_QOS, ctx.getRequestedQos().ordinal());
customizeDownstreamMessageProperties(props, ctx);
setTtdRequestConnectionCloseHandler(ctx.getRoutingContext(), commandConsumerTracker.result(), tenant, deviceId, currentSpan);
if (EndpointType.EVENT.equals(endpoint)) {
ctx.getTimeToLive().ifPresent(ttl -> props.put(MessageHelper.SYS_HEADER_PROPERTY_TTL, ttl.toSeconds()));
return CompositeFuture.all(getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
} else {
// unsettled
return CompositeFuture.all(getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), ctx.getRequestedQos(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
}
}).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 -> {
if (ctx.response().closed()) {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
currentSpan.finish();
// close the response here, ensuring that the TracingHandler bodyEndHandler gets called
ctx.response().end();
} else {
final CommandContext commandContext = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
setResponsePayload(ctx.response(), commandContext, currentSpan);
ctx.getRoutingContext().addBodyEndHandler(ok -> {
log.trace("successfully processed [{}] message for device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId);
if (commandContext != null) {
commandContext.getTracingSpan().log("forwarded command to device in HTTP response body");
commandContext.accept();
metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
currentSpan.finish();
});
ctx.response().exceptionHandler(t -> {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "failed to forward command to device in HTTP response body", t);
commandContext.release(t);
metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.UNDELIVERABLE, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
currentSpan.log("failed to send HTTP response to device");
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
});
ctx.response().end();
}
return proceed;
}).recover(t -> {
log.debug("cannot process [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
final boolean responseClosedPrematurely = ctx.response().closed();
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 = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in HTTP response body, HTTP request handling failed", t);
commandContext.release(t);
currentSpan.log("released command for device");
}
final ProcessingOutcome outcome;
if (ClientErrorException.class.isInstance(t)) {
outcome = ProcessingOutcome.UNPROCESSABLE;
ctx.fail(t);
} else {
outcome = ProcessingOutcome.UNDELIVERABLE;
final String errorMessage = t instanceof ServerErrorException ? ((ServerErrorException) t).getClientFacingMessage() : null;
HttpUtils.serviceUnavailable(ctx.getRoutingContext(), 2, Strings.isNullOrEmpty(errorMessage) ? "temporarily unavailable" : errorMessage);
}
if (responseClosedPrematurely) {
log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
}
metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), outcome, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
TracingHelper.logError(currentSpan, t);
commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome 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);
}
}
Aggregations