use of io.vertx.kafka.client.consumer.KafkaConsumerRecord in project hono by eclipse.
the class KafkaBasedInternalCommandConsumerTest method testHandleCommandMessageSendErrorResponse.
/**
* Verifies that an error response is sent to the application if the tenant of the target device
* is unknown or cannot be retrieved.
*/
@ParameterizedTest
@ValueSource(ints = { HttpURLConnection.HTTP_NOT_FOUND, HttpURLConnection.HTTP_UNAVAILABLE })
void testHandleCommandMessageSendErrorResponse(final int tenantServiceErrorCode) {
final String tenantId = "myTenant";
final String deviceId = "4711";
final String subject = "subject";
final Handler<CommandContext> commandHandler = VertxMockSupport.mockHandler();
commandHandlers.putCommandHandler(tenantId, deviceId, null, commandHandler, context);
when(tenantClient.get(eq("myTenant"), any())).thenReturn(Future.failedFuture(ServiceInvocationException.create(tenantServiceErrorCode)));
final KafkaConsumerRecord<String, Buffer> commandRecord = getCommandRecord(deviceId, getHeaders(tenantId, deviceId, subject, 0L));
internalCommandConsumer.handleCommandMessage(commandRecord);
verify(commandHandler, never()).handle(any(KafkaBasedCommandContext.class));
verify(commandResponseSender).sendCommandResponse(argThat(t -> t.getTenantId().equals("myTenant")), argThat(r -> r.getDeviceId().equals("4711")), argThat(cr -> cr.getStatus() == tenantServiceErrorCode), any());
}
use of io.vertx.kafka.client.consumer.KafkaConsumerRecord in project hono by eclipse.
the class KafkaBasedCommandSender method subscribeForCommandResponse.
private Future<Void> subscribeForCommandResponse(final String tenantId, final Span span) {
if (commandResponseConsumers.get(tenantId) != null) {
LOGGER.debug("command response consumer already exists for tenant [{}]", tenantId);
span.log("command response consumer already exists");
return Future.succeededFuture();
}
final Map<String, String> consumerConfig = this.consumerConfig.getConsumerConfig(HonoTopic.Type.COMMAND_RESPONSE.toString());
final String autoOffsetResetConfigValue = consumerConfig.get(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG);
// Ensure that 'auto.offset.reset' is always set to 'latest'.
if (autoOffsetResetConfigValue != null && !autoOffsetResetConfigValue.equals("latest")) {
LOGGER.warn("[auto.offset.reset] value is set to other than [latest]. It will be ignored and internally set to [latest]");
}
consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
// Use a unique group-id so that all command responses for this tenant are received by this consumer.
// Thereby the responses can be correlated with the command that has been sent.
consumerConfig.put(ConsumerConfig.GROUP_ID_CONFIG, tenantId + "-" + UUID.randomUUID());
final String topic = new HonoTopic(HonoTopic.Type.COMMAND_RESPONSE, tenantId).toString();
final Handler<KafkaConsumerRecord<String, Buffer>> recordHandler = record -> {
getCommandResponseHandler(tenantId).handle(new KafkaDownstreamMessage(record));
};
final HonoKafkaConsumer consumer = new HonoKafkaConsumer(vertx, Set.of(topic), recordHandler, consumerConfig);
consumer.setPollTimeout(Duration.ofMillis(this.consumerConfig.getPollTimeout()));
Optional.ofNullable(kafkaConsumerSupplier).ifPresent(consumer::setKafkaConsumerSupplier);
return consumer.start().recover(error -> {
LOGGER.debug("error creating command response consumer for tenant [{}]", tenantId, error);
TracingHelper.logError(span, "error creating command response consumer", error);
return Future.failedFuture(error);
}).onSuccess(v -> {
LOGGER.debug("created command response consumer for tenant [{}]", tenantId);
span.log("created command response consumer");
commandResponseConsumers.put(tenantId, consumer);
});
}
use of io.vertx.kafka.client.consumer.KafkaConsumerRecord in project hono by eclipse.
the class KafkaBasedCommandConsumerFactoryImplIT method testCommandsGetForwardedInIncomingOrder.
/**
* Verifies that records, published on the tenant-specific Kafka command topic, get received by
* the consumer created by the factory and get forwarded on the internal command topic in the
* same order they were published.
*
* @param ctx The vert.x test context.
* @throws InterruptedException if test execution gets interrupted.
*/
@Test
@Timeout(value = 10, timeUnit = TimeUnit.SECONDS)
public void testCommandsGetForwardedInIncomingOrder(final VertxTestContext ctx) throws InterruptedException {
final String tenantId = "tenant_" + UUID.randomUUID();
final VertxTestContext setup = new VertxTestContext();
final int numTestCommands = 10;
final List<KafkaConsumerRecord<String, Buffer>> receivedRecords = new ArrayList<>();
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() == numTestCommands) {
allRecordsReceivedPromise.tryComplete();
}
};
final Deque<Promise<Void>> completionPromisesQueue = new LinkedList<>();
// don't let getting the target adapter instance finish immediately
// - let the futures complete in the reverse order
final Supplier<Future<Void>> targetAdapterInstanceGetterCompletionFutureSupplier = () -> {
final Promise<Void> resultPromise = Promise.promise();
completionPromisesQueue.addFirst(resultPromise);
// complete all promises in reverse order when processing the last command
if (completionPromisesQueue.size() == numTestCommands) {
completionPromisesQueue.forEach(Promise::complete);
}
return resultPromise.future();
};
final Context vertxContext = vertx.getOrCreateContext();
vertxContext.runOnContext(v0 -> {
final HonoKafkaConsumer internalConsumer = getInternalCommandConsumer(recordHandler);
final KafkaBasedCommandConsumerFactoryImpl consumerFactory = getKafkaBasedCommandConsumerFactory(targetAdapterInstanceGetterCompletionFutureSupplier, tenantId);
CompositeFuture.join(internalConsumer.start(), consumerFactory.start()).compose(f -> createCommandConsumer(tenantId, consumerFactory)).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 long timerId = vertx.setTimer(8000, tid -> {
LOG.info("received records:{}{}", System.lineSeparator(), receivedRecords.stream().map(Object::toString).collect(Collectors.joining("," + System.lineSeparator())));
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);
});
ctx.completeNow();
}));
}
use of io.vertx.kafka.client.consumer.KafkaConsumerRecord in project hono by eclipse.
the class HonoKafkaConsumerIT method testConsumerAutoCreatesTopicAndReadsLatestRecordsPublishedAfterStart.
/**
* Verifies that a HonoKafkaConsumer that is using a not yet existing topic and that is configured with
* "latest" as offset reset strategy, only receives records on the auto-created topic 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.
*/
@ParameterizedTest
@MethodSource("partitionAssignmentStrategies")
@Timeout(value = 10, timeUnit = TimeUnit.SECONDS)
public void testConsumerAutoCreatesTopicAndReadsLatestRecordsPublishedAfterStart(final String partitionAssignmentStrategy, final VertxTestContext ctx) {
// prepare consumer
final Map<String, String> consumerConfig = IntegrationTestSupport.getKafkaConsumerConfig().getConsumerConfig("test");
applyPartitionAssignmentStrategy(consumerConfig, partitionAssignmentStrategy);
consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
final AtomicReference<Promise<Void>> nextRecordReceivedPromiseRef = new AtomicReference<>();
final List<KafkaConsumerRecord<String, Buffer>> receivedRecords = new ArrayList<>();
final Handler<KafkaConsumerRecord<String, Buffer>> recordHandler = record -> {
receivedRecords.add(record);
Optional.ofNullable(nextRecordReceivedPromiseRef.get()).ifPresent(Promise::complete);
};
final String topic = "test_" + UUID.randomUUID();
topicsToDeleteAfterTests.add(topic);
kafkaConsumer = new HonoKafkaConsumer(vertx, Set.of(topic), recordHandler, consumerConfig);
// start consumer
kafkaConsumer.start().onComplete(ctx.succeeding(v -> {
ctx.verify(() -> {
assertThat(receivedRecords.size()).isEqualTo(0);
});
final Promise<Void> nextRecordReceivedPromise = Promise.promise();
nextRecordReceivedPromiseRef.set(nextRecordReceivedPromise);
LOG.debug("consumer started, publish record to be received by the consumer");
final String recordKey = "addedAfterStartKey";
publish(topic, recordKey, Buffer.buffer("testPayload"));
nextRecordReceivedPromise.future().onComplete(ar -> {
ctx.verify(() -> {
assertThat(receivedRecords.size()).isEqualTo(1);
assertThat(receivedRecords.get(0).key()).isEqualTo(recordKey);
});
ctx.completeNow();
});
}));
}
use of io.vertx.kafka.client.consumer.KafkaConsumerRecord in project hono by eclipse.
the class HonoKafkaConsumerIT method testConsumerReadsAllRecordsForDynamicallyCreatedTopics.
/**
* Verifies that a HonoKafkaConsumer configured with "latest" as offset reset strategy and a topic pattern
* subscription receives records published after multiple <em>ensureTopicIsAmongSubscribedTopicPatternTopics()</em>
* invocations have been 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 testConsumerReadsAllRecordsForDynamicallyCreatedTopics(final String partitionAssignmentStrategy, final VertxTestContext ctx) throws InterruptedException {
final String patternPrefix = "test_" + UUID.randomUUID() + "_";
final int numTopicsAndRecords = 3;
final Pattern topicPattern = Pattern.compile(Pattern.quote(patternPrefix) + ".*");
// prepare consumer
final Map<String, String> consumerConfig = IntegrationTestSupport.getKafkaConsumerConfig().getConsumerConfig("test");
applyPartitionAssignmentStrategy(consumerConfig, partitionAssignmentStrategy);
consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
final Promise<Void> allRecordsReceivedPromise = Promise.promise();
final List<KafkaConsumerRecord<String, Buffer>> receivedRecords = new ArrayList<>();
final Handler<KafkaConsumerRecord<String, Buffer>> recordHandler = record -> {
receivedRecords.add(record);
if (receivedRecords.size() == numTopicsAndRecords) {
allRecordsReceivedPromise.complete();
}
};
kafkaConsumer = new HonoKafkaConsumer(vertx, topicPattern, recordHandler, consumerConfig);
// start consumer
kafkaConsumer.start().onComplete(ctx.succeeding(v -> {
ctx.verify(() -> {
assertThat(receivedRecords.size()).isEqualTo(0);
});
LOG.debug("consumer started, create new topics implicitly by invoking ensureTopicIsAmongSubscribedTopicPatternTopics()");
final String recordKey = "addedAfterStartKey";
for (int i = 0; i < numTopicsAndRecords; i++) {
final String topic = patternPrefix + i;
kafkaConsumer.ensureTopicIsAmongSubscribedTopicPatternTopics(topic).onComplete(ctx.succeeding(v2 -> {
LOG.debug("publish record to be received by the consumer");
publish(topic, recordKey, Buffer.buffer("testPayload"));
}));
}
allRecordsReceivedPromise.future().onComplete(ar -> {
ctx.verify(() -> {
assertThat(receivedRecords.size()).isEqualTo(numTopicsAndRecords);
receivedRecords.forEach(record -> assertThat(record.key()).isEqualTo(recordKey));
});
ctx.completeNow();
});
}));
if (!ctx.awaitCompletion(9, TimeUnit.SECONDS)) {
ctx.failNow(new IllegalStateException(String.format("timeout waiting for expected number of records (%d) to be received; received records: %d", numTopicsAndRecords, receivedRecords.size())));
}
}
Aggregations