diff --git a/pom.xml b/pom.xml
index 3c0229222..ebbdae0a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -271,6 +271,10 @@
io.quarkus
quarkus-flyway
+
+ io.quarkus
+ quarkus-jfr
+
io.quarkus
quarkus-junit5
diff --git a/schema/openapi.yaml b/schema/openapi.yaml
index 7ccd335f9..d77b91598 100644
--- a/schema/openapi.yaml
+++ b/schema/openapi.yaml
@@ -634,6 +634,7 @@ components:
enum:
- TARGET
- CUSTOM
+ - PRESET
type: string
UUID:
format: uuid
@@ -1164,7 +1165,9 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/Template'
+ items:
+ $ref: '#/components/schemas/Template'
+ type: array
description: OK
"401":
description: Not Authorized
@@ -1217,6 +1220,59 @@ paths:
- SecurityScheme: []
tags:
- Event Templates
+ /api/v4/event_templates/{templateType}:
+ get:
+ parameters:
+ - in: path
+ name: templateType
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ items:
+ $ref: '#/components/schemas/Template'
+ type: array
+ description: OK
+ "401":
+ description: Not Authorized
+ "403":
+ description: Not Allowed
+ security:
+ - SecurityScheme: []
+ tags:
+ - Event Templates
+ /api/v4/event_templates/{templateType}/{templateName}:
+ get:
+ parameters:
+ - in: path
+ name: templateName
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: templateType
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/xml:
+ schema:
+ type: string
+ description: OK
+ "401":
+ description: Not Authorized
+ "403":
+ description: Not Allowed
+ security:
+ - SecurityScheme: []
+ tags:
+ - Event Templates
/api/v4/grafana/{encodedKey}:
post:
parameters:
diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm
index 6d68137c4..1973416a0 100644
--- a/src/main/docker/Dockerfile.jvm
+++ b/src/main/docker/Dockerfile.jvm
@@ -92,6 +92,7 @@ ENTRYPOINT [ "/deployments/app/entrypoint.bash", "/opt/jboss/container/java/run/
# We make distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 src/main/docker/include/cryostat.jfc /usr/lib/jvm/jre/lib/jfr/
+COPY --chown=185 src/main/docker/include/template_presets/* /opt/cryostat.d/presets.d/
COPY --chown=185 src/main/docker/include/genpass.bash /deployments/app/
COPY --chown=185 src/main/docker/include/entrypoint.bash /deployments/app/
COPY --chown=185 src/main/docker/include/truststore-setup.bash /deployments/app/
diff --git a/src/main/docker/include/template_presets/quarkus.jfc b/src/main/docker/include/template_presets/quarkus.jfc
new file mode 100644
index 000000000..30e761f50
--- /dev/null
+++ b/src/main/docker/include/template_presets/quarkus.jfc
@@ -0,0 +1,12 @@
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
diff --git a/src/main/java/io/cryostat/ConfigProperties.java b/src/main/java/io/cryostat/ConfigProperties.java
index e92832b53..af0cf153a 100644
--- a/src/main/java/io/cryostat/ConfigProperties.java
+++ b/src/main/java/io/cryostat/ConfigProperties.java
@@ -54,7 +54,8 @@ public class ConfigProperties {
"storage.transient-archives.enabled";
public static final String STORAGE_TRANSIENT_ARCHIVES_TTL = "storage.transient-archives.ttl";
- public static final String TEMPLATES_DIR = "templates-dir";
+ public static final String CUSTOM_TEMPLATES_DIR = "templates-dir";
+ public static final String PRESET_TEMPLATES_DIR = "preset-templates-dir";
public static final String SSL_TRUSTSTORE_DIR = "ssl.truststore.dir";
public static final String URI_RANGE = "cryostat.target.uri-range";
diff --git a/src/main/java/io/cryostat/events/EventTemplates.java b/src/main/java/io/cryostat/events/EventTemplates.java
index 5beac994b..7a7a95a7b 100644
--- a/src/main/java/io/cryostat/events/EventTemplates.java
+++ b/src/main/java/io/cryostat/events/EventTemplates.java
@@ -21,6 +21,7 @@
import io.cryostat.core.FlightRecorderException;
import io.cryostat.core.templates.MutableTemplateService.InvalidXmlException;
+import io.cryostat.core.templates.TemplateService;
import io.cryostat.libcryostat.sys.FileSystem;
import io.cryostat.libcryostat.templates.InvalidEventTemplateException;
import io.cryostat.libcryostat.templates.Template;
@@ -35,7 +36,9 @@
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.Logger;
@@ -60,6 +63,7 @@ public class EventTemplates {
@Inject FileSystem fs;
@Inject TargetTemplateService.Factory targetTemplateServiceFactory;
@Inject S3TemplateService customTemplateService;
+ @Inject PresetTemplateService presetTemplateService;
@Inject Logger logger;
@GET
@@ -69,21 +73,47 @@ public List listTemplates() throws Exception {
var list = new ArrayList();
list.add(ALL_EVENTS_TEMPLATE);
list.addAll(customTemplateService.getTemplates());
+ list.addAll(presetTemplateService.getTemplates());
return list;
}
@GET
+ @Path("/{templateType}")
@Blocking
@RolesAllowed("read")
- public Template getTemplate(@RestPath String templateName)
+ public List getTemplates(@RestPath String templateType)
throws IOException, FlightRecorderException {
- if (StringUtils.isBlank(templateName)) {
- throw new BadRequestException();
+ TemplateType tt = TemplateType.valueOf(templateType);
+ switch (tt) {
+ case CUSTOM:
+ return customTemplateService.getTemplates();
+ case PRESET:
+ return presetTemplateService.getTemplates();
+ default:
+ throw new BadRequestException();
+ }
+ }
+
+ @GET
+ @Path("/{templateType}/{templateName}")
+ @Blocking
+ @RolesAllowed("read")
+ @Produces(MediaType.APPLICATION_XML)
+ public String getTemplate(@RestPath String templateType, @RestPath String templateName)
+ throws IOException, FlightRecorderException {
+ TemplateType tt = TemplateType.valueOf(templateType);
+ TemplateService svc;
+ switch (tt) {
+ case CUSTOM:
+ svc = customTemplateService;
+ break;
+ case PRESET:
+ svc = presetTemplateService;
+ break;
+ default:
+ throw new BadRequestException();
}
- return customTemplateService.getTemplates().stream()
- .filter(t -> t.getName().equals(templateName))
- .findFirst()
- .orElseThrow();
+ return svc.getXml(templateName, tt).orElseThrow(() -> new NotFoundException());
}
@POST
diff --git a/src/main/java/io/cryostat/events/PresetTemplateService.java b/src/main/java/io/cryostat/events/PresetTemplateService.java
new file mode 100644
index 000000000..8c2c0a1cf
--- /dev/null
+++ b/src/main/java/io/cryostat/events/PresetTemplateService.java
@@ -0,0 +1,209 @@
+/*
+ * 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.events;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.openjdk.jmc.common.unit.IConstrainedMap;
+import org.openjdk.jmc.common.unit.SimpleConstrainedMap;
+import org.openjdk.jmc.common.unit.UnitLookup;
+import org.openjdk.jmc.flightrecorder.configuration.events.EventConfiguration;
+import org.openjdk.jmc.flightrecorder.configuration.events.EventOptionID;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.JFCGrammar;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLAttributeInstance;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLModel;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLTagInstance;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLValidationResult;
+
+import io.cryostat.ConfigProperties;
+import io.cryostat.core.FlightRecorderException;
+import io.cryostat.core.templates.TemplateService;
+import io.cryostat.libcryostat.sys.FileSystem;
+import io.cryostat.libcryostat.templates.InvalidEventTemplateException;
+import io.cryostat.libcryostat.templates.Template;
+import io.cryostat.libcryostat.templates.TemplateType;
+
+import io.quarkus.runtime.StartupEvent;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+import org.jsoup.Jsoup;
+import org.jsoup.parser.Parser;
+
+@ApplicationScoped
+public class PresetTemplateService implements TemplateService {
+
+ @ConfigProperty(name = ConfigProperties.PRESET_TEMPLATES_DIR)
+ Path dir;
+
+ @Inject Logger logger;
+ @Inject FileSystem fs;
+
+ private final Map map = new HashMap<>();
+
+ void onStart(@Observes StartupEvent evt) throws IOException {
+ if (!checkDir()) {
+ return;
+ }
+ Files.walk(dir)
+ .filter(Files::isRegularFile)
+ .filter(Files::isReadable)
+ .forEach(
+ p -> {
+ try {
+ Template template = convertObject(p);
+ map.put(template.getName(), p);
+ } catch (InvalidEventTemplateException
+ | IOException
+ | ParseException e) {
+ logger.error(e);
+ }
+ });
+ }
+
+ private boolean checkDir() {
+ return Files.exists(dir)
+ && Files.isReadable(dir)
+ && Files.isExecutable(dir)
+ && Files.isDirectory(dir);
+ }
+
+ @Override
+ public Optional> getEvents(
+ String templateName, TemplateType unused) throws FlightRecorderException {
+ try (var stream = getModel(templateName)) {
+ return Optional.of(
+ new EventConfiguration(parseXml(stream))
+ .getEventOptions(
+ new SimpleConstrainedMap<>(
+ UnitLookup.PLAIN_TEXT.getPersister())));
+ } catch (IOException | ParseException e) {
+ logger.error(e);
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public List getTemplates() throws FlightRecorderException {
+ return getObjects().stream()
+ .map(
+ t -> {
+ try {
+ return convertObject(t);
+ } catch (InvalidEventTemplateException
+ | ParseException
+ | IOException e) {
+ logger.error(e);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .toList();
+ }
+
+ @Override
+ public Optional getXml(String templateName, TemplateType unused)
+ throws FlightRecorderException {
+ try (var stream = getModel(templateName)) {
+ return Optional.of(
+ Jsoup.parse(stream, StandardCharsets.UTF_8.name(), "", Parser.xmlParser())
+ .outerHtml());
+ } catch (IOException e) {
+ logger.error(e);
+ return Optional.empty();
+ }
+ }
+
+ private InputStream getModel(String name) throws IOException {
+ return Files.newInputStream(map.get(name));
+ }
+
+ private Collection getObjects() {
+ return map.values();
+ }
+
+ private Template convertObject(Path file)
+ throws InvalidEventTemplateException, IOException, ParseException {
+ try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file))) {
+ XMLModel model = parseXml(bis);
+ return createTemplate(model);
+ }
+ }
+
+ private Template createTemplate(XMLModel model) throws IOException, ParseException {
+ XMLTagInstance configuration = model.getRoot();
+ XMLAttributeInstance labelAttr = null;
+ for (XMLAttributeInstance attr : configuration.getAttributeInstances()) {
+ if (attr.getAttribute().getName().equals("label")) {
+ labelAttr = attr;
+ break;
+ }
+ }
+
+ if (labelAttr == null) {
+ throw new IllegalArgumentException(
+ new InvalidEventTemplateException(
+ "Template has no configuration label attribute"));
+ }
+
+ String templateName = labelAttr.getExplicitValue().replaceAll("[\\W]+", "_");
+
+ XMLTagInstance root = model.getRoot();
+ root.setValue(JFCGrammar.ATTRIBUTE_LABEL_MANDATORY, templateName);
+
+ String description = getAttributeValue(root, "description");
+ String provider = getAttributeValue(root, "provider");
+
+ return new Template(templateName, description, provider, TemplateType.PRESET);
+ }
+
+ private String getAttributeValue(XMLTagInstance node, String valueKey) {
+ return node.getAttributeInstances().stream()
+ .filter(i -> Objects.equals(valueKey, i.getAttribute().getName()))
+ .map(i -> i.getValue())
+ .findFirst()
+ .get();
+ }
+
+ private XMLModel parseXml(InputStream inputStream) throws IOException, ParseException {
+ try (inputStream) {
+ var model = EventConfiguration.createModel(inputStream);
+ model.checkErrors();
+
+ for (XMLValidationResult result : model.getResults()) {
+ if (result.isError()) {
+ throw new IllegalArgumentException(
+ new InvalidEventTemplateException(result.getText()));
+ }
+ }
+ return model;
+ }
+ }
+}
diff --git a/src/main/java/io/cryostat/events/S3TemplateService.java b/src/main/java/io/cryostat/events/S3TemplateService.java
index f33c2f4d8..35113f181 100644
--- a/src/main/java/io/cryostat/events/S3TemplateService.java
+++ b/src/main/java/io/cryostat/events/S3TemplateService.java
@@ -82,7 +82,7 @@ public class S3TemplateService implements MutableTemplateService {
@ConfigProperty(name = ConfigProperties.AWS_BUCKET_NAME_EVENT_TEMPLATES)
String bucket;
- @ConfigProperty(name = ConfigProperties.TEMPLATES_DIR)
+ @ConfigProperty(name = ConfigProperties.CUSTOM_TEMPLATES_DIR)
Path dir;
@Inject S3Client storage;
diff --git a/src/main/java/io/cryostat/events/TargetEventTemplates.java b/src/main/java/io/cryostat/events/TargetEventTemplates.java
index b9d2592b4..65f97eb2b 100644
--- a/src/main/java/io/cryostat/events/TargetEventTemplates.java
+++ b/src/main/java/io/cryostat/events/TargetEventTemplates.java
@@ -20,9 +20,11 @@
import java.util.Comparator;
import java.util.List;
+import io.cryostat.core.templates.TemplateService;
import io.cryostat.libcryostat.templates.Template;
import io.cryostat.libcryostat.templates.TemplateType;
import io.cryostat.targets.Target;
+import io.cryostat.targets.TargetConnectionManager;
import io.smallrye.common.annotation.Blocking;
import jakarta.annotation.security.RolesAllowed;
@@ -50,6 +52,8 @@ public class TargetEventTemplates {
@Inject TargetTemplateService.Factory targetTemplateServiceFactory;
@Inject S3TemplateService customTemplateService;
+ @Inject PresetTemplateService presetTemplateService;
+ @Inject TargetConnectionManager connectionManager;
@Inject Logger logger;
@GET
@@ -65,6 +69,7 @@ public List listTargetTemplates(@RestPath long id) throws Exception {
.thenComparing(Comparator.comparing(Template::getProvider));
list.addAll(targetTemplateServiceFactory.create(target).getTemplates());
list.addAll(customTemplateService.getTemplates());
+ list.addAll(presetTemplateService.getTemplates());
Collections.sort(list, comparator);
return list;
}
@@ -82,24 +87,20 @@ public String getTargetTemplate(
throw new BadRequestException();
}
Target target = Target.find("id", id).singleResult();
- String xml;
+ TemplateService svc;
switch (templateType) {
case TARGET:
- xml =
- targetTemplateServiceFactory
- .create(target)
- .getXml(templateName, templateType)
- .orElseThrow(() -> new NotFoundException());
+ svc = targetTemplateServiceFactory.create(target);
break;
case CUSTOM:
- xml =
- customTemplateService
- .getXml(templateName, templateType)
- .orElseThrow(() -> new NotFoundException());
+ svc = customTemplateService;
+ break;
+ case PRESET:
+ svc = presetTemplateService;
break;
default:
throw new BadRequestException();
}
- return xml;
+ return svc.getXml(templateName, templateType).orElseThrow(() -> new NotFoundException());
}
}
diff --git a/src/main/java/io/cryostat/recordings/RecordingHelper.java b/src/main/java/io/cryostat/recordings/RecordingHelper.java
index dd9912491..1ab5f9ed3 100644
--- a/src/main/java/io/cryostat/recordings/RecordingHelper.java
+++ b/src/main/java/io/cryostat/recordings/RecordingHelper.java
@@ -60,6 +60,7 @@
import io.cryostat.core.FlightRecorderException;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.events.EventTemplates;
+import io.cryostat.events.PresetTemplateService;
import io.cryostat.events.S3TemplateService;
import io.cryostat.events.TargetTemplateService;
import io.cryostat.libcryostat.sys.Clock;
@@ -153,6 +154,7 @@ public class RecordingHelper {
@Inject EventOptionsBuilder.Factory eventOptionsBuilderFactory;
@Inject TargetTemplateService.Factory targetTemplateServiceFactory;
@Inject S3TemplateService customTemplateService;
+ @Inject PresetTemplateService presetTemplateService;
@Inject Scheduler scheduler;
@Inject
@@ -377,6 +379,15 @@ private Uni startRecordingImpl(
IConstrainedMap recordingOptions = optionsBuilder.build();
switch (template.getType()) {
+ case PRESET:
+ return conn.getService()
+ .start(
+ recordingOptions,
+ presetTemplateService
+ .getXml(
+ template.getName(),
+ TemplateType.CUSTOM)
+ .orElseThrow());
case CUSTOM:
return conn.getService()
.start(
@@ -631,7 +642,17 @@ public Template getPreferredTemplate(
return Optional.empty();
}
};
-
+ Supplier> preset =
+ () -> {
+ try {
+ return presetTemplateService.getTemplates().stream()
+ .filter(t -> t.getName().equals(templateName))
+ .findFirst();
+ } catch (FlightRecorderException e) {
+ logger.warn(e);
+ return Optional.empty();
+ }
+ };
Supplier> remote =
() -> {
try {
@@ -645,6 +666,7 @@ public Template getPreferredTemplate(
};
if (templateType == null) {
return custom.get()
+ .or(() -> preset.get())
.or(() -> remote.get())
.orElseThrow(
() ->
@@ -658,8 +680,11 @@ public Template getPreferredTemplate(
return remote.get().orElseThrow();
case CUSTOM:
return custom.get().orElseThrow();
+ case PRESET:
+ return preset.get().orElseThrow();
default:
return custom.get()
+ .or(() -> preset.get())
.or(() -> remote.get())
.orElseThrow(
() ->
diff --git a/src/main/java/io/cryostat/targets/AgentJFRService.java b/src/main/java/io/cryostat/targets/AgentJFRService.java
index c120df6cd..37d9db91c 100644
--- a/src/main/java/io/cryostat/targets/AgentJFRService.java
+++ b/src/main/java/io/cryostat/targets/AgentJFRService.java
@@ -257,7 +257,7 @@ public IRecordingDescriptor start(IConstrainedMap recordingOptions, Temp
QuantityConversionException,
EventOptionException,
EventTypeException {
- if (template.getType().equals(TemplateType.CUSTOM)) {
+ if (!template.getType().equals(TemplateType.TARGET)) {
return start(
recordingOptions,
templateService.getXml(template.getName(), template.getType()).orElseThrow());
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ba4ecb4f8..0ae9da059 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -48,6 +48,7 @@ cryostat.target.uri-range=PUBLIC
conf-dir=/opt/cryostat.d
templates-dir=${conf-dir}/templates.d
+preset-templates-dir=${conf-dir}/presets.d
ssl.truststore=${conf-dir}/truststore.p12
ssl.truststore.dir=/truststore
ssl.truststore.pass-file=${conf-dir}/truststore.pass
diff --git a/src/test/java/io/cryostat/AbstractTransactionalTestBase.java b/src/test/java/io/cryostat/AbstractTransactionalTestBase.java
index 2c7e18493..6c6537570 100644
--- a/src/test/java/io/cryostat/AbstractTransactionalTestBase.java
+++ b/src/test/java/io/cryostat/AbstractTransactionalTestBase.java
@@ -17,12 +17,20 @@
import static io.restassured.RestAssured.given;
+import java.time.Duration;
+
+import io.cryostat.util.HttpStatusCodeIdentifier;
+
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import org.apache.http.client.utils.URLEncodedUtils;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.flywaydb.core.Flyway;
+import org.jboss.logging.Logger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
public abstract class AbstractTransactionalTestBase {
@@ -31,8 +39,48 @@ public abstract class AbstractTransactionalTestBase {
URLEncodedUtils.formatSegments(SELF_JMX_URL).substring(1);
public static final String SELFTEST_ALIAS = "selftest";
+ @ConfigProperty(name = "storage.buckets.archives.name")
+ String archivesBucket;
+
+ @ConfigProperty(name = "test.storage.timeout", defaultValue = "5m")
+ Duration storageTimeout;
+
+ @ConfigProperty(name = "test.storage.retry", defaultValue = "5s")
+ Duration storageRetry;
+
+ @Inject Logger logger;
+ @Inject S3Client storage;
@Inject Flyway flyway;
+ @BeforeEach
+ void waitForStorage() throws InterruptedException {
+ long totalTime = 0;
+ while (!bucketExists(archivesBucket)) {
+ long start = System.nanoTime();
+ Thread.sleep(storageRetry.toMillis());
+ long elapsed = System.nanoTime() - start;
+ totalTime += elapsed;
+ if (Duration.ofNanos(totalTime).compareTo(storageTimeout) > 0) {
+ throw new IllegalStateException("Storage took too long to become ready");
+ }
+ }
+ }
+
+ private boolean bucketExists(String bucket) {
+ boolean exists = false;
+ try {
+ exists =
+ HttpStatusCodeIdentifier.isSuccessCode(
+ storage.headBucket(HeadBucketRequest.builder().bucket(bucket).build())
+ .sdkHttpResponse()
+ .statusCode());
+ logger.debugv("Storage bucket \"{0}\" exists? {1}", bucket, exists);
+ } catch (Exception e) {
+ logger.warn(e);
+ }
+ return exists;
+ }
+
@BeforeEach
void migrate() {
flyway.migrate();
diff --git a/src/test/java/io/cryostat/events/EventTemplatesTest.java b/src/test/java/io/cryostat/events/EventTemplatesTest.java
index a8a170431..c5d31ed88 100644
--- a/src/test/java/io/cryostat/events/EventTemplatesTest.java
+++ b/src/test/java/io/cryostat/events/EventTemplatesTest.java
@@ -48,6 +48,66 @@ void testListNone() {
.body("[0].provider", Matchers.equalTo("Cryostat"));
}
+ @Test
+ void testListTargets() {
+ given().log()
+ .all()
+ .when()
+ .get("/TARGET")
+ .then()
+ .log()
+ .all()
+ .and()
+ .assertThat()
+ .statusCode(400);
+ }
+
+ @Test
+ void testListNonsense() {
+ given().log()
+ .all()
+ .when()
+ .get("/NONSENSE")
+ .then()
+ .log()
+ .all()
+ .and()
+ .assertThat()
+ .statusCode(400);
+ }
+
+ @Test
+ void testListCustoms() {
+ given().log()
+ .all()
+ .when()
+ .get("/CUSTOM")
+ .then()
+ .log()
+ .all()
+ .and()
+ .assertThat()
+ .statusCode(200)
+ .contentType(ContentType.JSON)
+ .body("size()", Matchers.equalTo(0));
+ }
+
+ @Test
+ void testListPresets() {
+ given().log()
+ .all()
+ .when()
+ .get("/PRESET")
+ .then()
+ .log()
+ .all()
+ .and()
+ .assertThat()
+ .statusCode(200)
+ .contentType(ContentType.JSON)
+ .body("size()", Matchers.equalTo(0));
+ }
+
@Test
void testDeleteInvalid() {
given().log()
diff --git a/src/test/java/itest/PresetTemplatesIT.java b/src/test/java/itest/PresetTemplatesIT.java
new file mode 100644
index 000000000..12b69f65e
--- /dev/null
+++ b/src/test/java/itest/PresetTemplatesIT.java
@@ -0,0 +1,84 @@
+/*
+ * 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 itest;
+
+import java.io.File;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.openjdk.jmc.flightrecorder.configuration.events.EventConfiguration;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLAttributeInstance;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLModel;
+import org.openjdk.jmc.flightrecorder.configuration.model.xml.XMLTagInstance;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.JsonArray;
+import io.vertx.ext.web.client.HttpRequest;
+import itest.bases.StandardSelfTest;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@QuarkusIntegrationTest
+public class PresetTemplatesIT extends StandardSelfTest {
+
+ @Test
+ public void shouldListPresetTemplates() throws Exception {
+ CompletableFuture future = new CompletableFuture<>();
+ HttpRequest req = webClient.get("/api/v4/event_templates/PRESET");
+ req.send(
+ ar -> {
+ if (ar.failed()) {
+ future.completeExceptionally(ar.cause());
+ return;
+ }
+ future.complete(ar.result().bodyAsJsonArray());
+ });
+ JsonArray response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ MatcherAssert.assertThat(response.size(), Matchers.equalTo(1));
+ }
+
+ @Test
+ public void shouldHavePresetQuarkusTemplate() throws Exception {
+ String url =
+ String.format("/api/v4/event_templates/PRESET/Quarkus", getSelfReferenceTargetId());
+ File file =
+ downloadFile(url, "quarkus", ".jfc")
+ .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .toFile();
+
+ XMLModel model = EventConfiguration.createModel(file);
+ model.checkErrors();
+
+ Assertions.assertFalse(model.hasErrors());
+
+ XMLTagInstance configuration = model.getRoot();
+ XMLAttributeInstance labelAttr = null;
+ for (XMLAttributeInstance attr : configuration.getAttributeInstances()) {
+ if (attr.getAttribute().getName().equals("label")) {
+ labelAttr = attr;
+ break;
+ }
+ }
+
+ MatcherAssert.assertThat(labelAttr, Matchers.notNullValue());
+
+ String templateName = labelAttr.getExplicitValue();
+ MatcherAssert.assertThat(templateName, Matchers.equalTo("Quarkus"));
+ }
+}
diff --git a/src/test/java/itest/RulesPostJsonIT.java b/src/test/java/itest/RulesPostJsonIT.java
index fa136dcad..2971d9901 100644
--- a/src/test/java/itest/RulesPostJsonIT.java
+++ b/src/test/java/itest/RulesPostJsonIT.java
@@ -34,6 +34,7 @@
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@@ -85,6 +86,7 @@ void testAddRuleThrowsWhenJsonAttributesNull() throws Exception {
@Test
@Order(2)
+ @Disabled("https://github.com/quarkusio/quarkus/issues/44976")
void testAddRuleThrowsWhenMimeUnsupported() throws Exception {
CompletableFuture response = new CompletableFuture<>();
@@ -107,6 +109,7 @@ void testAddRuleThrowsWhenMimeUnsupported() throws Exception {
@Test
@Order(3)
+ @Disabled("https://github.com/quarkusio/quarkus/issues/44976")
void testAddRuleThrowsWhenMimeInvalid() throws Exception {
CompletableFuture response = new CompletableFuture<>();