Skip to content

Commit d18ccc5

Browse files
add kafka Spring starter smoke test (#11262)
Co-authored-by: Jean Bisutti <[email protected]>
1 parent 68cf431 commit d18ccc5

File tree

15 files changed

+249
-79
lines changed

15 files changed

+249
-79
lines changed
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
services:
2+
zookeeper:
3+
image: confluentinc/cp-zookeeper:6.2.10
4+
environment:
5+
ZOOKEEPER_CLIENT_PORT: 2181
6+
ZOOKEEPER_TICK_TIME: 2000
7+
ports:
8+
- "22181:2181"
9+
10+
kafka:
11+
image: confluentinc/cp-kafka:6.2.10
12+
depends_on:
13+
- zookeeper
14+
ports:
15+
- "29092:29092"
16+
environment:
17+
KAFKA_BROKER_ID: 1
18+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
19+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
20+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
21+
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
22+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

.github/workflows/reusable-native-tests.yml

+7
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ jobs:
1919
java-version: "21"
2020
components: "native-image"
2121
- name: Running test
22+
env:
23+
DOCKER_COMPOSE_TEST: "true"
2224
run: |
2325
echo "GRAALVM_HOME: $GRAALVM_HOME"
2426
echo "JAVA_HOME: $JAVA_HOME"
2527
java --version
2628
native-image --version
29+
# Testcontainers does not work in some cases with GraalVM native images,
30+
# therefore we're starting a Kafka container manually for the tests
31+
docker compose -f .github/graal-native-docker-compose.yaml up -d
32+
# don't wait for startup - gradle compile takes long enough
2733
./gradlew nativeTest
34+
docker compose -f .github/graal-native-docker-compose.yaml down # is this needed?

instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts

-5
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ dependencies {
6767
testLibrary("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
6868
exclude("org.junit.vintage", "junit-vintage-engine")
6969
}
70-
testImplementation("org.testcontainers:kafka")
7170
testImplementation("javax.servlet:javax.servlet-api:3.1.0")
7271
testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0")
7372

@@ -151,10 +150,6 @@ tasks {
151150
options.compilerArgs.add("-parameters")
152151
}
153152

154-
test {
155-
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
156-
}
157-
158153
withType<Test>().configureEach {
159154
systemProperty("testLatestDeps", latestDepTest)
160155

smoke-tests-otel-starter/spring-boot-2/build.gradle.kts

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ dependencies {
1010
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
1111
runtimeOnly("com.h2database:h2")
1212
implementation("org.apache.commons:commons-dbcp2")
13-
implementation("org.springframework.kafka:spring-kafka") // not tested here, just make sure there are no warnings when it's included
13+
implementation("org.springframework.kafka:spring-kafka")
1414
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
1515

1616
implementation(project(":smoke-tests-otel-starter:spring-boot-common"))
17+
18+
testImplementation("org.testcontainers:junit-jupiter")
19+
testImplementation("org.testcontainers:kafka")
1720
testImplementation("org.springframework.boot:spring-boot-starter-test")
1821
}
1922

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.spring.smoketest;
7+
8+
import org.junit.jupiter.api.condition.DisabledInNativeImage;
9+
10+
@DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test
11+
public class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {}

smoke-tests-otel-starter/spring-boot-3/build.gradle.kts

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ dependencies {
1515
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
1616
runtimeOnly("com.h2database:h2")
1717
implementation("org.apache.commons:commons-dbcp2")
18-
implementation("org.springframework.kafka:spring-kafka") // not tested here, just make sure there are no warnings when it's included
18+
implementation("org.springframework.kafka:spring-kafka")
1919
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
2020

2121
implementation(project(":smoke-tests-otel-starter:spring-boot-common"))
22+
23+
testImplementation("org.testcontainers:junit-jupiter")
24+
testImplementation("org.testcontainers:kafka")
2225
testImplementation("org.springframework.boot:spring-boot-starter-test")
2326
}
2427

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.spring.smoketest;
7+
8+
import org.junit.jupiter.api.condition.EnabledInNativeImage;
9+
import org.springframework.boot.test.context.SpringBootTest;
10+
11+
/**
12+
* GraalVM native image doesn't support Testcontainers in our case, so the docker container is
13+
* started manually before running the tests.
14+
*
15+
* <p>In other cases, it does work, e.g. <a
16+
* href="https://info.michael-simons.eu/2023/10/25/run-your-integration-tests-against-testcontainers-with-graalvm-native-image/">here</a>,
17+
* it's not yet clear why it doesn't work in our case.
18+
*
19+
* <p>In CI, this is done in reusable-native-tests.yml. If you want to run the tests locally, you
20+
* need to start the container manually: see .github/workflows/reusable-native-tests.yml for the
21+
* command.
22+
*/
23+
@SpringBootTest(
24+
classes = {
25+
OtelSpringStarterSmokeTestApplication.class,
26+
SpringSmokeOtelConfiguration.class,
27+
AbstractKafkaSpringStarterSmokeTest.KafkaConfig.class
28+
},
29+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
30+
@EnabledInNativeImage // see JvmMongodbSpringStarterSmokeTest for the JVM test
31+
@RequiresDockerComposeEnvVariable
32+
public class GraalVmNativeKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.spring.smoketest;
7+
8+
import org.junit.jupiter.api.condition.DisabledInNativeImage;
9+
10+
@DisabledInNativeImage // See GraalVmNativeKafkaSpringStarterSmokeTest for the GraalVM native test
11+
public class KafkaSpringStarterSmokeTest extends AbstractJvmKafkaSpringStarterSmokeTest {}

smoke-tests-otel-starter/spring-boot-common/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ dependencies {
1414
compileOnly("org.springframework.boot:spring-boot-starter-test")
1515
compileOnly("org.springframework.boot:spring-boot-starter-data-jdbc")
1616
compileOnly("org.apache.commons:commons-dbcp2")
17+
compileOnly("org.springframework.kafka:spring-kafka")
18+
compileOnly("org.testcontainers:junit-jupiter")
19+
compileOnly("org.testcontainers:kafka")
1720

1821
api(project(":smoke-tests-otel-starter:spring-smoke-testing"))
1922

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.spring.smoketest;
7+
8+
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
10+
import io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka.KafkaInstrumentationAutoConfiguration;
11+
import java.time.Duration;
12+
import org.junit.jupiter.api.AfterAll;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
16+
import org.springframework.boot.autoconfigure.AutoConfigurations;
17+
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
18+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
19+
import org.springframework.kafka.core.KafkaTemplate;
20+
import org.testcontainers.containers.KafkaContainer;
21+
import org.testcontainers.containers.wait.strategy.Wait;
22+
import org.testcontainers.utility.DockerImageName;
23+
24+
/** Spring has a test container integration, but that doesn't work for Spring Boot 2 */
25+
public class AbstractJvmKafkaSpringStarterSmokeTest extends AbstractKafkaSpringStarterSmokeTest {
26+
static KafkaContainer kafka;
27+
28+
private ApplicationContextRunner contextRunner;
29+
30+
@BeforeAll
31+
static void setUpKafka() {
32+
kafka =
33+
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.10"))
34+
.withEnv("KAFKA_HEAP_OPTS", "-Xmx256m")
35+
.waitingFor(Wait.forLogMessage(".*started \\(kafka.server.KafkaServer\\).*", 1))
36+
.withStartupTimeout(Duration.ofMinutes(1));
37+
kafka.start();
38+
}
39+
40+
@AfterAll
41+
static void tearDownKafka() {
42+
kafka.stop();
43+
}
44+
45+
@BeforeEach
46+
void setUpContext() {
47+
contextRunner =
48+
new ApplicationContextRunner()
49+
.withAllowBeanDefinitionOverriding(true)
50+
.withConfiguration(
51+
AutoConfigurations.of(
52+
OpenTelemetryAutoConfiguration.class,
53+
SpringSmokeOtelConfiguration.class,
54+
KafkaAutoConfiguration.class,
55+
KafkaInstrumentationAutoConfiguration.class,
56+
KafkaConfig.class))
57+
.withPropertyValues(
58+
"spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(),
59+
"spring.kafka.consumer.auto-offset-reset=earliest",
60+
"spring.kafka.consumer.linger-ms=10",
61+
"spring.kafka.listener.idle-between-polls=1000",
62+
"spring.kafka.producer.transaction-id-prefix=test-");
63+
}
64+
65+
@SuppressWarnings("unchecked")
66+
@Override
67+
@Test
68+
void shouldInstrumentProducerAndConsumer() {
69+
contextRunner.run(
70+
applicationContext -> {
71+
testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class));
72+
kafkaTemplate = applicationContext.getBean(KafkaTemplate.class);
73+
super.shouldInstrumentProducerAndConsumer();
74+
});
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -3,98 +3,47 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka;
6+
package io.opentelemetry.spring.smoketest;
77

88
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
99
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
1010

1111
import io.opentelemetry.api.OpenTelemetry;
1212
import io.opentelemetry.api.trace.SpanKind;
13-
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
1413
import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes;
15-
import java.time.Duration;
1614
import org.apache.kafka.clients.admin.NewTopic;
1715
import org.apache.kafka.clients.consumer.ConsumerRecord;
1816
import org.assertj.core.api.AbstractLongAssert;
1917
import org.assertj.core.api.AbstractStringAssert;
20-
import org.junit.jupiter.api.AfterAll;
21-
import org.junit.jupiter.api.BeforeAll;
22-
import org.junit.jupiter.api.BeforeEach;
2318
import org.junit.jupiter.api.Test;
24-
import org.junit.jupiter.api.extension.RegisterExtension;
25-
import org.springframework.boot.autoconfigure.AutoConfigurations;
26-
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
27-
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
28-
import org.springframework.context.ConfigurableApplicationContext;
19+
import org.springframework.beans.factory.annotation.Autowired;
2920
import org.springframework.context.annotation.Bean;
3021
import org.springframework.context.annotation.Configuration;
3122
import org.springframework.kafka.annotation.KafkaListener;
3223
import org.springframework.kafka.config.TopicBuilder;
3324
import org.springframework.kafka.core.KafkaTemplate;
34-
import org.testcontainers.containers.KafkaContainer;
35-
import org.testcontainers.containers.wait.strategy.Wait;
36-
import org.testcontainers.utility.DockerImageName;
3725

38-
class KafkaIntegrationTest {
26+
abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest {
3927

40-
@RegisterExtension
41-
static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create();
42-
43-
static KafkaContainer kafka;
44-
45-
private ApplicationContextRunner contextRunner;
46-
47-
@BeforeAll
48-
static void setUpKafka() {
49-
kafka =
50-
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.10"))
51-
.withEnv("KAFKA_HEAP_OPTS", "-Xmx256m")
52-
.waitingFor(Wait.forLogMessage(".*started \\(kafka.server.KafkaServer\\).*", 1))
53-
.withStartupTimeout(Duration.ofMinutes(1));
54-
kafka.start();
55-
}
56-
57-
@AfterAll
58-
static void tearDownKafka() {
59-
kafka.stop();
60-
}
61-
62-
@BeforeEach
63-
void setUpContext() {
64-
contextRunner =
65-
new ApplicationContextRunner()
66-
.withConfiguration(
67-
AutoConfigurations.of(
68-
KafkaAutoConfiguration.class,
69-
KafkaInstrumentationAutoConfiguration.class,
70-
TestConfig.class))
71-
.withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry)
72-
.withPropertyValues(
73-
"spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers(),
74-
"spring.kafka.consumer.auto-offset-reset=earliest",
75-
"spring.kafka.consumer.linger-ms=10",
76-
"spring.kafka.listener.idle-between-polls=1000",
77-
"spring.kafka.producer.transaction-id-prefix=test-");
78-
}
28+
@Autowired protected KafkaTemplate<String, String> kafkaTemplate;
7929

8030
@Test
8131
void shouldInstrumentProducerAndConsumer() {
82-
contextRunner.run(KafkaIntegrationTest::runShouldInstrumentProducerAndConsumer);
83-
}
84-
85-
// In kafka 2 ops.send is deprecated. We are using it to avoid reflection because kafka 3 also has
86-
// ops.send, although with different return type.
87-
@SuppressWarnings({"unchecked", "deprecation"})
88-
private static void runShouldInstrumentProducerAndConsumer(
89-
ConfigurableApplicationContext applicationContext) {
90-
KafkaTemplate<String, String> kafkaTemplate = applicationContext.getBean(KafkaTemplate.class);
32+
testing.clearAllExportedData(); // ignore data from application startup
9133

9234
testing.runWithSpan(
9335
"producer",
9436
() -> {
9537
kafkaTemplate.executeInTransaction(
9638
ops -> {
97-
ops.send("testTopic", "10", "testSpan");
39+
// return type is incompatible between Spring Boot 2 and 3
40+
try {
41+
ops.getClass()
42+
.getDeclaredMethod("send", String.class, Object.class, Object.class)
43+
.invoke(ops, "testTopic", "10", "testSpan");
44+
} catch (Exception e) {
45+
throw new IllegalStateException(e);
46+
}
9847
return 0;
9948
});
10049
});
@@ -128,7 +77,7 @@ private static void runShouldInstrumentProducerAndConsumer(
12877
span.hasName("testTopic process")
12978
.hasKind(SpanKind.CONSUMER)
13079
.hasParent(trace.getSpan(1))
131-
.hasAttributesSatisfyingExactly(
80+
.hasAttributesSatisfying(
13281
equalTo(MessagingIncubatingAttributes.MESSAGING_SYSTEM, "kafka"),
13382
equalTo(
13483
MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME,
@@ -155,7 +104,9 @@ private static void runShouldInstrumentProducerAndConsumer(
155104
}
156105

157106
@Configuration
158-
static class TestConfig {
107+
public static class KafkaConfig {
108+
109+
@Autowired OpenTelemetry openTelemetry;
159110

160111
@Bean
161112
public NewTopic testTopic() {
@@ -164,7 +115,12 @@ public NewTopic testTopic() {
164115

165116
@KafkaListener(id = "testListener", topics = "testTopic")
166117
public void listener(ConsumerRecord<String, String> record) {
167-
testing.runWithSpan("consumer", () -> {});
118+
openTelemetry
119+
.getTracer("consumer", "1.0")
120+
.spanBuilder("consumer")
121+
.setSpanKind(SpanKind.CONSUMER)
122+
.startSpan()
123+
.end();
168124
}
169125
}
170126
}

smoke-tests-otel-starter/spring-boot-common/src/main/resources/application.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ otel:
88
resource:
99
attributes:
1010
attributeFromYaml: true # boolean will be automatically converted to string by spring
11+
12+
spring:
13+
kafka:
14+
consumer:
15+
auto-offset-reset: earliest
16+
listener:
17+
idle-between-polls: 1000
18+
producer:
19+
transaction-id-prefix: test-

0 commit comments

Comments
 (0)