use of io.vertx.core.Future in project hono by eclipse.
the class IntegrationTestSupport method deleteTenantKafkaTopics.
private Future<Void> deleteTenantKafkaTopics(final List<String> tenantsToDelete) {
if (!isUsingKafkaMessaging()) {
return Future.succeededFuture();
}
// topics for the given tenants are not deleted right away: It could be that the offset-commit interval of the CommandRouter
// command consumer (5s) hasn't elapsed yet and it has to be avoided to delete the topics before the consumer has
// committed corresponding offsets (otherwise the consumer will retry the commit for some time and be blocked during that time)
final Promise<Void> tenantTopicsDeletionDonePromise = Promise.promise();
tenantsToDeleteTopicsForAfterDelay.add(Pair.of(tenantsToDelete, Instant.now()));
final List<String> tenantsToDeleteTopicsForNow = new LinkedList<>();
final Instant nowMinusCommitInterval = Instant.now().minus(// commit interval with added buffer
AsyncHandlingAutoCommitKafkaConsumer.DEFAULT_COMMIT_INTERVAL.plusSeconds(1));
final Iterator<Pair<List<String>, Instant>> iterator = tenantsToDeleteTopicsForAfterDelay.iterator();
while (iterator.hasNext()) {
final Pair<List<String>, Instant> tenantsToDeleteAndInstantPair = iterator.next();
if (tenantsToDeleteAndInstantPair.two().isBefore(nowMinusCommitInterval)) {
tenantsToDeleteTopicsForNow.addAll(tenantsToDeleteAndInstantPair.one());
iterator.remove();
}
}
if (!tenantsToDeleteTopicsForNow.isEmpty()) {
final KafkaAdminClient adminClient = KafkaAdminClient.create(vertx, getKafkaAdminClientConfig().getAdminClientConfig("test"));
final Promise<Void> adminClientClosedPromise = Promise.promise();
LOGGER.debug("deleting topics for temporary tenants {}", tenantsToDeleteTopicsForNow);
final List<String> topicNames = tenantsToDeleteTopicsForNow.stream().flatMap(tenant -> HonoTopic.Type.MESSAGING_API_TYPES.stream().map(type -> new HonoTopic(type, tenant).toString())).collect(Collectors.toList());
adminClient.deleteTopics(topicNames, ar -> {
// note that the result will probably have failed with an UnknownTopicOrPartitionException here;
// not necessarily all tenant topics may have been created before
LOGGER.debug("done triggering deletion of topics for tenants {}", tenantsToDeleteTopicsForNow);
adminClient.close(adminClientClosedPromise);
});
adminClientClosedPromise.future().recover(thr -> {
LOGGER.warn("error closing Kafka admin client", thr);
return Future.succeededFuture();
}).onComplete(tenantTopicsDeletionDonePromise);
} else {
tenantTopicsDeletionDonePromise.complete();
}
return tenantTopicsDeletionDonePromise.future();
}
use of io.vertx.core.Future in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method handleBeforeCredentialsValidation.
/**
* Handles any operations that should be invoked as part of the authentication process after the credentials got
* determined and before they get validated. Can be used to perform checks using the credentials and tenant
* information before the potentially expensive credentials validation is done
* <p>
* The default implementation updates the trace sampling priority in the execution context tracing span.
* <p>
* Subclasses should override this method in order to perform additional operations after calling this super method.
*
* @param credentials The credentials.
* @param executionContext The execution context, including the TenantObject.
* @return A future indicating the outcome of the operation. A failed future will fail the authentication attempt.
*/
protected Future<Void> handleBeforeCredentialsValidation(final DeviceCredentials credentials, final SaslResponseContext executionContext) {
final String tenantId = credentials.getTenantId();
final Span span = executionContext.getTracingSpan();
final String authId = credentials.getAuthId();
return getTenantConfiguration(tenantId, span.context()).recover(t -> Future.failedFuture(CredentialsApiAuthProvider.mapNotFoundToBadCredentialsException(t))).compose(tenantObject -> {
TracingHelper.setDeviceTags(span, tenantId, null, authId);
final OptionalInt traceSamplingPriority = TenantTraceSamplingHelper.applyTraceSamplingPriority(tenantObject, authId, span);
executionContext.getProtonConnection().attachments().set(AmqpAdapterConstants.KEY_TRACE_SAMPLING_PRIORITY, OptionalInt.class, traceSamplingPriority);
return Future.succeededFuture();
});
}
use of io.vertx.core.Future in project hono by eclipse.
the class VertxBasedAmqpProtocolAdapter method createCommandConsumer.
private Future<CommandConsumer> createCommandConsumer(final ProtonSender sender, final ResourceIdentifier sourceAddress, final Device authenticatedDevice, final Span span) {
final Handler<CommandContext> commandHandler = commandContext -> {
final Sample timer = metrics.startTimer();
addMicrometerSample(commandContext, timer);
Tags.COMPONENT.set(commandContext.getTracingSpan(), getTypeName());
final Command command = commandContext.getCommand();
final Future<TenantObject> tenantTracker = getTenantConfiguration(sourceAddress.getTenantId(), commandContext.getTracingContext());
tenantTracker.compose(tenantObject -> {
if (!command.isValid()) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed command message"));
}
if (!HonoProtonHelper.isLinkOpenAndConnected(sender)) {
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "sender link is not open"));
}
return checkMessageLimit(tenantObject, command.getPayloadSize(), commandContext.getTracingContext());
}).compose(success -> {
// check the via-gateways, ensuring that the gateway may act on behalf of the device at this point in time
if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
return getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, commandContext.getTracingContext());
}
return Future.succeededFuture();
}).compose(success -> {
onCommandReceived(tenantTracker.result(), sender, commandContext);
return Future.succeededFuture();
}).otherwise(failure -> {
if (failure instanceof ClientErrorException) {
commandContext.reject(failure);
} else {
commandContext.release(failure);
}
metrics.reportCommand(command.isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, sourceAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.from(failure), command.getPayloadSize(), timer);
return null;
});
};
final Future<RegistrationAssertion> tokenTracker = Optional.ofNullable(authenticatedDevice).map(v -> getRegistrationAssertion(authenticatedDevice.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice, span.context())).orElseGet(Future::succeededFuture);
if (authenticatedDevice != null && !authenticatedDevice.getDeviceId().equals(sourceAddress.getResourceId())) {
// gateway scenario
return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), authenticatedDevice.getDeviceId(), commandHandler, null, span.context()));
} else {
return tokenTracker.compose(v -> getCommandConsumerFactory().createCommandConsumer(sourceAddress.getTenantId(), sourceAddress.getResourceId(), commandHandler, null, span.context()));
}
}
use of io.vertx.core.Future in project hono by eclipse.
the class AbstractHonoResource method doUploadMessage.
/**
* Forwards a message to the south bound Telemetry or Event API of the messaging infrastructure configured
* for the tenant that the origin device belongs to.
* <p>
* Depending on the outcome of the attempt to upload the message, the CoAP response code is set as
* described by the <a href="https://www.eclipse.org/hono/docs/user-guide/coap-adapter/">CoAP adapter user guide</a>
*
* @param context The request that contains the uploaded message.
* @param endpoint The type of API endpoint to forward the message to.
* @return A future indicating the outcome of the operation.
* The future will be succeeded if the message has been forwarded successfully.
* In this case one of the context's <em>respond</em> methods will have been invoked to send a CoAP response
* back to the device.
* Otherwise the future will be failed with a {@link org.eclipse.hono.client.ServiceInvocationException}.
* @throws NullPointerException if any of the parameters are {@code null}.
*/
protected final Future<Void> doUploadMessage(final CoapContext context, final MetricsTags.EndpointType endpoint) {
Objects.requireNonNull(context);
Objects.requireNonNull(endpoint);
final String contentType = context.getContentType();
final Buffer payload = context.getPayload();
if (contentType == null) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request message must contain content-format option"));
} else if (payload.length() == 0 && !context.isEmptyNotification()) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "request contains no body but is not marked as empty notification"));
} else {
final String gatewayId = context.getGatewayId();
final String tenantId = context.getOriginDevice().getTenantId();
final String deviceId = context.getOriginDevice().getDeviceId();
final MetricsTags.QoS qos = context.isConfirmable() ? MetricsTags.QoS.AT_LEAST_ONCE : MetricsTags.QoS.AT_MOST_ONCE;
final Span currentSpan = TracingHelper.buildChildSpan(getTracer(), context.getTracingContext(), "upload " + endpoint.getCanonicalName(), getAdapter().getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantId).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), context.isDeviceAuthenticated()).withTag(Constants.HEADER_QOS_LEVEL, qos.asTag().getValue()).start();
final Promise<Void> responseReady = Promise.promise();
final Future<RegistrationAssertion> tokenTracker = getAdapter().getRegistrationAssertion(tenantId, deviceId, context.getAuthenticatedDevice(), currentSpan.context());
final Future<TenantObject> tenantTracker = getAdapter().getTenantClient().get(tenantId, currentSpan.context());
final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(getAdapter().isAdapterEnabled(tenantObject), getAdapter().checkMessageLimit(tenantObject, payload.length(), currentSpan.context())).map(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 = context.getTimeUntilDisconnect();
return getAdapter().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, context, responseReady, currentSpan));
return commandConsumerTracker.compose(commandConsumer -> {
final Map<String, Object> props = getAdapter().getDownstreamMessageProperties(context);
Optional.ofNullable(commandConsumer).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
customizeDownstreamMessageProperties(props, context);
if (context.isConfirmable()) {
context.startAcceptTimer(vertx, tenantTracker.result(), getAdapter().getConfig().getTimeoutToAck());
}
final Future<Void> sendResult;
if (endpoint == EndpointType.EVENT) {
sendResult = getAdapter().getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context());
} else {
sendResult = getAdapter().getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), context.getRequestedQos(), contentType, payload, props, currentSpan.context());
}
return CompositeFuture.all(sendResult, responseReady.future()).mapEmpty();
}).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 -> {
final CommandContext commandContext = context.get(CommandContext.KEY_COMMAND_CONTEXT);
final Response response = new Response(ResponseCode.CHANGED);
if (commandContext != null) {
addCommandToResponse(response, commandContext, currentSpan);
commandContext.accept();
getAdapter().getMetrics().reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenantId, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
}
LOG.trace("successfully processed message for device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName());
getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), MetricsTags.ProcessingOutcome.FORWARDED, qos, payload.length(), getTtdStatus(context), context.getTimer());
context.respond(response);
currentSpan.finish();
return (Void) null;
}).recover(t -> {
LOG.debug("cannot process message from device [tenantId: {}, deviceId: {}, endpoint: {}]", tenantId, deviceId, endpoint.getCanonicalName(), t);
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 = context.get(CommandContext.KEY_COMMAND_CONTEXT);
if (commandContext != null) {
TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in CoAP response, CoAP request handling failed", t);
commandContext.release(t);
currentSpan.log("released command for device");
}
getAdapter().getMetrics().reportTelemetry(endpoint, tenantId, tenantTracker.result(), ClientErrorException.class.isInstance(t) ? MetricsTags.ProcessingOutcome.UNPROCESSABLE : MetricsTags.ProcessingOutcome.UNDELIVERABLE, qos, payload.length(), getTtdStatus(context), context.getTimer());
TracingHelper.logError(currentSpan, t);
commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
return Future.failedFuture(t);
});
}
}
use of io.vertx.core.Future 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")));
}
Aggregations