You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/06/02 16:30:56 UTC
(camel) branch main updated: CAMEL-23641: Add camel-test-infra-jaeger module (#23684)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new f81f56c1ecde CAMEL-23641: Add camel-test-infra-jaeger module (#23684)
f81f56c1ecde is described below
commit f81f56c1ecdecb21e0fad95f2874649de3898347
Author: Adriano Machado <60...@users.noreply.github.com>
AuthorDate: Tue Jun 2 12:30:48 2026 -0400
CAMEL-23641: Add camel-test-infra-jaeger module (#23684)
* CAMEL-23641: Add camel-test-infra-jaeger module
Introduces a new test infrastructure module backed by the Jaeger 2.x
all-in-one container (cr.jaegertracing.io/jaegertracing/jaeger:2.18.0),
which bundles an OTLP collector (gRPC :4317, HTTP :4318) and the Jaeger
query UI (:16686) in a single image.
Includes JaegerTestApp, a manual demo that starts the container, sends
three realistic Camel-style traces (cache-miss/DB, order write, Kafka
payment flow) with proper SpanKind and semantic attributes, then waits
for user input before shutting down.
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
Signed-off-by: Adriano Machado <60...@users.noreply.github.com>
rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
* CAMEL-23641: Fix container image URL and add port configuration for Jaeger
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
---
.../apache/camel/catalog/test-infra/metadata.json | 10 +
test-infra/camel-test-infra-all/pom.xml | 11 +
.../src/generated/resources/META-INF/metadata.json | 10 +
test-infra/camel-test-infra-jaeger/pom.xml | 63 +++++
.../test/infra/jaeger/common/JaegerProperties.java | 32 +++
.../infra/jaeger/services/JaegerContainer.java | 66 +++++
.../infra/jaeger/services/JaegerInfraService.java | 45 +++
.../services/JaegerLocalContainerInfraService.java | 98 +++++++
.../jaeger/services/JaegerRemoteInfraService.java | 60 ++++
.../test/infra/jaeger/services/JaegerService.java | 26 ++
.../jaeger/services/JaegerServiceFactory.java | 41 +++
.../src/main/resources/META-INF/MANIFEST.MF | 1 +
.../infra/jaeger/services/container.properties | 19 ++
.../camel/test/infra/jaeger/JaegerTestApp.java | 308 +++++++++++++++++++++
test-infra/pom.xml | 1 +
15 files changed, 791 insertions(+)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
index 928559b5e6a1..cca277570b14 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
@@ -548,6 +548,16 @@
"artifactId" : "camel-test-infra-ftp",
"version" : "4.21.0-SNAPSHOT",
"serviceVersion" : "2.18.0"
+}, {
+ "service" : "org.apache.camel.test.infra.jaeger.services.JaegerInfraService",
+ "description" : "Jaeger is a distributed tracing backend with OTLP collector and UI",
+ "implementation" : "org.apache.camel.test.infra.jaeger.services.JaegerLocalContainerInfraService",
+ "alias" : [ "jaeger" ],
+ "aliasImplementation" : [ ],
+ "groupId" : "org.apache.camel",
+ "artifactId" : "camel-test-infra-jaeger",
+ "version" : "4.21.0-SNAPSHOT",
+ "serviceVersion" : "2.18.0"
}, {
"service" : "org.apache.camel.test.infra.infinispan.services.InfinispanInfraService",
"description" : "Infinispan is a distributed in-memory key/value data store",
diff --git a/test-infra/camel-test-infra-all/pom.xml b/test-infra/camel-test-infra-all/pom.xml
index 87f59e65ca90..1b8cbf21b51d 100644
--- a/test-infra/camel-test-infra-all/pom.xml
+++ b/test-infra/camel-test-infra-all/pom.xml
@@ -276,6 +276,11 @@
<artifactId>camel-test-infra-ignite</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-jaeger</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-infra-neo4j</artifactId>
@@ -578,6 +583,12 @@
<artifactId>camel-test-infra-ignite</artifactId>
</dependency>
</fileSet>
+ <fileSet>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-jaeger</artifactId>
+ </dependency>
+ </fileSet>
<fileSet>
<dependency>
<groupId>org.apache.camel</groupId>
diff --git a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
index 928559b5e6a1..cca277570b14 100644
--- a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
+++ b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
@@ -548,6 +548,16 @@
"artifactId" : "camel-test-infra-ftp",
"version" : "4.21.0-SNAPSHOT",
"serviceVersion" : "2.18.0"
+}, {
+ "service" : "org.apache.camel.test.infra.jaeger.services.JaegerInfraService",
+ "description" : "Jaeger is a distributed tracing backend with OTLP collector and UI",
+ "implementation" : "org.apache.camel.test.infra.jaeger.services.JaegerLocalContainerInfraService",
+ "alias" : [ "jaeger" ],
+ "aliasImplementation" : [ ],
+ "groupId" : "org.apache.camel",
+ "artifactId" : "camel-test-infra-jaeger",
+ "version" : "4.21.0-SNAPSHOT",
+ "serviceVersion" : "2.18.0"
}, {
"service" : "org.apache.camel.test.infra.infinispan.services.InfinispanInfraService",
"description" : "Infinispan is a distributed in-memory key/value data store",
diff --git a/test-infra/camel-test-infra-jaeger/pom.xml b/test-infra/camel-test-infra-jaeger/pom.xml
new file mode 100644
index 000000000000..7879f717edcf
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>camel-test-infra-parent</artifactId>
+ <groupId>org.apache.camel</groupId>
+ <relativePath>../camel-test-infra-parent/pom.xml</relativePath>
+ <version>4.21.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>camel-test-infra-jaeger</artifactId>
+ <name>Camel :: Test Infra :: Jaeger</name>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-bom</artifactId>
+ <version>${opentelemetry-version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-infra-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-sdk</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-exporter-otlp</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/common/JaegerProperties.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/common/JaegerProperties.java
new file mode 100644
index 000000000000..d360baa43d93
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/common/JaegerProperties.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.common;
+
+public final class JaegerProperties {
+ public static final String JAEGER_CONTAINER = "jaeger.container";
+ public static final String HOST = "jaeger.host";
+ public static final String COLLECTOR_GRPC_PORT = "jaeger.collector.grpc.port";
+ public static final String COLLECTOR_HTTP_PORT = "jaeger.collector.http.port";
+ public static final String QUERY_UI_PORT = "jaeger.query.ui.port";
+
+ public static final int DEFAULT_COLLECTOR_GRPC_PORT = 4317;
+ public static final int DEFAULT_COLLECTOR_HTTP_PORT = 4318;
+ public static final int DEFAULT_QUERY_UI_PORT = 16686;
+
+ private JaegerProperties() {
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerContainer.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerContainer.java
new file mode 100644
index 000000000000..ed54b5c34fed
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerContainer.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.test.infra.common.LocalPropertyResolver;
+import org.apache.camel.test.infra.jaeger.common.JaegerProperties;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+public class JaegerContainer extends GenericContainer<JaegerContainer> {
+ public static final String CONTAINER_NAME = "jaeger";
+
+ public JaegerContainer() {
+ super(LocalPropertyResolver.getProperty(JaegerLocalContainerInfraService.class, JaegerProperties.JAEGER_CONTAINER));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+ .withExposedPorts(
+ JaegerProperties.DEFAULT_COLLECTOR_GRPC_PORT,
+ JaegerProperties.DEFAULT_COLLECTOR_HTTP_PORT,
+ JaegerProperties.DEFAULT_QUERY_UI_PORT)
+ .waitingFor(Wait.forHttp("/").forPort(JaegerProperties.DEFAULT_QUERY_UI_PORT));
+ }
+
+ public JaegerContainer(String imageName) {
+ super(DockerImageName.parse(imageName));
+
+ this.withNetworkAliases(CONTAINER_NAME)
+ .withExposedPorts(
+ JaegerProperties.DEFAULT_COLLECTOR_GRPC_PORT,
+ JaegerProperties.DEFAULT_COLLECTOR_HTTP_PORT,
+ JaegerProperties.DEFAULT_QUERY_UI_PORT)
+ .waitingFor(Wait.forHttp("/").forPort(JaegerProperties.DEFAULT_QUERY_UI_PORT));
+ }
+
+ public JaegerContainer withFixedPort(int hostPort, int containerPort) {
+ addFixedExposedPort(hostPort, containerPort);
+ return this;
+ }
+
+ @SuppressWarnings("resource")
+ // NOTE: the object must be closed by the client.
+ public static JaegerContainer initContainer(String imageName, String networkAlias) {
+ return new JaegerContainer(imageName) // NOSONAR
+ .withNetworkAliases(networkAlias)
+ .withExposedPorts(
+ JaegerProperties.DEFAULT_COLLECTOR_GRPC_PORT,
+ JaegerProperties.DEFAULT_COLLECTOR_HTTP_PORT,
+ JaegerProperties.DEFAULT_QUERY_UI_PORT)
+ .waitingFor(Wait.forHttp("/").forPort(JaegerProperties.DEFAULT_QUERY_UI_PORT));
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerInfraService.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerInfraService.java
new file mode 100644
index 000000000000..a46a10fd62f5
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerInfraService.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.test.infra.common.services.InfrastructureService;
+
+/**
+ * Test infra service for Jaeger distributed tracing
+ */
+public interface JaegerInfraService extends InfrastructureService {
+
+ String host();
+
+ int collectorGrpcPort();
+
+ int collectorHttpPort();
+
+ int queryUiPort();
+
+ default String collectorGrpcEndpoint() {
+ return String.format("http://%s:%d", host(), collectorGrpcPort());
+ }
+
+ default String collectorHttpEndpoint() {
+ return String.format("http://%s:%d", host(), collectorHttpPort());
+ }
+
+ default String queryUiUrl() {
+ return String.format("http://%s:%d", host(), queryUiPort());
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerLocalContainerInfraService.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerLocalContainerInfraService.java
new file mode 100644
index 000000000000..5c0c51a31bed
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerLocalContainerInfraService.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.spi.annotations.InfraService;
+import org.apache.camel.test.infra.common.services.ContainerEnvironmentUtil;
+import org.apache.camel.test.infra.common.services.ContainerService;
+import org.apache.camel.test.infra.jaeger.common.JaegerProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@InfraService(service = JaegerInfraService.class,
+ description = "Jaeger is a distributed tracing backend with OTLP collector and UI",
+ serviceAlias = { "jaeger" })
+public class JaegerLocalContainerInfraService implements JaegerInfraService, ContainerService<JaegerContainer> {
+ private static final Logger LOG = LoggerFactory.getLogger(JaegerLocalContainerInfraService.class);
+
+ private final JaegerContainer container;
+
+ public JaegerLocalContainerInfraService() {
+ container = new JaegerContainer();
+ String name = ContainerEnvironmentUtil.containerName(this.getClass());
+ if (name != null) {
+ container.withCreateContainerCmdModifier(cmd -> cmd.withName(name));
+ }
+ }
+
+ public JaegerLocalContainerInfraService(String imageName) {
+ container = JaegerContainer.initContainer(imageName, JaegerContainer.CONTAINER_NAME);
+ String name = ContainerEnvironmentUtil.containerName(this.getClass());
+ if (name != null) {
+ container.withCreateContainerCmdModifier(cmd -> cmd.withName(name));
+ }
+ }
+
+ @Override
+ public void registerProperties() {
+ System.setProperty(JaegerProperties.HOST, host());
+ System.setProperty(JaegerProperties.COLLECTOR_GRPC_PORT, String.valueOf(collectorGrpcPort()));
+ System.setProperty(JaegerProperties.COLLECTOR_HTTP_PORT, String.valueOf(collectorHttpPort()));
+ System.setProperty(JaegerProperties.QUERY_UI_PORT, String.valueOf(queryUiPort()));
+ }
+
+ @Override
+ public void initialize() {
+ LOG.info("Trying to start the Jaeger container");
+ container.start();
+
+ registerProperties();
+ LOG.info("Jaeger instance running at {} (OTLP gRPC: {}, OTLP HTTP: {}, UI: {})",
+ host(), collectorGrpcEndpoint(), collectorHttpEndpoint(), queryUiUrl());
+ }
+
+ @Override
+ public void shutdown() {
+ LOG.info("Stopping the Jaeger container");
+ container.stop();
+ }
+
+ @Override
+ public JaegerContainer getContainer() {
+ return container;
+ }
+
+ @Override
+ public String host() {
+ return container.getHost();
+ }
+
+ @Override
+ public int collectorGrpcPort() {
+ return container.getMappedPort(JaegerProperties.DEFAULT_COLLECTOR_GRPC_PORT);
+ }
+
+ @Override
+ public int collectorHttpPort() {
+ return container.getMappedPort(JaegerProperties.DEFAULT_COLLECTOR_HTTP_PORT);
+ }
+
+ @Override
+ public int queryUiPort() {
+ return container.getMappedPort(JaegerProperties.DEFAULT_QUERY_UI_PORT);
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerRemoteInfraService.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerRemoteInfraService.java
new file mode 100644
index 000000000000..102ec37e3c00
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerRemoteInfraService.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.test.infra.jaeger.common.JaegerProperties;
+
+public class JaegerRemoteInfraService implements JaegerInfraService {
+
+ @Override
+ public void registerProperties() {
+ // NO-OP
+ }
+
+ @Override
+ public void initialize() {
+ registerProperties();
+ }
+
+ @Override
+ public void shutdown() {
+ // NO-OP
+ }
+
+ @Override
+ public String host() {
+ return System.getProperty(JaegerProperties.HOST);
+ }
+
+ @Override
+ public int collectorGrpcPort() {
+ String port = System.getProperty(JaegerProperties.COLLECTOR_GRPC_PORT);
+ return port == null ? JaegerProperties.DEFAULT_COLLECTOR_GRPC_PORT : Integer.parseInt(port);
+ }
+
+ @Override
+ public int collectorHttpPort() {
+ String port = System.getProperty(JaegerProperties.COLLECTOR_HTTP_PORT);
+ return port == null ? JaegerProperties.DEFAULT_COLLECTOR_HTTP_PORT : Integer.parseInt(port);
+ }
+
+ @Override
+ public int queryUiPort() {
+ String port = System.getProperty(JaegerProperties.QUERY_UI_PORT);
+ return port == null ? JaegerProperties.DEFAULT_QUERY_UI_PORT : Integer.parseInt(port);
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerService.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerService.java
new file mode 100644
index 000000000000..23684423878f
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerService.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.test.infra.common.services.ContainerTestService;
+import org.apache.camel.test.infra.common.services.TestService;
+
+/**
+ * Test infra service for Jaeger
+ */
+public interface JaegerService extends TestService, JaegerInfraService, ContainerTestService {
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerServiceFactory.java b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerServiceFactory.java
new file mode 100644
index 000000000000..112af427f8f3
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/java/org/apache/camel/test/infra/jaeger/services/JaegerServiceFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger.services;
+
+import org.apache.camel.test.infra.common.services.SimpleTestServiceBuilder;
+
+public final class JaegerServiceFactory {
+ private JaegerServiceFactory() {
+ }
+
+ public static SimpleTestServiceBuilder<JaegerService> builder() {
+ return new SimpleTestServiceBuilder<>("jaeger");
+ }
+
+ public static JaegerService createService() {
+ return builder()
+ .addLocalMapping(JaegerLocalContainerService::new)
+ .addRemoteMapping(JaegerRemoteService::new)
+ .build();
+ }
+
+ public static class JaegerRemoteService extends JaegerRemoteInfraService implements JaegerService {
+ }
+
+ public static class JaegerLocalContainerService extends JaegerLocalContainerInfraService implements JaegerService {
+ }
+}
diff --git a/test-infra/camel-test-infra-jaeger/src/main/resources/META-INF/MANIFEST.MF b/test-infra/camel-test-infra-jaeger/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 000000000000..9d885be53412
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1 @@
+Manifest-Version: 1.0
diff --git a/test-infra/camel-test-infra-jaeger/src/main/resources/org/apache/camel/test/infra/jaeger/services/container.properties b/test-infra/camel-test-infra-jaeger/src/main/resources/org/apache/camel/test/infra/jaeger/services/container.properties
new file mode 100644
index 000000000000..91ddf29a64f6
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/main/resources/org/apache/camel/test/infra/jaeger/services/container.properties
@@ -0,0 +1,19 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+jaeger.container=quay.io/jaegertracing/jaeger:2.18.0
+jaeger.container.version.exclude=rc,beta
+jaeger.container.version.freeze.major=true
diff --git a/test-infra/camel-test-infra-jaeger/src/test/java/org/apache/camel/test/infra/jaeger/JaegerTestApp.java b/test-infra/camel-test-infra-jaeger/src/test/java/org/apache/camel/test/infra/jaeger/JaegerTestApp.java
new file mode 100644
index 000000000000..e6cf51c870ce
--- /dev/null
+++ b/test-infra/camel-test-infra-jaeger/src/test/java/org/apache/camel/test/infra/jaeger/JaegerTestApp.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.jaeger;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import org.apache.camel.test.infra.jaeger.services.JaegerLocalContainerInfraService;
+
+/**
+ * Manual test application that starts a Jaeger container, sends sample OTEL traces, and waits for the user to press
+ * Enter before shutting down.
+ *
+ * Run with: mvn test-compile exec:java -Dexec.mainClass=org.apache.camel.test.infra.jaeger.JaegerTestApp
+ * -Dexec.classpathScope=test -pl test-infra/camel-test-infra-jaeger
+ */
+public final class JaegerTestApp {
+
+ private static final String SERVICE_NAME = "camel-jaeger-test-app";
+
+ private JaegerTestApp() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ JaegerLocalContainerInfraService jaeger = new JaegerLocalContainerInfraService();
+ jaeger.initialize();
+
+ System.out.println("=================================================");
+ System.out.println("Jaeger UI: " + jaeger.queryUiUrl());
+ System.out.println("OTLP gRPC: " + jaeger.collectorGrpcEndpoint());
+ System.out.println("OTLP HTTP: " + jaeger.collectorHttpEndpoint());
+ System.out.println("=================================================");
+
+ OtlpHttpSpanExporter exporter = OtlpHttpSpanExporter.builder()
+ .setEndpoint(jaeger.collectorHttpEndpoint() + "/v1/traces")
+ .build();
+
+ Resource resource = Resource.getDefault().merge(
+ Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), SERVICE_NAME)));
+
+ SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(exporter))
+ .setResource(resource)
+ .build();
+
+ try (OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
+ .setTracerProvider(tracerProvider)
+ .build()) {
+ Tracer tracer = sdk.getTracer(SERVICE_NAME);
+ sendSampleTraces(tracer);
+
+ tracerProvider.forceFlush().join(10, TimeUnit.SECONDS);
+
+ System.out.println();
+ System.out.println("Sample traces sent. Open the Jaeger UI at: " + jaeger.queryUiUrl());
+ System.out.println("Press Enter to stop...");
+ System.in.read();
+ } finally {
+ jaeger.shutdown();
+ }
+ }
+
+ private static void sendSampleTraces(Tracer tracer) throws InterruptedException {
+ traceGetProductRoute(tracer);
+ traceSubmitOrderRoute(tracer);
+ traceProcessPaymentRoute(tracer);
+ }
+
+ /**
+ * Simulates: GET /api/products/{id}
+ *
+ * Flow: try cache → miss → DB lookup → populate cache → build response
+ */
+ private static void traceGetProductRoute(Tracer tracer) throws InterruptedException {
+ Span route = tracer.spanBuilder("camel-route: get-product-route")
+ .setSpanKind(SpanKind.SERVER)
+ .setAttribute("camel.route.id", "get-product-route")
+ .setAttribute("camel.uri", "platform-http:/api/products/{id}")
+ .setAttribute("http.method", "GET")
+ .setAttribute("http.url", "/api/products/42")
+ .startSpan();
+
+ try (Scope ignored = route.makeCurrent()) {
+ // Step 1: cache probe
+ Span cacheGet = tracer.spanBuilder("spring-redis://products")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "spring-redis://products?command=GET")
+ .setAttribute("db.system", "redis")
+ .setAttribute("db.operation", "GET")
+ .setAttribute("cache.key", "product:42")
+ .startSpan();
+ sleep(5, 15);
+ cacheGet.addEvent("cache.miss");
+ cacheGet.end();
+
+ // Step 2: DB lookup on cache miss
+ Span dbSelect = tracer.spanBuilder("jpa://org.apache.camel.example.Product")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "jpa://org.apache.camel.example.Product?query=findById")
+ .setAttribute("db.system", "postgresql")
+ .setAttribute("db.name", "cameldb")
+ .setAttribute("db.operation", "SELECT")
+ .setAttribute("db.statement", "SELECT * FROM products WHERE id = 42")
+ .startSpan();
+ sleep(60, 110);
+ dbSelect.end();
+
+ // Step 3: processor — map JPA entity to response DTO
+ Span mapProcessor = tracer.spanBuilder("camel-processor: ProductResponseMapper")
+ .setAttribute("camel.processor.type", "bean")
+ .setAttribute("camel.bean.method", "toResponse")
+ .startSpan();
+ sleep(5, 15);
+ mapProcessor.end();
+
+ // Step 4: populate cache for next request
+ Span cachePut = tracer.spanBuilder("spring-redis://products")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "spring-redis://products?command=SET&timeToLive=300")
+ .setAttribute("db.system", "redis")
+ .setAttribute("db.operation", "SET")
+ .setAttribute("cache.key", "product:42")
+ .startSpan();
+ sleep(3, 10);
+ cachePut.end();
+
+ // post-processing: marshal to JSON
+ sleep(5, 15);
+ route.setAttribute("http.status_code", 200L);
+ } finally {
+ route.end();
+ }
+ System.out.println("Sent trace: get-product-route");
+ }
+
+ /**
+ * Simulates: POST /api/orders
+ *
+ * Flow: validate payload → persist order → publish to Kafka → reply
+ */
+ private static void traceSubmitOrderRoute(Tracer tracer) throws InterruptedException {
+ Span route = tracer.spanBuilder("camel-route: submit-order-route")
+ .setSpanKind(SpanKind.SERVER)
+ .setAttribute("camel.route.id", "submit-order-route")
+ .setAttribute("camel.uri", "platform-http:/api/orders")
+ .setAttribute("http.method", "POST")
+ .setAttribute("http.url", "/api/orders")
+ .startSpan();
+
+ try (Scope ignored = route.makeCurrent()) {
+ // Step 1: validate incoming payload
+ Span validator = tracer.spanBuilder("camel-processor: OrderPayloadValidator")
+ .setAttribute("camel.processor.type", "bean")
+ .setAttribute("camel.bean.method", "validate")
+ .startSpan();
+ sleep(8, 20);
+ validator.end();
+
+ // Step 2: persist the order
+ Span dbInsert = tracer.spanBuilder("jdbc://cameldb")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "jdbc://cameldb")
+ .setAttribute("db.system", "postgresql")
+ .setAttribute("db.name", "cameldb")
+ .setAttribute("db.operation", "INSERT")
+ .setAttribute("db.statement", "INSERT INTO orders (customer_id, total, status) VALUES (?, ?, 'PENDING')")
+ .startSpan();
+ sleep(40, 80);
+ dbInsert.end();
+
+ // Step 3: enrich with inventory data before publishing
+ Span inventoryCheck = tracer.spanBuilder("http://inventory-service/api/stock")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "http://inventory-service/api/stock?bridgeEndpoint=true")
+ .setAttribute("http.method", "GET")
+ .setAttribute("net.peer.name", "inventory-service")
+ .startSpan();
+ sleep(25, 55);
+ inventoryCheck.end();
+
+ // Step 4: publish order-created event to Kafka
+ Span kafkaPublish = tracer.spanBuilder("kafka://order-events")
+ .setSpanKind(SpanKind.PRODUCER)
+ .setAttribute("camel.uri", "kafka://order-events?brokers=kafka:9092")
+ .setAttribute("messaging.system", "kafka")
+ .setAttribute("messaging.destination", "order-events")
+ .setAttribute("messaging.operation", "publish")
+ .startSpan();
+ sleep(15, 35);
+ kafkaPublish.end();
+
+ // post-processing: build 201 response
+ sleep(5, 12);
+ route.setAttribute("http.status_code", 201L);
+ } finally {
+ route.end();
+ }
+ System.out.println("Sent trace: submit-order-route");
+ }
+
+ /**
+ * Simulates: payment processing triggered from a Kafka consumer
+ *
+ * Flow: consume event → call payment gateway → fraud check → persist transaction → dead-letter on fraud flag
+ */
+ private static void traceProcessPaymentRoute(Tracer tracer) throws InterruptedException {
+ Span route = tracer.spanBuilder("camel-route: payment-processor-route")
+ .setSpanKind(SpanKind.CONSUMER)
+ .setAttribute("camel.route.id", "payment-processor-route")
+ .setAttribute("camel.uri", "kafka://payment-requests?brokers=kafka:9092&groupId=payment-svc")
+ .setAttribute("messaging.system", "kafka")
+ .setAttribute("messaging.destination", "payment-requests")
+ .setAttribute("messaging.operation", "receive")
+ .startSpan();
+
+ try (Scope ignored = route.makeCurrent()) {
+ // Step 1: unmarshal and validate
+ Span unmarshal = tracer.spanBuilder("camel-processor: PaymentRequestUnmarshaller")
+ .setAttribute("camel.processor.type", "unmarshal")
+ .setAttribute("camel.data.format", "jackson")
+ .startSpan();
+ sleep(5, 12);
+ unmarshal.end();
+
+ // Step 2: call external payment gateway
+ Span gateway = tracer.spanBuilder("http://payment-gateway.internal/v2/charge")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "http://payment-gateway.internal/v2/charge")
+ .setAttribute("http.method", "POST")
+ .setAttribute("net.peer.name", "payment-gateway.internal")
+ .setAttribute("http.url", "/v2/charge")
+ .startSpan();
+ sleep(90, 160);
+ gateway.setAttribute("http.status_code", 200L);
+ gateway.end();
+
+ // Step 3: fraud detection processor
+ Span fraud = tracer.spanBuilder("camel-processor: FraudDetectionEnricher")
+ .setAttribute("camel.processor.type", "bean")
+ .setAttribute("camel.bean.method", "evaluate")
+ .startSpan();
+ sleep(20, 45);
+ fraud.addEvent("fraud.score.computed", Attributes.of(
+ AttributeKey.doubleKey("fraud.score"), ThreadLocalRandom.current().nextDouble(0.01, 0.15)));
+ fraud.end();
+
+ // Step 4: persist the transaction record
+ Span txPersist = tracer.spanBuilder("jpa://org.apache.camel.example.Transaction")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("camel.uri", "jpa://org.apache.camel.example.Transaction?usePersist=true")
+ .setAttribute("db.system", "postgresql")
+ .setAttribute("db.name", "cameldb")
+ .setAttribute("db.operation", "INSERT")
+ .setAttribute("db.statement", "INSERT INTO transactions (order_id, amount, status, gateway_ref)")
+ .startSpan();
+ sleep(30, 65);
+ txPersist.end();
+
+ // Step 5: publish confirmation event
+ Span confirmation = tracer.spanBuilder("kafka://payment-confirmations")
+ .setSpanKind(SpanKind.PRODUCER)
+ .setAttribute("camel.uri", "kafka://payment-confirmations?brokers=kafka:9092")
+ .setAttribute("messaging.system", "kafka")
+ .setAttribute("messaging.destination", "payment-confirmations")
+ .setAttribute("messaging.operation", "publish")
+ .startSpan();
+ sleep(10, 25);
+ confirmation.end();
+
+ // post-processing: commit offset
+ sleep(3, 8);
+ } finally {
+ route.end();
+ }
+ System.out.println("Sent trace: payment-processor-route");
+ }
+
+ // Intentional sleep: this is a manual demo app, not an automated test.
+ // The delays produce realistic span durations in the Jaeger UI so traces look like real Camel route executions.
+ private static void sleep(long minMs, long maxMs) throws InterruptedException {
+ Thread.sleep(ThreadLocalRandom.current().nextLong(minMs, maxMs));
+ }
+}
diff --git a/test-infra/pom.xml b/test-infra/pom.xml
index db6c640e43ae..6c2846b6412b 100644
--- a/test-infra/pom.xml
+++ b/test-infra/pom.xml
@@ -99,6 +99,7 @@
<module>camel-test-infra-ibmmq</module>
<module>camel-test-infra-docling</module>
<module>camel-test-infra-iggy</module>
+ <module>camel-test-infra-jaeger</module>
<module>camel-test-infra-mcp-everything</module>
<module>camel-test-infra-all</module>
</modules>