Search in sources :

Example 1 with TimeUntilDisconnectNotification

use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.

the class DownstreamMessage method getTimeUntilDisconnectNotification.

/**
 * Returns the time until disconnection notification of this downstream message.
 *
 * @return A notification if the message contains a TTD value {@link Optional#empty()} otherwise.
 */
default Optional<TimeUntilDisconnectNotification> getTimeUntilDisconnectNotification() {
    final Integer ttd = getTimeTillDisconnect();
    final Instant creationTime = getCreationTime();
    if (ttd == null) {
        return Optional.empty();
    } else if (ttd == 0 || TimeUntilDisconnectNotification.isDeviceCurrentlyConnected(ttd, creationTime != null ? creationTime.toEpochMilli() : null)) {
        final String tenantId = getTenantId();
        final String deviceId = getDeviceId();
        if (tenantId != null && deviceId != null) {
            final TimeUntilDisconnectNotification notification = new TimeUntilDisconnectNotification(tenantId, deviceId, ttd, creationTime);
            return Optional.of(notification);
        }
    }
    return Optional.empty();
}
Also used : Instant(java.time.Instant) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification)

Example 2 with TimeUntilDisconnectNotification

use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.

the class CommandAndControlMqttIT method testSendCommandFailsForCommandNotAcknowledgedByDevice.

/**
 * Verifies that the adapter forwards the <em>released</em> disposition back to the
 * application if the device hasn't sent an acknowledgement for the command message
 * published to the device.
 *
 * @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 = 20)
public void testSendCommandFailsForCommandNotAcknowledgedByDevice(final MqttCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
    final MqttQoS subscribeQos = MqttQoS.AT_LEAST_ONCE;
    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint ready = setup.checkpoint(2);
    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
    final int totalNoOfCommandsToSend = 2;
    final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
    final AtomicInteger receivedMessagesCounter = new AtomicInteger(0);
    final AtomicInteger counter = new AtomicInteger();
    final Handler<MqttPublishMessage> commandConsumer = msg -> {
        LOGGER.trace("received command [{}] - no response sent here", msg.topicName());
        final ResourceIdentifier topic = ResourceIdentifier.fromString(msg.topicName());
        ctx.verify(() -> {
            endpointConfig.assertCommandPublishTopicStructure(topic, commandTargetDeviceId, false, "setValue");
        });
        receivedMessagesCounter.incrementAndGet();
    };
    final Function<Buffer, Future<Void>> commandSender = payload -> {
        counter.incrementAndGet();
        return helper.sendCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", payload, helper.getSendCommandTimeout(counter.get() == 1)).mapEmpty();
    };
    helper.registry.addDeviceToTenant(tenantId, deviceId, password).compose(ok -> connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password)).compose(ok -> injectMqttClientPubAckBlocker(new AtomicBoolean(true))).compose(ok -> createConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
        LOGGER.info("received notification [{}]", notification);
        setup.verify(() -> assertThat(notification).isNotNull());
        if (notification.getTtd() == -1) {
            ready.flag();
        }
    })).compose(conAck -> subscribeToCommands(commandTargetDeviceId, commandConsumer, endpointConfig, subscribeQos)).onComplete(setup.succeeding(ok -> ready.flag()));
    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;
    }
    final AtomicInteger commandsSent = new AtomicInteger(0);
    final AtomicLong lastReceivedTimestamp = new AtomicLong(0);
    final long start = System.currentTimeMillis();
    while (commandsSent.get() < totalNoOfCommandsToSend) {
        final CountDownLatch commandSent = new CountDownLatch(1);
        context.runOnContext(go -> {
            final Buffer msg = Buffer.buffer("value: " + commandsSent.getAndIncrement());
            commandSender.apply(msg).onComplete(sendAttempt -> {
                if (sendAttempt.succeeded()) {
                    LOGGER.debug("sending command {} succeeded unexpectedly", commandsSent.get());
                } else {
                    if (sendAttempt.cause() instanceof ServerErrorException && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE && !(sendAttempt.cause() instanceof SendMessageTimeoutException)) {
                        LOGGER.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
                        lastReceivedTimestamp.set(System.currentTimeMillis());
                        commandsFailed.countDown();
                        if (commandsFailed.getCount() % 20 == 0) {
                            LOGGER.info("commands failed as expected: {}", totalNoOfCommandsToSend - commandsFailed.getCount());
                        }
                    } else {
                        LOGGER.debug("sending command {} failed with an unexpected error", commandsSent.get(), sendAttempt.cause());
                    }
                }
                commandSent.countDown();
            });
        });
        commandSent.await();
    }
    // have to wait an extra MqttAdapterProperties.DEFAULT_COMMAND_ACK_TIMEOUT (100ms) for each command message
    final long timeToWait = totalNoOfCommandsToSend * 300;
    if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
        LOGGER.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
    }
    assertThat(receivedMessagesCounter.get()).isEqualTo(totalNoOfCommandsToSend);
    final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
    LOGGER.info("commands sent: {}, commands failed: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
    if (commandsCompleted == commandsSent.get()) {
        ctx.completeNow();
    } else {
        ctx.failNow(new java.lang.IllegalStateException("did not complete all commands sent"));
    }
}
Also used : HttpURLConnection(java.net.HttpURLConnection) BeforeEach(org.junit.jupiter.api.BeforeEach) DownstreamMessage(org.eclipse.hono.application.client.DownstreamMessage) MqttPublishMessage(io.vertx.mqtt.messages.MqttPublishMessage) Timeout(io.vertx.junit5.Timeout) MessagingType(org.eclipse.hono.util.MessagingType) IntegrationTestSupport(org.eclipse.hono.tests.IntegrationTestSupport) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) ChannelPromise(io.netty.channel.ChannelPromise) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Map(java.util.Map) MqttClientImpl(io.vertx.mqtt.impl.MqttClientImpl) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) Method(java.lang.reflect.Method) MethodSource(org.junit.jupiter.params.provider.MethodSource) ChannelOutboundHandlerAdapter(io.netty.channel.ChannelOutboundHandlerAdapter) MessageContext(org.eclipse.hono.application.client.MessageContext) SubscriberRole(org.eclipse.hono.tests.CommandEndpointConfiguration.SubscriberRole) Truth.assertWithMessage(com.google.common.truth.Truth.assertWithMessage) IllegalStateException(javax.jms.IllegalStateException) MessageHelper(org.eclipse.hono.util.MessageHelper) VertxExtension(io.vertx.junit5.VertxExtension) EventConstants(org.eclipse.hono.util.EventConstants) Future(io.vertx.core.Future) CountDownLatch(java.util.concurrent.CountDownLatch) Stream(java.util.stream.Stream) Buffer(io.vertx.core.buffer.Buffer) Checkpoint(io.vertx.junit5.Checkpoint) NetSocketInternal(io.vertx.core.net.impl.NetSocketInternal) VertxTestContext(io.vertx.junit5.VertxTestContext) GenericKafkaSender(org.eclipse.hono.tests.GenericKafkaSender) MqttQoS(io.netty.handler.codec.mqtt.MqttQoS) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) Message(org.apache.qpid.proton.message.Message) MqttPubAckMessage(io.netty.handler.codec.mqtt.MqttPubAckMessage) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Truth.assertThat(com.google.common.truth.Truth.assertThat) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) TimeUnit(java.util.concurrent.TimeUnit) AtomicLong(java.util.concurrent.atomic.AtomicLong) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MessageConsumer(org.eclipse.hono.application.client.MessageConsumer) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) NoopSpan(io.opentracing.noop.NoopSpan) GenericSenderLink(org.eclipse.hono.client.amqp.GenericSenderLink) Handler(io.vertx.core.Handler) Buffer(io.vertx.core.buffer.Buffer) VertxTestContext(io.vertx.junit5.VertxTestContext) CountDownLatch(java.util.concurrent.CountDownLatch) Checkpoint(io.vertx.junit5.Checkpoint) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Checkpoint(io.vertx.junit5.Checkpoint) AtomicLong(java.util.concurrent.atomic.AtomicLong) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) MqttPublishMessage(io.vertx.mqtt.messages.MqttPublishMessage) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) Future(io.vertx.core.Future) ServerErrorException(org.eclipse.hono.client.ServerErrorException) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) MqttQoS(io.netty.handler.codec.mqtt.MqttQoS) Timeout(io.vertx.junit5.Timeout) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MethodSource(org.junit.jupiter.params.provider.MethodSource)

Example 3 with TimeUntilDisconnectNotification

use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.

the class CommandAndControlMqttIT method testSendCommandViaAmqpFailsForMalformedMessage.

/**
 * Verifies that the adapter rejects malformed command messages sent by applications.
 * <p>
 * This test is applicable only if the messaging network type is AMQP.
 *
 * @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 = 20)
@AssumeMessagingSystem(type = MessagingType.amqp)
public void testSendCommandViaAmqpFailsForMalformedMessage(final MqttCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
    final AtomicReference<GenericSenderLink> amqpCmdSenderRef = new AtomicReference<>();
    final String linkTargetAddress = endpointConfig.getSenderLinkTargetAddress(tenantId);
    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint ready = setup.checkpoint(2);
    createConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
        LOGGER.info("received notification [{}]", notification);
        if (notification.getTtd() == -1) {
            ready.flag();
        }
    }).compose(consumer -> helper.registry.addDeviceToTenant(tenantId, deviceId, password)).compose(ok -> connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password)).compose(conAck -> subscribeToCommands(commandTargetDeviceId, msg -> {
        // all commands should get rejected because they fail to pass the validity check
        ctx.failNow(new IllegalStateException("should not have received command"));
    }, endpointConfig, MqttQoS.AT_MOST_ONCE)).compose(ok -> helper.createGenericAmqpMessageSender(endpointConfig.getNorthboundEndpoint(), tenantId)).onComplete(setup.succeeding(genericSender -> {
        LOGGER.debug("created generic sender for sending commands [target address: {}]", linkTargetAddress);
        amqpCmdSenderRef.set(genericSender);
        ready.flag();
    }));
    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;
    }
    final Checkpoint failedAttempts = ctx.checkpoint(2);
    final String messageAddress = endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId);
    LOGGER.debug("sending command message lacking subject");
    final Message messageWithoutSubject = ProtonHelper.message("input data");
    messageWithoutSubject.setAddress(messageAddress);
    messageWithoutSubject.setMessageId("message-id");
    messageWithoutSubject.setReplyTo("reply/to/address");
    amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutSubject, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
        ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
        failedAttempts.flag();
    }));
    LOGGER.debug("sending command message lacking message ID and correlation ID");
    final Message messageWithoutId = ProtonHelper.message("input data");
    messageWithoutId.setAddress(messageAddress);
    messageWithoutId.setSubject("setValue");
    messageWithoutId.setReplyTo("reply/to/address");
    amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutId, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
        ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
        failedAttempts.flag();
    }));
}
Also used : HttpURLConnection(java.net.HttpURLConnection) BeforeEach(org.junit.jupiter.api.BeforeEach) DownstreamMessage(org.eclipse.hono.application.client.DownstreamMessage) MqttPublishMessage(io.vertx.mqtt.messages.MqttPublishMessage) Timeout(io.vertx.junit5.Timeout) MessagingType(org.eclipse.hono.util.MessagingType) IntegrationTestSupport(org.eclipse.hono.tests.IntegrationTestSupport) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) ChannelPromise(io.netty.channel.ChannelPromise) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Map(java.util.Map) MqttClientImpl(io.vertx.mqtt.impl.MqttClientImpl) ResourceIdentifier(org.eclipse.hono.util.ResourceIdentifier) Method(java.lang.reflect.Method) MethodSource(org.junit.jupiter.params.provider.MethodSource) ChannelOutboundHandlerAdapter(io.netty.channel.ChannelOutboundHandlerAdapter) MessageContext(org.eclipse.hono.application.client.MessageContext) SubscriberRole(org.eclipse.hono.tests.CommandEndpointConfiguration.SubscriberRole) Truth.assertWithMessage(com.google.common.truth.Truth.assertWithMessage) IllegalStateException(javax.jms.IllegalStateException) MessageHelper(org.eclipse.hono.util.MessageHelper) VertxExtension(io.vertx.junit5.VertxExtension) EventConstants(org.eclipse.hono.util.EventConstants) Future(io.vertx.core.Future) CountDownLatch(java.util.concurrent.CountDownLatch) Stream(java.util.stream.Stream) Buffer(io.vertx.core.buffer.Buffer) Checkpoint(io.vertx.junit5.Checkpoint) NetSocketInternal(io.vertx.core.net.impl.NetSocketInternal) VertxTestContext(io.vertx.junit5.VertxTestContext) GenericKafkaSender(org.eclipse.hono.tests.GenericKafkaSender) MqttQoS(io.netty.handler.codec.mqtt.MqttQoS) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) Message(org.apache.qpid.proton.message.Message) MqttPubAckMessage(io.netty.handler.codec.mqtt.MqttPubAckMessage) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Truth.assertThat(com.google.common.truth.Truth.assertThat) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) TimeUnit(java.util.concurrent.TimeUnit) AtomicLong(java.util.concurrent.atomic.AtomicLong) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MessageConsumer(org.eclipse.hono.application.client.MessageConsumer) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) NoopSpan(io.opentracing.noop.NoopSpan) GenericSenderLink(org.eclipse.hono.client.amqp.GenericSenderLink) Handler(io.vertx.core.Handler) IllegalStateException(javax.jms.IllegalStateException) Checkpoint(io.vertx.junit5.Checkpoint) DownstreamMessage(org.eclipse.hono.application.client.DownstreamMessage) MqttPublishMessage(io.vertx.mqtt.messages.MqttPublishMessage) Truth.assertWithMessage(com.google.common.truth.Truth.assertWithMessage) Message(org.apache.qpid.proton.message.Message) MqttPubAckMessage(io.netty.handler.codec.mqtt.MqttPubAckMessage) VertxTestContext(io.vertx.junit5.VertxTestContext) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) GenericSenderLink(org.eclipse.hono.client.amqp.GenericSenderLink) AtomicReference(java.util.concurrent.atomic.AtomicReference) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) Timeout(io.vertx.junit5.Timeout) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MethodSource(org.junit.jupiter.params.provider.MethodSource)

Example 4 with TimeUntilDisconnectNotification

use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.

the class CommandAndControlAmqpIT method testSendCommandFailsWhenNoCredit.

/**
 * Verifies that the adapter immediately forwards the <em>released</em> disposition
 * if there is no credit left for sending the command to the device.
 * <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 testSendCommandFailsWhenNoCredit(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
    final String firstCommandSubject = "firstCommandSubject";
    final Promise<Void> firstCommandReceived = Promise.promise();
    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint setupDone = setup.checkpoint();
    final Checkpoint preconditions = setup.checkpoint(1);
    connectToAdapter(tenantId, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
        log.info("received notification [{}]", notification);
        setup.verify(() -> assertThat(notification).isNotNull());
        if (notification.getTtd() == -1) {
            preconditions.flag();
        }
    })).compose(con -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId)).onSuccess(recv -> {
        recv.handler((delivery, msg) -> {
            log.info("received command [name: {}, reply-to: {}, correlation-id: {}]", msg.getSubject(), msg.getReplyTo(), msg.getCorrelationId());
            ctx.verify(() -> {
                assertThat(msg.getSubject()).isEqualTo(firstCommandSubject);
            });
            firstCommandReceived.complete();
            ProtonHelper.accepted(delivery, true);
        // don't send credits
        });
        // just give 1 initial credit
        recv.flow(1);
    }).onComplete(setup.succeeding(v -> setupDone.flag()));
    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;
    }
    // send first command
    helper.sendOneWayCommand(tenantId, commandTargetDeviceId, firstCommandSubject, "text/plain", Buffer.buffer("cmd"), helper.getSendCommandTimeout(true)).onFailure(ctx::failNow).compose(ok -> {
        log.info("sent first command [subject: {}]", firstCommandSubject);
        return firstCommandReceived.future();
    }).compose(ok -> helper.sendCommand(tenantId, commandTargetDeviceId, "secondCommandSubject", "text/plain", Buffer.buffer("cmd"), helper.getSendCommandTimeout(false))).onComplete(ctx.failing(t -> {
        ctx.verify(() -> {
            assertThat(t).isInstanceOf(ServerErrorException.class);
            assertThat(((ServerErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
            // with no explicit credit check, the AMQP adapter would just run into the
            // "waiting for delivery update" timeout (after 1s) and the error here would be caused
            // by a request timeout in the sendOneWayCommand() method above
            assertThat(t).isNotInstanceOf(SendMessageTimeoutException.class);
            assertThat(t.getMessage()).doesNotContain("timed out");
        });
        ctx.completeNow();
    }));
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) BeforeEach(org.junit.jupiter.api.BeforeEach) DownstreamMessage(org.eclipse.hono.application.client.DownstreamMessage) BiFunction(java.util.function.BiFunction) Tenant(org.eclipse.hono.service.management.tenant.Tenant) HonoProtonHelper(org.eclipse.hono.util.HonoProtonHelper) Timeout(io.vertx.junit5.Timeout) MessagingType(org.eclipse.hono.util.MessagingType) IntegrationTestSupport(org.eclipse.hono.tests.IntegrationTestSupport) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) Duration(java.time.Duration) Map(java.util.Map) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) MethodSource(org.junit.jupiter.params.provider.MethodSource) ResourceLimits(org.eclipse.hono.util.ResourceLimits) MessageContext(org.eclipse.hono.application.client.MessageContext) SubscriberRole(org.eclipse.hono.tests.CommandEndpointConfiguration.SubscriberRole) Truth.assertWithMessage(com.google.common.truth.Truth.assertWithMessage) DownstreamMessageAssertions(org.eclipse.hono.tests.DownstreamMessageAssertions) ProtonQoS(io.vertx.proton.ProtonQoS) MessageHelper(org.eclipse.hono.util.MessageHelper) VertxExtension(io.vertx.junit5.VertxExtension) EventConstants(org.eclipse.hono.util.EventConstants) Future(io.vertx.core.Future) Test(org.junit.jupiter.api.Test) CountDownLatch(java.util.concurrent.CountDownLatch) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) Stream(java.util.stream.Stream) Buffer(io.vertx.core.buffer.Buffer) Optional(java.util.Optional) ProtonSender(io.vertx.proton.ProtonSender) Checkpoint(io.vertx.junit5.Checkpoint) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted) VertxTestContext(io.vertx.junit5.VertxTestContext) GenericKafkaSender(org.eclipse.hono.tests.GenericKafkaSender) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) ClientErrorException(org.eclipse.hono.client.ClientErrorException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) Supplier(java.util.function.Supplier) Constants(org.eclipse.hono.util.Constants) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) Message(org.apache.qpid.proton.message.Message) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) ProtonHelper(io.vertx.proton.ProtonHelper) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Truth.assertThat(com.google.common.truth.Truth.assertThat) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) TimeUnit(java.util.concurrent.TimeUnit) AtomicLong(java.util.concurrent.atomic.AtomicLong) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MessageConsumer(org.eclipse.hono.application.client.MessageConsumer) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) NoopSpan(io.opentracing.noop.NoopSpan) GenericSenderLink(org.eclipse.hono.client.amqp.GenericSenderLink) Handler(io.vertx.core.Handler) Checkpoint(io.vertx.junit5.Checkpoint) VertxTestContext(io.vertx.junit5.VertxTestContext) TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification) ServerErrorException(org.eclipse.hono.client.ServerErrorException) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) Timeout(io.vertx.junit5.Timeout) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MethodSource(org.junit.jupiter.params.provider.MethodSource)

Example 5 with TimeUntilDisconnectNotification

use of org.eclipse.hono.util.TimeUntilDisconnectNotification in project hono by eclipse.

the class HonoExampleApplicationBase method handlePermanentlyConnectedCommandReadinessNotification.

/**
 * Handle a ttd notification for permanently connected devices.
 * <p>
 * Instead of immediately handling the notification, it is first put to a map and a timer is started to handle it
 * later. Notifications for the same device that are received before the timer expired, will overwrite the original
 * notification. By this an <em>event flickering</em> (like it could occur when starting the app while several
 * notifications were persisted in the messaging network) is handled correctly.
 * <p>
 * If the contained <em>ttd</em> is set to -1, a command will be sent periodically every
 * {@link HonoExampleConstants#COMMAND_INTERVAL_FOR_DEVICES_CONNECTED_WITH_UNLIMITED_EXPIRY} seconds to the device
 * until a new notification was received with a <em>ttd</em> set to 0.
 *
 * @param notification The notification of a permanently connected device to handle.
 */
private void handlePermanentlyConnectedCommandReadinessNotification(final TimeUntilDisconnectNotification notification) {
    final String keyForDevice = notification.getTenantAndDeviceId();
    final TimeUntilDisconnectNotification previousNotification = pendingTtdNotification.get(keyForDevice);
    if (previousNotification != null) {
        if (notification.getCreationTime().isAfter(previousNotification.getCreationTime())) {
            LOG.info("Set new ttd value [{}] of notification for [{}]", notification.getTtd(), notification.getTenantAndDeviceId());
            pendingTtdNotification.put(keyForDevice, notification);
        } else {
            LOG.trace("Received notification for [{}] that was already superseded by newer [{}]", notification, previousNotification);
        }
    } else {
        pendingTtdNotification.put(keyForDevice, notification);
        // there was no notification available already, so start a handler now
        vertx.setTimer(1000, timerId -> {
            LOG.debug("Handle device notification for [{}].", notification.getTenantAndDeviceId());
            // now take the notification from the pending map and handle it
            final TimeUntilDisconnectNotification notificationToHandle = pendingTtdNotification.remove(keyForDevice);
            if (notificationToHandle != null) {
                if (notificationToHandle.getTtd() == -1) {
                    LOG.info("Device notified as being ready to receive a command until further notice : [{}].", notificationToHandle);
                    // cancel a still existing timer for this device (if found)
                    cancelPeriodicCommandSender(notification);
                    // immediately send the first command
                    sendCommand(notificationToHandle);
                    // for devices that stay connected, start a periodic timer now that repeatedly sends a command
                    // to the device
                    vertx.setPeriodic((long) HonoExampleConstants.COMMAND_INTERVAL_FOR_DEVICES_CONNECTED_WITH_UNLIMITED_EXPIRY * 1000, id -> {
                        sendCommand(notificationToHandle);
                        // register a canceler for this timer directly after it was created
                        setPeriodicCommandSenderTimerCanceler(id, notification);
                    });
                } else {
                    LOG.info("Device notified as not being ready to receive a command (anymore) : [{}].", notification);
                    cancelPeriodicCommandSender(notificationToHandle);
                    LOG.debug("Device will not receive further commands : [{}].", notification.getTenantAndDeviceId());
                }
            }
        });
    }
}
Also used : TimeUntilDisconnectNotification(org.eclipse.hono.util.TimeUntilDisconnectNotification)

Aggregations

TimeUntilDisconnectNotification (org.eclipse.hono.util.TimeUntilDisconnectNotification)12 Future (io.vertx.core.Future)10 Handler (io.vertx.core.Handler)10 Buffer (io.vertx.core.buffer.Buffer)10 Map (java.util.Map)10 DownstreamMessage (org.eclipse.hono.application.client.DownstreamMessage)10 MessageConsumer (org.eclipse.hono.application.client.MessageConsumer)10 MessageContext (org.eclipse.hono.application.client.MessageContext)10 Truth.assertThat (com.google.common.truth.Truth.assertThat)8 Truth.assertWithMessage (com.google.common.truth.Truth.assertWithMessage)8 NoopSpan (io.opentracing.noop.NoopSpan)8 Promise (io.vertx.core.Promise)8 Checkpoint (io.vertx.junit5.Checkpoint)8 Timeout (io.vertx.junit5.Timeout)8 VertxExtension (io.vertx.junit5.VertxExtension)8 VertxTestContext (io.vertx.junit5.VertxTestContext)8 ProtonHelper (io.vertx.proton.ProtonHelper)8 HttpURLConnection (java.net.HttpURLConnection)8 CountDownLatch (java.util.concurrent.CountDownLatch)8 TimeUnit (java.util.concurrent.TimeUnit)8