use of org.eclipse.hono.client.command.Command 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 org.eclipse.hono.client.command.Command in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method handleRemoteSenderOpenForCommands.
/**
* This method is invoked when a device wants to open a link for receiving commands.
* <p>
* The link will be closed immediately if
* <ul>
* <li>the device does not specify a source address for its receive link or</li>
* <li>the source address cannot be parsed or does not point to the command endpoint or</li>
* <li>the AMQP adapter is disabled for the tenant that the device belongs to.</li>
* </ul>
*
* @param connection The AMQP connection to the device.
* @param sender The link to use for sending commands to the device.
*/
protected void handleRemoteSenderOpenForCommands(final ProtonConnection connection, final ProtonSender sender) {
final Device authenticatedDevice = getAuthenticatedDevice(connection);
final OptionalInt traceSamplingPriority = getTraceSamplingPriority(connection);
final Span span = newSpan("attach device command receiver link", authenticatedDevice, traceSamplingPriority);
getResourceIdentifier(sender.getRemoteSource()).compose(address -> validateAddress(address, authenticatedDevice)).compose(validAddress -> {
// validAddress ALWAYS contains the tenant and device ID
if (CommandConstants.isCommandEndpoint(validAddress.getEndpoint())) {
return openCommandSenderLink(connection, sender, validAddress, authenticatedDevice, span, traceSamplingPriority);
} else {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "no such node"));
}
}).map(consumer -> {
span.log("link established");
return consumer;
}).recover(t -> {
if (t instanceof ServiceInvocationException) {
closeLinkWithError(sender, t, span);
} else {
closeLinkWithError(sender, new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "Invalid source address"), span);
}
return Future.failedFuture(t);
}).onComplete(s -> {
span.finish();
});
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class AbstractVertxBasedHttpProtocolAdapter 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 ctx The device's currently executing HTTP request.
* @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> 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 RoutingContext ctx, final Handler<AsyncResult<Void>> responseReady, final Span uploadMessageSpan) {
Objects.requireNonNull(tenantObject);
Objects.requireNonNull(deviceId);
Objects.requireNonNull(ctx);
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(tracer, uploadMessageSpan.context(), "create consumer and wait for command", 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(tracer, waitForCommandSpan.context(), "process received command").withTag(Tags.COMPONENT.getKey(), 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(), getTypeName());
commandContext.logCommandToSpan(processCommandSpan);
final Command command = commandContext.getCommand();
final Sample commandSample = getMetrics().startTimer();
if (isCommandValid(command, processCommandSpan)) {
if (requestProcessed.compareAndSet(false, true)) {
waitForCommandSpan.finish();
checkMessageLimit(tenantObject, command.getPayloadSize(), processCommandSpan.context()).onComplete(result -> {
if (result.succeeded()) {
addMicrometerSample(commandContext, commandSample);
// put command context to routing context and notify
ctx.put(CommandContext.KEY_COMMAND_CONTEXT, commandContext);
} else {
commandContext.reject(result.cause());
TracingHelper.logError(processCommandSpan, "rejected command for device", result.cause());
metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantObject.getTenantId(), tenantObject, ProcessingOutcome.from(result.cause()), command.getPayloadSize(), commandSample);
}
cancelCommandReceptionTimer(ctx);
setTtdStatus(ctx, 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);
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 {
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 = getCommandConsumerFactory().createCommandConsumer(tenantObject.getTenantId(), deviceId, gatewayId, commandHandler, Duration.ofSeconds(ttdSecs), waitForCommandSpan.context());
} else {
commandConsumerFuture = 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(ctx, requestProcessed, responseReady, ttdSecs, waitForCommandSpan);
}
// 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(tracer, waitForCommandSpan.context(), "close consumer").withTag(Tags.COMPONENT.getKey(), 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 org.eclipse.hono.client.command.Command in project hono by eclipse.
the class AbstractVertxBasedHttpProtocolAdapter method setNonEmptyResponsePayload.
/**
* Response to a request with a non-empty command response.
* <p>
* The default implementation sets the command headers and the status to {@link HttpURLConnection#HTTP_OK}.
*
* @param response The response to update.
* @param commandContext The command context, will not be {@code null}.
* @param currentSpan The current tracing span.
*/
protected void setNonEmptyResponsePayload(final HttpServerResponse response, final CommandContext commandContext, final Span currentSpan) {
final Command command = commandContext.getCommand();
response.putHeader(Constants.HEADER_COMMAND, command.getName());
currentSpan.setTag(Constants.HEADER_COMMAND, command.getName());
log.debug("adding command [name: {}, request-id: {}] to response for device [tenant-id: {}, device-id: {}]", command.getName(), command.getRequestId(), command.getTenant(), command.getGatewayOrDeviceId());
if (!command.isOneWay()) {
response.putHeader(Constants.HEADER_COMMAND_REQUEST_ID, command.getRequestId());
currentSpan.setTag(Constants.HEADER_COMMAND_REQUEST_ID, command.getRequestId());
}
if (command.isTargetedAtGateway()) {
response.putHeader(Constants.HEADER_COMMAND_TARGET_DEVICE, command.getDeviceId());
currentSpan.setTag(Constants.HEADER_COMMAND_TARGET_DEVICE, command.getDeviceId());
}
response.setStatusCode(HttpURLConnection.HTTP_OK);
HttpUtils.setResponseBody(response, command.getPayload(), command.getContentType());
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class LoraProtocolAdapter method sendCommandToGateway.
private Future<Void> sendCommandToGateway(final CommandContext commandContext, final LoraProvider loraProvider, final CommandEndpoint commandEndpoint) {
if (commandEndpoint == null) {
return Future.failedFuture("gateway has no command endpoint defined");
} else if (!commandEndpoint.isUriValid()) {
return Future.failedFuture(String.format("gateway has command endpoint with invalid uri [%s]", commandEndpoint.getUri()));
}
final Command command = commandContext.getCommand();
final Promise<Void> sendPromise = Promise.promise();
final Buffer payload = Optional.ofNullable(command.getPayload()).orElseGet(Buffer::buffer);
final String subject = command.getName();
final LoraCommand loraCommand = loraProvider.getCommand(commandEndpoint, command.getDeviceId(), payload, subject);
commandContext.getTracingSpan().log(String.format("sending loraCommand to LNS [%s]", loraCommand.getUri()));
LOG.debug("sending loraCommand to LNS [{}]", loraCommand.getUri());
if (LOG.isTraceEnabled()) {
LOG.trace("command payload:{}{}", System.lineSeparator(), loraCommand.getPayload().encodePrettily());
}
final HttpRequest<Buffer> request = webClient.postAbs(loraCommand.getUri());
commandEndpoint.getHeaders().forEach(request::putHeader);
loraProvider.getDefaultHeaders().forEach(request::putHeader);
request.sendJson(loraCommand.getPayload()).onFailure(sendPromise::tryFail).onSuccess(httpClientResponse -> {
Tags.HTTP_STATUS.set(commandContext.getTracingSpan(), httpClientResponse.statusCode());
if (StatusCodeMapper.isSuccessful(httpClientResponse.statusCode())) {
sendPromise.tryComplete();
} else {
sendPromise.tryFail(httpClientResponse.statusMessage());
}
});
return sendPromise.future();
}
Aggregations