Skip to content

Commit

Permalink
GH-666: KafkaEmbedded doWithAdmin, addTopics
Browse files Browse the repository at this point in the history
Resolves #666

Allow arbitrary `AdminClient` operations and adding topics.

Polishing - PR Comments
  • Loading branch information
garyrussell authored and artembilan committed May 7, 2018
1 parent 5fe9d5d commit be073a8
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 9 deletions.
Expand Up @@ -59,6 +59,7 @@
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;

import kafka.common.KafkaException;
import kafka.server.KafkaConfig;
import kafka.server.KafkaServer;
import kafka.server.NotRunning;
Expand Down Expand Up @@ -234,19 +235,45 @@ public void before() throws Exception { //NOSONAR
this.kafkaPorts[i] = TestUtils.boundPort(server, SecurityProtocol.PLAINTEXT);
}
}
Map<String, Object> adminConfigs = new HashMap<>();
adminConfigs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, getBrokersAsString());
AdminClient admin = AdminClient.create(adminConfigs);
List<NewTopic> newTopics = Arrays.stream(this.topics)
.map(t -> new NewTopic(t, this.partitionsPerTopic, (short) this.count))
.collect(Collectors.toList());
CreateTopicsResult createTopics = admin.createTopics(newTopics);
createTopics.all().get();
admin.close();
addTopics(this.topics);
System.setProperty(SPRING_EMBEDDED_KAFKA_BROKERS, getBrokersAsString());
System.setProperty(SPRING_EMBEDDED_ZOOKEEPER_CONNECT, getZookeeperConnectionString());
}

/**
* Add topics to the existing broker(s) using the configured number of partitions.
* @param topics the topics.
* @since 2.1
*/
public void addTopics(String... topics) {
doWithAdmin(admin -> {
List<NewTopic> newTopics = Arrays.stream(topics)
.map(t -> new NewTopic(t, this.partitionsPerTopic, (short) this.count))
.collect(Collectors.toList());
CreateTopicsResult createTopics = admin.createTopics(newTopics);
try {
createTopics.all().get();
}
catch (Exception e) {
throw new KafkaException(e);
}
});
}

/**
* Create an {@link AdminClient} invoke the callback and reliable close the
* admin.
* @param callback the callback.
* @since 2.1
*/
public void doWithAdmin(java.util.function.Consumer<AdminClient> callback) {
Map<String, Object> adminConfigs = new HashMap<>();
adminConfigs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, getBrokersAsString());
try (AdminClient admin = AdminClient.create(adminConfigs)) {
callback.accept(admin);
}
}

public Properties createBrokerProperties(int i) {
if (testUtilsCreateBrokerConfigMethod == null) {
return TestUtils.createBrokerConfig(i, this.zkConnect, this.controlledShutdown,
Expand Down
48 changes: 48 additions & 0 deletions src/reference/asciidoc/testing.adoc
Expand Up @@ -102,6 +102,54 @@ Convenient constants `KafkaEmbedded.SPRING_EMBEDDED_KAFKA_BROKERS` and `KafkaEmb
With the `KafkaEmbedded.brokerProperties(Map<String, String>)` you can provide additional properties for the Kafka server(s).
See https://kafka.apache.org/documentation/#brokerconfigs[Kafka Config] for more information about possible broker properties.


==== Using the Same Broker(s) for Multiple Test Classes

There is no built-in support for this, but it can be achieved with something similar to the following:

[source, java]
----
public final class KafkaEmbeddedHolder {
private static KafkaEmbedded kafkaEmbedded = new KafkaEmbedded(1, false);
private static boolean started;
public static KafkaEmbedded getKafkaEmbedded() {
if (!started) {
try {
kafkaEmbedded.before();
}
catch (Exception e) {
throw new KafkaException(e);
}
started = true;
}
return kafkaEmbedded;
}
private KafkaEmbeddedHolder() {
super();
}
}
----

And then, in each test class:

[source, java]
----
static {
KafkaEmbeddedHolder.getKafkaEmbedded().addTopics(topic1, topic2);
}
private static KafkaEmbedded embeddedKafka = KafkaEmbeddedHolder.getKafkaEmbedded();
----

IMPORTANT: This example provides no mechanism for shutting down the broker(s) when all tests are complete.
This could be a problem if, say, you run your tests in a gradle daemon.
You should not use this technique in such a situation, or use something to call `destroy()` on the `KafkaEmbedded` when your tests are complete.

==== @EmbeddedKafka Annotation
It is generally recommended to use the rule as a `@ClassRule` to avoid starting/stopping the broker between tests (and use a different topic for each test).
Starting with _version 2.0_, if you are using Spring's test application context caching, you can also declare a `KafkaEmbedded` bean, so a single broker can be used across multiple test classes.
Expand Down

0 comments on commit be073a8

Please sign in to comment.