Search in sources :

Example 36 with Promise

use of io.vertx.core.Promise in project hono by eclipse.

the class HonoConnectionImplTest method testConnectFailsIfAnotherConnectAttemptIsScheduled.

/**
 * Verifies that a connection attempt by the client is failed if it occurs while another connect invocation is
 * waiting for the next reconnect attempt.
 *
 * @param ctx The test execution context.
 */
@Test
public void testConnectFailsIfAnotherConnectAttemptIsScheduled(final VertxTestContext ctx) {
    final long reconnectMinDelay = 20L;
    // GIVEN a client that is configured to connect to a peer
    // to which the connection is only getting established on the 2nd attempt, after some delay.
    // the reconnect timer handler only shall get invoked on demand (after a corresponding promise gets completed)
    final AtomicBoolean reconnectTimerStarted = new AtomicBoolean();
    final Promise<Void> reconnectTimerContinuePromise = Promise.promise();
    when(vertx.setTimer(eq(reconnectMinDelay), any())).thenAnswer(invocation -> {
        reconnectTimerStarted.set(true);
        final Handler<Long> reconnectTimerHandler = invocation.getArgument(1);
        reconnectTimerContinuePromise.future().onComplete(ar -> reconnectTimerHandler.handle(0L));
        return 1L;
    });
    connectionFactory = new DisconnectHandlerProvidingConnectionFactory(con) {

        @Override
        public void connect(final ProtonClientOptions options, final String username, final String password, final String containerId, final Handler<AsyncResult<ProtonConnection>> closeHandler, final Handler<ProtonConnection> disconnectHandler, final Handler<AsyncResult<ProtonConnection>> connectionResultHandler) {
            super.connect(options, username, password, containerId, closeHandler, disconnectHandler, connectionResultHandler);
        }
    };
    connectionFactory.setExpectedFailingConnectionAttempts(1);
    props.setReconnectMinDelay(reconnectMinDelay);
    honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
    // WHEN trying to connect
    final Future<HonoConnection> connectFuture = honoConnection.connect();
    assertThat(reconnectTimerStarted.get()).isTrue();
    // and starting another connect attempt before the connection has been established
    final Future<HonoConnection> connectFuture2 = honoConnection.connect();
    // and letting the first attempt finish
    reconnectTimerContinuePromise.complete();
    // THEN the first connect invocation succeeds and the second is failed
    connectFuture.onComplete(ctx.succeeding(cause -> {
        ctx.verify(() -> {
            assertThat(connectFuture2.failed()).isTrue();
            assertThat(connectFuture2.cause()).isInstanceOf(ClientErrorException.class);
            assertThat(((ClientErrorException) connectFuture2.cause()).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_CONFLICT);
        });
        ctx.completeNow();
    }));
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) BeforeEach(org.junit.jupiter.api.BeforeEach) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) Context(io.vertx.core.Context) Timeout(io.vertx.junit5.Timeout) ConnectionFactory(org.eclipse.hono.connection.ConnectionFactory) SaslSystemException(io.vertx.proton.sasl.SaslSystemException) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) Mockito.doAnswer(org.mockito.Mockito.doAnswer) DisconnectListener(org.eclipse.hono.client.DisconnectListener) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) ClientConfigProperties(org.eclipse.hono.config.ClientConfigProperties) Predicate(java.util.function.Predicate) ProtonQoS(io.vertx.proton.ProtonQoS) VertxExtension(io.vertx.junit5.VertxExtension) Future(io.vertx.core.Future) AdditionalAnswers(org.mockito.AdditionalAnswers) Test(org.junit.jupiter.api.Test) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) VertxMockSupport(org.eclipse.hono.test.VertxMockSupport) ProtonSender(io.vertx.proton.ProtonSender) Mockito.mock(org.mockito.Mockito.mock) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) VertxTestContext(io.vertx.junit5.VertxTestContext) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Target(org.apache.qpid.proton.amqp.messaging.Target) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Supplier(java.util.function.Supplier) CompositeFuture(io.vertx.core.CompositeFuture) ProtonSession(io.vertx.proton.ProtonSession) TelemetryConstants(org.eclipse.hono.util.TelemetryConstants) ArgumentCaptor(org.mockito.ArgumentCaptor) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) Symbol(org.apache.qpid.proton.amqp.Symbol) BiConsumer(java.util.function.BiConsumer) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) AsyncResult(io.vertx.core.AsyncResult) HonoConnection(org.eclipse.hono.client.HonoConnection) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Mockito.times(org.mockito.Mockito.times) Mockito.when(org.mockito.Mockito.when) Truth.assertThat(com.google.common.truth.Truth.assertThat) Mockito.verify(org.mockito.Mockito.verify) TimeUnit(java.util.concurrent.TimeUnit) Mockito.never(org.mockito.Mockito.never) Source(org.apache.qpid.proton.amqp.transport.Source) Handler(io.vertx.core.Handler) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) HonoConnection(org.eclipse.hono.client.HonoConnection) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) ProtonConnection(io.vertx.proton.ProtonConnection) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) AsyncResult(io.vertx.core.AsyncResult) Test(org.junit.jupiter.api.Test)

Example 37 with Promise

use of io.vertx.core.Promise in project hono by eclipse.

the class HonoConnectionImplTest method testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts.

/**
 * Verifies that when the client tries to re-connect to a server instance if the
 * connection is closed by the peer, the configured number of reconnect attempts is taken
 * into account, skipping the reconnect if that number is zero.
 *
 * @param ctx The test context.
 */
@Test
public void testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts(final VertxTestContext ctx) {
    // GIVEN a client that is connected to a server but should do no automatic reconnect
    props.setReconnectAttempts(0);
    honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
    final Promise<HonoConnection> connected = Promise.promise();
    @SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
    honoConnection.addDisconnectListener(disconnectListener);
    final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
    honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
    honoConnection.connect(new ProtonClientOptions().setReconnectAttempts(0)).onComplete(connected);
    connected.future().onComplete(ctx.succeeding(c -> {
        // WHEN the peer closes the connection
        connectionFactory.getCloseHandler().handle(Future.failedFuture("shutting down for maintenance"));
        ctx.verify(() -> {
            // THEN the client invokes the registered disconnect handler
            verify(disconnectListener).onDisconnect(honoConnection);
            // and the original connection has been closed locally
            verify(con).close();
            verify(con).disconnectHandler(null);
            // and no further connect invocation has been done
            assertThat(connectionFactory.getConnectInvocations()).isEqualTo(1);
            assertThat(reconnectListenerInvocations.get()).isEqualTo(0);
        });
        ctx.completeNow();
    }));
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) BeforeEach(org.junit.jupiter.api.BeforeEach) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) Context(io.vertx.core.Context) Timeout(io.vertx.junit5.Timeout) ConnectionFactory(org.eclipse.hono.connection.ConnectionFactory) SaslSystemException(io.vertx.proton.sasl.SaslSystemException) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) Mockito.doAnswer(org.mockito.Mockito.doAnswer) DisconnectListener(org.eclipse.hono.client.DisconnectListener) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) ClientConfigProperties(org.eclipse.hono.config.ClientConfigProperties) Predicate(java.util.function.Predicate) ProtonQoS(io.vertx.proton.ProtonQoS) VertxExtension(io.vertx.junit5.VertxExtension) Future(io.vertx.core.Future) AdditionalAnswers(org.mockito.AdditionalAnswers) Test(org.junit.jupiter.api.Test) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) VertxMockSupport(org.eclipse.hono.test.VertxMockSupport) ProtonSender(io.vertx.proton.ProtonSender) Mockito.mock(org.mockito.Mockito.mock) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) VertxTestContext(io.vertx.junit5.VertxTestContext) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Target(org.apache.qpid.proton.amqp.messaging.Target) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Supplier(java.util.function.Supplier) CompositeFuture(io.vertx.core.CompositeFuture) ProtonSession(io.vertx.proton.ProtonSession) TelemetryConstants(org.eclipse.hono.util.TelemetryConstants) ArgumentCaptor(org.mockito.ArgumentCaptor) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) Symbol(org.apache.qpid.proton.amqp.Symbol) BiConsumer(java.util.function.BiConsumer) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) AsyncResult(io.vertx.core.AsyncResult) HonoConnection(org.eclipse.hono.client.HonoConnection) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Mockito.times(org.mockito.Mockito.times) Mockito.when(org.mockito.Mockito.when) Truth.assertThat(com.google.common.truth.Truth.assertThat) Mockito.verify(org.mockito.Mockito.verify) TimeUnit(java.util.concurrent.TimeUnit) Mockito.never(org.mockito.Mockito.never) Source(org.apache.qpid.proton.amqp.transport.Source) Handler(io.vertx.core.Handler) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) HonoConnection(org.eclipse.hono.client.HonoConnection) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) Test(org.junit.jupiter.api.Test)

Example 38 with Promise

use of io.vertx.core.Promise in project hono by eclipse.

the class HonoConnectionImplTest method testRemoteSessionCloseTriggersReconnection.

/**
 * Verifies that the client tries to reconnect to the peer if the peer
 * closes the connection's session.
 *
 * @param ctx The test context.
 */
@Test
public void testRemoteSessionCloseTriggersReconnection(final VertxTestContext ctx) {
    // GIVEN a client that is connected to a server
    final Promise<HonoConnection> connected = Promise.promise();
    @SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
    honoConnection.addDisconnectListener(disconnectListener);
    final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
    honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
    props.setServerRole("service-provider");
    honoConnection.connect(new ProtonClientOptions()).onComplete(connected);
    connectionFactory.setExpectedSucceedingConnectionAttempts(1);
    connected.future().onComplete(ctx.succeeding(c -> {
        ctx.verify(() -> {
            // WHEN the peer closes the session
            final ArgumentCaptor<Handler<AsyncResult<ProtonSession>>> sessionCloseHandler = VertxMockSupport.argumentCaptorHandler();
            verify(session).closeHandler(sessionCloseHandler.capture());
            sessionCloseHandler.getValue().handle(Future.succeededFuture(session));
            // THEN the client invokes the registered disconnect handler
            verify(disconnectListener).onDisconnect(honoConnection);
            // and the original connection has been closed locally
            verify(con).close();
            verify(con).disconnectHandler(null);
            // and the connection is re-established
            assertThat(connectionFactory.await()).isTrue();
            assertThat(reconnectListenerInvocations.get()).isEqualTo(1);
        });
        ctx.completeNow();
    }));
}
Also used : HttpURLConnection(java.net.HttpURLConnection) ProtonConnection(io.vertx.proton.ProtonConnection) ProtonReceiver(io.vertx.proton.ProtonReceiver) BeforeEach(org.junit.jupiter.api.BeforeEach) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) Context(io.vertx.core.Context) Timeout(io.vertx.junit5.Timeout) ConnectionFactory(org.eclipse.hono.connection.ConnectionFactory) SaslSystemException(io.vertx.proton.sasl.SaslSystemException) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonMessageHandler(io.vertx.proton.ProtonMessageHandler) Mockito.doAnswer(org.mockito.Mockito.doAnswer) DisconnectListener(org.eclipse.hono.client.DisconnectListener) AmqpError(org.apache.qpid.proton.amqp.transport.AmqpError) ClientConfigProperties(org.eclipse.hono.config.ClientConfigProperties) Predicate(java.util.function.Predicate) ProtonQoS(io.vertx.proton.ProtonQoS) VertxExtension(io.vertx.junit5.VertxExtension) Future(io.vertx.core.Future) AdditionalAnswers(org.mockito.AdditionalAnswers) Test(org.junit.jupiter.api.Test) ErrorCondition(org.apache.qpid.proton.amqp.transport.ErrorCondition) VertxMockSupport(org.eclipse.hono.test.VertxMockSupport) ProtonSender(io.vertx.proton.ProtonSender) Mockito.mock(org.mockito.Mockito.mock) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) VertxTestContext(io.vertx.junit5.VertxTestContext) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Target(org.apache.qpid.proton.amqp.messaging.Target) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Supplier(java.util.function.Supplier) CompositeFuture(io.vertx.core.CompositeFuture) ProtonSession(io.vertx.proton.ProtonSession) TelemetryConstants(org.eclipse.hono.util.TelemetryConstants) ArgumentCaptor(org.mockito.ArgumentCaptor) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) Symbol(org.apache.qpid.proton.amqp.Symbol) BiConsumer(java.util.function.BiConsumer) UnsignedLong(org.apache.qpid.proton.amqp.UnsignedLong) AsyncResult(io.vertx.core.AsyncResult) HonoConnection(org.eclipse.hono.client.HonoConnection) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Mockito.times(org.mockito.Mockito.times) Mockito.when(org.mockito.Mockito.when) Truth.assertThat(com.google.common.truth.Truth.assertThat) Mockito.verify(org.mockito.Mockito.verify) TimeUnit(java.util.concurrent.TimeUnit) Mockito.never(org.mockito.Mockito.never) Source(org.apache.qpid.proton.amqp.transport.Source) Handler(io.vertx.core.Handler) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) HonoConnection(org.eclipse.hono.client.HonoConnection) ArgumentCaptor(org.mockito.ArgumentCaptor) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ProtonClientOptions(io.vertx.proton.ProtonClientOptions) AsyncResult(io.vertx.core.AsyncResult) Test(org.junit.jupiter.api.Test)

Example 39 with Promise

use of io.vertx.core.Promise in project hono by eclipse.

the class KafkaBasedCommandSenderTest method sendCommandAndReceiveResponse.

private void sendCommandAndReceiveResponse(final VertxTestContext ctx, final String correlationId, final Integer responseStatus, final String responsePayload, final boolean expectSuccess, final int expectedStatusCode) {
    final Context context = vertx.getOrCreateContext();
    final Promise<Void> onProducerRecordSentPromise = Promise.promise();
    mockProducer = new MockProducer<>(true, new StringSerializer(), new BufferSerializer()) {

        @Override
        public synchronized java.util.concurrent.Future<RecordMetadata> send(final ProducerRecord<String, Buffer> record, final Callback callback) {
            return super.send(record, (metadata, exception) -> {
                callback.onCompletion(metadata, exception);
                context.runOnContext(v -> {
                    // decouple from current execution in order to run after the "send" result handler
                    onProducerRecordSentPromise.complete();
                });
            });
        }
    };
    final var producerFactory = CachingKafkaProducerFactory.testFactory(vertx, (n, c) -> KafkaClientUnitTestHelper.newKafkaProducer(mockProducer));
    commandSender = new KafkaBasedCommandSender(vertx, consumerConfig, producerFactory, producerConfig, NoopTracerFactory.create());
    final Map<String, Object> headerProperties = new HashMap<>();
    headerProperties.put("appKey", "appValue");
    final String command = "setVolume";
    final ConsumerRecord<String, Buffer> commandResponseRecord = commandResponseRecord(tenantId, deviceId, correlationId, responseStatus, Buffer.buffer(responsePayload));
    final String responseTopic = new HonoTopic(HonoTopic.Type.COMMAND_RESPONSE, tenantId).toString();
    final TopicPartition responseTopicPartition = new TopicPartition(responseTopic, 0);
    mockConsumer.setRebalancePartitionAssignmentAfterSubscribe(List.of(responseTopicPartition));
    mockConsumer.updatePartitions(responseTopicPartition, KafkaMockConsumer.DEFAULT_NODE);
    mockConsumer.updateBeginningOffsets(Map.of(responseTopicPartition, 0L));
    mockConsumer.updateEndOffsets(Map.of(responseTopicPartition, 0L));
    onProducerRecordSentPromise.future().onComplete(ar -> {
        LOG.debug("producer record sent, add command response record to mockConsumer");
        // Send a command response with the same correlation id as that of the command
        mockConsumer.addRecord(commandResponseRecord);
    });
    // This correlation id is used for both command and its response.
    commandSender.setCorrelationIdSupplier(() -> correlationId);
    commandSender.setKafkaConsumerSupplier(() -> mockConsumer);
    context.runOnContext(v -> {
        // Send a command to the device
        commandSender.sendCommand(tenantId, deviceId, command, "text/plain", Buffer.buffer("test"), headerProperties).onComplete(ar -> {
            ctx.verify(() -> {
                if (expectSuccess) {
                    // assert that send operation succeeded
                    assertThat(ar.succeeded()).isTrue();
                    // Verify the command response that has been received
                    final DownstreamMessage<KafkaMessageContext> response = ar.result();
                    assertThat(response.getDeviceId()).isEqualTo(deviceId);
                    assertThat(response.getStatus()).isEqualTo(responseStatus);
                    assertThat(response.getPayload().toString()).isEqualTo(responsePayload);
                } else {
                    // assert that send operation failed
                    assertThat(ar.succeeded()).isFalse();
                    assertThat(ar.cause()).isInstanceOf(ServiceInvocationException.class);
                    assertThat(((ServiceInvocationException) ar.cause()).getErrorCode()).isEqualTo(expectedStatusCode);
                    assertThat(ar.cause().getMessage()).isEqualTo(responsePayload);
                }
            });
            ctx.completeNow();
            mockConsumer.close();
            commandSender.stop();
        });
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) BeforeEach(org.junit.jupiter.api.BeforeEach) MessagingKafkaConsumerConfigProperties(org.eclipse.hono.client.kafka.consumer.MessagingKafkaConsumerConfigProperties) DownstreamMessage(org.eclipse.hono.application.client.DownstreamMessage) MessagingKafkaProducerConfigProperties(org.eclipse.hono.client.kafka.producer.MessagingKafkaProducerConfigProperties) KafkaMessageContext(org.eclipse.hono.application.client.kafka.KafkaMessageContext) LoggerFactory(org.slf4j.LoggerFactory) OffsetResetStrategy(org.apache.kafka.clients.consumer.OffsetResetStrategy) Context(io.vertx.core.Context) Timeout(io.vertx.junit5.Timeout) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) Duration(java.time.Duration) Map(java.util.Map) StringSerializer(org.apache.kafka.common.serialization.StringSerializer) TracingMockSupport(org.eclipse.hono.test.TracingMockSupport) JsonObject(io.vertx.core.json.JsonObject) TimestampType(org.apache.kafka.common.record.TimestampType) TopicPartition(org.apache.kafka.common.TopicPartition) KafkaMockConsumer(org.eclipse.hono.kafka.test.KafkaMockConsumer) CachingKafkaProducerFactory(org.eclipse.hono.client.kafka.producer.CachingKafkaProducerFactory) UUID(java.util.UUID) RecordMetadata(org.apache.kafka.clients.producer.RecordMetadata) MessageHelper(org.eclipse.hono.util.MessageHelper) VertxExtension(io.vertx.junit5.VertxExtension) Test(org.junit.jupiter.api.Test) List(java.util.List) Buffer(io.vertx.core.buffer.Buffer) Header(org.apache.kafka.common.header.Header) ConsumerRecord(org.apache.kafka.clients.consumer.ConsumerRecord) Span(io.opentracing.Span) Callback(org.apache.kafka.clients.producer.Callback) VertxTestContext(io.vertx.junit5.VertxTestContext) ProducerRecord(org.apache.kafka.clients.producer.ProducerRecord) BufferSerializer(io.vertx.kafka.client.serialization.BufferSerializer) HashMap(java.util.HashMap) RecordHeader(org.apache.kafka.common.header.internals.RecordHeader) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) ArrayList(java.util.ArrayList) RecordHeaders(org.apache.kafka.common.header.internals.RecordHeaders) KafkaClientUnitTestHelper(org.eclipse.hono.kafka.test.KafkaClientUnitTestHelper) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) NoopTracerFactory(io.opentracing.noop.NoopTracerFactory) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) Truth.assertThat(com.google.common.truth.Truth.assertThat) Mockito.verify(org.mockito.Mockito.verify) TimeUnit(java.util.concurrent.TimeUnit) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) AfterEach(org.junit.jupiter.api.AfterEach) SendMessageTimeoutException(org.eclipse.hono.client.SendMessageTimeoutException) NoopSpan(io.opentracing.noop.NoopSpan) MockProducer(org.apache.kafka.clients.producer.MockProducer) KafkaMessageContext(org.eclipse.hono.application.client.kafka.KafkaMessageContext) BufferSerializer(io.vertx.kafka.client.serialization.BufferSerializer) HashMap(java.util.HashMap) ServiceInvocationException(org.eclipse.hono.client.ServiceInvocationException) StringSerializer(org.apache.kafka.common.serialization.StringSerializer) KafkaMessageContext(org.eclipse.hono.application.client.kafka.KafkaMessageContext) Context(io.vertx.core.Context) VertxTestContext(io.vertx.junit5.VertxTestContext) Buffer(io.vertx.core.buffer.Buffer) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) Callback(org.apache.kafka.clients.producer.Callback) TopicPartition(org.apache.kafka.common.TopicPartition) JsonObject(io.vertx.core.json.JsonObject)

Example 40 with Promise

use of io.vertx.core.Promise in project hono by eclipse.

the class RequestResponseClient method sendRequest.

/**
 * Sends a request message via this client's sender link to the peer.
 * <p>
 * This method first checks if the sender has any credit left. If not, the result handler is failed immediately.
 * Otherwise, the request message is sent and a timer is started which fails the result handler,
 * if no response is received within <em>requestTimeoutMillis</em> milliseconds.
 * <p>
 * The given span is never finished by this method.
 *
 * @param request The message to send.
 * @param responseMapper A function mapping a raw AMQP message and a proton delivery to the response type.
 * @param currentSpan The <em>Opentracing</em> span used to trace the request execution.
 * @return A future indicating the outcome of the operation.
 *         The future will be failed with a {@link ServerErrorException} if the request cannot be sent to
 *         the remote service, e.g. because there is no connection to the service or there are no credits
 *         available for sending the request or the request timed out.
 */
private Future<R> sendRequest(final Message request, final BiFunction<Message, ProtonDelivery, R> responseMapper, final Span currentSpan) {
    final String requestTargetAddress = Optional.ofNullable(request.getAddress()).orElse(linkTargetAddress);
    Tags.MESSAGE_BUS_DESTINATION.set(currentSpan, requestTargetAddress);
    Tags.SPAN_KIND.set(currentSpan, Tags.SPAN_KIND_CLIENT);
    Tags.HTTP_METHOD.set(currentSpan, request.getSubject());
    if (tenantId != null) {
        currentSpan.setTag(MessageHelper.APP_PROPERTY_TENANT_ID, tenantId);
    }
    return connection.executeOnContext((Promise<R> res) -> {
        if (sender.sendQueueFull()) {
            LOG.debug("cannot send request to peer, no credit left for link [link target: {}]", linkTargetAddress);
            res.fail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "no credit available for sending request"));
            sampler.queueFull(tenantId);
        } else {
            final Map<String, Object> details = new HashMap<>(3);
            final Object correlationId = Optional.ofNullable(request.getCorrelationId()).orElse(request.getMessageId());
            if (correlationId instanceof String) {
                details.put(TracingHelper.TAG_CORRELATION_ID.getKey(), correlationId);
            }
            details.put(TracingHelper.TAG_CREDIT.getKey(), sender.getCredit());
            details.put(TracingHelper.TAG_QOS.getKey(), sender.getQoS().toString());
            currentSpan.log(details);
            final TriTuple<Promise<R>, BiFunction<Message, ProtonDelivery, R>, Span> handler = TriTuple.of(res, responseMapper, currentSpan);
            TracingHelper.injectSpanContext(connection.getTracer(), currentSpan.context(), request);
            replyMap.put(correlationId, handler);
            final SendMessageSampler.Sample sample = sampler.start(tenantId);
            sender.send(request, deliveryUpdated -> {
                final Promise<R> failedResult = Promise.promise();
                final DeliveryState remoteState = deliveryUpdated.getRemoteState();
                sample.completed(remoteState);
                if (Rejected.class.isInstance(remoteState)) {
                    final Rejected rejected = (Rejected) remoteState;
                    if (rejected.getError() != null) {
                        LOG.debug("service did not accept request [target address: {}, subject: {}, correlation ID: {}]: {}", requestTargetAddress, request.getSubject(), correlationId, rejected.getError());
                        failedResult.fail(StatusCodeMapper.fromTransferError(rejected.getError()));
                        cancelRequest(correlationId, failedResult.future());
                    } else {
                        LOG.debug("service did not accept request [target address: {}, subject: {}, correlation ID: {}]", requestTargetAddress, request.getSubject(), correlationId);
                        failedResult.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST));
                        cancelRequest(correlationId, failedResult.future());
                    }
                } else if (Accepted.class.isInstance(remoteState)) {
                    LOG.trace("service has accepted request [target address: {}, subject: {}, correlation ID: {}]", requestTargetAddress, request.getSubject(), correlationId);
                    currentSpan.log("request accepted by peer");
                    // if no reply-to is set, the request is assumed to be one-way (no response is expected)
                    if (request.getReplyTo() == null) {
                        if (replyMap.remove(correlationId) != null) {
                            res.complete();
                        } else {
                            LOG.trace("accepted request won't be acted upon, request already cancelled [target address: {}, subject: {}, correlation ID: {}]", requestTargetAddress, request.getSubject(), correlationId);
                        }
                    }
                } else if (Released.class.isInstance(remoteState)) {
                    LOG.debug("service did not accept request [target address: {}, subject: {}, correlation ID: {}], remote state: {}", requestTargetAddress, request.getSubject(), correlationId, remoteState);
                    failedResult.fail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
                    cancelRequest(correlationId, failedResult.future());
                } else if (Modified.class.isInstance(remoteState)) {
                    LOG.debug("service did not accept request [target address: {}, subject: {}, correlation ID: {}], remote state: {}", requestTargetAddress, request.getSubject(), correlationId, remoteState);
                    final Modified modified = (Modified) deliveryUpdated.getRemoteState();
                    failedResult.fail(modified.getUndeliverableHere() ? new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND) : new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
                    cancelRequest(correlationId, failedResult.future());
                } else if (remoteState == null) {
                    // possible scenario here: sender link got closed while waiting on the delivery update
                    final String furtherInfo = !sender.isOpen() ? ", sender link was closed in between" : "";
                    LOG.warn("got undefined delivery state for service request{} [target address: {}, subject: {}, correlation ID: {}]", furtherInfo, requestTargetAddress, request.getSubject(), correlationId);
                    failedResult.fail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
                    cancelRequest(correlationId, failedResult.future());
                }
            });
            if (requestTimeoutMillis > 0) {
                connection.getVertx().setTimer(requestTimeoutMillis, tid -> {
                    if (cancelRequest(correlationId, () -> new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "request timed out after " + requestTimeoutMillis + "ms"))) {
                        sample.timeout();
                    }
                });
            }
            if (LOG.isDebugEnabled()) {
                final String deviceId = MessageHelper.getDeviceId(request);
                if (deviceId == null) {
                    LOG.debug("sent request [target address: {}, subject: {}, correlation ID: {}] to service", requestTargetAddress, request.getSubject(), correlationId);
                } else {
                    LOG.debug("sent request [target address: {}, subject: {}, correlation ID: {}, device ID: {}] to service", requestTargetAddress, request.getSubject(), correlationId, deviceId);
                }
            }
        }
    });
}
Also used : Modified(org.apache.qpid.proton.amqp.messaging.Modified) HashMap(java.util.HashMap) Rejected(org.apache.qpid.proton.amqp.messaging.Rejected) Span(io.opentracing.Span) Accepted(org.apache.qpid.proton.amqp.messaging.Accepted) Promise(io.vertx.core.Promise) DeliveryState(org.apache.qpid.proton.amqp.transport.DeliveryState) BiFunction(java.util.function.BiFunction) ClientErrorException(org.eclipse.hono.client.ClientErrorException) ServerErrorException(org.eclipse.hono.client.ServerErrorException) SendMessageSampler(org.eclipse.hono.client.SendMessageSampler)

Aggregations

Promise (io.vertx.core.Promise)155 Future (io.vertx.core.Future)122 Handler (io.vertx.core.Handler)95 List (java.util.List)86 Vertx (io.vertx.core.Vertx)85 Buffer (io.vertx.core.buffer.Buffer)83 TimeUnit (java.util.concurrent.TimeUnit)79 HttpURLConnection (java.net.HttpURLConnection)66 Logger (org.slf4j.Logger)63 LoggerFactory (org.slf4j.LoggerFactory)63 Optional (java.util.Optional)62 AsyncResult (io.vertx.core.AsyncResult)61 Truth.assertThat (com.google.common.truth.Truth.assertThat)60 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)60 VertxTestContext (io.vertx.junit5.VertxTestContext)59 Test (org.junit.jupiter.api.Test)58 Map (java.util.Map)54 UUID (java.util.UUID)52 ArrayList (java.util.ArrayList)51 JsonObject (io.vertx.core.json.JsonObject)50