Search in sources :

Example 11 with HonoKafkaConsumer

use of org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer in project hono by eclipse.

the class HonoKafkaConsumerIT method testConsumerReadsLatestRecordsPublishedAfterStart.

/**
 * Verifies that a HonoKafkaConsumer configured with "latest" as offset reset strategy only receives
 * records published after the consumer <em>start()</em> method has completed.
 *
 * @param partitionAssignmentStrategy The partition assignment strategy to use for the consumer.
 * @param ctx The vert.x test context.
 * @throws InterruptedException if test execution gets interrupted.
 */
@ParameterizedTest
@MethodSource("partitionAssignmentStrategies")
@Timeout(value = 10, timeUnit = TimeUnit.SECONDS)
public void testConsumerReadsLatestRecordsPublishedAfterStart(final String partitionAssignmentStrategy, final VertxTestContext ctx) throws InterruptedException {
    final int numTopics = 2;
    final int numPartitions = 5;
    final int numTestRecordsPerTopic = 20;
    final Set<String> topics = IntStream.range(0, numTopics).mapToObj(i -> "test_" + i + "_" + UUID.randomUUID()).collect(Collectors.toSet());
    final String publishTestTopic = topics.iterator().next();
    final VertxTestContext setup = new VertxTestContext();
    createTopics(topics, numPartitions).compose(v -> publishRecords(numTestRecordsPerTopic, "key_", topics)).onComplete(setup.succeedingThenComplete());
    assertThat(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }
    LOG.debug("topics created and (to be ignored) test records published");
    // prepare consumer
    final Map<String, String> consumerConfig = IntegrationTestSupport.getKafkaConsumerConfig().getConsumerConfig("test");
    applyPartitionAssignmentStrategy(consumerConfig, partitionAssignmentStrategy);
    consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
    final String publishedAfterStartRecordKey = "publishedAfterStartKey";
    final Handler<KafkaConsumerRecord<String, Buffer>> recordHandler = record -> {
        // verify received record
        ctx.verify(() -> assertThat(record.key()).isEqualTo(publishedAfterStartRecordKey));
        ctx.completeNow();
    };
    kafkaConsumer = new HonoKafkaConsumer(vertx, topics, recordHandler, consumerConfig);
    // start consumer
    kafkaConsumer.start().onComplete(ctx.succeeding(v -> {
        LOG.debug("consumer started, publish record to be received by the consumer");
        publish(publishTestTopic, publishedAfterStartRecordKey, Buffer.buffer("testPayload"));
    }));
    if (!ctx.awaitCompletion(9, TimeUnit.SECONDS)) {
        ctx.failNow(new IllegalStateException("timeout waiting for record to be received"));
    }
}
Also used : LoggerFactory(org.slf4j.LoggerFactory) KafkaProducer(io.vertx.kafka.client.producer.KafkaProducer) Timeout(io.vertx.junit5.Timeout) AfterAll(org.junit.jupiter.api.AfterAll) MessagingType(org.eclipse.hono.util.MessagingType) IntegrationTestSupport(org.eclipse.hono.tests.IntegrationTestSupport) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) BeforeAll(org.junit.jupiter.api.BeforeAll) Duration(java.time.Duration) Map(java.util.Map) TopicConfig(org.apache.kafka.common.config.TopicConfig) MethodSource(org.junit.jupiter.params.provider.MethodSource) Collection(java.util.Collection) Set(java.util.Set) ConsumerConfig(org.apache.kafka.clients.consumer.ConsumerConfig) UUID(java.util.UUID) Instant(java.time.Instant) VertxExtension(io.vertx.junit5.VertxExtension) Collectors(java.util.stream.Collectors) Future(io.vertx.core.Future) TopicPartition(io.vertx.kafka.client.common.TopicPartition) Test(org.junit.jupiter.api.Test) List(java.util.List) Stream(java.util.stream.Stream) Buffer(io.vertx.core.buffer.Buffer) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Optional(java.util.Optional) KafkaConsumer(io.vertx.kafka.client.consumer.KafkaConsumer) Pattern(java.util.regex.Pattern) IntStream(java.util.stream.IntStream) VertxTestContext(io.vertx.junit5.VertxTestContext) HonoKafkaConsumer(org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer) RecordMetadata(io.vertx.kafka.client.producer.RecordMetadata) AtomicReference(java.util.concurrent.atomic.AtomicReference) ArrayList(java.util.ArrayList) CompositeFuture(io.vertx.core.CompositeFuture) ProducerConfig(org.apache.kafka.clients.producer.ProducerConfig) NewTopic(io.vertx.kafka.admin.NewTopic) Logger(org.slf4j.Logger) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) Truth.assertThat(com.google.common.truth.Truth.assertThat) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) TimeUnit(java.util.concurrent.TimeUnit) AfterEach(org.junit.jupiter.api.AfterEach) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) CooperativeStickyAssignor(org.apache.kafka.clients.consumer.CooperativeStickyAssignor) Handler(io.vertx.core.Handler) KafkaProducerRecord(io.vertx.kafka.client.producer.KafkaProducerRecord) KafkaAdminClient(io.vertx.kafka.admin.KafkaAdminClient) VertxTestContext(io.vertx.junit5.VertxTestContext) HonoKafkaConsumer(org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Timeout(io.vertx.junit5.Timeout) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) MethodSource(org.junit.jupiter.params.provider.MethodSource)

Example 12 with HonoKafkaConsumer

use of org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer in project hono by eclipse.

the class KafkaBasedCommandConsumerFactoryImplIT method testCommandsGetForwardedIfOneConsumerInstanceGetsClosed.

/**
 * Verifies that records, published on the tenant-specific Kafka command topic, get received
 * and forwarded by consumers created by factory instances even if one factory and its contained
 * consumer gets closed in the middle of processing some of the commands.
 *
 * @param ctx The vert.x test context.
 * @throws InterruptedException if test execution gets interrupted.
 */
@Test
@Timeout(value = 10, timeUnit = TimeUnit.SECONDS)
public void testCommandsGetForwardedIfOneConsumerInstanceGetsClosed(final VertxTestContext ctx) throws InterruptedException {
    final String tenantId = "tenant_" + UUID.randomUUID();
    final VertxTestContext setup = new VertxTestContext();
    // Scenario to test:
    // - first command gets sent, forwarded and received without any imposed delay
    // - second command gets sent, received by the factory consumer instance; processing gets blocked
    // while trying to get the target adapter instance
    // - for the rest of the commands, retrieval of the target adapter instance is successful, but they won't
    // get forwarded until processing of the second command is finished
    // - now the factory consumer gets closed and a new factory/consumer gets started; at that point
    // also the processing of the second command gets finished
    // 
    // Expected outcome:
    // - processing of the second command and all following commands by the first consumer gets aborted, so that
    // these commands don't get forwarded on the internal command topic
    // - instead, the second consumer takes over at the offset of the first command (position must have been committed
    // when closing the first consumer) and processes and forwards all commands starting with the second command
    final int numTestCommands = 10;
    final List<KafkaConsumerRecord<String, Buffer>> receivedRecords = new ArrayList<>();
    final Promise<Void> firstRecordReceivedPromise = Promise.promise();
    final Promise<Void> allRecordsReceivedPromise = Promise.promise();
    final List<String> receivedCommandSubjects = new ArrayList<>();
    final Handler<KafkaConsumerRecord<String, Buffer>> recordHandler = record -> {
        receivedRecords.add(record);
        LOG.trace("received {}", record);
        receivedCommandSubjects.add(KafkaRecordHelper.getSubject(record.headers()).orElse(""));
        if (receivedRecords.size() == 1) {
            firstRecordReceivedPromise.complete();
        }
        if (receivedRecords.size() == numTestCommands) {
            allRecordsReceivedPromise.tryComplete();
        }
    };
    final Promise<Void> firstConsumerAllGetAdapterInstanceInvocationsDone = Promise.promise();
    final LinkedList<Promise<Void>> firstConsumerGetAdapterInstancePromisesQueue = new LinkedList<>();
    // don't let getting the target adapter instance finish immediately
    final Supplier<Future<Void>> firstConsumerGetAdapterInstanceSupplier = () -> {
        final Promise<Void> resultPromise = Promise.promise();
        firstConsumerGetAdapterInstancePromisesQueue.addFirst(resultPromise);
        // don't complete the future for the second command here yet
        if (firstConsumerGetAdapterInstancePromisesQueue.size() != 2) {
            resultPromise.complete();
        }
        if (firstConsumerGetAdapterInstancePromisesQueue.size() == numTestCommands) {
            firstConsumerAllGetAdapterInstanceInvocationsDone.complete();
        }
        return resultPromise.future();
    };
    final AtomicReference<KafkaBasedCommandConsumerFactoryImpl> consumerFactory1Ref = new AtomicReference<>();
    final Context vertxContext = vertx.getOrCreateContext();
    vertxContext.runOnContext(v0 -> {
        final HonoKafkaConsumer internalConsumer = getInternalCommandConsumer(recordHandler);
        final KafkaBasedCommandConsumerFactoryImpl consumerFactory1 = getKafkaBasedCommandConsumerFactory(firstConsumerGetAdapterInstanceSupplier, tenantId);
        consumerFactory1Ref.set(consumerFactory1);
        CompositeFuture.join(internalConsumer.start(), consumerFactory1.start()).compose(f -> createCommandConsumer(tenantId, consumerFactory1)).onComplete(setup.succeedingThenComplete());
    });
    assertThat(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }
    LOG.debug("command consumer started");
    final List<String> sentCommandSubjects = new ArrayList<>();
    IntStream.range(0, numTestCommands).forEach(i -> {
        final String subject = "cmd_" + i;
        sentCommandSubjects.add(subject);
        sendOneWayCommand(tenantId, "myDeviceId", subject);
    });
    final AtomicInteger secondConsumerGetAdapterInstanceInvocations = new AtomicInteger();
    // wait for first record on internal topic to have been received ...
    CompositeFuture.join(firstConsumerAllGetAdapterInstanceInvocationsDone.future(), firstRecordReceivedPromise.future()).compose(v -> {
        // ... and wait some more, making sure that the offset of the first record has been committed
        final Promise<Void> delayPromise = Promise.promise();
        vertx.setTimer(500, tid -> delayPromise.complete());
        return delayPromise.future();
    }).onComplete(v -> {
        LOG.info("stopping first consumer factory");
        consumerFactory1Ref.get().stop().onComplete(ctx.succeeding(ar -> {
            LOG.info("factory stopped");
            // no delay on getting the target adapter instance added here
            final KafkaBasedCommandConsumerFactoryImpl consumerFactory2 = getKafkaBasedCommandConsumerFactory(() -> {
                secondConsumerGetAdapterInstanceInvocations.incrementAndGet();
                return Future.succeededFuture();
            }, tenantId);
            consumerFactory2.start().onComplete(ctx.succeeding(ar2 -> {
                LOG.info("creating command consumer in new consumer factory");
                createCommandConsumer(tenantId, consumerFactory2).onComplete(ctx.succeeding(ar3 -> {
                    LOG.debug("consumer created");
                    firstConsumerGetAdapterInstancePromisesQueue.forEach(Promise::tryComplete);
                }));
            }));
        }));
    });
    final long timerId = vertx.setTimer(8000, tid -> {
        LOG.info("received records:\n{}", receivedRecords.stream().map(Object::toString).collect(Collectors.joining(",\n")));
        allRecordsReceivedPromise.tryFail(String.format("only received %d out of %d expected messages after 8s", receivedRecords.size(), numTestCommands));
    });
    allRecordsReceivedPromise.future().onComplete(ctx.succeeding(v -> {
        vertx.cancelTimer(timerId);
        ctx.verify(() -> {
            assertThat(receivedCommandSubjects).isEqualTo(sentCommandSubjects);
            // all but the first command should have been processed by the second consumer
            assertThat(secondConsumerGetAdapterInstanceInvocations.get()).isEqualTo(numTestCommands - 1);
        });
        ctx.completeNow();
    }));
}
Also used : BeforeEach(org.junit.jupiter.api.BeforeEach) MessagingKafkaConsumerConfigProperties(org.eclipse.hono.client.kafka.consumer.MessagingKafkaConsumerConfigProperties) LoggerFactory(org.slf4j.LoggerFactory) Context(io.vertx.core.Context) KafkaProducer(io.vertx.kafka.client.producer.KafkaProducer) Timeout(io.vertx.junit5.Timeout) AfterAll(org.junit.jupiter.api.AfterAll) 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) BeforeAll(org.junit.jupiter.api.BeforeAll) Map(java.util.Map) TracingMockSupport(org.eclipse.hono.test.TracingMockSupport) JsonObject(io.vertx.core.json.JsonObject) DeviceConnectionConstants(org.eclipse.hono.util.DeviceConnectionConstants) CachingKafkaProducerFactory(org.eclipse.hono.client.kafka.producer.CachingKafkaProducerFactory) Set(java.util.Set) CommandTargetMapper(org.eclipse.hono.commandrouter.CommandTargetMapper) ConsumerConfig(org.apache.kafka.clients.consumer.ConsumerConfig) UUID(java.util.UUID) TenantClient(org.eclipse.hono.client.registry.TenantClient) KafkaBasedCommandConsumerFactoryImpl(org.eclipse.hono.commandrouter.impl.kafka.KafkaBasedCommandConsumerFactoryImpl) VertxExtension(io.vertx.junit5.VertxExtension) Collectors(java.util.stream.Collectors) Future(io.vertx.core.Future) AsyncHandlingAutoCommitKafkaConsumer(org.eclipse.hono.client.kafka.consumer.AsyncHandlingAutoCommitKafkaConsumer) TestInfo(org.junit.jupiter.api.TestInfo) Test(org.junit.jupiter.api.Test) List(java.util.List) KafkaProducerFactory(org.eclipse.hono.client.kafka.producer.KafkaProducerFactory) Buffer(io.vertx.core.buffer.Buffer) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) Span(io.opentracing.Span) Mockito.mock(org.mockito.Mockito.mock) IntStream(java.util.stream.IntStream) VertxTestContext(io.vertx.junit5.VertxTestContext) X500Principal(javax.security.auth.x500.X500Principal) HonoKafkaConsumer(org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer) Lifecycle(org.eclipse.hono.util.Lifecycle) Deque(java.util.Deque) AtomicReference(java.util.concurrent.atomic.AtomicReference) Supplier(java.util.function.Supplier) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) CompositeFuture(io.vertx.core.CompositeFuture) Timer(io.micrometer.core.instrument.Timer) LinkedList(java.util.LinkedList) Logger(org.slf4j.Logger) Tracer(io.opentracing.Tracer) Promise(io.vertx.core.Promise) Vertx(io.vertx.core.Vertx) Mockito.when(org.mockito.Mockito.when) KafkaRecordHelper(org.eclipse.hono.client.kafka.KafkaRecordHelper) Truth.assertThat(com.google.common.truth.Truth.assertThat) AssumeMessagingSystem(org.eclipse.hono.tests.AssumeMessagingSystem) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) TimeUnit(java.util.concurrent.TimeUnit) NoopKafkaClientMetricsSupport(org.eclipse.hono.client.kafka.metrics.NoopKafkaClientMetricsSupport) HonoTopic(org.eclipse.hono.client.kafka.HonoTopic) CommandRouterMetrics(org.eclipse.hono.commandrouter.CommandRouterMetrics) AfterEach(org.junit.jupiter.api.AfterEach) Handler(io.vertx.core.Handler) KafkaHeader(io.vertx.kafka.client.producer.KafkaHeader) KafkaProducerRecord(io.vertx.kafka.client.producer.KafkaProducerRecord) KafkaAdminClient(io.vertx.kafka.admin.KafkaAdminClient) Context(io.vertx.core.Context) VertxTestContext(io.vertx.junit5.VertxTestContext) SpanContext(io.opentracing.SpanContext) KafkaBasedCommandConsumerFactoryImpl(org.eclipse.hono.commandrouter.impl.kafka.KafkaBasedCommandConsumerFactoryImpl) VertxTestContext(io.vertx.junit5.VertxTestContext) ArrayList(java.util.ArrayList) KafkaConsumerRecord(io.vertx.kafka.client.consumer.KafkaConsumerRecord) AtomicReference(java.util.concurrent.atomic.AtomicReference) LinkedList(java.util.LinkedList) Promise(io.vertx.core.Promise) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) HonoKafkaConsumer(org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer) Future(io.vertx.core.Future) CompositeFuture(io.vertx.core.CompositeFuture) JsonObject(io.vertx.core.json.JsonObject) TenantObject(org.eclipse.hono.util.TenantObject) Test(org.junit.jupiter.api.Test) Timeout(io.vertx.junit5.Timeout)

Aggregations

HonoKafkaConsumer (org.eclipse.hono.client.kafka.consumer.HonoKafkaConsumer)12 CompositeFuture (io.vertx.core.CompositeFuture)10 Future (io.vertx.core.Future)10 Handler (io.vertx.core.Handler)10 Vertx (io.vertx.core.Vertx)10 Buffer (io.vertx.core.buffer.Buffer)10 KafkaConsumerRecord (io.vertx.kafka.client.consumer.KafkaConsumerRecord)10 List (java.util.List)10 Set (java.util.Set)10 Collectors (java.util.stream.Collectors)10 Promise (io.vertx.core.Promise)9 Map (java.util.Map)9 UUID (java.util.UUID)9 ConsumerConfig (org.apache.kafka.clients.consumer.ConsumerConfig)9 Logger (org.slf4j.Logger)9 LoggerFactory (org.slf4j.LoggerFactory)9 Truth.assertThat (com.google.common.truth.Truth.assertThat)8 Timeout (io.vertx.junit5.Timeout)8 VertxExtension (io.vertx.junit5.VertxExtension)8 VertxTestContext (io.vertx.junit5.VertxTestContext)8