use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method openCommandSenderLink.
private Future<CommandConsumer> openCommandSenderLink(final ProtonConnection connection, final ProtonSender sender, final ResourceIdentifier address, final Device authenticatedDevice, final Span span, final OptionalInt traceSamplingPriority) {
return createCommandConsumer(sender, address, authenticatedDevice, span).map(consumer -> {
final String tenantId = address.getTenantId();
final String deviceId = address.getResourceId();
sender.setSource(sender.getRemoteSource());
sender.setTarget(sender.getRemoteTarget());
sender.setQoS(ProtonQoS.AT_LEAST_ONCE);
final Handler<AsyncResult<ProtonSender>> detachHandler = link -> {
final Span detachHandlerSpan = newSpan("detach device command receiver link", authenticatedDevice, traceSamplingPriority);
removeCommandSubscription(connection, address.toString());
onLinkDetach(sender);
closeCommandConsumer(consumer, address, authenticatedDevice, true, detachHandlerSpan).onComplete(v -> detachHandlerSpan.finish());
};
HonoProtonHelper.setCloseHandler(sender, detachHandler);
HonoProtonHelper.setDetachHandler(sender, detachHandler);
sender.open();
// At this point, the remote peer's receiver link is successfully opened and is ready to receive
// commands. Send "device ready for command" notification downstream.
log.debug("established link [address: {}] for sending commands to device", address);
sendConnectedTtdEvent(tenantId, deviceId, authenticatedDevice, span.context());
registerCommandSubscription(connection, new CommandSubscription(consumer, address));
return consumer;
}).recover(t -> Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "cannot create command consumer")));
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method handleRemoteReceiverOpen.
/**
* This method is invoked when a device wants to open a link for uploading messages.
* <p>
* The same link is used by the device to upload telemetry data, events and command
* responses to be forwarded downstream.
* <p>
* If the attach frame contains a target address, this method simply closes the link,
* otherwise, it accepts and opens the link.
*
* @param conn The connection through which the request is initiated.
* @param receiver The receiver link for receiving the data.
*/
protected void handleRemoteReceiverOpen(final ProtonConnection conn, final ProtonReceiver receiver) {
final Device authenticatedDevice = getAuthenticatedDevice(conn);
final OptionalInt traceSamplingPriority = getTraceSamplingPriority(conn);
final Span span = newSpan("attach device sender link", authenticatedDevice, traceSamplingPriority);
span.log(Map.of("snd-settle-mode", receiver.getRemoteQoS()));
final String remoteTargetAddress = Optional.ofNullable(receiver.getRemoteTarget()).map(Target::getAddress).orElse(null);
if (!Strings.isNullOrEmpty(remoteTargetAddress)) {
log.debug("client provided target address [{}] in open frame, closing link [container: {}, {}]", remoteTargetAddress, conn.getRemoteContainer(), authenticatedDevice);
span.log(Map.of("target address", remoteTargetAddress));
final Exception ex = new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "container supports anonymous terminus only");
closeLinkWithError(receiver, ex, span);
} else {
receiver.setTarget(receiver.getRemoteTarget());
receiver.setSource(receiver.getRemoteSource());
receiver.setQoS(receiver.getRemoteQoS());
receiver.setPrefetch(30);
// manage disposition handling manually
receiver.setAutoAccept(false);
receiver.maxMessageSizeExceededHandler(recv -> {
final Span errorSpan = newSpan("upload message", authenticatedDevice, traceSamplingPriority);
log.debug("incoming message size exceeds configured maximum of {} bytes; link will be detached [container: {}, {}]", getConfig().getMaxPayloadSize(), conn.getRemoteContainer(), authenticatedDevice);
TracingHelper.logError(errorSpan, String.format("incoming message size exceeds configured maximum of %s bytes", getConfig().getMaxPayloadSize()));
errorSpan.log("device sender link will be detached");
errorSpan.finish();
});
HonoProtonHelper.setCloseHandler(receiver, remoteDetach -> onLinkDetach(receiver));
HonoProtonHelper.setDetachHandler(receiver, remoteDetach -> onLinkDetach(receiver));
receiver.handler((delivery, message) -> {
try {
final SpanContext spanContext = TracingHelper.extractSpanContext(tracer, message);
final Span msgSpan = newSpan("upload message", authenticatedDevice, traceSamplingPriority, spanContext);
HonoProtonHelper.onReceivedMessageDeliveryUpdatedFromRemote(delivery, d -> {
log.debug("got unexpected disposition update for message received from device [remote state: {}, container: {}, {}]", delivery.getRemoteState(), conn.getRemoteContainer(), authenticatedDevice);
msgSpan.log("got unexpected disposition from device [remote state: " + delivery.getRemoteState() + "]");
});
msgSpan.log(Map.of(Tags.MESSAGE_BUS_DESTINATION.getKey(), message.getAddress(), "settled", delivery.remotelySettled()));
final AmqpContext ctx = AmqpContext.fromMessage(delivery, message, msgSpan, authenticatedDevice);
ctx.setTimer(metrics.startTimer());
final Future<Void> spanPreparationFuture = authenticatedDevice == null ? applyTraceSamplingPriorityForAddressTenant(ctx.getAddress(), msgSpan) : Future.succeededFuture();
spanPreparationFuture.compose(ar -> onMessageReceived(ctx).onSuccess(ok -> msgSpan.finish()).onFailure(error -> closeConnectionOnTerminalError(error, conn, ctx, msgSpan)));
} catch (final Exception ex) {
log.warn("error handling message [container: {}, {}]", conn.getRemoteContainer(), authenticatedDevice, ex);
if (!conn.isDisconnected()) {
ProtonHelper.released(delivery, true);
}
}
});
receiver.open();
log.debug("established link for receiving messages from device [container: {}, {}]", conn.getRemoteContainer(), authenticatedDevice);
span.log("link established");
}
span.finish();
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class AbstractVertxBasedMqttProtocolAdapter method uploadCommandResponseMessage.
/**
* Uploads a command response message.
*
* @param ctx The context in which the MQTT message has been published.
* @param targetAddress The address that the response should be forwarded to.
* @return A future indicating the outcome of the operation.
* <p>
* The future will succeed if the message has been uploaded successfully.
* Otherwise, the future will fail with a {@link ServiceInvocationException}.
* @throws NullPointerException if any of the parameters are {@code null}.
*/
public final Future<Void> uploadCommandResponseMessage(final MqttContext ctx, final ResourceIdentifier targetAddress) {
Objects.requireNonNull(ctx);
Objects.requireNonNull(targetAddress);
final String[] addressPath = targetAddress.getResourcePath();
Integer status = null;
String reqId = null;
final Future<CommandResponse> commandResponseTracker;
if (addressPath.length <= CommandConstants.TOPIC_POSITION_RESPONSE_STATUS) {
commandResponseTracker = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "command response topic has too few segments"));
} else {
try {
status = Integer.parseInt(addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_STATUS]);
} catch (final NumberFormatException e) {
log.trace("got invalid status code [{}] [tenant-id: {}, device-id: {}]", addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_STATUS], targetAddress.getTenantId(), targetAddress.getResourceId());
}
if (status != null) {
reqId = addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_REQ_ID];
final CommandResponse commandResponse = CommandResponse.fromRequestId(reqId, targetAddress.getTenantId(), targetAddress.getResourceId(), ctx.message().payload(), ctx.contentType(), status);
commandResponseTracker = commandResponse != null ? Future.succeededFuture(commandResponse) : Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "command response topic contains invalid data"));
} else {
// status code could not be parsed
commandResponseTracker = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "invalid status code"));
}
}
final Span currentSpan = TracingHelper.buildChildSpan(tracer, ctx.getTracingContext(), "upload Command response", getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, targetAddress.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, targetAddress.getResourceId()).withTag(Constants.HEADER_COMMAND_RESPONSE_STATUS, status).withTag(Constants.HEADER_COMMAND_REQUEST_ID, reqId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), ctx.authenticatedDevice() != null).start();
final int payloadSize = Optional.ofNullable(ctx.message().payload()).map(Buffer::length).orElse(0);
final Future<TenantObject> tenantTracker = getTenantConfiguration(targetAddress.getTenantId(), ctx.getTracingContext());
return CompositeFuture.all(tenantTracker, commandResponseTracker).compose(success -> {
final Future<RegistrationAssertion> deviceRegistrationTracker = getRegistrationAssertion(targetAddress.getTenantId(), targetAddress.getResourceId(), ctx.authenticatedDevice(), currentSpan.context());
final Future<Void> tenantValidationTracker = CompositeFuture.all(isAdapterEnabled(tenantTracker.result()), checkMessageLimit(tenantTracker.result(), payloadSize, currentSpan.context())).mapEmpty();
return CompositeFuture.all(deviceRegistrationTracker, tenantValidationTracker).compose(ok -> sendCommandResponse(tenantTracker.result(), deviceRegistrationTracker.result(), commandResponseTracker.result(), currentSpan.context()));
}).compose(delivery -> {
log.trace("successfully forwarded command response from device [tenant-id: {}, device-id: {}]", targetAddress.getTenantId(), targetAddress.getResourceId());
metrics.reportCommand(Direction.RESPONSE, targetAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.FORWARDED, payloadSize, ctx.getTimer());
// check that the remote MQTT client is still connected before sending PUBACK
if (ctx.isAtLeastOnce() && ctx.deviceEndpoint().isConnected()) {
currentSpan.log(EVENT_SENDING_PUBACK);
ctx.acknowledge();
}
currentSpan.finish();
return Future.<Void>succeededFuture();
}).recover(t -> {
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
metrics.reportCommand(Direction.RESPONSE, targetAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.from(t), payloadSize, ctx.getTimer());
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class LoraProtocolAdapter method handleCommand.
private void handleCommand(final CommandContext commandContext) {
Tags.COMPONENT.set(commandContext.getTracingSpan(), getTypeName());
final Sample timer = metrics.startTimer();
final Command command = commandContext.getCommand();
if (command.getGatewayId() == null) {
final String errorMsg = "no gateway defined for command";
LOG.debug("{} [{}]", errorMsg, command);
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, errorMsg));
return;
}
final String tenant = command.getTenant();
final String gatewayId = command.getGatewayId();
final LoraProvider loraProvider = Optional.ofNullable(commandSubscriptions.get(new SubscriptionKey(tenant, gatewayId))).map(Pair::two).orElse(null);
if (loraProvider == null) {
LOG.debug("received command for unknown gateway [{}] for tenant [{}]", gatewayId, tenant);
TracingHelper.logError(commandContext.getTracingSpan(), String.format("received command for unknown gateway [%s]", gatewayId));
commandContext.release(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "received command for unknown gateway"));
return;
}
final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, commandContext.getTracingContext());
tenantTracker.compose(tenantObject -> {
if (command.isValid()) {
return checkMessageLimit(tenantObject, command.getPayloadSize(), commandContext.getTracingContext());
} else {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed command message"));
}
}).compose(success -> getRegistrationClient().assertRegistration(tenant, gatewayId, null, commandContext.getTracingContext())).compose(registrationAssertion -> sendCommandToGateway(commandContext, loraProvider, registrationAssertion.getCommandEndpoint())).onSuccess(aVoid -> {
addMicrometerSample(commandContext, timer);
commandContext.accept();
metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), MetricsTags.ProcessingOutcome.FORWARDED, command.getPayloadSize(), timer);
}).onFailure(t -> {
LOG.debug("error sending command", t);
commandContext.release(t);
metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), MetricsTags.ProcessingOutcome.from(t), command.getPayloadSize(), timer);
});
}
use of org.eclipse.hono.client.command.Command in project hono by eclipse.
the class LoraProtocolAdapter method registerCommandConsumerIfNeeded.
private void registerCommandConsumerIfNeeded(final LoraProvider provider, final Device gatewayDevice, final SpanContext context) {
final String tenantId = gatewayDevice.getTenantId();
final String gatewayId = gatewayDevice.getDeviceId();
final SubscriptionKey key = new SubscriptionKey(tenantId, gatewayId);
if (commandSubscriptions.containsKey(key)) {
return;
}
// use FOLLOWS_FROM span since this operation is decoupled from the rest of the request handling
final Span currentSpan = TracingHelper.buildFollowsFromSpan(tracer, context, "create command consumer").withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).start();
TracingHelper.setDeviceTags(currentSpan, tenantId, gatewayId);
TAG_LORA_PROVIDER.set(currentSpan, provider.getProviderName());
getRegistrationClient().assertRegistration(tenantId, gatewayId, null, currentSpan.context()).onFailure(thr -> {
LOG.debug("error asserting gateway registration, no command consumer will be created [tenant: {}, gateway-id: {}]", tenantId, gatewayId);
TracingHelper.logError(currentSpan, "error asserting gateway registration, no command consumer will be created", thr);
}).compose(assertion -> {
if (assertion.getCommandEndpoint() == null) {
LOG.debug("gateway has no command endpoint defined, skipping command consumer creation [tenant: {}, gateway-id: {}]", tenantId, gatewayId);
currentSpan.log("gateway has no command endpoint defined, skipping command consumer creation");
return Future.succeededFuture((Void) null);
}
return getCommandConsumerFactory().createCommandConsumer(tenantId, gatewayId, this::handleCommand, null, currentSpan.context()).onFailure(thr -> TracingHelper.logError(currentSpan, thr)).map(commandConsumer -> commandSubscriptions.put(key, Pair.of(commandConsumer, provider))).mapEmpty();
}).onComplete(ar -> currentSpan.finish());
}
Aggregations