From b99b4df008e017f6d556a6cc3c50eaaff8ff8c2d Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Fri, 14 Apr 2023 13:04:49 +0200 Subject: [PATCH] Initial Observability extension - devservices, devresources, LGTM --- .idea/icon.png | Bin 6799 -> 0 bytes .idea/icon_dark.png | Bin 6856 -> 0 bytes .idea/runConfigurations/mvnDebug.xml | 15 -- bom/application/pom.xml | 44 ++++ .../java/io/quarkus/deployment/Feature.java | 1 + .../quarkus/runtime/util/EnumerationUtil.java | 69 ++++++ devtools/bom-descriptor-json/pom.xml | 13 ++ docs/pom.xml | 13 ++ docs/src/main/asciidoc/validation.adoc | 4 +- .../devservices/common/ContainerLocator.java | 26 +++ .../observability-devservices/common/pom.xml | 27 +++ .../common/ContainerConstants.java | 15 ++ .../config/AbstractContainerConfig.java | 51 +++++ .../common/config/AbstractGrafanaConfig.java | 51 +++++ .../common/config/ConfigUtils.java | 20 ++ .../common/config/ContainerConfig.java | 69 ++++++ .../common/config/DevTarget.java | 16 ++ .../common/config/GrafanaConfig.java | 33 +++ .../common/config/LgtmConfig.java | 23 ++ .../common/config/ModulesConfiguration.java | 8 + .../deployment/pom.xml | 85 +++++++ .../deployment/DevResourcesBuildItem.java | 6 + .../deployment/DevResourcesProcessor.java | 51 +++++ .../deployment/ObservabilityProcessor.java | 211 ++++++++++++++++++ extensions/observability-devservices/pom.xml | 25 +++ .../observability-devservices/runtime/pom.xml | 85 +++++++ .../runtime/DevResourceShutdownRecorder.java | 12 + .../runtime/DevResourcesConfigBuilder.java | 18 ++ .../config/ObservabilityConfiguration.java | 35 +++ .../sub/lgtm/pom.xml | 31 +++ .../testcontainers/pom.xml | 46 ++++ .../testcontainers/GrafanaContainer.java | 35 +++ .../testcontainers/LgtmContainer.java | 45 ++++ .../ObservabilityContainer.java | 66 ++++++ .../testlibs/devresource-common/pom.xml | 44 ++++ .../devresource/ContainerResource.java | 41 ++++ .../DevResourceLifecycleManager.java | 106 +++++++++ .../devresource/DevResources.java | 88 ++++++++ .../devresource/DevResourcesConfigSource.java | 28 +++ .../testlibs/devresource-lgtm/pom.xml | 35 +++ .../devresource/lgtm/LgtmResource.java | 57 +++++ ...ty.devresource.DevResourceLifecycleManager | 1 + .../testlibs/pom.xml | 22 ++ extensions/pom.xml | 1 + integration-tests/observability-lgtm/pom.xml | 87 ++++++++ .../observability/example/SimpleEndpoint.java | 48 ++++ .../src/main/resources/application.properties | 13 ++ .../observability/test/LgtmLifecycleTest.java | 17 ++ .../observability/test/LgtmResourcesTest.java | 14 ++ .../observability/test/LgtmServicesTest.java | 11 + .../observability/test/LgtmTestBase.java | 31 +++ .../test/support/DevResourcesTestProfile.java | 14 ++ .../test/support/GrafanaClient.java | 92 ++++++++ .../QuarkusTestResourceTestProfile.java | 14 ++ .../test/support/QueryResult.java | 70 ++++++ .../observability/test/support/User.java | 14 ++ integration-tests/pom.xml | 1 + 57 files changed, 2081 insertions(+), 17 deletions(-) delete mode 100644 .idea/icon.png delete mode 100644 .idea/icon_dark.png delete mode 100644 .idea/runConfigurations/mvnDebug.xml create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/util/EnumerationUtil.java create mode 100644 extensions/observability-devservices/common/pom.xml create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/ContainerConstants.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractContainerConfig.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractGrafanaConfig.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ConfigUtils.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ContainerConfig.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/DevTarget.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/GrafanaConfig.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/LgtmConfig.java create mode 100644 extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ModulesConfiguration.java create mode 100644 extensions/observability-devservices/deployment/pom.xml create mode 100644 extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesBuildItem.java create mode 100644 extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesProcessor.java create mode 100644 extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/ObservabilityProcessor.java create mode 100644 extensions/observability-devservices/pom.xml create mode 100644 extensions/observability-devservices/runtime/pom.xml create mode 100644 extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourceShutdownRecorder.java create mode 100644 extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourcesConfigBuilder.java create mode 100644 extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/config/ObservabilityConfiguration.java create mode 100644 extensions/observability-devservices/sub/lgtm/pom.xml create mode 100644 extensions/observability-devservices/testcontainers/pom.xml create mode 100644 extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/GrafanaContainer.java create mode 100644 extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/LgtmContainer.java create mode 100644 extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/ObservabilityContainer.java create mode 100644 extensions/observability-devservices/testlibs/devresource-common/pom.xml create mode 100644 extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/ContainerResource.java create mode 100644 extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourceLifecycleManager.java create mode 100644 extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResources.java create mode 100644 extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourcesConfigSource.java create mode 100644 extensions/observability-devservices/testlibs/devresource-lgtm/pom.xml create mode 100644 extensions/observability-devservices/testlibs/devresource-lgtm/src/main/java/io/quarkus/observability/devresource/lgtm/LgtmResource.java create mode 100644 extensions/observability-devservices/testlibs/devresource-lgtm/src/main/resources/META-INF/services/io.quarkus.observability.devresource.DevResourceLifecycleManager create mode 100644 extensions/observability-devservices/testlibs/pom.xml create mode 100644 integration-tests/observability-lgtm/pom.xml create mode 100644 integration-tests/observability-lgtm/src/main/java/io/quarkus/observability/example/SimpleEndpoint.java create mode 100644 integration-tests/observability-lgtm/src/main/resources/application.properties create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmLifecycleTest.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmResourcesTest.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmServicesTest.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmTestBase.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/DevResourcesTestProfile.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/GrafanaClient.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QuarkusTestResourceTestProfile.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QueryResult.java create mode 100644 integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/User.java diff --git a/.idea/icon.png b/.idea/icon.png deleted file mode 100644 index 7ef06962ecc8cc30b5c6369ed3bab2d6177ba939..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6799 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 HWbpq1X_U=^ diff --git a/.idea/icon_dark.png b/.idea/icon_dark.png deleted file mode 100644 index ac7f15e5133a549b4f16ffd3e793f2bb23262ae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6856 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 diff --git a/.idea/runConfigurations/mvnDebug.xml b/.idea/runConfigurations/mvnDebug.xml deleted file mode 100644 index 522dd94a4e0822..00000000000000 --- a/.idea/runConfigurations/mvnDebug.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d6258da6757480..baedff9b013e16 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -226,6 +226,8 @@ 1.0.0 3.0.0 2.12.3 + + 0.16.0 1.0.10 @@ -481,6 +483,18 @@ import + + + io.prometheus + simpleclient + ${prometheus.version} + + + io.prometheus + simpleclient_common + ${prometheus.version} + + @@ -2962,6 +2976,36 @@ quarkus-virtual-threads-deployment ${project.version} + + io.quarkus + quarkus-observability-devservices-common + ${project.version} + + + io.quarkus + quarkus-observability-devservices + ${project.version} + + + io.quarkus + quarkus-observability-devservices-lgtm + ${project.version} + + + io.quarkus + quarkus-observability-testcontainers + ${project.version} + + + io.quarkus + quarkus-observability-devresource-common + ${project.version} + + + io.quarkus + quarkus-observability-devresource-lgtm + ${project.version} + diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java index a504a565e058f0..6373d0c09e28b5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java @@ -68,6 +68,7 @@ public enum Feature { NARAYANA_LRA, NARAYANA_STM, NEO4J, + OBSERVABILITY, OIDC, OIDC_CLIENT, OIDC_CLIENT_FILTER, diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/EnumerationUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/EnumerationUtil.java new file mode 100644 index 00000000000000..6dbab086bb060a --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/EnumerationUtil.java @@ -0,0 +1,69 @@ +package io.quarkus.runtime.util; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * Transform to "old school" Enumeration from Iterator/Spliterator/Stream + */ +public class EnumerationUtil { + public static Enumeration from(Iterator iterator) { + Objects.requireNonNull(iterator); + + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public T nextElement() { + return iterator.next(); + } + }; + } + + public static Enumeration from(Spliterator spliterator) { + Objects.requireNonNull(spliterator); + + class Adapter implements Enumeration, Consumer { + boolean valueReady; + T nextElement; + + public void accept(T t) { + this.valueReady = true; + this.nextElement = t; + } + + public boolean hasMoreElements() { + if (!this.valueReady) { + spliterator.tryAdvance(this); + } + + return this.valueReady; + } + + public T nextElement() { + if (!this.valueReady && !this.hasMoreElements()) { + throw new NoSuchElementException(); + } else { + this.valueReady = false; + T t = this.nextElement; + this.nextElement = null; + return t; + } + } + } + + return new Adapter(); + } + + public static Enumeration from(Stream stream) { + return from(stream.spliterator()); + } +} diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index bdc9ab6b66b464..d0b4bae4f91609 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -1526,6 +1526,19 @@ + + io.quarkus + quarkus-observability-devservices + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc diff --git a/docs/pom.xml b/docs/pom.xml index e74b32a435af15..cd0e096a5cb6d4 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -1542,6 +1542,19 @@ + + io.quarkus + quarkus-observability-devservices-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc-deployment diff --git a/docs/src/main/asciidoc/validation.adoc b/docs/src/main/asciidoc/validation.adoc index 26798ae318aa2b..680b82f41b5a04 100644 --- a/docs/src/main/asciidoc/validation.adoc +++ b/docs/src/main/asciidoc/validation.adoc @@ -130,7 +130,7 @@ public class BookResource { ---- <1> The `Validator` instance is injected via CDI. -Yes it does not compile, `Result` is missing, but we will add it very soon. +Yes it does not compile, `QueryResult` is missing, but we will add it very soon. The method parameter (`book`) is created from the JSON payload automatically. @@ -139,7 +139,7 @@ It returns a set of violations. If this set is empty, it means the object is valid. In case of failures, the messages are concatenated and sent back to the browser. -Let's now create the `Result` class as an inner class: +Let's now create the `QueryResult` class as an inner class: [source, java] ---- diff --git a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java index 4c7835827fb47c..a9086956531364 100644 --- a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java +++ b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/ContainerLocator.java @@ -1,7 +1,9 @@ package io.quarkus.devservices.common; import java.util.Arrays; +import java.util.Objects; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.BiPredicate; import org.jboss.logging.Logger; @@ -61,6 +63,30 @@ public Optional locateContainer(String serviceName, boolean sh } } + /** + * @return container id, if exists + */ + public Optional locateContainer(String serviceName, boolean shared, LaunchMode launchMode, + BiConsumer consumer) { + if (shared && launchMode == LaunchMode.DEVELOPMENT) { + return lookup(serviceName) + .map(container -> { + Arrays.stream(container.getPorts()) + .filter(cp -> Objects.nonNull(cp.getPublicPort()) && Objects.nonNull(cp.getPrivatePort())) + .forEach(cp -> { + ContainerAddress containerAddress = new ContainerAddress( + container.getId(), + DockerClientFactory.instance().dockerHostIpAddress(), + cp.getPublicPort()); + consumer.accept(cp.getPrivatePort(), containerAddress); + }); + return container.getId(); + }); + } else { + return Optional.empty(); + } + } + public Optional locatePublicPort(String serviceName, boolean shared, LaunchMode launchMode, int privatePort) { if (shared && launchMode == LaunchMode.DEVELOPMENT) { return lookup(serviceName) diff --git a/extensions/observability-devservices/common/pom.xml b/extensions/observability-devservices/common/pom.xml new file mode 100644 index 00000000000000..09c708df871977 --- /dev/null +++ b/extensions/observability-devservices/common/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-devservices-common + Quarkus - Observability Devservices - Common + + + + io.quarkus + quarkus-core + provided + + + io.smallrye.config + smallrye-config-core + provided + + + diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/ContainerConstants.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/ContainerConstants.java new file mode 100644 index 00000000000000..e33965792f9fe2 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/ContainerConstants.java @@ -0,0 +1,15 @@ +package io.quarkus.observability.common; + +public final class ContainerConstants { + + // Images + + public static final String LGTM = "grafana/otel-lgtm:0.2.0"; + + // Ports + + public static final int GRAFANA_PORT = 3000; + + public static final int OTEL_GRPC_EXPORTER_PORT = 4317; + public static final int OTEL_HTTP_EXPORTER_PORT = 4318; +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractContainerConfig.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractContainerConfig.java new file mode 100644 index 00000000000000..3ddefb22929946 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractContainerConfig.java @@ -0,0 +1,51 @@ +package io.quarkus.observability.common.config; + +import java.util.Locale; +import java.util.Optional; +import java.util.Set; + +public abstract class AbstractContainerConfig implements ContainerConfig { + + private final String imageName; + private final boolean shared; + + public AbstractContainerConfig(String imageName) { + this(imageName, true); + } + + public AbstractContainerConfig(String imageName, boolean shared) { + this.imageName = imageName; + this.shared = shared; + } + + @Override + public boolean enabled() { + return true; + } + + @Override + public String imageName() { + return imageName; + } + + @Override + public boolean shared() { + return shared; + } + + @Override + public Optional> networkAliases() { + return Optional.empty(); + } + + @Override + public String label() { + String sn = getClass().getSimpleName().toLowerCase(Locale.ROOT); + return "quarkus-dev-resource-" + sn; + } + + @Override + public String serviceName() { + return "quarkus"; + } +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractGrafanaConfig.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractGrafanaConfig.java new file mode 100644 index 00000000000000..b73cea0232961f --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/AbstractGrafanaConfig.java @@ -0,0 +1,51 @@ +package io.quarkus.observability.common.config; + +import java.time.Duration; + +import io.quarkus.observability.common.ContainerConstants; + +public abstract class AbstractGrafanaConfig extends AbstractContainerConfig implements GrafanaConfig { + + private final String username; + private final String password; + private final int grafanaPort; + + public AbstractGrafanaConfig(String imageName) { + this(imageName, true, "admin", "admin", ContainerConstants.GRAFANA_PORT); + } + + public AbstractGrafanaConfig(String imageName, boolean shared) { + this(imageName, shared, "admin", "admin", ContainerConstants.GRAFANA_PORT); + } + + public AbstractGrafanaConfig(String imageName, String username, String password, int grafanaPort) { + this(imageName, true, username, password, grafanaPort); + } + + public AbstractGrafanaConfig(String imageName, boolean shared, String username, String password, int grafanaPort) { + super(imageName, shared); + this.username = username; + this.password = password; + this.grafanaPort = grafanaPort; + } + + @Override + public String username() { + return username; + } + + @Override + public String password() { + return password; + } + + @Override + public int grafanaPort() { + return grafanaPort; + } + + @Override + public Duration timeout() { + return Duration.ofMinutes(1); + } +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ConfigUtils.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ConfigUtils.java new file mode 100644 index 00000000000000..74ddaad6985bc3 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ConfigUtils.java @@ -0,0 +1,20 @@ +package io.quarkus.observability.common.config; + +public class ConfigUtils { + + public static boolean isEnabled(ContainerConfig config) { + if (config != null && config.enabled()) { + DevTarget target = config.getClass().getAnnotation(DevTarget.class); + if (target != null) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + cl.loadClass(target.value()); + return true; + } catch (ClassNotFoundException ignore) { + } + } + } + return false; + } + +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ContainerConfig.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ContainerConfig.java new file mode 100644 index 00000000000000..d6bb59e981d58f --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ContainerConfig.java @@ -0,0 +1,69 @@ +package io.quarkus.observability.common.config; + +import java.util.Optional; +import java.util.Set; + +import io.smallrye.config.WithDefault; + +public interface ContainerConfig { + + /** + * If DevServices has been explicitly enabled or disabled. DevServices is generally enabled + * by default, unless there is an existing configuration present. + *

+ * When DevServices is enabled Quarkus will attempt to automatically configure and start + * a containers when running in Dev or Test mode and when Docker is running. + */ + @WithDefault("true") + boolean enabled(); + + /** + * The container image name to use, for container based DevServices providers. + */ + String imageName(); + + /** + * Indicates if the container managed by Quarkus Dev Services is shared. + * When shared, Quarkus looks for running containers using label-based service discovery. + * If a matching container is found, it is used, and so a second one is not started. + * Otherwise, Dev Services starts a new container. + *

+ * The discovery uses the {@code quarkus-dev-service-label} label. + * The value is configured using the {@code service-name} property. + *

+ * Container sharing is only used in dev mode. + */ + @WithDefault("true") + boolean shared(); + + /** + * Network aliases. + * + * @return metwork aliases + */ + Optional> networkAliases(); + + /** + * The full name of the label attached to the started container. + * This label is used when {@code shared} is set to {@code true}. + * In this case, before starting a container, Dev Services for looks for a container with th label + * set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it + * starts a new container with this label set to the specified value. + *

+ * This property is used when you need multiple shared containers. + */ + String label(); + + /** + * The value of the {@code quarkus-dev-service} label attached to the started container. + * This property is used when {@code shared} is set to {@code true}. + * In this case, before starting a container, Dev Services for looks for a container with the + * {@code quarkus-dev-service} label + * set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it + * starts a new container with the {@code quarkus-dev-service} label set to the specified value. + *

+ * This property is used when you need multiple shared containers. + */ + @WithDefault("quarkus") + String serviceName(); +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/DevTarget.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/DevTarget.java new file mode 100644 index 00000000000000..001da8a94f44d1 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/DevTarget.java @@ -0,0 +1,16 @@ +package io.quarkus.observability.common.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DevTarget { + /** + * The dev resource we require on the classpath, + * for this config to fully kick-in. + */ + String value(); +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/GrafanaConfig.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/GrafanaConfig.java new file mode 100644 index 00000000000000..229522ab8e027b --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/GrafanaConfig.java @@ -0,0 +1,33 @@ +package io.quarkus.observability.common.config; + +import java.time.Duration; + +import io.smallrye.config.WithDefault; + +public interface GrafanaConfig extends ContainerConfig { + + // copied from ContainerConfig, config hierarchy workaround + + @WithDefault("true") + boolean enabled(); + + @WithDefault("true") + boolean shared(); + + @WithDefault("quarkus") + String serviceName(); + + // --- + + @WithDefault("admin") + String username(); + + @WithDefault("admin") + String password(); + + @WithDefault("3000") + int grafanaPort(); + + @WithDefault("PT1M") + Duration timeout(); +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/LgtmConfig.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/LgtmConfig.java new file mode 100644 index 00000000000000..ae7a239cb647f5 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/LgtmConfig.java @@ -0,0 +1,23 @@ +package io.quarkus.observability.common.config; + +import java.util.Optional; +import java.util.Set; + +import io.quarkus.observability.common.ContainerConstants; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; + +@ConfigGroup +public interface LgtmConfig extends GrafanaConfig { + @WithDefault(ContainerConstants.LGTM) + String imageName(); + + @WithDefault("lgtm,lgtm.testcontainer.docker") + Optional> networkAliases(); + + @WithDefault("quarkus-dev-service-lgtm") + String label(); + + @WithDefault("4318") + int otlpPort(); +} diff --git a/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ModulesConfiguration.java b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ModulesConfiguration.java new file mode 100644 index 00000000000000..7625f622234ce8 --- /dev/null +++ b/extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ModulesConfiguration.java @@ -0,0 +1,8 @@ +package io.quarkus.observability.common.config; + +import io.quarkus.runtime.annotations.ConfigDocSection; + +public interface ModulesConfiguration { + @ConfigDocSection + LgtmConfig lgtm(); +} diff --git a/extensions/observability-devservices/deployment/pom.xml b/extensions/observability-devservices/deployment/pom.xml new file mode 100644 index 00000000000000..e73404fc0ddf51 --- /dev/null +++ b/extensions/observability-devservices/deployment/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-devservices-deployment + Quarkus - Observability Devservices - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-devservices-common + + + io.quarkus + quarkus-devtools-utilities + + + io.quarkus + quarkus-kubernetes-spi + + + io.quarkus + quarkus-observability-devservices + + + + io.quarkus + quarkus-junit5-internal + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + org.awaitility + awaitility + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesBuildItem.java b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesBuildItem.java new file mode 100644 index 00000000000000..4bde7af481a59b --- /dev/null +++ b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesBuildItem.java @@ -0,0 +1,6 @@ +package io.quarkus.observability.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +final class DevResourcesBuildItem extends SimpleBuildItem { +} diff --git a/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesProcessor.java b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesProcessor.java new file mode 100644 index 00000000000000..88d17c7c34d13b --- /dev/null +++ b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/DevResourcesProcessor.java @@ -0,0 +1,51 @@ +package io.quarkus.observability.deployment; + +import java.util.function.BooleanSupplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.observability.runtime.DevResourceShutdownRecorder; +import io.quarkus.observability.runtime.DevResourcesConfigBuilder; +import io.quarkus.observability.runtime.config.ObservabilityConfiguration; + +@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = DevResourcesProcessor.IsEnabled.class) +class DevResourcesProcessor { + private static final Logger log = LoggerFactory.getLogger(DevResourcesProcessor.class); + private static final String FEATURE = "devresources"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + public RunTimeConfigBuilderBuildItem registerDevResourcesConfigSource() { + log.info("Adding dev resources config builder"); + return new RunTimeConfigBuilderBuildItem(DevResourcesConfigBuilder.class); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public DevResourcesBuildItem shutdownDevResources(DevResourceShutdownRecorder recorder, ShutdownContextBuildItem shutdown) { + recorder.shutdown(shutdown); + return new DevResourcesBuildItem(); + } + + public static class IsEnabled implements BooleanSupplier { + ObservabilityConfiguration config; + + public boolean getAsBoolean() { + return config.devResources() && !config.enabled(); + } + } + +} diff --git a/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/ObservabilityProcessor.java b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/ObservabilityProcessor.java new file mode 100644 index 00000000000000..e12f5133c89cc8 --- /dev/null +++ b/extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/ObservabilityProcessor.java @@ -0,0 +1,211 @@ +package io.quarkus.observability.deployment; + +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jboss.logging.Logger; +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DockerStatusBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; +import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; +import io.quarkus.devservices.common.ContainerLocator; +import io.quarkus.devservices.common.ContainerShutdownCloseable; +import io.quarkus.observability.common.config.ContainerConfig; +import io.quarkus.observability.common.config.ModulesConfiguration; +import io.quarkus.observability.devresource.DevResourceLifecycleManager; +import io.quarkus.observability.devresource.DevResources; +import io.quarkus.observability.runtime.config.ObservabilityConfiguration; +import io.quarkus.runtime.LaunchMode; + +@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class, + ObservabilityProcessor.IsEnabled.class }) +class ObservabilityProcessor { + private static final Logger log = Logger.getLogger(ObservabilityProcessor.class); + + private static final Map devServices = new ConcurrentHashMap<>(); + private static final Map capturedDevServicesConfigurations = new ConcurrentHashMap<>(); + private static final Map firstStart = new ConcurrentHashMap<>(); + + public static class IsEnabled implements BooleanSupplier { + ObservabilityConfiguration config; + + public boolean getAsBoolean() { + return config.enabled() && !config.devResources(); + } + } + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(Feature.OBSERVABILITY); + } + + private String devId(DevResourceLifecycleManager dev) { + String sn = dev.getClass().getSimpleName(); + int p = sn.indexOf("Resource"); + return sn.substring(0, p != -1 ? p : sn.length()); + } + + @BuildStep + public void startContainers(LaunchModeBuildItem launchMode, + DockerStatusBuildItem dockerStatusBuildItem, + ObservabilityConfiguration configuration, + Optional consoleInstalledBuildItem, + CuratedApplicationShutdownBuildItem closeBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem, + GlobalDevServicesConfig devServicesConfig, + BuildProducer services) { + + if (!configuration.enabled()) { + log.infof("Observability dev services are disabled in config"); + return; + } + + if (!dockerStatusBuildItem.isDockerAvailable()) { + log.warn("Please get a working Docker instance"); + return; + } + + @SuppressWarnings("rawtypes") + List resources = DevResources.resources(); + // this should throw an exception on a duplicate + //noinspection ResultOfMethodCallIgnored + resources.stream().collect(Collectors.toMap(this::devId, Function.identity())); + + @SuppressWarnings("rawtypes") + Stream stream = resources.stream(); + if (configuration.parallel()) { + stream = stream.parallel(); + } + + stream.forEach(dev -> { + String devId = devId(dev); + + DevServicesResultBuildItem.RunningDevService devService = devServices.remove(devId); + ContainerConfig currentDevServicesConfiguration = dev.config(configuration); + + if (devService != null) { + ContainerConfig capturedDevServicesConfiguration = capturedDevServicesConfigurations.remove(devId); + boolean restartRequired = !currentDevServicesConfiguration.equals(capturedDevServicesConfiguration); + if (!restartRequired) { + services.produce(devService.toBuildItem()); + return; + } + try { + devService.close(); + } catch (Throwable e) { + log.errorf("Failed to stop %s container", devId, e); + } + } + + capturedDevServicesConfigurations.put(devId, currentDevServicesConfiguration); + + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + devId + " Dev Services Starting:", + consoleInstalledBuildItem, + loggingSetupBuildItem); + try { + DevServicesResultBuildItem.RunningDevService newDevService = startContainer( + devId, + dev, + currentDevServicesConfiguration, + configuration, + devServicesConfig.timeout); + if (newDevService == null) { + compressor.closeAndDumpCaptured(); + return; + } else { + compressor.close(); + } + + devService = newDevService; + devServices.put(devId, newDevService); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); + } + + if (firstStart.computeIfAbsent(devId, x -> true)) { + Runnable closeTask = () -> { + DevServicesResultBuildItem.RunningDevService current = devServices.get(devId); + if (current != null) { + try { + current.close(); + } catch (Throwable t) { + log.errorf("Failed to stop %s container", devId, t); + } + } + firstStart.remove(devId); + //noinspection resource + devServices.remove(devId); + capturedDevServicesConfigurations.remove(devId); + }; + closeBuildItem.addCloseTask(closeTask, true); + } + + services.produce(devService.toBuildItem()); + }); + } + + private DevServicesResultBuildItem.RunningDevService startContainer( + String devId, + DevResourceLifecycleManager dev, + ContainerConfig capturedDevServicesConfiguration, + ModulesConfiguration root, + Optional timeout) { + + if (!capturedDevServicesConfiguration.enabled()) { + // explicitly disabled + log.debugf("Not starting Dev Services for %s as it has been disabled in the config", devId); + return null; + } + + if (!dev.enable()) { + return null; + } + + final Supplier defaultContainerSupplier = () -> { + GenericContainer container = dev.container(capturedDevServicesConfiguration, root); + timeout.ifPresent(container::withStartupTimeout); + Map config = dev.start(); + log.infof("Dev Service %s started, config: %s", devId, config); + return new DevServicesResultBuildItem.RunningDevService( + Feature.OBSERVABILITY.getName(), container.getContainerId(), + new ContainerShutdownCloseable(container, capturedDevServicesConfiguration.serviceName()), config); + }; + + Map config = new LinkedHashMap<>(); // old config + ContainerLocator containerLocator = new ContainerLocator(capturedDevServicesConfiguration.label(), 0); // can be 0, as we don't use it + return containerLocator + .locateContainer( + capturedDevServicesConfiguration.serviceName(), capturedDevServicesConfiguration.shared(), + LaunchMode.current(), (p, ca) -> config.putAll(dev.config(p, ca.getHost(), ca.getPort()))) + .map(cid -> { + log.infof("Dev Service %s re-used, config: %s", devId, config); + return new DevServicesResultBuildItem.RunningDevService(Feature.OBSERVABILITY.getName(), cid, + null, config); + }) + .orElseGet(defaultContainerSupplier); + } + +} diff --git a/extensions/observability-devservices/pom.xml b/extensions/observability-devservices/pom.xml new file mode 100644 index 00000000000000..c5bfa4c704bf84 --- /dev/null +++ b/extensions/observability-devservices/pom.xml @@ -0,0 +1,25 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-observability-devservices-parent + Quarkus - Observability Devservices Parent + pom + + common + testcontainers + testlibs + deployment + runtime + + sub/lgtm + + \ No newline at end of file diff --git a/extensions/observability-devservices/runtime/pom.xml b/extensions/observability-devservices/runtime/pom.xml new file mode 100644 index 00000000000000..e3c27b122a685b --- /dev/null +++ b/extensions/observability-devservices/runtime/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-devservices + Quarkus - Observability Devservices - Runtime + Serve and consume Observability devservices + + + io.quarkus + quarkus-core + + + + io.quarkus + quarkus-observability-devservices-common + + + io.quarkus + quarkus-observability-devresource-common + + + + + io.quarkus + quarkus-junit5-internal + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + generate-extension-descriptor + + extension-descriptor + + process-resources + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourceShutdownRecorder.java b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourceShutdownRecorder.java new file mode 100644 index 00000000000000..0d63c09b081dc0 --- /dev/null +++ b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourceShutdownRecorder.java @@ -0,0 +1,12 @@ +package io.quarkus.observability.runtime; + +import io.quarkus.observability.devresource.DevResources; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class DevResourceShutdownRecorder { + public void shutdown(ShutdownContext context) { + context.addLastShutdownTask(DevResources::stop); + } +} diff --git a/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourcesConfigBuilder.java b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourcesConfigBuilder.java new file mode 100644 index 00000000000000..8d327a1d4bb0cd --- /dev/null +++ b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/DevResourcesConfigBuilder.java @@ -0,0 +1,18 @@ +package io.quarkus.observability.runtime; + +import io.quarkus.observability.devresource.DevResourcesConfigSource; +import io.quarkus.runtime.configuration.ConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilder; + +public class DevResourcesConfigBuilder implements ConfigBuilder { + @Override + public SmallRyeConfigBuilder configBuilder(SmallRyeConfigBuilder builder) { + return builder.withSources(new DevResourcesConfigSource()); + } + + @Override + public int priority() { + // greater than any default Microprofile ConfigSource + return 500; + } +} diff --git a/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/config/ObservabilityConfiguration.java b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/config/ObservabilityConfiguration.java new file mode 100644 index 00000000000000..e9327be656c1ef --- /dev/null +++ b/extensions/observability-devservices/runtime/src/main/java/io/quarkus/observability/runtime/config/ObservabilityConfiguration.java @@ -0,0 +1,35 @@ +package io.quarkus.observability.runtime.config; + +import io.quarkus.observability.common.config.ModulesConfiguration; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +@ConfigMapping(prefix = "quarkus.observability") +@ConfigRoot(phase = ConfigPhase.BUILD_TIME) +public interface ObservabilityConfiguration extends ModulesConfiguration { + /** + * If DevServices has been explicitly enabled or disabled. DevServices is generally enabled + * by default, unless there is an existing configuration present. + *

+ * When DevServices is enabled Quarkus will attempt to automatically configure and start + * a containers when running in Dev or Test mode and when Docker is running. + */ + @WithDefault("true") + boolean enabled(); + + /** + * Enable simplified usage of dev resources, + * instead of full observability processing. + * Make sure @code{enabled} is set to false. + */ + @WithDefault("false") + boolean devResources(); + + /** + * Do we start the dev services in parallel. + */ + @WithDefault("false") + boolean parallel(); +} diff --git a/extensions/observability-devservices/sub/lgtm/pom.xml b/extensions/observability-devservices/sub/lgtm/pom.xml new file mode 100644 index 00000000000000..e4f992eb6c2009 --- /dev/null +++ b/extensions/observability-devservices/sub/lgtm/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + ../../pom.xml + + + quarkus-observability-devservices-lgtm + Quarkus - Observability Devservices - LGTM + Observability Devservices - LGTM + + + true + + + + + io.quarkus + quarkus-observability-devservices + + + io.quarkus + quarkus-observability-devresource-lgtm + + + diff --git a/extensions/observability-devservices/testcontainers/pom.xml b/extensions/observability-devservices/testcontainers/pom.xml new file mode 100644 index 00000000000000..15adf238fd06bc --- /dev/null +++ b/extensions/observability-devservices/testcontainers/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-testcontainers + Quarkus Observability - Testcontainers + Quarkus Observability Devservices - Testcontainers + + + + io.quarkus + quarkus-devservices-common + + + io.quarkus + quarkus-observability-devservices-common + + + org.testcontainers + testcontainers + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock + + + org.junit.jupiter + junit-jupiter-api + test + + + + \ No newline at end of file diff --git a/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/GrafanaContainer.java b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/GrafanaContainer.java new file mode 100644 index 00000000000000..0218c6911b3cf1 --- /dev/null +++ b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/GrafanaContainer.java @@ -0,0 +1,35 @@ +package io.quarkus.observability.testcontainers; + +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategy; + +import io.quarkus.observability.common.config.GrafanaConfig; + +@SuppressWarnings("resource") +public abstract class GrafanaContainer, C extends GrafanaConfig> + extends ObservabilityContainer { + protected static final String DATASOURCES_PATH = "/etc/grafana/provisioning/datasources/custom.yaml"; + + protected C config; + + public GrafanaContainer(C config) { + super(config); + this.config = config; + withEnv("GF_SECURITY_ADMIN_USER", config.username()); + withEnv("GF_SECURITY_ADMIN_PASSWORD", config.password()); + addExposedPort(config.grafanaPort()); + waitingFor(grafanaWaitStrategy()); + } + + public int getGrafanaPort() { + return getMappedPort(config.grafanaPort()); + } + + private WaitStrategy grafanaWaitStrategy() { + return new HttpWaitStrategy() + .forPath("/") + .forPort(config.grafanaPort()) + .forStatusCode(200) + .withStartupTimeout(config.timeout()); + } +} diff --git a/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/LgtmContainer.java b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/LgtmContainer.java new file mode 100644 index 00000000000000..fd31bab3be9c07 --- /dev/null +++ b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/LgtmContainer.java @@ -0,0 +1,45 @@ +package io.quarkus.observability.testcontainers; + +import java.util.Optional; +import java.util.Set; + +import io.quarkus.observability.common.ContainerConstants; +import io.quarkus.observability.common.config.AbstractGrafanaConfig; +import io.quarkus.observability.common.config.LgtmConfig; + +public class LgtmContainer extends GrafanaContainer { + protected static final String LGTM_NETWORK_ALIAS = "ltgm.testcontainer.docker"; + + public LgtmContainer() { + this(new LgtmConfigImpl()); + } + + public LgtmContainer(LgtmConfig config) { + super(config); + addExposedPorts(config.otlpPort()); + } + + public int getOtlpPort() { + return getMappedPort(config.otlpPort()); + } + + protected static class LgtmConfigImpl extends AbstractGrafanaConfig implements LgtmConfig { + public LgtmConfigImpl() { + this(ContainerConstants.LGTM); + } + + public LgtmConfigImpl(String imageName) { + super(imageName); + } + + @Override + public Optional> networkAliases() { + return Optional.of(Set.of("lgtm", LGTM_NETWORK_ALIAS)); + } + + @Override + public int otlpPort() { + return ContainerConstants.OTEL_HTTP_EXPORTER_PORT; + } + } +} diff --git a/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/ObservabilityContainer.java b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/ObservabilityContainer.java new file mode 100644 index 00000000000000..21ae98aafafa16 --- /dev/null +++ b/extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/ObservabilityContainer.java @@ -0,0 +1,66 @@ +package io.quarkus.observability.testcontainers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.utility.DockerImageName; + +import io.quarkus.observability.common.config.ContainerConfig; + +@SuppressWarnings("resource") +public abstract class ObservabilityContainer, C extends ContainerConfig> + extends GenericContainer { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Logger dockerLog = LoggerFactory.getLogger(getClass().getName() + ".docker"); + + public ObservabilityContainer(C config) { + super(DockerImageName.parse(config.imageName())); + withLogConsumer(frame -> logger().debug(frame.getUtf8String().stripTrailing())); + withLabel(config.label(), config.serviceName()); + Optional> aliases = config.networkAliases(); + aliases.map(s -> s.toArray(new String[0])).ifPresent(this::withNetworkAliases); + if (config.shared()) { + withNetwork(Network.SHARED); + } + } + + protected byte[] getResourceAsBytes(String resource) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(resource)) { + return in.readAllBytes(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + @SuppressWarnings("OctalInteger") + protected void addFileToContainer(byte[] content, String pathInContainer) { + logger().info("Content [{}]: \n{}", pathInContainer, new String(content, StandardCharsets.UTF_8)); + copyFileToContainer(Transferable.of(content, 0777), pathInContainer); + } + + @Override + protected Logger logger() { + return dockerLog; + } + + @Override + public void start() { + log.info("Starting {} ...", getClass().getSimpleName()); + super.start(); + } + + @Override + public void stop() { + log.info("Stopping {}...", getClass().getSimpleName()); + super.stop(); + } +} diff --git a/extensions/observability-devservices/testlibs/devresource-common/pom.xml b/extensions/observability-devservices/testlibs/devresource-common/pom.xml new file mode 100644 index 00000000000000..48ef16889da1d7 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-common/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + quarkus-observability-testlibs + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-devresource-common + Quarkus - Observability - DevResource Common + Simple DevResource abstraction + + + + org.eclipse.microprofile.config + microprofile-config-api + + + org.jboss.logging + jboss-logging + + + io.quarkus + quarkus-observability-devservices-common + + + org.testcontainers + testcontainers + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock + + + \ No newline at end of file diff --git a/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/ContainerResource.java b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/ContainerResource.java new file mode 100644 index 00000000000000..f44581ed9c9a49 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/ContainerResource.java @@ -0,0 +1,41 @@ +package io.quarkus.observability.devresource; + +import java.util.Map; + +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.observability.common.config.ContainerConfig; + +/** + * A container resource abstraction + */ +public abstract class ContainerResource, C extends ContainerConfig> + implements DevResourceLifecycleManager { + + protected T container; + + protected T set(T container) { + this.container = container; + return container; + } + + @Override + public Map start() { + if (container == null) { + container = defaultContainer(); + } + container.start(); + return doStart(); + } + + @Override + public void stop() { + if (container != null) { + container.stop(); + } + } + + protected abstract T defaultContainer(); + + protected abstract Map doStart(); +} diff --git a/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourceLifecycleManager.java b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourceLifecycleManager.java new file mode 100644 index 00000000000000..ff7020b27c9314 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourceLifecycleManager.java @@ -0,0 +1,106 @@ +package io.quarkus.observability.devresource; + +import java.util.Map; + +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.observability.common.config.ContainerConfig; +import io.quarkus.observability.common.config.ModulesConfiguration; + +/** + * Compatible with {@link io.quarkus.test.common.QuarkusTestResourceLifecycleManager} + * so that classes can implement both interfaces at the same time. + */ +public interface DevResourceLifecycleManager { + + // Put order constants here -- order by dependency + + int METRICS = 5000; + int SCRAPER = 7500; + int GRAFANA = 10000; + int JAEGER = 20000; + int OTEL = 20000; + + //---- + + /** + * Get resource's config from main observability configuration. + * + * @param configuration main observability configuration + * @return module's config + */ + T config(ModulesConfiguration configuration); + + /** + * Should we enable / start this dev resource. + * e.g. we could already have actual service running + * Each impl should provide its own reason on why it disabled dev service. + * + * @return true if ok to start new dev service, false otherwise + */ + default boolean enable() { + return true; + } + + /** + * Create container from config. + * + * @param config the config + * @return container id + */ + default GenericContainer container(T config) { + throw new IllegalStateException("Should be implemented!"); + } + + /** + * Create container from config. + * + * @param config the config + * @param root the all modules config + * @return container id + */ + default GenericContainer container(T config, ModulesConfiguration root) { + return container(config); + } + + /** + * Deduct current config from params. + * If port are too dynamic / configured, it's hard to deduct, + * since configuration is not part of the devservice state. + * e.g. different ports then usual - Grafana UI is 3000, if you do not use 3000, + * it's hard or impossible to know which port belongs to certain property. + * + * @return A map of system properties that should be set for the running dev-mode app + */ + Map config(int privatePort, String host, int publicPort); + + /** + * Start the dev resource. + * + * @return A map of system properties that should be set for the running dev-mode app + */ + Map start(); + + /** + * Stop the dev resource. + */ + void stop(); + + /** + * Called even before {@link #start()} so that the implementation can prepare itself + * to be used as dev resource (as opposed to test resource which uses a different + * init() method). + */ + default void initDev() { + } + + /** + * If multiple dev resources are located, + * this control the order of which they will be executed. + * + * @return The order to be executed. The larger the number, the later the resource is invoked. + */ + default int order() { + return 0; + } +} diff --git a/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResources.java b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResources.java new file mode 100644 index 00000000000000..0dbd79ed0579eb --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResources.java @@ -0,0 +1,88 @@ +package io.quarkus.observability.devresource; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import org.jboss.logging.Logger; + +/** + * A registry of dev resources. + */ +@SuppressWarnings("rawtypes") +public class DevResources { + private static final Logger log = Logger.getLogger(DevResources.class); + + private static List resources; + private static Map map; + + /** + * @return list of found dev resources. + */ + public static synchronized List resources() { + if (resources == null) { + log.info("Activating dev resources"); + + resources = ServiceLoader + .load(DevResourceLifecycleManager.class, Thread.currentThread().getContextClassLoader()) + .stream() + .map(ServiceLoader.Provider::get) + .sorted(Comparator.comparing(DevResourceLifecycleManager::order)) + .collect(Collectors.toList()); + + log.infof("Found dev resources: %s", resources); + } + return resources; + } + + /** + * Ensures all dev resources are started and returns a map of config properties. + * + * @return a map of config properties to be returned by {@link DevResourcesConfigSource} + */ + static synchronized Map ensureStarted() { + if (map == null) { + try { + for (var res : resources()) { + res.initDev(); + } + } catch (Exception e) { + log.error("Exception initializing dev resource manager", e); + throw e; + } + try { + var map = new HashMap(); + for (var res : resources()) { + var resMap = res.start(); + log.infof("Dev resource [%s] contributed config: %s", res.getClass().getSimpleName(), resMap); + map.putAll(resMap); + } + DevResources.map = Collections.unmodifiableMap(map); + } catch (Exception e) { + log.error("Exception starting dev resource", e); + throw e; + } + } + return map; + } + + /** + * Stops all dev resources. + */ + public static synchronized void stop() { + if (map != null) { + for (var i = resources().listIterator(resources().size()); i.hasPrevious();) { + try { + i.previous().stop(); + } catch (Exception e) { + log.warn("Exception stopping dev resource", e); + } + } + map = null; + } + } +} diff --git a/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourcesConfigSource.java b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourcesConfigSource.java new file mode 100644 index 00000000000000..b3458c5be2be23 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-common/src/main/java/io/quarkus/observability/devresource/DevResourcesConfigSource.java @@ -0,0 +1,28 @@ +package io.quarkus.observability.devresource; + +import java.util.Set; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +public class DevResourcesConfigSource implements ConfigSource { + @Override + public Set getPropertyNames() { + return DevResources.ensureStarted().keySet(); + } + + @Override + public String getValue(String propertyName) { + return DevResources.ensureStarted().get(propertyName); + } + + @Override + public String getName() { + return "DevResourcesConfigSource"; + } + + @Override + public int getOrdinal() { + // greater than any default Microprofile ConfigSource + return 500; + } +} diff --git a/extensions/observability-devservices/testlibs/devresource-lgtm/pom.xml b/extensions/observability-devservices/testlibs/devresource-lgtm/pom.xml new file mode 100644 index 00000000000000..1d6805d3768840 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-lgtm/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + quarkus-observability-testlibs + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-devresource-lgtm + Quarkus - Observability - OTel LGTM DevResource + LGTM DevResource + + + true + + + + + io.quarkus + quarkus-observability-devresource-common + + + io.quarkus + quarkus-observability-testcontainers + + + io.quarkus + quarkus-test-common + + + + \ No newline at end of file diff --git a/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/java/io/quarkus/observability/devresource/lgtm/LgtmResource.java b/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/java/io/quarkus/observability/devresource/lgtm/LgtmResource.java new file mode 100644 index 00000000000000..5bea73775a6aad --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/java/io/quarkus/observability/devresource/lgtm/LgtmResource.java @@ -0,0 +1,57 @@ +package io.quarkus.observability.devresource.lgtm; + +import java.util.Map; + +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.observability.common.ContainerConstants; +import io.quarkus.observability.common.config.LgtmConfig; +import io.quarkus.observability.common.config.ModulesConfiguration; +import io.quarkus.observability.devresource.ContainerResource; +import io.quarkus.observability.devresource.DevResourceLifecycleManager; +import io.quarkus.observability.testcontainers.LgtmContainer; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class LgtmResource extends ContainerResource + implements QuarkusTestResourceLifecycleManager { + + @Override + public LgtmConfig config(ModulesConfiguration configuration) { + return configuration.lgtm(); + } + + @Override + public GenericContainer container(LgtmConfig config, ModulesConfiguration root) { + return set(new LgtmContainer(config)); + } + + @Override + public Map config(int privatePort, String host, int publicPort) { + switch (privatePort) { + case ContainerConstants.GRAFANA_PORT: + return Map.of("quarkus.grafana.url", String.format("%s:%s", host, publicPort)); + case ContainerConstants.OTEL_GRPC_EXPORTER_PORT: + case ContainerConstants.OTEL_HTTP_EXPORTER_PORT: + return Map.of("quarkus.otel-collector.url", String.format("%s:%s", host, publicPort)); + } + return Map.of(); + } + + @Override + protected LgtmContainer defaultContainer() { + return new LgtmContainer(); + } + + @Override + public Map doStart() { + String host = container.getHost(); + return Map.of( + "quarkus.grafana.url", String.format("%s:%s", host, container.getGrafanaPort()), + "quarkus.otel-collector.url", String.format("%s:%s", host, container.getOtlpPort())); + } + + @Override + public int order() { + return DevResourceLifecycleManager.GRAFANA; + } +} diff --git a/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/resources/META-INF/services/io.quarkus.observability.devresource.DevResourceLifecycleManager b/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/resources/META-INF/services/io.quarkus.observability.devresource.DevResourceLifecycleManager new file mode 100644 index 00000000000000..73702014d34256 --- /dev/null +++ b/extensions/observability-devservices/testlibs/devresource-lgtm/src/main/resources/META-INF/services/io.quarkus.observability.devresource.DevResourceLifecycleManager @@ -0,0 +1 @@ +io.quarkus.observability.devresource.lgtm.LgtmResource diff --git a/extensions/observability-devservices/testlibs/pom.xml b/extensions/observability-devservices/testlibs/pom.xml new file mode 100644 index 00000000000000..4ed75add78b12b --- /dev/null +++ b/extensions/observability-devservices/testlibs/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + quarkus-observability-devservices-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-observability-testlibs + pom + Quarkus Observability Devservices - Test Libraries + Quarkus Observability - Test Libraries + + + devresource-common + devresource-lgtm + + + \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index f405528ad17822..894b24412776f4 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -52,6 +52,7 @@ micrometer-registry-prometheus opentelemetry info + observability-devservices resteasy-classic diff --git a/integration-tests/observability-lgtm/pom.xml b/integration-tests/observability-lgtm/pom.xml new file mode 100644 index 00000000000000..b557d9b558f473 --- /dev/null +++ b/integration-tests/observability-lgtm/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-integration-test-observability-lgtm + Quarkus - Integration Tests - Observability LGTM + + + + io.quarkus + quarkus-observability-devservices-lgtm + + + io.quarkus + quarkus-resteasy-reactive + + + + io.quarkiverse.micrometer.registry + quarkus-micrometer-registry-otlp + 3.2.4 + + + com.fasterxml.jackson.core + jackson-databind + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-resteasy-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + diff --git a/integration-tests/observability-lgtm/src/main/java/io/quarkus/observability/example/SimpleEndpoint.java b/integration-tests/observability-lgtm/src/main/java/io/quarkus/observability/example/SimpleEndpoint.java new file mode 100644 index 00000000000000..17684efd1e9440 --- /dev/null +++ b/integration-tests/observability-lgtm/src/main/java/io/quarkus/observability/example/SimpleEndpoint.java @@ -0,0 +1,48 @@ +package io.quarkus.observability.example; + +import java.security.SecureRandom; +import java.util.Random; + +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; + +import org.jboss.logging.Logger; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; + +@Path("/api") +public class SimpleEndpoint { + private static final Logger log = Logger.getLogger(SimpleEndpoint.class); + + @Inject + MeterRegistry registry; + + Random random = new SecureRandom(); + double[] arr = new double[1]; + + @PostConstruct + public void start() { + String key = System.getProperty("tag-key", "test"); + Gauge.builder("xvalue", arr, a -> arr[0]) + .baseUnit("X") + .description("Some random x") + .tag(key, "x") + .register(registry); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/poke") + public String poke(@QueryParam("f") int f) { + log.infof("Poke %s", f); + double x = random.nextDouble() * f; + arr[0] = x; + return "poke:" + x; + } +} diff --git a/integration-tests/observability-lgtm/src/main/resources/application.properties b/integration-tests/observability-lgtm/src/main/resources/application.properties new file mode 100644 index 00000000000000..59b0a4a9869f17 --- /dev/null +++ b/integration-tests/observability-lgtm/src/main/resources/application.properties @@ -0,0 +1,13 @@ +# Disable default binders +quarkus.micrometer.binder-enabled-default=false + +quarkus.log.category."io.quarkus.observability".level=DEBUG + +#micrometer +quarkus.micrometer.export.otlp.enabled=true +quarkus.micrometer.export.otlp.publish=true +quarkus.micrometer.export.otlp.step=PT5S +quarkus.micrometer.export.otlp.default-registry=true +quarkus.micrometer.export.otlp.url=http://${quarkus.otel-collector.url}/v1/metrics + +#quarkus.observability.lgtm.image-name=grafana/otel-lgtm diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmLifecycleTest.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmLifecycleTest.java new file mode 100644 index 00000000000000..8660c82c95b932 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmLifecycleTest.java @@ -0,0 +1,17 @@ +package io.quarkus.observability.test; + +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import io.quarkus.observability.devresource.lgtm.LgtmResource; +import io.quarkus.observability.test.support.QuarkusTestResourceTestProfile; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@QuarkusTestResource(value = LgtmResource.class, restrictToAnnotatedClass = true) +@TestProfile(QuarkusTestResourceTestProfile.class) +@DisabledOnOs(OS.WINDOWS) +public class LgtmLifecycleTest extends LgtmTestBase { +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmResourcesTest.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmResourcesTest.java new file mode 100644 index 00000000000000..6cd232235b38f5 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmResourcesTest.java @@ -0,0 +1,14 @@ +package io.quarkus.observability.test; + +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import io.quarkus.observability.test.support.DevResourcesTestProfile; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(DevResourcesTestProfile.class) +@DisabledOnOs(OS.WINDOWS) +public class LgtmResourcesTest extends LgtmTestBase { +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmServicesTest.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmServicesTest.java new file mode 100644 index 00000000000000..33b7e0c13da7cb --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmServicesTest.java @@ -0,0 +1,11 @@ +package io.quarkus.observability.test; + +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@DisabledOnOs(OS.WINDOWS) +public class LgtmServicesTest extends LgtmTestBase { +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmTestBase.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmTestBase.java new file mode 100644 index 00000000000000..37b166511dab25 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/LgtmTestBase.java @@ -0,0 +1,31 @@ +package io.quarkus.observability.test; + +import java.util.concurrent.TimeUnit; + +import org.awaitility.Awaitility; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import io.quarkus.observability.test.support.GrafanaClient; +import io.restassured.RestAssured; + +@SuppressWarnings("NewClassNamingConvention") +public class LgtmTestBase { + + @ConfigProperty(name = "quarkus.grafana.url") + String url; + + @Test + public void testTracing() throws Exception { + String response = RestAssured.get("/api/poke?f=100").body().asString(); + System.out.println(response); + GrafanaClient client = new GrafanaClient("http://" + url, "admin", "admin"); + Awaitility.await().atMost(61, TimeUnit.SECONDS).until( + client::user, + u -> "admin".equals(u.login)); + Awaitility.await().atMost(61, TimeUnit.SECONDS).until( + () -> client.query("xvalue_X"), + result -> !result.data.result.isEmpty()); + } + +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/DevResourcesTestProfile.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/DevResourcesTestProfile.java new file mode 100644 index 00000000000000..1110a110730984 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/DevResourcesTestProfile.java @@ -0,0 +1,14 @@ +package io.quarkus.observability.test.support; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +public class DevResourcesTestProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of( + "quarkus.observability.dev-resources", "true", + "quarkus.observability.enabled", "false"); + } +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/GrafanaClient.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/GrafanaClient.java new file mode 100644 index 00000000000000..40f31cc6895891 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/GrafanaClient.java @@ -0,0 +1,92 @@ +package io.quarkus.observability.test.support; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class GrafanaClient { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final String url; + private final String username; + private final String password; + + public GrafanaClient(String url, String username, String password) { + this.url = url; + this.username = username; + this.password = password; + } + + private void handle( + String path, + Function method, + HttpResponse.BodyHandler bodyHandler, + BiConsumer, T> consumer) { + try { + String credentials = username + ":" + password; + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); + + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest.Builder builder = HttpRequest.newBuilder() + .uri(URI.create(url + path)) + .header("Authorization", "Basic " + encodedCredentials); + HttpRequest request = method.apply(builder).build(); + + HttpResponse response = httpClient.send(request, bodyHandler); + int code = response.statusCode(); + if (code < 200 || code > 299) { + throw new IllegalStateException("Bad response: " + code + " >> " + response.body()); + } + consumer.accept(response, response.body()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + + public User user() { + AtomicReference ref = new AtomicReference<>(); + handle( + "/api/user", + HttpRequest.Builder::GET, + HttpResponse.BodyHandlers.ofString(), + (r, b) -> { + try { + User user = MAPPER.readValue(b, User.class); + ref.set(user); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + }); + return ref.get(); + } + + public QueryResult query(String query) { + AtomicReference ref = new AtomicReference<>(); + handle( + "/api/datasources/proxy/1/api/v1/query?query=" + query, + HttpRequest.Builder::GET, + HttpResponse.BodyHandlers.ofString(), + (r, b) -> { + try { + QueryResult result = MAPPER.readValue(b, QueryResult.class); + ref.set(result); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + }); + return ref.get(); + } +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QuarkusTestResourceTestProfile.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QuarkusTestResourceTestProfile.java new file mode 100644 index 00000000000000..b60772d61d550c --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QuarkusTestResourceTestProfile.java @@ -0,0 +1,14 @@ +package io.quarkus.observability.test.support; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +public class QuarkusTestResourceTestProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of( + "quarkus.observability.dev-resources", "false", + "quarkus.observability.enabled", "false"); + } +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QueryResult.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QueryResult.java new file mode 100644 index 00000000000000..a58ab98f2f9e79 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/QueryResult.java @@ -0,0 +1,70 @@ +package io.quarkus.observability.test.support; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryResult { + public String status; + public Data data; + + // getters and setters + + @Override + public String toString() { + return "QueryResult{" + + "status='" + status + '\'' + + ", data=" + data + + '}'; + } + + public static class Data { + public String resultType; + public List result; + + // getters and setters + + @Override + public String toString() { + return "Data{" + + "resultType='" + resultType + '\'' + + ", result=" + result + + '}'; + } + } + + public static class ResultItem { + public Metric metric; + public List value; + + // getters and setters + + @Override + public String toString() { + return "ResultItem{" + + "metric=" + metric + + ", value=" + value + + '}'; + } + } + + public static class Metric { + @JsonProperty("__name__") + public String name; + public String job; + public String test; + + // getters and setters + + @Override + public String toString() { + return "Metric{" + + "name='" + name + '\'' + + ", job='" + job + '\'' + + ", test='" + test + '\'' + + '}'; + } + } +} diff --git a/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/User.java b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/User.java new file mode 100644 index 00000000000000..f617cd2b23bcf0 --- /dev/null +++ b/integration-tests/observability-lgtm/src/test/java/io/quarkus/observability/test/support/User.java @@ -0,0 +1,14 @@ +package io.quarkus.observability.test.support; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class User { + @JsonProperty + public int id; + @JsonProperty + public String email; + @JsonProperty + public String login; +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index e0b6a8a90dd0fb..ec30792f9f26a0 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -277,6 +277,7 @@ flyway liquibase liquibase-mongodb + observability-lgtm oidc oidc-client oidc-client-reactive