use of io.vertx.core.Future in project hono by eclipse.
the class TelemetryHttpIT method testUploadQos1MessageFailsIfDeliveryStateNotUpdated.
/**
* Verifies that the upload of a QoS 1 telemetry message fails with a 503 status code
* when the consumer doesn't update the message delivery state and the
* <em>sendMessageTimeout</em> has elapsed.
*
* @param vertx The vert.x instance.
* @param ctx The test context
* @throws InterruptedException if test is interrupted while running.
*/
@Test
@AssumeMessagingSystem(type = MessagingType.amqp)
public void testUploadQos1MessageFailsIfDeliveryStateNotUpdated(final Vertx vertx, final VertxTestContext ctx) throws InterruptedException {
final AmqpApplicationClient amqpApplicationClient = (AmqpApplicationClient) helper.applicationClient;
// GIVEN a device and a north bound message consumer that doesn't update the message delivery state
final Tenant tenant = new Tenant();
final Checkpoint messageReceived = ctx.checkpoint();
final Checkpoint deliveryStateCheckDone = ctx.checkpoint();
final Checkpoint httpResponseReceived = ctx.checkpoint();
final VertxTestContext setup = new VertxTestContext();
final AtomicReference<ProtonDelivery> deliveryRef = new AtomicReference<>();
helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, PWD).compose(ok -> amqpApplicationClient.createTelemetryConsumer(tenantId, msg -> {
final Promise<Void> result = Promise.promise();
final var delivery = msg.getMessageContext().getDelivery();
deliveryRef.set(delivery);
logger.debug("received message: {}", msg.getMessageContext().getRawMessage());
ctx.verify(() -> {
assertThat(delivery.remotelySettled()).isFalse();
assertThat(delivery.getRemoteState()).isNull();
});
messageReceived.flag();
// don't update the delivery state here
return result.future();
}, remoteClose -> {
})).onComplete(setup.succeedingThenComplete());
assertThat(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
// WHEN the device tries to upload a telemetry message
final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap().add(HttpHeaders.CONTENT_TYPE, "binary/octet-stream").add(HttpHeaders.AUTHORIZATION, authorization).add(HttpHeaders.ORIGIN, ORIGIN_URI).add(Constants.HEADER_QOS_LEVEL, "1");
final Future<HttpResponse<Buffer>> httpResponseFuture = httpClient.create(getEndpointUri(), Buffer.buffer("hello"), requestHeaders, // THEN the message gets rejected by the HTTP adapter with a 503
ResponsePredicate.status(HttpURLConnection.HTTP_UNAVAILABLE));
httpResponseFuture.onComplete(ctx.succeeding(response -> {
ctx.verify(() -> {
final var body = response.bodyAsJsonObject();
assertThat(body.getString(RequestResponseApiConstants.FIELD_ERROR)).isEqualTo(ServiceInvocationException.getLocalizedMessage(SendMessageTimeoutException.CLIENT_FACING_MESSAGE_KEY));
});
httpResponseReceived.flag();
// verify that the telemetry message delivery is remotely settled via the timeout handling in the adapter
vertx.setTimer(50, tid -> {
ctx.verify(() -> {
final ProtonDelivery delivery = deliveryRef.get();
assertThat(delivery).isNotNull();
assertThat(delivery.remotelySettled()).isTrue();
assertThat(delivery.getRemoteState()).isNotNull();
assertThat(delivery.getRemoteState().getType()).isEqualTo(DeliveryState.DeliveryStateType.Released);
});
deliveryStateCheckDone.flag();
});
}));
}
use of io.vertx.core.Future in project hono by eclipse.
the class KafkaBasedEventSenderIT method createTopics.
private static Future<Void> createTopics(final Collection<String> topicNames, final int numPartitions, final Map<String, String> topicConfig) {
topicsToDeleteAfterTests.addAll(topicNames);
final Promise<Void> resultPromise = Promise.promise();
final List<NewTopic> topics = topicNames.stream().map(t -> new NewTopic(t, numPartitions, REPLICATION_FACTOR).setConfig(topicConfig)).collect(Collectors.toList());
adminClient.createTopics(topics, resultPromise);
return resultPromise.future();
}
use of io.vertx.core.Future in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandFailsForCommandRejectedByDevice.
/**
* Verifies that the adapter forwards the <em>rejected</em> disposition, received from a device, back to the
* application.
* <p>
* If Kafka is used, this means a corresponding error command response is published.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testSendCommandFailsForCommandRejectedByDevice(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final int totalNoOfCommandsToSend = 3;
connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig, (cmdReceiver, cmdResponseSender) -> createRejectingCommandConsumer(ctx, cmdReceiver), totalNoOfCommandsToSend);
if (ctx.failed()) {
return;
}
final String replyId = "reply-id";
final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
final AtomicInteger commandsSent = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong();
final long start = System.currentTimeMillis();
final long commandTimeout = IntegrationTestSupport.getSendCommandTimeout();
final Handler<Void> failureNotificationReceivedHandler = v -> {
lastReceivedTimestamp.set(System.currentTimeMillis());
commandsFailed.countDown();
};
final VertxTestContext setup = new VertxTestContext();
final Future<MessageConsumer> kafkaAsyncErrorResponseConsumer = IntegrationTestSupport.isUsingKafkaMessaging() ? helper.createDeliveryFailureCommandResponseConsumer(ctx, tenantId, HttpURLConnection.HTTP_BAD_REQUEST, response -> {
ctx.verify(() -> {
DownstreamMessageAssertions.assertMessageContainsTimeToLive(response, TTL_COMMAND_RESPONSE);
});
failureNotificationReceivedHandler.handle(null);
}, REJECTED_COMMAND_ERROR_MESSAGE::equals) : Future.succeededFuture(null);
kafkaAsyncErrorResponseConsumer.onComplete(setup.succeedingThenComplete());
assertWithMessage("setup of command response consumer finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
while (commandsSent.get() < totalNoOfCommandsToSend) {
final CountDownLatch commandSent = new CountDownLatch(1);
context.runOnContext(go -> {
final String correlationId = String.valueOf(commandsSent.getAndIncrement());
final Buffer msg = Buffer.buffer("value: " + commandsSent.get());
helper.applicationClient.sendAsyncCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", msg, correlationId, replyId, null).onComplete(sendAttempt -> {
if (IntegrationTestSupport.isUsingAmqpMessaging()) {
if (sendAttempt.succeeded()) {
log.info("sending command {} via AMQP succeeded unexpectedly", commandsSent.get());
} else {
if (sendAttempt.cause() instanceof ClientErrorException && ((ClientErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_BAD_REQUEST && REJECTED_COMMAND_ERROR_MESSAGE.equals(sendAttempt.cause().getMessage())) {
log.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
failureNotificationReceivedHandler.handle(null);
} else {
log.info("sending command {} failed with an unexpected error", commandsSent.get(), sendAttempt.cause());
}
}
} else if (sendAttempt.failed()) {
log.debug("sending command {} via Kafka failed unexpectedly", commandsSent.get(), sendAttempt.cause());
}
commandSent.countDown();
});
});
commandSent.await();
}
final long timeToWait = 300 + (totalNoOfCommandsToSend * commandTimeout);
if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
log.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
}
final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
log.info("commands sent: {}, commands failed: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
Optional.ofNullable(kafkaAsyncErrorResponseConsumer.result()).map(MessageConsumer::close).orElseGet(Future::succeededFuture).onComplete(ar -> {
if (commandsCompleted == commandsSent.get()) {
ctx.completeNow();
} else {
ctx.failNow(new IllegalStateException("did not complete all commands sent"));
}
});
}
use of io.vertx.core.Future in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandFailsForNoConsumer.
/**
* Verifies that the adapter immediately forwards the <em>released</em> disposition
* if there is no consumer for a sent command.
* <p>
* If Kafka is used, this means a corresponding error command response is published.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testSendCommandFailsForNoConsumer(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final String otherDeviceId = helper.getRandomDeviceId(tenantId);
final VertxTestContext setup = new VertxTestContext();
connectToAdapter(tenantId, otherDeviceId, password, (Supplier<Future<MessageConsumer>>) null).compose(v -> helper.registry.addDeviceToTenant(tenantId, deviceId, password)).compose(con -> subscribeToCommands(endpointConfig, tenantId, otherDeviceId).map(recv -> {
recv.handler((delivery, msg) -> ctx.failNow(new IllegalStateException("should not have received command")));
return null;
})).onComplete(setup.succeedingThenComplete());
assertWithMessage("setup of adapter finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
helper.sendCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", Buffer.buffer("cmd"), helper.getSendCommandTimeout(true)).onComplete(ctx.failing(t -> {
ctx.verify(() -> {
assertThat(t).isInstanceOf(ServerErrorException.class);
assertThat(((ServerErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
assertThat(t).isNotInstanceOf(SendMessageTimeoutException.class);
});
ctx.completeNow();
}));
}
use of io.vertx.core.Future 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());
}
Aggregations