diff --git a/compose/auth_proxy.yml b/compose/auth_proxy.yml index 649489867..c656cc2f1 100644 --- a/compose/auth_proxy.yml +++ b/compose/auth_proxy.yml @@ -11,12 +11,8 @@ services: QUARKUS_HTTP_PROXY_ENABLE_FORWARDED_HOST: 'true' QUARKUS_HTTP_PROXY_ENABLE_FORWARDED_PREFIX: 'true' QUARKUS_HTTP_PROXY_TRUSTED_PROXIES: 127.0.0.1:${CRYOSTAT_HTTP_PORT} - healthcheck: - test: curl --fail http://cryostat:8181/health/liveness || exit 1 - interval: 10s - retries: 3 - start_period: 30s - timeout: 5s + QUARKUS_HTTP_ACCESS_LOG_PATTERN: long + QUARKUS_HTTP_ACCESS_LOG_ENABLED: 'true' auth: # the proxy does not actually depend on cryostat being up, but we use this # to ensure that when the smoketest tries to open the auth login page in a @@ -30,7 +26,7 @@ services: limits: cpus: '0.1' memory: 32m - image: ${OAUTH2_PROXY_IMAGE:-quay.io/oauth2-proxy/oauth2-proxy:latest} + image: ${OAUTH2_PROXY_IMAGE:-quay.io/oauth2-proxy/oauth2-proxy:latest-alpine} command: --alpha-config=/tmp/auth_proxy_alpha_config.yaml volumes: - auth_proxy_cfg:/tmp @@ -47,10 +43,10 @@ services: # OAUTH2_PROXY_SKIP_AUTH_ROUTES: .* restart: unless-stopped healthcheck: - test: wget -q --spider http://localhost:8080/ping || exit 1 + test: wget --no-check-certificate -q --spider http://localhost:8080/ping || exit 1 interval: 10s retries: 3 - start_period: 30s + start_period: 10s timeout: 5s volumes: diff --git a/compose/cryostat-grafana.yml b/compose/cryostat-grafana.yml index 1ae75b868..2127838b2 100644 --- a/compose/cryostat-grafana.yml +++ b/compose/cryostat-grafana.yml @@ -26,5 +26,5 @@ services: test: curl --fail http://localhost:3000/ || exit 1 retries: 3 interval: 30s - start_period: 30s + start_period: 10s timeout: 1s diff --git a/compose/cryostat.yml b/compose/cryostat.yml index 2bc5a14b8..b21c04302 100644 --- a/compose/cryostat.yml +++ b/compose/cryostat.yml @@ -27,12 +27,14 @@ services: CRYOSTAT_DISCOVERY_DOCKER_ENABLED: ${CRYOSTAT_DISCOVERY_DOCKER_ENABLED:-true} JAVA_OPTS_APPEND: "-XX:+FlightRecorder -XX:StartFlightRecording=name=onstart,settings=default,disk=true,maxage=5m -XX:StartFlightRecording=name=startup,settings=profile,disk=true,duration=30s -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false" restart: unless-stopped - healthcheck: - test: curl --fail http://cryostat:${CRYOSTAT_HTTP_PORT}/health/liveness || exit 1 - interval: 10s - retries: 3 - start_period: 30s - timeout: 5s + # FIXME reenable this check. Somehow after upgrading to Quarkus 3.8, this check fails with 'connection refused', + # but the container comes up successfully without it and shelling into the container later to run curl succeeds + # healthcheck: + # test: curl --fail http://cryostat:${CRYOSTAT_HTTP_PORT}/health/liveness || exit 1 + # interval: 10s + # retries: 3 + # start_period: 30s + # timeout: 5s volumes: jmxtls_cfg: diff --git a/compose/db.yml b/compose/db.yml index 098bf2961..4adc6fd6a 100644 --- a/compose/db.yml +++ b/compose/db.yml @@ -26,7 +26,7 @@ services: test: pg_isready -U cryostat3 -d cryostat3 || exit 1 interval: 10s retries: 3 - start_period: 30s + start_period: 10s timeout: 5s volumes: diff --git a/compose/jfr-datasource.yml b/compose/jfr-datasource.yml index c0e847485..965c53701 100644 --- a/compose/jfr-datasource.yml +++ b/compose/jfr-datasource.yml @@ -25,5 +25,5 @@ services: test: curl --fail http://localhost:8080/ || exit 1 retries: 3 interval: 30s - start_period: 30s + start_period: 10s timeout: 1s diff --git a/pom.xml b/pom.xml index 4baaa0bab..5c032b73b 100644 --- a/pom.xml +++ b/pom.xml @@ -42,20 +42,19 @@ 9.0.0 1.16.1 - 2.13.0 + 2.16.1 4.4 5.2.1 3.13.0 - 1.7 + 1.8.0 0.4.4 3.25.5 9.37.3 1.19.8 quarkus-bom io.quarkus.platform - 3.2.12.Final - 2.3.6 - 4.1.108.Final + 3.8.6 + 2.3.10 3.6.0 3.7.1 @@ -66,19 +65,11 @@ 4.5 3.2.5 2 - 3.2.5 - 6.7.2 + 3.3.1 ${surefire.rerunFailingTestsCount} - - io.netty - netty-bom - ${io.netty.version} - pom - import - ${quarkus.platform.group-id} ${quarkus.platform.artifact-id} @@ -162,6 +153,10 @@ io.quarkus quarkus-vertx + + io.quarkus + quarkus-netty + io.quarkus quarkus-smallrye-openapi @@ -187,6 +182,18 @@ io.quarkus quarkus-websockets + + io.quarkus + quarkus-rest-client-reactive-jackson + + + io.quarkus + quarkus-cache + + + io.quarkus + quarkus-quartz + io.quarkus quarkus-hibernate-orm-panache @@ -203,6 +210,10 @@ io.quarkiverse.amazonservices quarkus-amazon-s3 + + io.quarkus + quarkus-kubernetes-client + software.amazon.awssdk url-connection-client @@ -210,12 +221,10 @@ org.apache.commons commons-lang3 - ${org.apache.commons.lang3.version} commons-codec commons-codec - ${org.apache.commons.codec.version} commons-io @@ -251,29 +260,12 @@ nimbus-jose-jwt ${com.nimbusds.jose.jwt.version} - - io.quarkus - quarkus-rest-client-reactive-jackson - - - io.quarkus - quarkus-cache - - - io.quarkus - quarkus-quartz - com.google.googlejavaformat google-java-format ${com.google.java-format.version} provided - - io.fabric8 - kubernetes-client - ${io.fabric8.client.version} - io.quarkus @@ -541,7 +533,6 @@ io.netty netty-transport-native-epoll - ${io.netty.version} ${io.netty.netty-transport-native-epoll.classifier} ${io.netty.netty-transport-native-epoll.scope} diff --git a/schema/openapi.yaml b/schema/openapi.yaml index 776f3ae73..207ef131f 100644 --- a/schema/openapi.yaml +++ b/schema/openapi.yaml @@ -85,6 +85,7 @@ components: DiscoveryPlugin: properties: builtin: + readOnly: true type: boolean callback: format: uri @@ -296,6 +297,7 @@ components: Target: properties: agent: + readOnly: true type: boolean alias: pattern: \S @@ -347,11 +349,6 @@ components: meta: $ref: '#/components/schemas/Meta' type: object - securitySchemes: - SecurityScheme: - description: Authentication - scheme: basic - type: http info: contact: email: cryostat-development@googlegroups.com @@ -362,7 +359,7 @@ info: name: Apache 2.0 url: https://github.com/cryostatio/cryostat3/blob/main/LICENSE title: Cryostat API - version: 3.0.1-snapshot + version: 3.0.2-snapshot openapi: 3.0.3 paths: /api/beta/credentials/{connectUrl}: @@ -913,7 +910,7 @@ paths: type: string requestBody: content: - application/json: + text/plain: schema: type: string responses: @@ -1906,7 +1903,7 @@ paths: responses: "200": content: - application/json: + text/plain: schema: type: string description: OK @@ -2393,13 +2390,13 @@ paths: type: integer requestBody: content: - application/json: + text/plain: schema: type: string responses: "200": content: - application/json: + text/plain: schema: type: string description: OK @@ -2429,7 +2426,7 @@ paths: responses: "200": content: - application/json: + text/plain: schema: type: string description: OK diff --git a/src/main/java/io/cryostat/HibernateFormatMapperCustomization.java b/src/main/java/io/cryostat/HibernateFormatMapperCustomization.java new file mode 100644 index 000000000..dd390869d --- /dev/null +++ b/src/main/java/io/cryostat/HibernateFormatMapperCustomization.java @@ -0,0 +1,46 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cryostat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.hibernate.orm.JsonFormat; +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; + +@JsonFormat +@PersistenceUnitExtension +/** + * @see https://github.com/quarkusio/quarkus/issues/42596 + */ +public class HibernateFormatMapperCustomization implements FormatMapper { + + private final JacksonJsonFormatMapper delegate = + new JacksonJsonFormatMapper(new ObjectMapper().findAndRegisterModules()); + + @Override + public T fromString( + CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.fromString(charSequence, javaType, wrapperOptions); + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + return delegate.toString(value, javaType, wrapperOptions); + } +} diff --git a/src/main/java/io/cryostat/credentials/Credentials.java b/src/main/java/io/cryostat/credentials/Credentials.java index 7f840be92..4d81ab6d4 100644 --- a/src/main/java/io/cryostat/credentials/Credentials.java +++ b/src/main/java/io/cryostat/credentials/Credentials.java @@ -25,7 +25,6 @@ import io.cryostat.expressions.MatchExpression; import io.cryostat.expressions.MatchExpression.TargetMatcher; -import io.smallrye.common.annotation.Blocking; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -113,7 +112,6 @@ static Map notificationResult(Credential credential) throws Scri return result; } - @Blocking static Map safeResult(Credential credential, TargetMatcher matcher) throws ScriptException { Map result = new HashMap<>(); @@ -124,7 +122,6 @@ static Map safeResult(Credential credential, TargetMatcher match return result; } - @Blocking static Map safeMatchedResult(Credential credential, TargetMatcher matcher) throws ScriptException { Map result = new HashMap<>(); diff --git a/src/main/java/io/cryostat/credentials/CredentialsFinder.java b/src/main/java/io/cryostat/credentials/CredentialsFinder.java index a8f1bda00..52239c820 100644 --- a/src/main/java/io/cryostat/credentials/CredentialsFinder.java +++ b/src/main/java/io/cryostat/credentials/CredentialsFinder.java @@ -24,7 +24,6 @@ import io.cryostat.targets.Target.TargetDiscovery; import io.quarkus.vertx.ConsumeEvent; -import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.apache.commons.collections4.BidiMap; @@ -52,7 +51,6 @@ void onMessage(TargetDiscovery event) { } } - @Blocking public Optional getCredentialsForTarget(Target target) { return Optional.ofNullable( cache.computeIfAbsent( @@ -73,7 +71,6 @@ public Optional getCredentialsForTarget(Target target) { .orElse(null))); } - @Blocking public Optional getCredentialsForConnectUrl(URI connectUrl) { return Target.find("connectUrl", connectUrl) .singleResultOptional() diff --git a/src/main/java/io/cryostat/discovery/ContainerDiscovery.java b/src/main/java/io/cryostat/discovery/ContainerDiscovery.java index 3243b27a0..35805723c 100644 --- a/src/main/java/io/cryostat/discovery/ContainerDiscovery.java +++ b/src/main/java/io/cryostat/discovery/ContainerDiscovery.java @@ -187,7 +187,6 @@ public abstract class ContainerDiscovery { protected long timerId; - @Transactional void onStart(@Observes StartupEvent evt) { if (!enabled()) { return; @@ -200,22 +199,29 @@ void onStart(@Observes StartupEvent evt) { return; } - DiscoveryNode universe = DiscoveryNode.getUniverse(); - if (DiscoveryNode.getRealm(getRealm()).isEmpty()) { - DiscoveryPlugin plugin = new DiscoveryPlugin(); - DiscoveryNode node = DiscoveryNode.environment(getRealm(), BaseNodeType.REALM); - plugin.realm = node; - plugin.builtin = true; - universe.children.add(node); - node.parent = universe; - plugin.persist(); - universe.persist(); - } - - logger.debugv("Starting {0} client", getRealm()); + QuarkusTransaction.requiringNew() + .run( + () -> { + logger.debugv("Starting {0} client", getRealm()); + + DiscoveryNode universe = DiscoveryNode.getUniverse(); + if (DiscoveryNode.getRealm(getRealm()).isEmpty()) { + DiscoveryPlugin plugin = new DiscoveryPlugin(); + DiscoveryNode node = + DiscoveryNode.environment(getRealm(), BaseNodeType.REALM); + plugin.realm = node; + plugin.builtin = true; + universe.children.add(node); + node.parent = universe; + plugin.persist(); + universe.persist(); + } - queryContainers(); - this.timerId = vertx.setPeriodic(pollPeriod.toMillis(), unused -> queryContainers()); + queryContainers(); + this.timerId = + vertx.setPeriodic( + pollPeriod.toMillis(), unused -> queryContainers()); + }); } void onStop(@Observes ShutdownEvent evt) { @@ -278,10 +284,9 @@ private Target toTarget(ContainerSpec desc) { target.connectUrl = connectUrl; target.alias = Optional.ofNullable(desc.Names.get(0)).orElse(desc.Id); target.labels = desc.Labels; - target.annotations = new Annotations(); - target.annotations - .cryostat() - .putAll( + target.annotations = + new Annotations( + null, Map.of( "REALM", // AnnotationKey.REALM, getRealm(), diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index bbcd6d26a..2110e5105 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -35,6 +35,7 @@ import io.cryostat.targets.TargetConnectionManager; import io.cryostat.util.URIUtil; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.runtime.StartupEvent; import io.vertx.mutiny.core.eventbus.EventBus; import jakarta.annotation.security.RolesAllowed; @@ -76,17 +77,24 @@ public class CustomDiscovery { @Transactional void onStart(@Observes StartupEvent evt) { - DiscoveryNode universe = DiscoveryNode.getUniverse(); - if (DiscoveryNode.getRealm(REALM).isEmpty()) { - DiscoveryPlugin plugin = new DiscoveryPlugin(); - DiscoveryNode node = DiscoveryNode.environment(REALM, BaseNodeType.REALM); - plugin.realm = node; - plugin.builtin = true; - universe.children.add(node); - node.parent = universe; - plugin.persist(); - universe.persist(); - } + QuarkusTransaction.requiringNew() + .run( + () -> { + logger.debugv("Starting {0} client", REALM); + + DiscoveryNode universe = DiscoveryNode.getUniverse(); + if (DiscoveryNode.getRealm(REALM).isEmpty()) { + DiscoveryPlugin plugin = new DiscoveryPlugin(); + DiscoveryNode node = + DiscoveryNode.environment(REALM, BaseNodeType.REALM); + plugin.realm = node; + plugin.builtin = true; + universe.children.add(node); + node.parent = universe; + plugin.persist(); + universe.persist(); + } + }); } @Transactional(rollbackOn = {JvmIdException.class}) @@ -217,9 +225,7 @@ Response doV2Create( credential.ifPresent(c -> c.persist()); target.activeRecordings = new ArrayList<>(); - target.labels = Map.of(); - target.annotations = new Annotations(); - target.annotations.cryostat().putAll(Map.of("REALM", REALM)); + target.annotations = new Annotations(null, Map.of("REALM", REALM)); DiscoveryNode node = DiscoveryNode.target(target, BaseNodeType.JVM); target.discoveryNode = node; diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index 62f5b5269..773b2420d 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -42,6 +42,7 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jwt.proc.BadJWTException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.runtime.ShutdownEvent; import io.quarkus.runtime.StartupEvent; import io.vertx.core.json.JsonObject; @@ -106,36 +107,46 @@ public class Discovery { @Inject Scheduler scheduler; @Inject URIUtil uriUtil; - @Transactional void onStart(@Observes StartupEvent evt) { - // ensure lazily initialized entries are created - DiscoveryNode.getUniverse(); - - DiscoveryPlugin.findAll().list().stream() - .filter(p -> !p.builtin) - .forEach( - plugin -> { - var dataMap = new JobDataMap(); - dataMap.put(PLUGIN_ID_MAP_KEY, plugin.id); - dataMap.put(REFRESH_MAP_KEY, true); - JobDetail jobDetail = - JobBuilder.newJob(RefreshPluginJob.class) - .withIdentity(plugin.id.toString(), JOB_STARTUP) - .usingJobData(dataMap) - .build(); - var trigger = - TriggerBuilder.newTrigger() - .usingJobData(jobDetail.getJobDataMap()) - .startNow() - .withSchedule( - SimpleScheduleBuilder.simpleSchedule() - .withRepeatCount(0)) - .build(); - try { - scheduler.scheduleJob(jobDetail, trigger); - } catch (SchedulerException e) { - logger.warn("Failed to schedule plugin prune job", e); - } + QuarkusTransaction.requiringNew() + .run( + () -> { + // ensure lazily initialized entries are created + DiscoveryNode.getUniverse(); + logger.debugv("Initializing {0} onStart", getClass()); + + DiscoveryPlugin.findAll().list().stream() + .filter(p -> !p.builtin) + .forEach( + plugin -> { + var dataMap = new JobDataMap(); + dataMap.put(PLUGIN_ID_MAP_KEY, plugin.id); + dataMap.put(REFRESH_MAP_KEY, true); + JobDetail jobDetail = + JobBuilder.newJob(RefreshPluginJob.class) + .withIdentity( + plugin.id.toString(), + JOB_STARTUP) + .usingJobData(dataMap) + .build(); + var trigger = + TriggerBuilder.newTrigger() + .usingJobData( + jobDetail.getJobDataMap()) + .startNow() + .withSchedule( + SimpleScheduleBuilder + .simpleSchedule() + .withRepeatCount(0)) + .build(); + try { + scheduler.scheduleJob(jobDetail, trigger); + } catch (SchedulerException e) { + logger.warn( + "Failed to schedule plugin prune job", + e); + } + }); }); } diff --git a/src/main/java/io/cryostat/discovery/DiscoveryNode.java b/src/main/java/io/cryostat/discovery/DiscoveryNode.java index f2f229410..461d411b1 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryNode.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryNode.java @@ -198,7 +198,14 @@ static class Listener { @Inject EventBus bus; @PrePersist - void prePersist(DiscoveryNode node) {} + void prePersist(DiscoveryNode node) { + if (node.children == null) { + node.children = new ArrayList<>(); + } + if (node.labels == null) { + node.labels = new HashMap<>(); + } + } @PostPersist void postPersist(DiscoveryNode node) {} diff --git a/src/main/java/io/cryostat/discovery/JDPDiscovery.java b/src/main/java/io/cryostat/discovery/JDPDiscovery.java index ee6ccd8b9..8033e1b8f 100644 --- a/src/main/java/io/cryostat/discovery/JDPDiscovery.java +++ b/src/main/java/io/cryostat/discovery/JDPDiscovery.java @@ -31,6 +31,7 @@ import io.cryostat.targets.Target.Annotations; import io.cryostat.util.URIUtil; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.runtime.ShutdownEvent; import io.quarkus.runtime.StartupEvent; import io.quarkus.vertx.ConsumeEvent; @@ -65,31 +66,37 @@ static JvmDiscoveryClient produceJvmDiscoveryClient() { @ConfigProperty(name = "cryostat.discovery.jdp.enabled") boolean enabled; - @Transactional void onStart(@Observes StartupEvent evt) { if (!enabled) { return; } - DiscoveryNode universe = DiscoveryNode.getUniverse(); - if (DiscoveryNode.getRealm(REALM).isEmpty()) { - DiscoveryPlugin plugin = new DiscoveryPlugin(); - DiscoveryNode node = DiscoveryNode.environment(REALM, BaseNodeType.REALM); - plugin.realm = node; - plugin.builtin = true; - universe.children.add(node); - node.parent = universe; - plugin.persist(); - universe.persist(); - } - - logger.debug("Starting JDP client"); - jdp.addListener(this); - try { - jdp.start(); - } catch (IOException ioe) { - logger.error("Failure starting JDP client", ioe); - } + QuarkusTransaction.requiringNew() + .run( + () -> { + logger.debugv("Starting {0} client", REALM); + + DiscoveryNode universe = DiscoveryNode.getUniverse(); + if (DiscoveryNode.getRealm(REALM).isEmpty()) { + DiscoveryPlugin plugin = new DiscoveryPlugin(); + DiscoveryNode node = + DiscoveryNode.environment(REALM, BaseNodeType.REALM); + plugin.realm = node; + plugin.builtin = true; + universe.children.add(node); + node.parent = universe; + plugin.persist(); + universe.persist(); + } + + logger.debug("Starting JDP client"); + jdp.addListener(this); + try { + jdp.start(); + } catch (IOException ioe) { + logger.error("Failure starting JDP client", ioe); + } + }); } void onStop(@Observes ShutdownEvent evt) { @@ -130,11 +137,9 @@ void handleJdpEvent(JvmDiscoveryEvent evt) { target.activeRecordings = new ArrayList<>(); target.connectUrl = connectUrl; target.alias = evt.getJvmDescriptor().getMainClass(); - target.labels = Map.of(); - target.annotations = new Annotations(); - target.annotations - .cryostat() - .putAll( + target.annotations = + new Annotations( + null, Map.of( "REALM", // AnnotationKey.REALM, REALM, diff --git a/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java b/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java index 5a57bfe5f..0d1839c2d 100644 --- a/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java +++ b/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java @@ -82,6 +82,8 @@ public class KubeApiDiscovery { @Inject KubeConfig kubeConfig; + @Inject KubernetesClient client; + @Inject EventBus bus; @ConfigProperty(name = "cryostat.discovery.kubernetes.enabled") @@ -111,7 +113,7 @@ protected HashMap> initialize() ns -> { result.put( ns, - client().endpoints() + client.endpoints() .inNamespace(ns) .inform( new EndpointsHandler(), @@ -127,7 +129,6 @@ protected HashMap> initialize() // Priority is set higher than default 0 such that onStart is called first before onAfterStart // This ensures realm node is persisted before initializing informers - @Transactional void onStart(@Observes @Priority(1) StartupEvent evt) { if (!enabled()) { return; @@ -138,22 +139,26 @@ void onStart(@Observes @Priority(1) StartupEvent evt) { return; } - DiscoveryNode universe = DiscoveryNode.getUniverse(); - if (DiscoveryNode.getRealm(REALM).isEmpty()) { - DiscoveryPlugin plugin = new DiscoveryPlugin(); - DiscoveryNode node = DiscoveryNode.environment(REALM, BaseNodeType.REALM); - plugin.realm = node; - plugin.builtin = true; - universe.children.add(node); - node.parent = universe; - plugin.persist(); - universe.persist(); - } - - logger.debugv("Starting {0} client", REALM); + QuarkusTransaction.requiringNew() + .run( + () -> { + logger.debugv("Starting {0} client", REALM); + + DiscoveryNode universe = DiscoveryNode.getUniverse(); + if (DiscoveryNode.getRealm(REALM).isEmpty()) { + DiscoveryPlugin plugin = new DiscoveryPlugin(); + DiscoveryNode node = + DiscoveryNode.environment(REALM, BaseNodeType.REALM); + plugin.realm = node; + plugin.builtin = true; + universe.children.add(node); + node.parent = universe; + plugin.persist(); + universe.persist(); + } + }); } - @Transactional void onAfterStart(@Observes StartupEvent evt) { if (!enabled() || !available()) { return; @@ -190,16 +195,6 @@ boolean available() { return false; } - KubernetesClient client() { - KubernetesClient client; - try { - client = kubeConfig.kubeClient(); - } catch (ConcurrentException e) { - throw new IllegalStateException(e); - } - return client; - } - private boolean isCompatiblePort(EndpointPort port) { return jmxPortNames.orElse(EMPTY_PORT_NAMES).contains(port.getName()) || jmxPortNumbers.orElse(EMPTY_PORT_NUMBERS).contains(port.getPort()); @@ -471,7 +466,7 @@ private Pair queryForNode( } HasMetadata kubeObj = - nodeType.getQueryFunction().apply(client()).apply(namespace).apply(name); + nodeType.getQueryFunction().apply(client).apply(namespace).apply(name); DiscoveryNode node = DiscoveryNode.getNode( @@ -488,12 +483,13 @@ private Pair queryForNode( newNode.nodeType = nodeType.getKind(); newNode.children = new ArrayList<>(); newNode.target = null; - newNode.labels = + Map labels = kubeObj != null ? kubeObj.getMetadata().getLabels() : new HashMap<>(); // Add namespace to label to retrieve node later - newNode.labels.put(DISCOVERY_NAMESPACE_LABEL_KEY, namespace); + labels.put(DISCOVERY_NAMESPACE_LABEL_KEY, namespace); + newNode.labels = labels; return newNode; }); return Pair.of(kubeObj, node); @@ -550,10 +546,6 @@ String getOwnNamespace() { boolean kubeApiAvailable() { return StringUtils.isNotBlank(serviceHost.orElse("")); } - - KubernetesClient kubeClient() throws ConcurrentException { - return kubeClient.get(); - } } private final class EndpointsHandler implements ResourceEventHandler { @@ -638,14 +630,10 @@ public Target toTarget() { target.activeRecordings = new ArrayList<>(); target.connectUrl = connectUrl; target.alias = objRef.getName(); - target.labels = obj != null ? obj.getMetadata().getLabels() : new HashMap<>(); - target.annotations = new Annotations(); - target.annotations - .platform() - .putAll(obj != null ? obj.getMetadata().getAnnotations() : Map.of()); - target.annotations - .cryostat() - .putAll( + target.labels = (obj != null ? obj.getMetadata().getLabels() : new HashMap<>()); + target.annotations = + new Annotations( + obj != null ? obj.getMetadata().getAnnotations() : Map.of(), Map.of( "REALM", REALM, diff --git a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java index 1ec68bb0f..c9ad2c81e 100644 --- a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java +++ b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java @@ -33,7 +33,6 @@ import io.quarkus.cache.CacheResult; import io.quarkus.cache.CompositeCacheKey; import io.quarkus.vertx.ConsumeEvent; -import io.smallrye.common.annotation.Blocking; import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -77,7 +76,6 @@ void onMessage(ExpressionEvent event) { } @Transactional - @Blocking @ConsumeEvent(value = Target.TARGET_JVM_DISCOVERY, blocking = true) void onMessage(TargetDiscovery event) { var target = Target.find("id", event.serviceRef().id).singleResultOptional(); diff --git a/src/main/java/io/cryostat/graphql/ActiveRecordings.java b/src/main/java/io/cryostat/graphql/ActiveRecordings.java index a9877ee20..e8fb5b145 100644 --- a/src/main/java/io/cryostat/graphql/ActiveRecordings.java +++ b/src/main/java/io/cryostat/graphql/ActiveRecordings.java @@ -42,7 +42,6 @@ import io.cryostat.targets.Target; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.api.Nullable; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -64,7 +63,6 @@ public class ActiveRecordings { @ConfigProperty(name = ConfigProperties.CONNECTIONS_FAILED_TIMEOUT) Duration timeout; - @Blocking @Transactional @Mutation @Description( @@ -106,7 +104,6 @@ public List createRecording( return recordings; } - @Blocking @Transactional @Mutation @Description( @@ -138,7 +135,6 @@ public List archiveRecording( return archives; } - @Blocking @Transactional @Mutation @Description( @@ -169,7 +165,6 @@ public List stopRecording( return list; } - @Blocking @Transactional @Mutation @Description( @@ -199,7 +194,6 @@ public List deleteRecording( return list; } - @Blocking @Transactional @Mutation @Description( @@ -222,7 +216,6 @@ public List createSnapshot(@NonNull DiscoveryNodeFilter nodes) return snapshots; } - @Blocking @Transactional @Description("Start a new Flight Recording on the specified Target") public ActiveRecording doStartRecording( @@ -245,7 +238,6 @@ public ActiveRecording doStartRecording( .atMost(timeout); } - @Blocking @Transactional @Description("Create a new Flight Recorder Snapshot on the specified Target") public ActiveRecording doSnapshot(@Source Target target) { @@ -253,7 +245,6 @@ public ActiveRecording doSnapshot(@Source Target target) { return recordingHelper.createSnapshot(fTarget).await().atMost(timeout); } - @Blocking @Transactional @Description("Stop the specified Flight Recording") public ActiveRecording doStop(@Source ActiveRecording recording) throws Exception { @@ -261,7 +252,6 @@ public ActiveRecording doStop(@Source ActiveRecording recording) throws Exceptio return recordingHelper.stopRecording(ar).await().atMost(timeout); } - @Blocking @Transactional @Description("Delete the specified Flight Recording") public ActiveRecording doDelete(@Source ActiveRecording recording) { @@ -269,7 +259,6 @@ public ActiveRecording doDelete(@Source ActiveRecording recording) { return recordingHelper.deleteRecording(ar).await().atMost(timeout); } - @Blocking @Description("Archive the specified Flight Recording") public ArchivedRecording doArchive(@Source ActiveRecording recording) throws Exception { var ar = ActiveRecording.find("id", recording.id).singleResult(); @@ -317,7 +306,6 @@ public RecordingOptions asOptions() { } } - @Blocking @Transactional @Description("Updates the metadata labels for an existing Flight Recording.") public ActiveRecording doPutMetadata( diff --git a/src/main/java/io/cryostat/graphql/ArchivedRecordings.java b/src/main/java/io/cryostat/graphql/ArchivedRecordings.java index 171b355de..941dcfc64 100644 --- a/src/main/java/io/cryostat/graphql/ArchivedRecordings.java +++ b/src/main/java/io/cryostat/graphql/ArchivedRecordings.java @@ -29,7 +29,6 @@ import io.cryostat.recordings.Recordings.Metadata; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.api.Nullable; import jakarta.inject.Inject; import org.eclipse.microprofile.graphql.GraphQLApi; @@ -42,7 +41,6 @@ public class ArchivedRecordings { @Inject RecordingHelper recordingHelper; - @Blocking @Query("archivedRecordings") public TargetNodes.ArchivedRecordings listArchivedRecordings(ArchivedRecordingsFilter filter) { var r = new TargetNodes.ArchivedRecordings(); @@ -78,7 +76,6 @@ public ArchivedRecording doDelete(@Source ArchivedRecording recording) { return recording; } - @Blocking @NonNull public ArchivedRecording doPutMetadata( @Source ArchivedRecording recording, MetadataLabels metadataInput) { diff --git a/src/main/java/io/cryostat/graphql/EnvironmentNodes.java b/src/main/java/io/cryostat/graphql/EnvironmentNodes.java index c5615312a..d70859989 100644 --- a/src/main/java/io/cryostat/graphql/EnvironmentNodes.java +++ b/src/main/java/io/cryostat/graphql/EnvironmentNodes.java @@ -20,7 +20,6 @@ import io.cryostat.discovery.DiscoveryNode; import io.cryostat.graphql.RootNode.DiscoveryNodeFilter; -import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.api.Nullable; import org.eclipse.microprofile.graphql.Description; import org.eclipse.microprofile.graphql.GraphQLApi; @@ -29,7 +28,6 @@ @GraphQLApi public class EnvironmentNodes { - @Blocking @Query("environmentNodes") @Description("Get all environment nodes in the discovery tree with optional filtering") public List environmentNodes(@Nullable DiscoveryNodeFilter filter) { diff --git a/src/main/java/io/cryostat/graphql/RootNode.java b/src/main/java/io/cryostat/graphql/RootNode.java index 4af6eefa8..670bbb99f 100644 --- a/src/main/java/io/cryostat/graphql/RootNode.java +++ b/src/main/java/io/cryostat/graphql/RootNode.java @@ -24,7 +24,6 @@ import io.cryostat.graphql.matchers.LabelSelectorMatcher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.api.Nullable; import org.eclipse.microprofile.graphql.Description; import org.eclipse.microprofile.graphql.GraphQLApi; @@ -34,14 +33,12 @@ @GraphQLApi public class RootNode { - @Blocking @Query("rootNode") @Description("Get the root target discovery node") public DiscoveryNode getRootNode() { return DiscoveryNode.getUniverse(); } - @Blocking @Description( "Get target nodes that are descendants of this node. That is, get the set of leaf nodes" + " from anywhere below this node's subtree.") diff --git a/src/main/java/io/cryostat/graphql/TargetNodes.java b/src/main/java/io/cryostat/graphql/TargetNodes.java index 7c77c68a8..a572d287b 100644 --- a/src/main/java/io/cryostat/graphql/TargetNodes.java +++ b/src/main/java/io/cryostat/graphql/TargetNodes.java @@ -33,7 +33,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import graphql.schema.DataFetchingEnvironment; -import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.api.Context; import io.smallrye.graphql.api.Nullable; import jakarta.inject.Inject; @@ -51,7 +50,6 @@ public class TargetNodes { @Inject RecordingHelper recordingHelper; @Inject TargetConnectionManager connectionManager; - @Blocking @Query("targetNodes") @Description("Get the Target discovery nodes, i.e. the leaf nodes of the discovery tree") public List getTargetNodes(DiscoveryNodeFilter filter) { @@ -69,12 +67,6 @@ public List getTargetNodes(DiscoveryNodeFilter filter) { .toList(); } - // private static Predicate distinctWith(Function fn) { - // Set observed = ConcurrentHashMap.newKeySet(); - // return t -> observed.add(fn.apply(t)); - // } - - @Blocking @Transactional public ActiveRecordings activeRecordings( @Source Target target, @Nullable ActiveRecordingsFilter filter) { @@ -90,7 +82,6 @@ public ActiveRecordings activeRecordings( return recordings; } - @Blocking public ArchivedRecordings archivedRecordings( @Source Target target, @Nullable ArchivedRecordingsFilter filter) { var fTarget = Target.getTargetById(target.id); @@ -105,7 +96,6 @@ public ArchivedRecordings archivedRecordings( return recordings; } - @Blocking @Transactional @Description("Get the active and archived recordings belonging to this target") public Recordings recordings(@Source Target target, Context context) { @@ -133,7 +123,6 @@ public Recordings recordings(@Source Target target, Context context) { return recordings; } - @Blocking @Description("Get live MBean metrics snapshot from the specified Target") public MBeanMetrics mbeanMetrics(@Source Target target) { var fTarget = Target.getTargetById(target.id); diff --git a/src/main/java/io/cryostat/recordings/RecordingOptionsBuilderFactory.java b/src/main/java/io/cryostat/recordings/RecordingOptionsBuilderFactory.java index 9b0dde250..ad77024e9 100644 --- a/src/main/java/io/cryostat/recordings/RecordingOptionsBuilderFactory.java +++ b/src/main/java/io/cryostat/recordings/RecordingOptionsBuilderFactory.java @@ -21,7 +21,6 @@ import io.cryostat.targets.Target; import io.cryostat.targets.TargetConnectionManager; -import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -31,7 +30,6 @@ public class RecordingOptionsBuilderFactory { @Inject RecordingOptionsCustomizerFactory customizerFactory; @Inject TargetConnectionManager connectionManager; - @Blocking public RecordingOptionsBuilder create(Target target) throws QuantityConversionException { return connectionManager.executeConnectedTask( target, diff --git a/src/main/java/io/cryostat/rules/RuleService.java b/src/main/java/io/cryostat/rules/RuleService.java index 7b2e2b3f1..daa1151cf 100644 --- a/src/main/java/io/cryostat/rules/RuleService.java +++ b/src/main/java/io/cryostat/rules/RuleService.java @@ -42,7 +42,6 @@ import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.runtime.StartupEvent; import io.quarkus.vertx.ConsumeEvent; -import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.infrastructure.Infrastructure; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; @@ -77,11 +76,14 @@ public class RuleService { private final List jobs = new CopyOnWriteArrayList<>(); - @Transactional - @Blocking void onStart(@Observes StartupEvent ev) { logger.trace("RuleService started"); - Rule.streamAll().filter(r -> r.enabled).forEach(this::applyRuleToMatchingTargets); + QuarkusTransaction.joiningExisting() + .run( + () -> + Rule.streamAll() + .filter(r -> r.enabled) + .forEach(this::applyRuleToMatchingTargets)); } @ConsumeEvent(value = Target.TARGET_JVM_DISCOVERY, blocking = true) diff --git a/src/main/java/io/cryostat/targets/AgentConnection.java b/src/main/java/io/cryostat/targets/AgentConnection.java index 27cac8481..e23c26b01 100644 --- a/src/main/java/io/cryostat/targets/AgentConnection.java +++ b/src/main/java/io/cryostat/targets/AgentConnection.java @@ -38,7 +38,6 @@ import io.cryostat.core.templates.TemplateService; import io.cryostat.events.S3TemplateService; -import io.smallrye.common.annotation.Blocking; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.jboss.logging.Logger; @@ -57,7 +56,6 @@ class AgentConnection implements JFRConnection { @Override public void close() throws Exception {} - @Blocking @Override public void connect() throws ConnectionException { if (!client.ping().await().atMost(client.getTimeout())) { @@ -129,7 +127,6 @@ public boolean isConnected() { return true; } - @Blocking @Override public MBeanMetrics getMBeanMetrics() throws ConnectionException, diff --git a/src/main/java/io/cryostat/targets/AgentJFRService.java b/src/main/java/io/cryostat/targets/AgentJFRService.java index b54949aa9..1d5b88435 100644 --- a/src/main/java/io/cryostat/targets/AgentJFRService.java +++ b/src/main/java/io/cryostat/targets/AgentJFRService.java @@ -51,7 +51,6 @@ import io.cryostat.core.templates.TemplateService; import io.cryostat.core.templates.TemplateType; -import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; import io.vertx.mutiny.core.buffer.Buffer; import org.jboss.logging.Logger; @@ -82,7 +81,6 @@ public String getVersion() { return "agent"; // TODO } - @Blocking @Override public void close(IRecordingDescriptor descriptor) throws FlightRecorderException { client.deleteRecording(descriptor.getId()).await().atMost(client.getTimeout()); @@ -93,7 +91,6 @@ public void enable() throws FlightRecorderException { throw new UnimplementedException(); } - @Blocking @Override public Collection getAvailableEventTypes() throws FlightRecorderException { @@ -106,13 +103,11 @@ public Map> getAvailableRecordingOptions() return KnownRecordingOptions.DESCRIPTORS_BY_KEY_V2; } - @Blocking @Override public List getAvailableRecordings() throws FlightRecorderException { return client.activeRecordings().await().atMost(client.getTimeout()); } - @Blocking @Override public IConstrainedMap getCurrentEventTypeSettings() throws FlightRecorderException { @@ -138,13 +133,11 @@ public IConstrainedMap getRecordingOptions(IRecordingDescriptor descript return new DefaultValueMap<>(Map.of()); } - @Blocking @Override public List getServerTemplates() throws FlightRecorderException { return client.eventTemplates().await().atMost(client.getTimeout()); } - @Blocking @Override public IRecordingDescriptor getSnapshotRecording() throws FlightRecorderException { return client.startSnapshot().await().atMost(client.getTimeout()); @@ -161,7 +154,6 @@ public boolean isEnabled() { return true; } - @Blocking @Override public InputStream openStream(IRecordingDescriptor descriptor, boolean removeOnClose) throws FlightRecorderException { @@ -194,7 +186,6 @@ public IRecordingDescriptor start( throw new UnimplementedException(); } - @Blocking @Override public void stop(IRecordingDescriptor descriptor) throws FlightRecorderException { client.stopRecording(descriptor.getId()).await().atMost(client.getTimeout()); @@ -207,7 +198,6 @@ public void updateEventOptions( throw new UnimplementedException(); } - @Blocking @Override public void updateRecordingOptions( IRecordingDescriptor descriptor, IConstrainedMap newSettings) @@ -217,7 +207,6 @@ public void updateRecordingOptions( .atMost(client.getTimeout()); } - @Blocking @Override public IRecordingDescriptor start(IConstrainedMap recordingOptions, String template) throws FlightRecorderException, @@ -256,7 +245,6 @@ public IRecordingDescriptor start(IConstrainedMap recordingOptions, Stri return client.startRecording(req).await().atMost(client.getTimeout()); } - @Blocking @Override public IRecordingDescriptor start(IConstrainedMap recordingOptions, Template template) throws io.cryostat.core.FlightRecorderException, diff --git a/src/main/java/io/cryostat/targets/Target.java b/src/main/java/io/cryostat/targets/Target.java index 89f902940..c4c95a4c9 100644 --- a/src/main/java/io/cryostat/targets/Target.java +++ b/src/main/java/io/cryostat/targets/Target.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -46,7 +47,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.quarkus.vertx.ConsumeEvent; -import io.smallrye.common.annotation.Blocking; import io.vertx.mutiny.core.eventbus.EventBus; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -170,14 +170,13 @@ public static record Annotations(Map platform, Map(), new HashMap<>()); + this(null, null); } public Map merged() { - Map merged = new HashMap<>(); - cryostat().entrySet().forEach((e) -> merged.put(e.getKey(), e.getValue())); - merged.putAll(platform()); - return merged; + Map merged = new HashMap<>(cryostat); + merged.putAll(platform); + return Collections.unmodifiableMap(merged); } } @@ -322,7 +321,6 @@ void onMessage(TargetDiscovery event) { @ConsumeEvent(value = Credential.CREDENTIALS_STORED, blocking = true) @Transactional - @Blocking void updateCredential(Credential credential) { Target.stream("#Target.unconnected") .forEach( @@ -341,7 +339,6 @@ void updateCredential(Credential credential) { }); } - @Blocking @PrePersist void prePersist(Target target) { if (StringUtils.isBlank(target.alias)) { @@ -352,6 +349,16 @@ void prePersist(Target target) { target.alias = encodedAlias; } + if (target.labels == null) { + target.labels = new HashMap<>(); + } + if (target.annotations == null) { + target.annotations = new Annotations(); + } + if (target.activeRecordings == null) { + target.activeRecordings = new ArrayList<>(); + } + try { if (StringUtils.isBlank(target.jvmId)) { updateTargetJvmId(target, null); @@ -361,7 +368,6 @@ void prePersist(Target target) { } } - @Blocking private void updateTargetJvmId(Target t, Credential credential) { try { t.jvmId = diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 1c45fc6f0..720a64c0c 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,6 +1,9 @@ quarkus.http.host=localhost quarkus.smallrye-openapi.info-title=Cryostat API (development) +quarkus.swagger-ui.enable=true +quarkus.smallrye-openapi.enable=true +quarkus.smallrye-openapi.management.enabled=true quarkus.http.cors=true # quarkus.http.cors.origins=http://localhost:9000,http://0.0.0.0:9000 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65f2d373d..8dc99f2e1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -57,6 +57,8 @@ quarkus.http.limits.max-body-size=1G quarkus.vertx.prefer-native-transport=true quarkus.smallrye-openapi.path=/api +quarkus.swagger-ui.enable=false +quarkus.smallrye-openapi.management.enabled=false quarkus.smallrye-openapi.info-title=Cryostat API quarkus.smallrye-openapi.info-version=${quarkus.application.version} quarkus.smallrye-openapi.info-description=Cloud-Native JDK Flight Recorder