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>