use of org.eclipse.hono.application.client.MessageConsumer in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandFailsForCommandNotAcknowledgedByDevice.
/**
* Verifies that the adapter forwards the <em>released</em> disposition back to the
* application if the device hasn't sent a disposition update for the delivery of
* the command message sent to the device.
* <p>
* If Kafka is used, this means a corresponding error command response is published.
*
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@Test
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testSendCommandFailsForCommandNotAcknowledgedByDevice(final VertxTestContext ctx) throws InterruptedException {
final AmqpCommandEndpointConfiguration endpointConfig = new AmqpCommandEndpointConfiguration(SubscriberRole.DEVICE);
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicInteger receivedMessagesCounter = new AtomicInteger(0);
final int totalNoOfCommandsToSend = 2;
// command handler won't send a disposition update
connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig, (cmdReceiver, cmdResponseSender) -> createNotSendingDeliveryUpdateCommandConsumer(ctx, cmdReceiver, receivedMessagesCounter), 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_UNAVAILABLE, response -> failureNotificationReceivedHandler.handle(null), null) : 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.debug("sending command {} succeeded unexpectedly", commandsSent.get());
} else {
if (sendAttempt.cause() instanceof ServerErrorException && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE && !(sendAttempt.cause() instanceof SendMessageTimeoutException)) {
log.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
failureNotificationReceivedHandler.handle(null);
} else {
log.debug("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);
}
assertThat(receivedMessagesCounter.get()).isEqualTo(totalNoOfCommandsToSend);
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 org.eclipse.hono.application.client.MessageConsumer in project hono by eclipse.
the class HonoExampleApplicationBase method consumeData.
/**
* Start the application client and set the message handling method to treat data that is received.
*/
protected void consumeData() {
final CompletableFuture<ApplicationClient<? extends MessageContext>> startup = new CompletableFuture<>();
if (client instanceof AmqpApplicationClient) {
final AmqpApplicationClient ac = (AmqpApplicationClient) client;
ac.addDisconnectListener(c -> LOG.info("lost connection to Hono, trying to reconnect ..."));
ac.addReconnectListener(c -> LOG.info("reconnected to Hono"));
}
client.start().compose(v -> CompositeFuture.all(createEventConsumer(), createTelemetryConsumer())).onSuccess(ok -> startup.complete(client)).onFailure(startup::completeExceptionally);
try {
startup.join();
LOG.info("Consumer ready for telemetry and event messages");
System.in.read();
} catch (final CompletionException e) {
LOG.error("{} consumer failed to start [{}:{}]", USE_KAFKA ? "Kafka" : "AMQP", HonoExampleConstants.HONO_MESSAGING_HOST, port, e.getCause());
} catch (final IOException e) {
// nothing we can do
}
final CompletableFuture<ApplicationClient<? extends MessageContext>> shutDown = new CompletableFuture<>();
@SuppressWarnings("rawtypes") final List<Future> closeFutures = new ArrayList<>();
Optional.ofNullable(eventConsumer).map(MessageConsumer::close).ifPresent(closeFutures::add);
Optional.ofNullable(telemetryConsumer).map(MessageConsumer::close).ifPresent(closeFutures::add);
Optional.ofNullable(client).map(Lifecycle::stop).ifPresent(closeFutures::add);
CompositeFuture.join(closeFutures).compose(ok -> vertx.close()).recover(t -> vertx.close()).onComplete(ar -> shutDown.complete(client));
// wait for clients to be closed
shutDown.join();
LOG.info("Consumer has been shut down");
}
use of org.eclipse.hono.application.client.MessageConsumer in project hono by eclipse.
the class CoapTestBase method testUploadMessages.
/**
* Uploads messages to the CoAP endpoint.
*
* @param ctx The test context to run on.
* @param tenantId The tenant that the device belongs to.
* @param warmUp A sender of messages used to warm up the adapter before running the test itself or {@code null} if
* no warm up should be performed.
* @param messageConsumer Consumer that is invoked when a message was received.
* @param requestSender The test device that will publish the data.
* @param numberOfMessages The number of messages that are uploaded.
* @param expectedQos The expected QoS level, may be {@code null} leading to expecting the default for event or telemetry.
* @throws InterruptedException if the test is interrupted before it has finished.
*/
protected void testUploadMessages(final VertxTestContext ctx, final String tenantId, final Supplier<Future<Void>> warmUp, final Consumer<DownstreamMessage<? extends MessageContext>> messageConsumer, final Function<Integer, Future<OptionSet>> requestSender, final int numberOfMessages, final QoS expectedQos) throws InterruptedException {
final CountDownLatch received = new CountDownLatch(numberOfMessages);
final VertxTestContext setup = new VertxTestContext();
createConsumer(tenantId, msg -> {
ctx.verify(() -> {
logger.trace("received {}", msg);
DownstreamMessageAssertions.assertTelemetryMessageProperties(msg, tenantId);
assertThat(msg.getQos()).isEqualTo(getExpectedQoS(expectedQos));
assertAdditionalMessageProperties(msg);
if (messageConsumer != null) {
messageConsumer.accept(msg);
}
});
received.countDown();
if (received.getCount() % 20 == 0) {
logger.info("messages received: {}", numberOfMessages - received.getCount());
}
}).compose(ok -> Optional.ofNullable(warmUp).map(w -> w.get()).orElseGet(() -> Future.succeededFuture())).onComplete(setup.succeedingThenComplete());
ctx.verify(() -> assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue());
final long start = System.currentTimeMillis();
final AtomicInteger messageCount = new AtomicInteger(0);
while (messageCount.get() < numberOfMessages && !ctx.failed()) {
final CountDownLatch sending = new CountDownLatch(1);
requestSender.apply(messageCount.getAndIncrement()).compose(this::assertCoapResponse).onComplete(attempt -> {
if (attempt.succeeded()) {
logger.debug("sent message {}", messageCount.get());
} else {
logger.info("failed to send message {}: {}", messageCount.get(), attempt.cause().getMessage());
ctx.failNow(attempt.cause());
}
sending.countDown();
});
if (messageCount.get() % 20 == 0) {
logger.info("messages sent: {}", messageCount.get());
}
sending.await();
}
if (ctx.failed()) {
return;
}
final long timeToWait = Math.max(TEST_TIMEOUT_MILLIS - 1000, Math.round(numberOfMessages * 20));
if (received.await(timeToWait, TimeUnit.MILLISECONDS)) {
logger.info("sent {} and received {} messages after {} milliseconds", messageCount, numberOfMessages - received.getCount(), System.currentTimeMillis() - start);
ctx.completeNow();
} else {
logger.info("sent {} and received {} messages after {} milliseconds", messageCount, numberOfMessages - received.getCount(), System.currentTimeMillis() - start);
ctx.failNow(new AssertionError("did not receive all messages sent"));
}
}
use of org.eclipse.hono.application.client.MessageConsumer in project hono by eclipse.
the class EventMqttIT method testMessagesExpire.
/**
* Verifies that an event from a device for which a default TTL has been
* specified cannot be consumed after the TTL has expired.
*
* @param ctx The vert.x test context.
* @throws InterruptedException if test execution gets interrupted.
*/
@Test
public void testMessagesExpire(final VertxTestContext ctx) throws InterruptedException {
// GIVEN a tenant for which all messages have a TTL of 500ms
final String tenantId = helper.getRandomTenantId();
final String deviceId = helper.getRandomDeviceId(tenantId);
final Tenant tenant = new Tenant();
// seconds
tenant.setDefaults(Map.of(MessageHelper.SYS_HEADER_PROPERTY_TTL, 3));
final VertxTestContext setup = new VertxTestContext();
helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, "secret").onComplete(setup.succeedingThenComplete());
assertThat(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
// WHEN a device that belongs to the tenant publishes an event
final AtomicInteger receivedMessageCount = new AtomicInteger(0);
connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), "secret").compose(connAck -> send(tenantId, deviceId, Buffer.buffer("hello"), false, null, (sendAttempt, result) -> {
if (sendAttempt.succeeded()) {
LOGGER.info("successfully sent event [tenant-id: {}, device-id: {}]", tenantId, deviceId);
result.complete();
} else {
result.fail(sendAttempt.cause());
}
})).compose(ok -> {
final Promise<MessageConsumer> consumerCreated = Promise.promise();
vertx.setTimer(4000, tid -> {
LOGGER.info("opening event consumer for tenant [{}]", tenantId);
// THEN no messages can be consumed after the TTL has expired
createConsumer(tenantId, msg -> receivedMessageCount.incrementAndGet()).onComplete(consumerCreated);
});
return consumerCreated.future();
}).compose(c -> {
final Promise<Void> done = Promise.promise();
vertx.setTimer(1000, tid -> {
if (receivedMessageCount.get() > 0) {
done.fail(new IllegalStateException("should not have received any events after TTL has expired"));
} else {
done.complete();
}
});
return done.future();
}).onComplete(ctx.succeedingThenComplete());
}
use of org.eclipse.hono.application.client.MessageConsumer in project hono by eclipse.
the class MqttPublishTestBase method doTestUploadMessages.
/**
* Uploads a number of messages and verifies that they are either received via the northbound consumer or that
* corresponding error messages are published to the client on the error topic.
*
* @param ctx The test context.
* @param tenantId The tenant identifier.
* @param connection The MQTT connection future.
* @param sender The message sender. The Future result is the correlation/message id of the sent message.
* @param consumerSupplier The message consumer. The result may be succeeded with a {@code null} value in case
* error message handling for a non-existing consumer shall get tested.
* @param errorMsgHandler The handler to invoke with received error messages or {@code null} if no error messages
* are expected. The future result is the error message correlation id.
* @param errorTopic The errorTopic to subscribe to. Will be ignored of errorMsgHandler is {@code null}.
* @throws InterruptedException if the test fails.
*/
protected void doTestUploadMessages(final VertxTestContext ctx, final String tenantId, final Future<MqttConnAckMessage> connection, final Function<Buffer, Future<String>> sender, final Function<Handler<DownstreamMessage<? extends MessageContext>>, Future<MessageConsumer>> consumerSupplier, final Function<MqttPublishMessage, Future<String>> errorMsgHandler, final String errorTopic) throws InterruptedException {
final boolean errorMessagesExpected = errorMsgHandler != null;
final CountDownLatch received = new CountDownLatch(MESSAGES_TO_SEND);
final AtomicInteger messageCount = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong(0);
// <correlation id of the sent telemetry/event message, errorMessageReceived promise>
final Map<String, Promise<Void>> pendingErrorMessages = new HashMap<>();
final AtomicBoolean consumerIsSet = new AtomicBoolean();
final VertxTestContext setup = new VertxTestContext();
connection.compose(ok -> consumerSupplier.apply(msg -> {
if (errorMessagesExpected) {
ctx.failNow(new IllegalStateException("consumer received message although sending was supposed to fail"));
return;
}
LOGGER.trace("received {}", msg);
ctx.verify(() -> {
DownstreamMessageAssertions.assertTelemetryMessageProperties(msg, tenantId);
assertThat(msg.getQos().ordinal()).isEqualTo(getQos().ordinal());
assertAdditionalMessageProperties(msg);
});
received.countDown();
lastReceivedTimestamp.set(System.currentTimeMillis());
if (received.getCount() % 50 == 0) {
LOGGER.info("messages received: {}", MESSAGES_TO_SEND - received.getCount());
}
})).compose(msgConsumer -> {
consumerIsSet.set(msgConsumer != null);
if (errorMsgHandler == null) {
return Future.succeededFuture();
}
mqttClient.publishHandler(msg -> {
LOGGER.trace("received error message [topic: {}]", msg.topicName());
errorMsgHandler.apply(msg).onSuccess(correlationId -> {
// correlate the error message with the corresponding publish operation and complete the publish operation promise here
pendingErrorMessages.compute(correlationId, (key, oldValue) -> {
final Promise<Void> promise = Optional.ofNullable(oldValue).orElseGet(Promise::promise);
promise.tryComplete();
// remove mapping if oldValue is set
return oldValue != null ? null : promise;
});
}).onFailure(ctx::failNow);
});
return subscribeToErrorTopic(errorTopic);
}).onComplete(setup.succeedingThenComplete());
assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
customizeConnectedClient();
final long start = System.currentTimeMillis();
while (messageCount.get() < MESSAGES_TO_SEND) {
final CountDownLatch messageHandlingCompleted = new CountDownLatch(errorMessagesExpected ? 2 : 1);
context.runOnContext(go -> {
final Buffer msg = Buffer.buffer("hello " + messageCount.getAndIncrement());
sender.apply(msg).onComplete(sendAttempt -> {
if (sendAttempt.failed()) {
LOGGER.error("error sending message {}", messageCount.get(), sendAttempt.cause());
}
if (messageCount.get() % 50 == 0) {
LOGGER.info("messages sent: " + messageCount.get());
}
messageHandlingCompleted.countDown();
if (errorMessagesExpected) {
if (sendAttempt.failed()) {
messageHandlingCompleted.countDown();
} else {
// wait til error message has been received
final String correlationId = sendAttempt.result();
final long timerId = vertx.setTimer(1000, tid -> {
Optional.ofNullable(pendingErrorMessages.remove(correlationId)).ifPresent(promise -> promise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "timeout waiting for error response")));
});
final Handler<AsyncResult<Void>> errorMessageReceivedOrTimeoutHandler = ar -> {
vertx.cancelTimer(timerId);
if (ar.succeeded()) {
received.countDown();
lastReceivedTimestamp.set(System.currentTimeMillis());
if (received.getCount() % 50 == 0) {
LOGGER.info("error messages received: {}", MESSAGES_TO_SEND - received.getCount());
}
} else {
LOGGER.warn("failed to handle error message with correlation id [{}]", correlationId, ar.cause());
}
messageHandlingCompleted.countDown();
};
pendingErrorMessages.compute(correlationId, (key, oldValue) -> {
final Promise<Void> promise = Optional.ofNullable(oldValue).orElseGet(Promise::promise);
promise.future().onComplete(errorMessageReceivedOrTimeoutHandler);
// remove mapping if oldValue is set
return oldValue != null ? null : promise;
});
}
}
});
});
messageHandlingCompleted.await();
}
// in case no consumer is set, waiting time needs to be longer (adapter will wait for credit when creating the first downstream sender)
final long timeToWait = getTimeToWait() + (!consumerIsSet.get() ? 2000 : 0);
if (!received.await(timeToWait, TimeUnit.MILLISECONDS)) {
LOGGER.info("Timeout of {} milliseconds reached, stop waiting to receive messages.", timeToWait);
}
if (lastReceivedTimestamp.get() == 0L) {
// no message has been received at all
lastReceivedTimestamp.set(System.currentTimeMillis());
}
final long messagesReceived = MESSAGES_TO_SEND - received.getCount();
LOGGER.info("sent {} and received {} messages in {} milliseconds", messageCount.get(), messagesReceived, lastReceivedTimestamp.get() - start);
assertMessageReceivedRatio(messagesReceived, messageCount.get(), ctx);
}
Aggregations