use of io.vertx.core.Promise in project hono by eclipse.
the class AmqpConnectionIT method testDeviceConnectionIsClosedOnDeviceOrTenantDisabledOrDeleted.
private void testDeviceConnectionIsClosedOnDeviceOrTenantDisabledOrDeleted(final VertxTestContext ctx, final String tenantId, final String deviceId, final Supplier<Future<?>> deviceRegistryChangeOperation) {
final String password = "secret";
final Promise<Void> disconnectedPromise = Promise.promise();
// GIVEN a connected device
helper.registry.addDeviceForTenant(tenantId, new Tenant(), deviceId, password).compose(ok -> connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password)).compose(con -> {
con.disconnectHandler(ignore -> disconnectedPromise.complete());
con.closeHandler(remoteClose -> {
con.close();
con.disconnect();
});
// WHEN corresponding device/tenant is removed/disabled
return deviceRegistryChangeOperation.get();
}).compose(ok -> disconnectedPromise.future()).onComplete(ctx.succeedingThenComplete());
}
use of io.vertx.core.Promise in project hono by eclipse.
the class AmqpConnectionIT method testConnectSucceedsWithAutoProvisioning.
/**
* Verifies that the adapter opens a connection if auto-provisioning is enabled for the device certificate.
*
* @param ctx The test context.
*/
@Test
public void testConnectSucceedsWithAutoProvisioning(final VertxTestContext ctx) {
final String tenantId = helper.getRandomTenantId();
final SelfSignedCertificate deviceCert = SelfSignedCertificate.create(UUID.randomUUID().toString());
final Promise<String> autoProvisionedDeviceId = Promise.promise();
helper.createAutoProvisioningNotificationConsumer(ctx, autoProvisionedDeviceId, tenantId).compose(ok -> helper.getCertificate(deviceCert.certificatePath())).compose(cert -> {
final var tenant = Tenants.createTenantForTrustAnchor(cert);
tenant.getTrustedCertificateAuthorities().get(0).setAutoProvisioningEnabled(true);
return helper.registry.addTenant(tenantId, tenant);
}).compose(ok -> connectToAdapter(deviceCert)).compose(ok -> autoProvisionedDeviceId.future()).compose(deviceId -> helper.registry.getRegistrationInfo(tenantId, deviceId)).onComplete(ctx.succeeding(registrationResult -> {
ctx.verify(() -> {
final var info = registrationResult.bodyAsJsonObject();
IntegrationTestSupport.assertDeviceStatusProperties(info.getJsonObject(RegistryManagementConstants.FIELD_STATUS), true);
});
ctx.completeNow();
}));
}
use of io.vertx.core.Promise 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.Promise 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.Promise in project hono by eclipse.
the class SQL method runTransactionally.
/**
* Run operation transactionally.
* <p>
* This function will perform the following operations:
* <ul>
* <li>Open a new connection</li>
* <li>Turn off auto-commit mode</li>
* <li>Call the provided function</li>
* <li>If the provided function failed, perform a <em>Rollback</em> operation</li>
* <li>If the provided function succeeded, perform a <em>Commit</em> operation</li>
* <li>Close the connection</li>
* </ul>
*
* @param client The client to use.
* @param tracer The tracer to use.
* @param function The function to execute while the transaction is open.
* @param context The span to log to.
* @param <T> The type of the result.
* @return A future, tracking the outcome of the operation.
*/
public static <T> Future<T> runTransactionally(final SQLClient client, final Tracer tracer, final SpanContext context, final BiFunction<SQLConnection, SpanContext, Future<T>> function) {
final Span span = startSqlSpan(tracer, context, "run transactionally", builder -> {
});
final Promise<SQLConnection> promise = Promise.promise();
client.getConnection(promise);
return promise.future().onSuccess(x -> {
final Map<String, Object> log = new HashMap<>();
log.put(Fields.EVENT, "success");
log.put(Fields.MESSAGE, "connection opened");
span.log(log);
}).flatMap(connection -> SQL.setAutoCommit(tracer, span.context(), connection, false).flatMap(y -> function.apply(connection, span.context()).compose(v -> SQL.commit(tracer, span.context(), connection).map(v), x -> SQL.rollback(tracer, span.context(), connection).flatMap(unused -> Future.failedFuture(x)))).onComplete(x -> connection.close())).onComplete(x -> span.finish());
}
Aggregations