This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 84f7ab58b7efb2ac5c003596d075cfa82bb68a0e Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Mon Feb 3 13:22:18 2025 +0000 Test observability-services otel tracing and JMX metrics Fixes #6968 --- .../deployment/ObservabilityServicesProcessor.java | 10 +- .../ObservabilityServicesConfigSourceFactory.java | 54 ++++++++ ...ObservabilityServicesRuntimeConfigBuilder.java} | 17 +-- integration-tests/observability-services/pom.xml | 25 ++++ .../it/health/ObservabilityServicesResource.java | 91 +++++++++++++ ...ilder.java => ObservabilityServicesRoutes.java} | 23 +--- .../it/health/SpanExporterProducer.java | 21 +-- .../it/health/SpanExporterResource.java | 72 ++++++++++ .../src/main/resources/application.properties | 11 +- .../it/ObservabilityServicesDefaultTest.java | 70 ---------- .../it/ObservabilityServicesIT.java | 2 +- .../it/ObservabilityServicesTest.java | 149 +++++++++++++++++++++ 12 files changed, 431 insertions(+), 114 deletions(-) diff --git a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java b/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java index b599823fd1..63be81aa25 100644 --- a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java +++ b/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java @@ -18,13 +18,21 @@ package org.apache.camel.quarkus.component.observabilityservices.deployment; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem; +import io.quarkus.opentelemetry.deployment.tracing.TracerEnabled; +import org.apache.camel.quarkus.component.observabilityservices.ObservabilityServicesRuntimeConfigBuilder; class ObservabilityServicesProcessor { - private static final String FEATURE = "camel-observability-services"; @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } + + // TODO: Remove this https://github.com/apache/camel-quarkus/issues/6967 + @BuildStep(onlyIf = TracerEnabled.class) + RunTimeConfigBuilderBuildItem observabilityServicesRuntimeConfiguration() { + return new RunTimeConfigBuilderBuildItem(ObservabilityServicesRuntimeConfigBuilder.class); + } } diff --git a/extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesConfigSourceFactory.java b/extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesConfigSourceFactory.java new file mode 100644 index 0000000000..2d2d7714db --- /dev/null +++ b/extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesConfigSourceFactory.java @@ -0,0 +1,54 @@ +/* + * 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.quarkus.component.observabilityservices; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigSourceFactory; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.PropertiesConfigSource; +import org.apache.camel.util.ObjectHelper; +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * TODO: Remove this https://github.com/apache/camel-quarkus/issues/6967 + */ +public class ObservabilityServicesConfigSourceFactory implements ConfigSourceFactory { + @Override + public Iterable<ConfigSource> getConfigSources(ConfigSourceContext context) { + ConfigValue sdkDisabled = context.getValue("quarkus.otel.sdk.disabled"); + + if (sdkDisabled != null && sdkDisabled.getValue().equals("false")) { + Map<String, String> properties = new HashMap<>(1); + ConfigValue otelSuppressedUris = context.getValue("quarkus.otel.traces.suppress-application-uris"); + String suppressedEndpoints = "/observe/*"; + + if (otelSuppressedUris != null && ObjectHelper.isNotEmpty(otelSuppressedUris.getValue())) { + suppressedEndpoints += "," + otelSuppressedUris.getValue(); + } + + properties.put("quarkus.otel.traces.suppress-application-uris", suppressedEndpoints); + return Set.of(new PropertiesConfigSource(properties, "camel-quarkus-observability-services", 300)); + } + + return Collections.emptySet(); + } +} diff --git a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java b/extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesRuntimeConfigBuilder.java similarity index 70% copy from extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java copy to extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesRuntimeConfigBuilder.java index b599823fd1..3a382cc37e 100644 --- a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java +++ b/extensions/observability-services/runtime/src/main/java/org.apache.camel.quarkus.component.observabilityservices/ObservabilityServicesRuntimeConfigBuilder.java @@ -14,17 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.quarkus.component.observabilityservices.deployment; +package org.apache.camel.quarkus.component.observabilityservices; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.runtime.configuration.ConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilder; -class ObservabilityServicesProcessor { - - private static final String FEATURE = "camel-observability-services"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); +public class ObservabilityServicesRuntimeConfigBuilder implements ConfigBuilder { + @Override + public SmallRyeConfigBuilder configBuilder(SmallRyeConfigBuilder builder) { + return builder.withSources(new ObservabilityServicesConfigSourceFactory()); } } diff --git a/integration-tests/observability-services/pom.xml b/integration-tests/observability-services/pom.xml index ea73469981..97c287347a 100644 --- a/integration-tests/observability-services/pom.xml +++ b/integration-tests/observability-services/pom.xml @@ -43,10 +43,22 @@ <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-observability-services</artifactId> </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-seda</artifactId> + </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk-testing</artifactId> + </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-resteasy-jsonb</artifactId> + </dependency> <!-- test dependencies --> <dependency> @@ -142,6 +154,19 @@ </exclusion> </exclusions> </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-seda-deployment</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> </dependencies> </profile> </profiles> diff --git a/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesResource.java b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesResource.java new file mode 100644 index 0000000000..12ecd0501a --- /dev/null +++ b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesResource.java @@ -0,0 +1,91 @@ +/* + * 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.quarkus.component.observabilityservices.it.health; + +import java.lang.management.ManagementFactory; +import java.util.Iterator; +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; + +import io.micrometer.prometheus.PrometheusMeterRegistry; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.FluentProducerTemplate; + +@Path("/observability-services") +public class ObservabilityServicesResource { + @Inject + FluentProducerTemplate fluentProducerTemplate; + + @Inject + PrometheusMeterRegistry prometheusMeterRegistry; + + @GET + @Path("/registry") + @Produces(MediaType.TEXT_PLAIN) + public Response prometheusMeterRegistry() { + if (prometheusMeterRegistry != null) { + return Response.ok().entity(prometheusMeterRegistry.getClass().getName()).build(); + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + + @POST + @Path("/trace") + @Produces(MediaType.TEXT_PLAIN) + public String trace(String message) { + return fluentProducerTemplate.to("direct:start") + .withBody(message) + .request(String.class); + } + + @GET + @Path("/jmx/attribute") + @Produces(MediaType.TEXT_PLAIN) + public String getMBeanAttribute(@QueryParam("name") String name, @QueryParam("attribute") String attribute) + throws Exception { + ObjectInstance mbean = getMBean(name); + if (mbean != null) { + return String.valueOf(getMBeanServer().getAttribute(mbean.getObjectName(), attribute)); + } + return null; + } + + private ObjectInstance getMBean(String name) throws MalformedObjectNameException { + ObjectName objectName = new ObjectName(name); + Set<ObjectInstance> mbeans = getMBeanServer().queryMBeans(objectName, null); + Iterator<ObjectInstance> iterator = mbeans.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } + return null; + } + + private MBeanServer getMBeanServer() { + return ManagementFactory.getPlatformMBeanServer(); + } +} diff --git a/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesHealthRouteBuilder.java b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesRoutes.java similarity index 51% rename from integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesHealthRouteBuilder.java rename to integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesRoutes.java index 0156316ab3..8f011b99d5 100644 --- a/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesHealthRouteBuilder.java +++ b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/ObservabilityServicesRoutes.java @@ -17,24 +17,15 @@ package org.apache.camel.quarkus.component.observabilityservices.it.health; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.spi.SupervisingRouteController; -public class ObservabilityServicesHealthRouteBuilder extends RouteBuilder { +public class ObservabilityServicesRoutes extends RouteBuilder { @Override - public void configure() { - from("direct:start").routeId("healthyRoute") - .setBody(constant("Hello Camel Quarkus")); + public void configure() throws Exception { + from("direct:start") + .to("seda:next"); - from("direct:disabled").routeId("disabledHealthRoute") - .log("This route will not show up in health checks as it is disabled in application.properties"); - - if (getContext().getRouteController() instanceof SupervisingRouteController) { - from("direct:supervising").routeId("supervisingRoute") - .to("log:end"); - - // Force a failure for SupervisingRouteController to try and recover (duplicate consumer on the same endpoint) - from("direct:supervising?timeout=100").routeId("brokenRoute") - .to("log:end"); - } + from("seda:next") + .transform().simple("modified ${body}") + .log("${body}"); } } diff --git a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterProducer.java similarity index 68% copy from extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java copy to integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterProducer.java index b599823fd1..23d5c7c7a3 100644 --- a/extensions/observability-services/deployment/src/main/java/org/apache/camel/quarkus/component/observabilityservices/deployment/ObservabilityServicesProcessor.java +++ b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterProducer.java @@ -14,17 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.quarkus.component.observabilityservices.deployment; +package org.apache.camel.quarkus.component.observabilityservices.it.health; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; -class ObservabilityServicesProcessor { - - private static final String FEATURE = "camel-observability-services"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); +@ApplicationScoped +public class SpanExporterProducer { + @Produces + @Singleton + public InMemorySpanExporter createInMemoryExporter() { + return InMemorySpanExporter.create(); } } diff --git a/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterResource.java b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterResource.java new file mode 100644 index 0000000000..c57f3d7a6e --- /dev/null +++ b/integration-tests/observability-services/src/main/java/org/apache/camel/quarkus/component/observabilityservices/it/health/SpanExporterResource.java @@ -0,0 +1,72 @@ +/* + * 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.quarkus.component.observabilityservices.it.health; + +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.data.SpanData; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObjectBuilder; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/spans") +public class SpanExporterResource { + @Inject + InMemorySpanExporter exporter; + + @GET + @Path("/export") + @Produces(MediaType.APPLICATION_JSON) + public JsonArray exportSpans() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + for (SpanData span : exporter.getFinishedSpanItems()) { + if (span.getName().contains("exporter")) { + // Ignore any trace events on this resource + continue; + } + + Map<AttributeKey<?>, Object> attributes = span.getAttributes().asMap(); + JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); + objectBuilder.add("spanId", span.getSpanId()); + objectBuilder.add("traceId", span.getTraceId()); + objectBuilder.add("parentId", span.getParentSpanId()); + objectBuilder.add("kind", span.getKind().name()); + + attributes.forEach((k, v) -> objectBuilder.add(String.valueOf(k), v.toString())); + + arrayBuilder.add(objectBuilder.build()); + } + + return arrayBuilder.build(); + } + + @POST + @Path("/reset") + public void resetSpanExporter() { + exporter.reset(); + } +} diff --git a/integration-tests/observability-services/src/main/resources/application.properties b/integration-tests/observability-services/src/main/resources/application.properties index 526e9a9a8d..75eac8f864 100644 --- a/integration-tests/observability-services/src/main/resources/application.properties +++ b/integration-tests/observability-services/src/main/resources/application.properties @@ -15,10 +15,9 @@ ## limitations under the License. ## --------------------------------------------------------------------------- -# -# Camel -# -camel.context.name = quarkus-camel-example +camel.context.name=observability-services-context -# Prevent unwanted routes appearing in the health check output -camel.health.exclude-pattern = disabledHealthRoute +quarkus.otel.sdk.disabled=false +quarkus.otel.bsp.export.timeout=1s +quarkus.otel.bsp.schedule.delay=50 +quarkus.otel.traces.suppress-application-uris=/spans/*,/observability-services/registry diff --git a/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesDefaultTest.java b/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesDefaultTest.java deleted file mode 100644 index cd1589aefc..0000000000 --- a/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesDefaultTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.quarkus.component.observabilityservices.it; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.RestAssured; -import io.restassured.http.ContentType; -import org.apache.http.HttpStatus; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -@QuarkusTest -class ObservabilityServicesDefaultTest { - - @Test - public void testHealthUpStatus() { - RestAssured.when().get("/observe/health").then() - .contentType(ContentType.JSON) - .header("Content-Type", containsString("charset=UTF-8")) - .body("status", is("UP"), - "checks.status.findAll().unique()", contains("UP"), - "checks.find { it.name == 'camel-routes' }", notNullValue(), - "checks.find { it.name == 'camel-consumers' }", notNullValue(), - "checks.find { it.name == 'context' }", notNullValue(), - "checks.find { it.name == 'context' }.data.'context.name'", notNullValue()); - } - - @Test - public void testLivenessUpStatus() { - RestAssured.when().get("/observe/health/live").then() - .contentType(ContentType.JSON) - .header("Content-Type", containsString("charset=UTF-8")) - .body("status", is("UP"), - "checks.status.findAll().unique()", contains("UP")); - } - - @Test - public void testReadinessUpStatus() { - RestAssured.when().get("/observe/health/ready").then() - .contentType(ContentType.JSON) - .header("Content-Type", containsString("charset=UTF-8")) - .body("status", is("UP"), - "checks.status.findAll().unique()", contains("UP")); - } - - @Test - public void testMetricsStatus() { - RestAssured.when().get("/observe/metrics").then() - .header("Content-Type", containsString("application/openmetrics-text")) - .statusCode(HttpStatus.SC_OK); - } -} diff --git a/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesIT.java b/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesIT.java index c1a786b925..043b40ec5d 100644 --- a/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesIT.java +++ b/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesIT.java @@ -19,6 +19,6 @@ package org.apache.camel.quarkus.component.observabilityservices.it; import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest -class ObservabilityServicesIT extends ObservabilityServicesDefaultTest { +class ObservabilityServicesIT extends ObservabilityServicesTest { } diff --git a/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesTest.java b/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesTest.java new file mode 100644 index 0000000000..7c7cf5b495 --- /dev/null +++ b/integration-tests/observability-services/src/test/java/org/apache/camel/quarkus/component/observabilityservices/it/ObservabilityServicesTest.java @@ -0,0 +1,149 @@ +/* + * 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.quarkus.component.observabilityservices.it; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@QuarkusTest +class ObservabilityServicesTest { + + @Test + void testHealthUpStatus() { + RestAssured.when().get("/observe/health").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status.findAll().unique()", contains("UP"), + "checks.find { it.name == 'camel-routes' }", notNullValue(), + "checks.find { it.name == 'camel-consumers' }", notNullValue(), + "checks.find { it.name == 'context' }", notNullValue(), + "checks.find { it.name == 'context' }.data.'context.name'", notNullValue()); + } + + @Test + void testLivenessUpStatus() { + RestAssured.when().get("/observe/health/live").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status.findAll().unique()", contains("UP")); + } + + @Test + void testReadinessUpStatus() { + RestAssured.when().get("/observe/health/ready").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status.findAll().unique()", contains("UP")); + } + + @Test + void testMetricsStatus() { + RestAssured.when().get("/observe/metrics").then() + .header("Content-Type", containsString("application/openmetrics-text")) + .statusCode(HttpStatus.SC_OK); + } + + @Test + void metricsRegistry() { + RestAssured.get("/observability-services/registry") + .then() + .statusCode(200) + .body(is(PrometheusMeterRegistry.class.getName())); + } + + @Test + void traceRoute() { + try { + String message = UUID.randomUUID().toString(); + RestAssured.given() + .body(message) + .post("/observability-services/trace") + .then() + .statusCode(200) + .body(is("modified " + message)); + + await().atMost(30, TimeUnit.SECONDS).pollDelay(50, TimeUnit.MILLISECONDS).until(() -> getSpans().size() == 5); + List<Map<String, String>> spans = getSpans(); + assertEquals(spans.get(0).get("parentId"), spans.get(1).get("spanId")); + assertEquals(spans.get(0).get("camel.uri"), "seda://next"); + assertEquals(spans.get(0).get("kind"), "INTERNAL"); + + assertEquals(spans.get(1).get("parentId"), spans.get(2).get("spanId")); + assertEquals(spans.get(1).get("camel.uri"), "seda://next"); + assertEquals(spans.get(1).get("kind"), "CLIENT"); + + assertEquals(spans.get(2).get("parentId"), spans.get(3).get("spanId")); + assertEquals(spans.get(2).get("camel.uri"), "direct://start"); + assertEquals(spans.get(2).get("kind"), "INTERNAL"); + + assertEquals(spans.get(3).get("parentId"), spans.get(4).get("spanId")); + assertEquals(spans.get(3).get("camel.uri"), "direct://start"); + assertEquals(spans.get(3).get("kind"), "CLIENT"); + + assertEquals(spans.get(4).get("parentId"), "0000000000000000"); + assertEquals(spans.get(4).get("code.function"), "trace"); + assertEquals(spans.get(4).get("url.path"), "/observability-services/trace"); + } finally { + RestAssured.given() + .post("/spans/reset") + .then() + .statusCode(204); + } + } + + @Test + void resolveMBeanAttribute() { + String name = "org.apache.camel:type=context,*"; + RestAssured.given() + .queryParam("name", name) + .queryParam("attribute", "CamelId") + .get("/observability-services/jmx/attribute") + .then() + .statusCode(200) + .body(is("observability-services-context")); + } + + static List<Map<String, String>> getSpans() { + return RestAssured.given() + .get("/spans/export") + .then() + .statusCode(200) + .extract() + .body() + .jsonPath() + .get(); + } +}