From aae22eb5fe1f5080a7049cf20be1169dc0e1a7a9 Mon Sep 17 00:00:00 2001 From: Fabio Burzigotti Date: Mon, 2 Dec 2024 18:05:12 +0100 Subject: [PATCH] [issues-317] - Adding tests to verify simultaneous configuration of both Micrometer and MP Telemetry corner cases for MP Fault Tolerance Fixing MicroProfileTelemetryServerSetup.java and renaming it to MicroProfileTelemetryServerConfiguration.java. Fix OpenTelemetryCollectorContainer and improve it, so that it can now provide new instance, and does not handle the Jaeger container lifecycle anymore --- .../MicrometerOtelIntegrationTestCase.java | 3 +- .../util/MicrometerServerSetup.java | 3 + .../test/resources/otel-collector-config.yaml | 2 +- microprofile-fault-tolerance/pom.xml | 5 + .../fault/tolerance/UndeployDeployTest.java | 155 ++++--- .../metrics/MultipleMetricsExtensionTest.java | 402 ++++++++++++++++++ .../test/resources/otel-collector-config.yaml | 2 +- pom.xml | 12 + tooling-observability/pom.xml | 8 + .../OpenTelemetryCollectorContainer.java | 85 +++- ...oProfileTelemetryServerConfiguration.java} | 6 +- .../MicrometerServerConfiguration.java | 2 +- 12 files changed, 595 insertions(+), 90 deletions(-) create mode 100644 microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/integration/metrics/MultipleMetricsExtensionTest.java rename tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/{MicroProfileTelemetryServerSetup.java => MicroProfileTelemetryServerConfiguration.java} (97%) rename {micrometer/src/test/java/org/jboss/eap/qe/micrometer/util => tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks}/MicrometerServerConfiguration.java (98%) diff --git a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/MicrometerOtelIntegrationTestCase.java b/micrometer/src/test/java/org/jboss/eap/qe/micrometer/MicrometerOtelIntegrationTestCase.java index 88838a2d..5f74216c 100644 --- a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/MicrometerOtelIntegrationTestCase.java +++ b/micrometer/src/test/java/org/jboss/eap/qe/micrometer/MicrometerOtelIntegrationTestCase.java @@ -18,6 +18,7 @@ import org.jboss.as.arquillian.api.ServerSetup; import org.jboss.eap.qe.micrometer.base.MetricResource; import org.jboss.eap.qe.micrometer.util.MicrometerServerSetup; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicrometerServerConfiguration; import org.jboss.eap.qe.microprofile.tooling.server.configuration.deployment.ConfigurationUtil; import org.jboss.eap.qe.observability.containers.OpenTelemetryCollectorContainer; import org.jboss.eap.qe.observability.prometheus.model.PrometheusMetric; @@ -40,7 +41,7 @@ * Tests that metrics can be pushed to the OpenTelemetry collector by Micrometer, and then exported to Jaeger. * This class is based on the similar one in WildFly, although it uses a different {@code @ServerSetup} task class, * i.e. {@link MicrometerServerSetup}, which provides the logic for executing the required configuration - * (see {@link org.jboss.eap.qe.micrometer.util.MicrometerServerConfiguration}) within the Arquillian container. + * (see {@link MicrometerServerConfiguration}) within the Arquillian container. */ @RunWith(Arquillian.class) @ServerSetup(MicrometerServerSetup.class) // Enables/Disables Micrometer extension/subsystem for Arquillian in-container tests diff --git a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerSetup.java b/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerSetup.java index fc1aebab..d27cebc2 100644 --- a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerSetup.java +++ b/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerSetup.java @@ -1,5 +1,6 @@ package org.jboss.eap.qe.micrometer.util; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicrometerServerConfiguration; import org.jboss.eap.qe.microprofile.tooling.server.configuration.arquillian.MicroProfileServerSetupTask; import org.jboss.eap.qe.observability.containers.OpenTelemetryCollectorContainer; import org.jboss.eap.qe.ts.common.docker.Docker; @@ -20,6 +21,7 @@ public void setup() throws Exception { } // start the OTel collector container otelCollector = OpenTelemetryCollectorContainer.getInstance(); + otelCollector.start(); // and pass Micrometer the OTel collector endopint URL MicrometerServerConfiguration.enableMicrometer(otelCollector.getOtlpHttpEndpoint()); } @@ -29,5 +31,6 @@ public void tearDown() throws Exception { MicrometerServerConfiguration.disableMicrometer(); // stop the OTel collector container otelCollector.stop(); + OpenTelemetryCollectorContainer.dispose(); } } diff --git a/micrometer/src/test/resources/otel-collector-config.yaml b/micrometer/src/test/resources/otel-collector-config.yaml index 4eacec9e..3185b5d4 100644 --- a/micrometer/src/test/resources/otel-collector-config.yaml +++ b/micrometer/src/test/resources/otel-collector-config.yaml @@ -18,7 +18,7 @@ processors: exporters: logging: - verbosity: detailed + verbosity: normal prometheus: endpoint: "0.0.0.0:49152" otlp: diff --git a/microprofile-fault-tolerance/pom.xml b/microprofile-fault-tolerance/pom.xml index b9608b4a..21877661 100644 --- a/microprofile-fault-tolerance/pom.xml +++ b/microprofile-fault-tolerance/pom.xml @@ -98,6 +98,11 @@ org.postgresql postgresql + + org.slf4j + slf4j-simple + test + diff --git a/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/UndeployDeployTest.java b/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/UndeployDeployTest.java index 80ed0992..0a73ffbb 100644 --- a/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/UndeployDeployTest.java +++ b/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/UndeployDeployTest.java @@ -15,7 +15,8 @@ import org.jboss.arquillian.junit.InSequence; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.eap.qe.microprofile.common.setuptasks.MicroProfileFaultToleranceServerConfiguration; -import org.jboss.eap.qe.microprofile.common.setuptasks.MicroProfileTelemetryServerSetup; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicroProfileTelemetryServerConfiguration; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicrometerServerConfiguration; import org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService; import org.jboss.eap.qe.microprofile.tooling.server.configuration.creaper.ManagementClientProvider; import org.jboss.eap.qe.microprofile.tooling.server.configuration.deployment.ConfigurationUtil; @@ -88,6 +89,9 @@ public static Archive createNonMPFTDeployment() { public static void setup() throws Exception { // Enable FT MicroProfileFaultToleranceServerConfiguration.enableFaultTolerance(); + // And disable Micrometer for good measure since were going to test MicroProfile Fault Tolerance integration + // with MP Telemetry 2.0 metrics too. + MicrometerServerConfiguration.disableMicrometer(); } /** @@ -134,73 +138,90 @@ public void testFaultToleranceMetricsAreTracedWithSameDeployments( } // start the OTel collector container otelCollector = OpenTelemetryCollectorContainer.getInstance(); - // Enable MP Telemetry based metrics, which rely on OpenTelemetry subsystem - MicroProfileTelemetryServerSetup.enableOpenTelemetry(); - MicroProfileTelemetryServerSetup.addOpenTelemetryCollectorConfiguration(otelCollector.getOtlpGrpcEndpoint()); - MicroProfileTelemetryServerSetup.enableMicroProfileTelemetry(); - // manually deploy our deployments - deployer.deploy(FIRST_DEPLOYMENT); - deployer.deploy(SECOND_DEPLOYMENT); - get(firstDeploymentUlr + "?operation=timeout&context=foobar&fail=true").then() - .assertThat() - .body(containsString("Fallback Hello, context = foobar")); - // timeout is not working because 2nd deployment has disabled it - get(secondDeploymentUlr + "?operation=timeout&context=foobar&fail=true").then() - .assertThat() - .body(containsString("Hello from @Timeout method, context = foobar")); - // fetch the collected metrics in prometheus format - List metricsToTest = Arrays.asList( - "ft_timeout_calls_total", - "ft_invocations_total"); - // give it some time to actually be able and report some metrics via the Pmetheus URL - Thread.sleep(5_000); - List metrics = OpenTelemetryCollectorContainer.getInstance().fetchMetrics(""); - // assert - metricsToTest.forEach(n -> Assert.assertTrue("Missing metric: " + n, - metrics.stream().anyMatch(m -> m.getKey().startsWith(n)))); + try { + otelCollector.start(); + try { + // Enable MP Telemetry based metrics, which rely on OpenTelemetry subsystem + MicroProfileTelemetryServerConfiguration.enableOpenTelemetry(); + MicroProfileTelemetryServerConfiguration + .addOpenTelemetryCollectorConfiguration(otelCollector.getOtlpGrpcEndpoint()); + MicroProfileTelemetryServerConfiguration.enableMicroProfileTelemetry(); + try { + // manually deploy our deployments + deployer.deploy(FIRST_DEPLOYMENT); + deployer.deploy(SECOND_DEPLOYMENT); + try { + get(firstDeploymentUlr + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .body(containsString("Fallback Hello, context = foobar")); + // timeout is not working because 2nd deployment has disabled it + get(secondDeploymentUlr + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .body(containsString("Hello from @Timeout method, context = foobar")); + // fetch the collected metrics in prometheus format + List metricsToTest = Arrays.asList( + "ft_timeout_calls_total", + "ft_invocations_total"); + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(5_000); + List metrics = OpenTelemetryCollectorContainer.getInstance().fetchMetrics(""); + // assert + metricsToTest.forEach(n -> Assert.assertTrue("Missing metric: " + n, + metrics.stream().anyMatch(m -> m.getKey().startsWith(n)))); - Assert.assertTrue("\"ft_timeout_calls_total\" not found or not expected", - metrics.stream() - .filter(m -> "ft_timeout_calls_total".equals(m.getKey())) - .filter(m -> m.getTags().entrySet().stream().anyMatch( - t -> "method".equals(t.getKey()) - && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" - .equals(t.getValue())) - && m.getTags().entrySet().stream().anyMatch( - t -> "timedOut".equals(t.getKey()) && "true".equals(t.getValue()))) - .anyMatch(m -> "1".equals(m.getValue()))); - Assert.assertTrue("\"ft_invocations_total\" (fallback applied) not found or not expected", - metrics.stream() - .filter(m -> "ft_invocations_total".equals(m.getKey())) - .filter(m -> m.getTags().entrySet().stream().anyMatch( - t -> "fallback".equals(t.getKey()) && "applied".equals(t.getValue())) - && m.getTags().entrySet().stream().anyMatch( - t -> "method".equals(t.getKey()) - && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" - .equals(t.getValue())) - && m.getTags().entrySet().stream().anyMatch( - t -> "result".equals(t.getKey()) && "valueReturned".equals(t.getValue()))) - .anyMatch(m -> "1".equals(m.getValue()))); - Assert.assertTrue("\"ft_invocations_total\" (fallback not applied) not found or not expected", - metrics.stream() - .filter(m -> "ft_invocations_total".equals(m.getKey())) - .filter(m -> m.getTags().entrySet().stream().anyMatch( - t -> "fallback".equals(t.getKey()) && "notApplied".equals(t.getValue())) - && m.getTags().entrySet().stream().anyMatch( - t -> "method".equals(t.getKey()) - && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" - .equals(t.getValue())) - && m.getTags().entrySet().stream().anyMatch( - t -> "result".equals(t.getKey()) && "valueReturned".equals(t.getValue()))) - .anyMatch(m -> "1".equals(m.getValue()))); - // disable MP Telemetry based metrics - MicroProfileTelemetryServerSetup.disableMicroProfileTelemetry(); - MicroProfileTelemetryServerSetup.disableOpenTelemetry(); - // stop the OTel collector container - otelCollector.stop(); - // undeploy - deployer.undeploy(FIRST_DEPLOYMENT); - deployer.undeploy(SECOND_DEPLOYMENT); + Assert.assertTrue("\"ft_timeout_calls_total\" not found or not expected", + metrics.stream() + .filter(m -> "ft_timeout_calls_total".equals(m.getKey())) + .filter(m -> m.getTags().entrySet().stream().anyMatch( + t -> "method".equals(t.getKey()) + && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" + .equals(t.getValue())) + && m.getTags().entrySet().stream().anyMatch( + t -> "timedOut".equals(t.getKey()) && "true".equals(t.getValue()))) + .anyMatch(m -> "1".equals(m.getValue()))); + Assert.assertTrue("\"ft_invocations_total\" (fallback applied) not found or not expected", + metrics.stream() + .filter(m -> "ft_invocations_total".equals(m.getKey())) + .filter(m -> m.getTags().entrySet().stream().anyMatch( + t -> "fallback".equals(t.getKey()) && "applied".equals(t.getValue())) + && m.getTags().entrySet().stream().anyMatch( + t -> "method".equals(t.getKey()) + && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" + .equals(t.getValue())) + && m.getTags().entrySet().stream().anyMatch( + t -> "result".equals(t.getKey()) + && "valueReturned".equals(t.getValue()))) + .anyMatch(m -> "1".equals(m.getValue()))); + Assert.assertTrue("\"ft_invocations_total\" (fallback not applied) not found or not expected", + metrics.stream() + .filter(m -> "ft_invocations_total".equals(m.getKey())) + .filter(m -> m.getTags().entrySet().stream().anyMatch( + t -> "fallback".equals(t.getKey()) && "notApplied".equals(t.getValue())) + && m.getTags().entrySet().stream().anyMatch( + t -> "method".equals(t.getKey()) + && "org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService.timeout" + .equals(t.getValue())) + && m.getTags().entrySet().stream().anyMatch( + t -> "result".equals(t.getKey()) + && "valueReturned".equals(t.getValue()))) + .anyMatch(m -> "1".equals(m.getValue()))); + } finally { + // undeploy + deployer.undeploy(FIRST_DEPLOYMENT); + deployer.undeploy(SECOND_DEPLOYMENT); + } + } finally { + // disable MP Telemetry based metrics + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + MicroProfileTelemetryServerConfiguration.disableOpenTelemetry(); + } + } finally { + // stop the OTel collector container + otelCollector.stop(); + } + } finally { + OpenTelemetryCollectorContainer.dispose(); + } } /** diff --git a/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/integration/metrics/MultipleMetricsExtensionTest.java b/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/integration/metrics/MultipleMetricsExtensionTest.java new file mode 100644 index 00000000..86113d53 --- /dev/null +++ b/microprofile-fault-tolerance/src/test/java/org/jboss/eap/qe/microprofile/fault/tolerance/integration/metrics/MultipleMetricsExtensionTest.java @@ -0,0 +1,402 @@ +package org.jboss.eap.qe.microprofile.fault.tolerance.integration.metrics; + +import static io.restassured.RestAssured.get; +import static org.hamcrest.Matchers.containsString; + +import java.net.URL; +import java.util.List; + +import org.apache.http.HttpStatus; +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicroProfileFaultToleranceServerConfiguration; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicroProfileTelemetryServerConfiguration; +import org.jboss.eap.qe.microprofile.common.setuptasks.MicrometerServerConfiguration; +import org.jboss.eap.qe.microprofile.fault.tolerance.deployments.v10.HelloService; +import org.jboss.eap.qe.microprofile.tooling.server.configuration.deployment.ConfigurationUtil; +import org.jboss.eap.qe.observability.containers.OpenTelemetryCollectorContainer; +import org.jboss.eap.qe.observability.prometheus.model.PrometheusMetric; +import org.jboss.eap.qe.ts.common.docker.Docker; +import org.jboss.eap.qe.ts.common.docker.junit.DockerRequiredTests; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +/** + * Verify the server behavior from the end user PoV, based on whether various extensions and subsystems that + * handle metrics generation and collection are available and enabled, or not. + * + * Uses a workflow that is implemented already in {@link org.jboss.eap.qe.microprofile.fault.tolerance.UndeployDeployTest}, + * i.e. several deployments are defined, and used via manual deploy conveniently. + * In order to be able to inject the deployments URLs, though, Arquillian needs for the deployments to be deployed + * initially, which is the reason for the first test in the sequence. Deployments are undeployed after each test. + */ +@RunAsClient +@RunWith(Arquillian.class) +@Category(DockerRequiredTests.class) +public class MultipleMetricsExtensionTest { + + private static final String FAULT_TOLERANCE_DEPLOYMENT = "FTDeployment"; + private static final String FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY = "FTDeploymentWithMPTelemetryEnabled"; + private static final String FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER = "FTDeploymentWithMPTelemetryEnabledButDisablingMicrometer"; + private static final String TESTED_FT_METRIC = "ft_timeout_"; + private static final int REQUEST_COUNT = 5; + + @ArquillianResource + private Deployer deployer; + + @BeforeClass + public static void setup() throws Exception { + // we need a Docker container for The OTel collector here, so throw an exception if a docker service is not available + try { + Docker.checkDockerPresent(); + } catch (Exception e) { + throw new IllegalStateException("Cannot verify Docker availability: " + e.getMessage()); + } + // Enable FT + MicroProfileFaultToleranceServerConfiguration.enableFaultTolerance(); + } + + /** + * A deployment containing MP Fault Tolerance operations that generate metrics + * + * @return A ShrinkWrap {@link Archive> containing a deployment that contains MP Fault Tolerance operations + */ + @Deployment(name = FAULT_TOLERANCE_DEPLOYMENT, managed = false) + public static Archive deployment() { + String mpConfig = "otel.metric.export.interval=100\nTimeout/enabled=true"; + return ShrinkWrap.create(WebArchive.class, FAULT_TOLERANCE_DEPLOYMENT + ".war") + .addPackages(true, HelloService.class.getPackage()) + .addAsManifestResource(ConfigurationUtil.BEANS_XML_FILE_LOCATION, "beans.xml") + .addAsManifestResource(new StringAsset(mpConfig), "microprofile-config.properties"); + } + + /** + * A deployment that enables MP Telemetry via the {@code otel.sdk.disabled=false} MP Config property. + * + * @return A ShrinkWrap {@link Archive> containing a deployment that enables MP Telemetry + */ + @Deployment(name = FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY, managed = false) + public static Archive deploymentWithMPTelemetryEnabled() { + String mpConfig = "otel.metric.export.interval=100\notel.service.name=" + FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY + + "\notel.sdk.disabled=false\nTimeout/enabled=true"; + return ShrinkWrap.create(WebArchive.class, FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY + ".war") + .addPackages(true, HelloService.class.getPackage()) + .addAsManifestResource(ConfigurationUtil.BEANS_XML_FILE_LOCATION, "beans.xml") + .addAsManifestResource(new StringAsset(mpConfig), "microprofile-config.properties"); + } + + /** + * A deployment that enables MP Telemetry via the {@code otel.sdk.disabled=false} MP Config property, and + * disabling MP Fault Tolerance metrics collection using Micrometer via the + * {@code smallrye.faulttolerance.micrometer.disabled} + * MP Config property. + * + * @return A ShrinkWrap {@link Archive> containing a deployment that enables MP Telemetry + */ + @Deployment(name = FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER, managed = false) + public static Archive deploymentWithMPTelemetryEnabledDisablingMicrometer() { + String mpConfig = "otel.metric.export.interval=100\notel.service.name=" + FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER + + "\notel.sdk.disabled=false\nTimeout/enabled=true\nsmallrye.faulttolerance.micrometer.disabled=true"; + return ShrinkWrap.create(WebArchive.class, FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER + ".war") + .addPackages(true, HelloService.class.getPackage()) + .addAsManifestResource(ConfigurationUtil.BEANS_XML_FILE_LOCATION, "beans.xml") + .addAsManifestResource(new StringAsset(mpConfig), "microprofile-config.properties"); + } + + /** + * Deploy all deployments, so it's possible to get their URLs in other tests. This is limitation of Arquillian + * when deployments are deployed manually (in tests) and thus their URL is not known in time when test is started. + * The only way how to workaround this issue is to deploy all deployments in first test which allows to inject + * deployment URL as params in other test methods. + */ + @Test + @InSequence(0) + public void deployAll() { + deployer.deploy(FAULT_TOLERANCE_DEPLOYMENT); + deployer.deploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY); + deployer.deploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER); + + deployer.undeploy(FAULT_TOLERANCE_DEPLOYMENT); + deployer.undeploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY); + deployer.undeploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER); + } + + /** + * Test to verify that no MicroProfile FaultTolerance related metrics are visible via the OTel collector Prometheus + * endpoint, when both the Micrometer and MicroProfile Telemetry extensions are not available. + * + * @param deploymentUrl Base {@link URL} of the app deployment + * @throws Exception When something fails during server configuration + */ + @Test + @InSequence(10) + public void noMetricsAreCollectedWhenMetricsExtensionsAreNotAvailable( + @ArquillianResource @OperateOnDeployment(FAULT_TOLERANCE_DEPLOYMENT) URL deploymentUrl) throws Exception { + // Remove the MP Telemetry extension + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + // And be sure Micrometer is not available as well + MicrometerServerConfiguration.disableMicrometer(); + // start the OTel collector container + OpenTelemetryCollectorContainer otelCollector = OpenTelemetryCollectorContainer.getNewInstance(); + otelCollector.start(); + try { + // deploy + deployer.deploy(FAULT_TOLERANCE_DEPLOYMENT); + try { + // call the app operation for some times + for (int i = 0; i < REQUEST_COUNT; i++) { + get(deploymentUrl + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body(containsString("Fallback Hello, context = foobar")); + } + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(1_000); + // fetch the collected metrics in prometheus format + List metrics = otelCollector.fetchMetrics(TESTED_FT_METRIC); + Assert.assertTrue( + TESTED_FT_METRIC + " metric found, which is not expected when both MP Telemetry and Micrometer " + + + "extensions are not available.", + metrics.stream().noneMatch(m -> m.getKey().startsWith(TESTED_FT_METRIC))); + } finally { + deployer.undeploy(FAULT_TOLERANCE_DEPLOYMENT); + } + } finally { + otelCollector.stop(); + } + } + + /** + * Test to verify that MP Fault Tolerance metrics are collected when Micrometer alone is available and configured + * + * @param deploymentUrl Base {@link URL} of the app deployment + * @throws Exception When something fails during server configuration + */ + @Test + @InSequence(10) + public void metricsAreCollectedWhenOnlyMicrometerExtensionIsAvailable( + @ArquillianResource @OperateOnDeployment(FAULT_TOLERANCE_DEPLOYMENT) URL deploymentUrl) throws Exception { + // Remove the MP Telemetry extension + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + // start the OTel collector container + OpenTelemetryCollectorContainer otelCollector = OpenTelemetryCollectorContainer.getNewInstance(); + otelCollector.start(); + try { + // And be sure Micrometer is available instead + MicrometerServerConfiguration.enableMicrometer(otelCollector.getOtlpHttpEndpoint()); + try { + // deploy + deployer.deploy(FAULT_TOLERANCE_DEPLOYMENT); + try { + // call the app operation for some times + for (int i = 0; i < REQUEST_COUNT; i++) { + get(deploymentUrl + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body(containsString("Fallback Hello, context = foobar")); + } + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(1_000); + // fetch the collected metrics in prometheus format + List metrics = otelCollector.fetchMetrics(TESTED_FT_METRIC); + Assert.assertFalse( + TESTED_FT_METRIC + + " metrics not found, which is not expected when the Micrometer extension is available, " + + + "i.e. FT metrics should be collected.", + metrics.isEmpty()); + } finally { + deployer.undeploy(FAULT_TOLERANCE_DEPLOYMENT); + } + } finally { + MicrometerServerConfiguration.disableMicrometer(); + } + } finally { + otelCollector.stop(); + } + } + + /** + * Test to verify that MP Fault Tolerance metrics are NOT collected when MicroProfile Telemetry alone is available + * but not enabled, i.e. via the {@code otel.sdk.disabled=false} MP Config property + * + * @param deploymentUrl Base {@link URL} of the app deployment + * @throws Exception When something fails during server configuration + */ + @Test + @InSequence(10) + public void noMetricsAreCollectedWhenOnlyMPTelemetryIsAvailableButNotEnabled( + @ArquillianResource @OperateOnDeployment(FAULT_TOLERANCE_DEPLOYMENT) URL deploymentUrl) throws Exception { + // Remove the Micrometer extension... + MicrometerServerConfiguration.disableMicrometer(); + // ... but make the MicroProfile Telemetry extension available + MicroProfileTelemetryServerConfiguration.enableMicroProfileTelemetry(); + try { + // start the OTel collector container + OpenTelemetryCollectorContainer otelCollector = OpenTelemetryCollectorContainer.getNewInstance(); + otelCollector.start(); + try { + // deploy an app that DOES NOT enable MP Telemetry + deployer.deploy(FAULT_TOLERANCE_DEPLOYMENT); + try { + // call the app operation for some times + for (int i = 0; i < REQUEST_COUNT; i++) { + get(deploymentUrl + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body(containsString("Fallback Hello, context = foobar")); + } + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(1_000); + // fetch the collected metrics in prometheus format + List metrics = otelCollector.fetchMetrics(TESTED_FT_METRIC); + Assert.assertTrue( + TESTED_FT_METRIC + " metrics found, which is not expected when only the MP Telemetry extension " + + + "is available but NOT enabled at application level.", + metrics.stream().noneMatch(m -> m.getKey().startsWith(TESTED_FT_METRIC))); + } finally { + deployer.undeploy(FAULT_TOLERANCE_DEPLOYMENT); + } + } finally { + otelCollector.stop(); + } + } finally { + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + } + } + + /** + * Test to verify that MP Fault Tolerance metrics are collected when MicroProfile Telemetry alone is available + * AND enabled, i.e. via the {@code otel.sdk.disabled=false} MP Config property + * + * @param deploymentUrl Base {@link URL} of the app deployment + * @throws Exception When something fails during server configuration + */ + @Test + @InSequence(10) + public void metricsAreCollectedWhenOnlyMPTelemetryExtensionIsAvailableAndEnabled( + @ArquillianResource @OperateOnDeployment(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY) URL deploymentUrl) + throws Exception { + // Remove the Micrometer extension + MicrometerServerConfiguration.disableMicrometer(); + // Make the MP Telemetry extension available + MicroProfileTelemetryServerConfiguration.enableMicroProfileTelemetry(); + try { + // start the OTel collector container + OpenTelemetryCollectorContainer otelCollector = OpenTelemetryCollectorContainer.getNewInstance(); + otelCollector.start(); + try { + // deploy an app that enables MP Telemetry + deployer.deploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY); + try { + // call the app operation for some times + for (int i = 0; i < REQUEST_COUNT; i++) { + get(deploymentUrl + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body(containsString("Fallback Hello, context = foobar")); + } + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(1_000); + // fetch the collected metrics in prometheus format + List metrics = otelCollector.fetchMetrics(TESTED_FT_METRIC); + Assert.assertFalse( + TESTED_FT_METRIC + + " metrics not found, which is not expected when the MP Telemetry extension is available " + + + "and enabled at the application level, i.e. FT metrics should be collected.", + metrics.isEmpty()); + } finally { + deployer.undeploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY); + } + } finally { + otelCollector.stop(); + } + } finally { + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + } + } + + /** + * Test to verify that MP Fault Tolerance metrics are collected uniquely when both MicroProfile Telemetry is + * and Micrometer subsystems are available, but MP Telemetry is enabled, i.e. via the {@code otel.sdk.disabled=false} + * MP Config property, while Micrometer is not (via the {@code smallrye.faulttolerance.micrometer.disabled=true} + * MP Config property). + * + * @param deploymentUrl Base {@link URL} of the app deployment + * @throws Exception When something fails during server configuration + */ + @Test + @InSequence(10) + public void metricsAreCollectedWhenBothExtensionsAreAvailableAndOnlyMPTelIsEnabled( + @ArquillianResource @OperateOnDeployment(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER) URL deploymentUrl) + throws Exception { + // Make the MP Telemetry extension available + MicroProfileTelemetryServerConfiguration.enableMicroProfileTelemetry(); + try { + // start the OTel collector container + OpenTelemetryCollectorContainer otelCollector = OpenTelemetryCollectorContainer.getNewInstance(); + otelCollector.start(); + try { + // And be sure Micrometer is available too + MicrometerServerConfiguration.enableMicrometer(otelCollector.getOtlpHttpEndpoint()); + try { + // deploy an app that enables MP Telemetry, and disables the Micrometer Fault Tolerance metrics collection + // instead, see + deployer.deploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER); + try { + // call the app operation for some times + for (int i = 0; i < REQUEST_COUNT; i++) { + get(deploymentUrl + "?operation=timeout&context=foobar&fail=true").then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body(containsString("Fallback Hello, context = foobar")); + } + // give it some time to actually be able and report some metrics via the Pmetheus URL + Thread.sleep(1_000); + // fetch the collected metrics in prometheus format + List metrics = otelCollector.fetchMetrics(TESTED_FT_METRIC); + Assert.assertFalse( + TESTED_FT_METRIC + + " metrics not found, which is not expected when the Micrometer extension is available, i.e. FT metrics should be collected.", + metrics.isEmpty()); + Assert.assertEquals( + "Duplicate metrics were found, which is not expected when both the Micrometer and MP Telemetry extension " + + + "are available but the deployment is explicitly enabling MP Telemetry while disabling Micrometer metrics collection instead.", + metrics.size(), metrics.stream().distinct().count()); + } finally { + deployer.undeploy(FAULT_TOLERANCE_DEPOYMENT_WITH_MP_TELEMETRY_DISABLING_MICROMETER); + } + } finally { + MicrometerServerConfiguration.disableMicrometer(); + } + } finally { + otelCollector.stop(); + } + } finally { + MicroProfileTelemetryServerConfiguration.disableMicroProfileTelemetry(); + } + } + + @AfterClass + public static void tearDown() throws Exception { + // disable FT + MicroProfileFaultToleranceServerConfiguration.disableFaultTolerance(); + } +} diff --git a/microprofile-fault-tolerance/src/test/resources/otel-collector-config.yaml b/microprofile-fault-tolerance/src/test/resources/otel-collector-config.yaml index 4eacec9e..3185b5d4 100644 --- a/microprofile-fault-tolerance/src/test/resources/otel-collector-config.yaml +++ b/microprofile-fault-tolerance/src/test/resources/otel-collector-config.yaml @@ -18,7 +18,7 @@ processors: exporters: logging: - verbosity: detailed + verbosity: normal prometheus: endpoint: "0.0.0.0:49152" otlp: diff --git a/pom.xml b/pom.xml index 172981bf..083d8e6f 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,8 @@ + 2.0.16 + --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED @@ -315,6 +317,16 @@ ${version.org.jboss.threads.jboss-threads} test + + org.slf4j + slf4j-api + ${version.slf4j} + + + org.slf4j + slf4j-simple + ${version.slf4j} + diff --git a/tooling-observability/pom.xml b/tooling-observability/pom.xml index 8b3909ee..5ed8dc8e 100644 --- a/tooling-observability/pom.xml +++ b/tooling-observability/pom.xml @@ -21,6 +21,14 @@ org.jboss.eap.qe tooling-docker + + org.wildfly.extras.creaper + creaper-core + + + org.jboss.eap.qe + tooling-server-configuration + \ No newline at end of file diff --git a/tooling-observability/src/main/java/org/jboss/eap/qe/observability/containers/OpenTelemetryCollectorContainer.java b/tooling-observability/src/main/java/org/jboss/eap/qe/observability/containers/OpenTelemetryCollectorContainer.java index 26ba86ee..fbc67d04 100644 --- a/tooling-observability/src/main/java/org/jboss/eap/qe/observability/containers/OpenTelemetryCollectorContainer.java +++ b/tooling-observability/src/main/java/org/jboss/eap/qe/observability/containers/OpenTelemetryCollectorContainer.java @@ -27,7 +27,14 @@ /** * Inspired by the similar class in Wildfly, this is an implementation of an OTel collector which uses the TS Docker - * APIs, instead ogf the Testcontainers based tooling which is available in WildFly + * APIs, instead of the Testcontainers based tooling which is available in WildFly. + * + * Both a singleton or a managed instance can be obtained by {@link OpenTelemetryCollectorContainer#getInstance()} or + * {@link OpenTelemetryCollectorContainer#getNewInstance()} methods respectively, and lifecycle methods (e.g.: + * {@link OpenTelemetryCollectorContainer#start()}, {@link OpenTelemetryCollectorContainer#stop()}, + * {@link OpenTelemetryCollectorContainer#dispose()} etc. must be used accordingly. + * + * Instances cannot be used simultaneously, since ports and general config are unique. */ public class OpenTelemetryCollectorContainer { private static OpenTelemetryCollectorContainer INSTANCE = null; @@ -53,7 +60,7 @@ public class OpenTelemetryCollectorContainer { private String otlpGrpcEndpoint; private String otlpHttpEndpoint; private String prometheusUrl; - private final Docker otelCollector; + private final Docker otelCollectorContainer; private String getLocalOtelCollectorConfigYamlAbsolutePath() { File tempFile = null; @@ -79,7 +86,7 @@ private String getLocalOtelCollectorConfigYamlAbsolutePath() { } private OpenTelemetryCollectorContainer() { - otelCollector = new Docker.Builder("otel-collector", + otelCollectorContainer = new Docker.Builder("otel-collector", "ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.103.1") .setContainerReadyCondition(() -> { try { @@ -101,18 +108,69 @@ private OpenTelemetryCollectorContainer() { .build(); } + /** + * Static method to get a unique instance of {@link OpenTelemetryCollectorContainer}. + * + * @return A unique instance of {@link OpenTelemetryCollectorContainer} + */ public static synchronized OpenTelemetryCollectorContainer getInstance() { if (INSTANCE == null) { - jaegerContainer = JaegerContainer.getInstance(); INSTANCE = new OpenTelemetryCollectorContainer(); - INSTANCE.start(); } return INSTANCE; } + /** + * Static method to get a unique instance of {@link OpenTelemetryCollectorContainer}. + * + * @param jaegerBackendContainer A {@link JaegerContainer} instance that will be used as the Jaeger backend, e.g.: + * for storing and retrieving traces. + * @return A unique instance of {@link OpenTelemetryCollectorContainer} + */ + public static synchronized OpenTelemetryCollectorContainer getInstance(JaegerContainer jaegerBackendContainer) { + if (INSTANCE == null) { + jaegerContainer = jaegerBackendContainer; + INSTANCE = new OpenTelemetryCollectorContainer(); + } + return INSTANCE; + } + + /** + * Static method to get a new instance of {@link OpenTelemetryCollectorContainer}, which should be managed by + * external code. + * + * @return An instance of {@link OpenTelemetryCollectorContainer} + */ + public static synchronized OpenTelemetryCollectorContainer getNewInstance() { + return new OpenTelemetryCollectorContainer(); + } + + /** + * Static method to get a new instance of {@link OpenTelemetryCollectorContainer}, which should be managed by + * external code. + * + * @param jaegerBackendContainer A {@link JaegerContainer} instance that will be used as the Jaeger backend, e.g.: + * for storing and retrieving traces. + * @return An instance of {@link OpenTelemetryCollectorContainer} + */ + public static synchronized OpenTelemetryCollectorContainer getNewInstance(JaegerContainer jaegerBackendContainer) { + OpenTelemetryCollectorContainer newInstance = new OpenTelemetryCollectorContainer(); + jaegerContainer = jaegerBackendContainer; + return newInstance; + } + + /** + * Set the unique instance reference to null. + */ + public static synchronized void dispose() { + if (INSTANCE != null) { + INSTANCE = null; + } + } + public void start() { try { - otelCollector.start(); + otelCollectorContainer.start(); } catch (Exception e) { throw new IllegalStateException("Starting the OTel container failed: " + e); } @@ -122,13 +180,8 @@ public void start() { } public synchronized void stop() { - if (jaegerContainer != null) { - jaegerContainer.stop(); - jaegerContainer = null; - } - INSTANCE = null; try { - otelCollector.stop(); + otelCollectorContainer.stop(); } catch (Exception e) { throw new IllegalStateException("Stopping the OTel container failed: " + e); } @@ -149,23 +202,23 @@ public String getPrometheusUrl() { public List fetchMetrics(String nameToMonitor) throws InterruptedException { String body = ""; try (Client client = ClientBuilder.newClient()) { - WebTarget target = client.target(OpenTelemetryCollectorContainer.getInstance().getPrometheusUrl()); + WebTarget target = client.target(this.getPrometheusUrl()); int attemptCount = 0; boolean found = false; // Request counts can vary. Setting high to help ensure test stability while (!found && attemptCount < 30) { - // Wait to give Micrometer time to export + // Wait to give metrics systems time to export Thread.sleep(1000); body = target.request().get().readEntity(String.class); - found = body.contains(nameToMonitor); + found = body.contains("\n" + nameToMonitor); attemptCount++; } } - return buildPrometheusMetrics(body); + return body.isEmpty() ? List.of() : buildPrometheusMetrics(body); } public List getTraces(String serviceName) throws InterruptedException { diff --git a/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerSetup.java b/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerConfiguration.java similarity index 97% rename from tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerSetup.java rename to tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerConfiguration.java index 15e67df1..ebc7cb51 100644 --- a/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerSetup.java +++ b/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicroProfileTelemetryServerConfiguration.java @@ -9,7 +9,7 @@ /** * Operations required to set up the server for MicroProfile Telemetry */ -public class MicroProfileTelemetryServerSetup { +public class MicroProfileTelemetryServerConfiguration { private static final Address OPENTELEMETRY_EXTENSION_ADDRESS = Address .extension("org.wildfly.extension.opentelemetry"); private static final Address OPENTELEMETRY_SUBSYSTEM_ADDRESS = Address @@ -163,10 +163,10 @@ public static void enableMicroProfileTelemetry() throws Exception { */ public static void enableMicroProfileTelemetry(OnlineManagementClient client) throws Exception { Operations operations = new Operations(client); - if (!openTelemetryExtensionExists(operations)) { + if (!microProfileTelemetryExtensionExists(operations)) { operations.add(MICROPROFILE_TELEMETRY_EXTENSION_ADDRESS); } - if (!openTelemetrySubsystemExists(operations)) { + if (!microProfileTelemetrySubsystemExists(operations)) { operations.add(MICROPROFILE_TELEMETRY_SUBSYSTEM_ADDRESS); } new Administration(client).reloadIfRequired(); diff --git a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerConfiguration.java b/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicrometerServerConfiguration.java similarity index 98% rename from micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerConfiguration.java rename to tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicrometerServerConfiguration.java index 644a00d2..63abe82a 100644 --- a/micrometer/src/test/java/org/jboss/eap/qe/micrometer/util/MicrometerServerConfiguration.java +++ b/tooling-server-configuration/src/main/java/org/jboss/eap/qe/microprofile/common/setuptasks/MicrometerServerConfiguration.java @@ -1,4 +1,4 @@ -package org.jboss.eap.qe.micrometer.util; +package org.jboss.eap.qe.microprofile.common.setuptasks; import org.jboss.eap.qe.microprofile.tooling.server.configuration.creaper.ManagementClientProvider; import org.wildfly.extras.creaper.core.online.OnlineManagementClient;