diff --git a/pom.xml b/pom.xml index fa4fd3d78..a5dbdc63f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,9 +58,9 @@ 1.23.0 4.5 3.3.1 + 2 3.3.1 - - + ${surefire.rerunFailingTestsCount} diff --git a/schema/openapi.yaml b/schema/openapi.yaml index 4f3fc32b3..d8b24ed8c 100644 --- a/schema/openapi.yaml +++ b/schema/openapi.yaml @@ -16,13 +16,13 @@ components: Annotations: properties: cryostat: - items: - $ref: '#/components/schemas/KeyValue' - type: array + additionalProperties: + type: string + type: object platform: - items: - $ref: '#/components/schemas/KeyValue' - type: array + additionalProperties: + type: string + type: object type: object ArchivedRecording: properties: @@ -66,9 +66,9 @@ components: format: int64 type: integer labels: - items: - $ref: '#/components/schemas/KeyValue' - type: array + additionalProperties: + type: string + type: object name: pattern: \S type: string @@ -125,13 +125,6 @@ components: value: {} type: object type: array - KeyValue: - properties: - key: - type: string - value: - type: string - type: object LinkedRecordingDescriptor: properties: continuous: @@ -320,9 +313,9 @@ components: jvmId: type: string labels: - items: - $ref: '#/components/schemas/KeyValue' - type: array + additionalProperties: + type: string + type: object required: - connectUrl - alias diff --git a/schema/schema.graphql b/schema/schema.graphql index c865733d3..a3b06cc12 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -86,11 +86,6 @@ type Entry_String_String { value: String } -type KeyValue { - key: String - value: String -} - type MBeanMetrics { jvmId: String memory: MemoryMetrics diff --git a/src/main/java/io/cryostat/ObjectMapperCustomization.java b/src/main/java/io/cryostat/ObjectMapperCustomization.java new file mode 100644 index 000000000..7284b0282 --- /dev/null +++ b/src/main/java/io/cryostat/ObjectMapperCustomization.java @@ -0,0 +1,83 @@ +/* + * 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 java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; +import com.fasterxml.jackson.databind.type.MapType; +import io.quarkus.jackson.ObjectMapperCustomizer; +import jakarta.inject.Singleton; + +@Singleton +public class ObjectMapperCustomization implements ObjectMapperCustomizer { + + @Override + public void customize(ObjectMapper objectMapper) { + // FIXME get this version information from the maven build somehow + SimpleModule mapModule = + new SimpleModule( + "MapSerialization", new Version(3, 0, 0, null, "io.cryostat", "cryostat")); + + mapModule.setSerializerModifier(new MapSerializerModifier()); + + objectMapper.registerModule(mapModule); + } + + static class MapSerializerModifier extends BeanSerializerModifier { + @Override + public JsonSerializer modifyMapSerializer( + SerializationConfig config, + MapType valueType, + BeanDescription beanDesc, + JsonSerializer serializer) { + if (valueType.getKeyType().getRawClass().equals(String.class) + && valueType.getContentType().getRawClass().equals(String.class)) { + return new MapSerializer(); + } + return serializer; + } + } + + static class MapSerializer extends JsonSerializer> { + + @Override + public void serialize(Map map, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartArray(); + + for (var entry : map.entrySet()) { + gen.writeStartObject(); + + gen.writePOJOField("key", entry.getKey()); + gen.writePOJOField("value", entry.getValue()); + + gen.writeEndObject(); + } + + gen.writeEndArray(); + } + } +} diff --git a/src/main/java/io/cryostat/discovery/ContainerDiscovery.java b/src/main/java/io/cryostat/discovery/ContainerDiscovery.java index cb63a17ec..c2bccae0e 100644 --- a/src/main/java/io/cryostat/discovery/ContainerDiscovery.java +++ b/src/main/java/io/cryostat/discovery/ContainerDiscovery.java @@ -280,18 +280,17 @@ private Target toTarget(ContainerSpec desc) { target.activeRecordings = new ArrayList<>(); target.connectUrl = connectUrl; target.alias = Optional.ofNullable(desc.Names.get(0)).orElse(desc.Id); - target.labels = KeyValue.listFromMap(desc.Labels); + target.labels = desc.Labels; target.annotations = new Annotations( null, - KeyValue.listFromMap( - Map.of( - "REALM", // AnnotationKey.REALM, - getRealm(), - "HOST", // AnnotationKey.HOST, - hostname, - "PORT", // "AnnotationKey.PORT, - Integer.toString(jmxPort)))); + Map.of( + "REALM", // AnnotationKey.REALM, + getRealm(), + "HOST", // AnnotationKey.HOST, + hostname, + "PORT", // "AnnotationKey.PORT, + Integer.toString(jmxPort))); return target; } @@ -300,8 +299,7 @@ private boolean isTargetUnderRealm(URI connectUrl) throws IllegalStateException // Check for any targets with the same connectUrl in other realms try { Target persistedTarget = Target.getTargetByConnectUrl(connectUrl); - String realmOfTarget = - KeyValue.mapFromList(persistedTarget.annotations.cryostat()).get("REALM"); + String realmOfTarget = persistedTarget.annotations.cryostat().get("REALM"); if (!getRealm().equals(realmOfTarget)) { logger.warnv( "Expected persisted target with serviceURL {0} to be under realm" diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index bf82982b3..b30c4d170 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -222,8 +222,7 @@ Response doV2Create( credential.ifPresent(c -> c.persist()); target.activeRecordings = new ArrayList<>(); - target.annotations = - new Annotations(null, KeyValue.listFromMap(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 1f1e13987..fcad79744 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -113,32 +113,38 @@ 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); - } + 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 13079b7ad..461d411b1 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryNode.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryNode.java @@ -16,7 +16,9 @@ package io.cryostat.discovery; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -71,7 +73,7 @@ public class DiscoveryNode extends PanacheEntity { @JdbcTypeCode(SqlTypes.JSON) @NotNull @JsonView(Views.Flat.class) - public List labels = new ArrayList<>(); + public Map labels = new HashMap<>(); @OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "parent") @JsonView(Views.Nested.class) @@ -135,7 +137,7 @@ public static DiscoveryNode environment(String name, NodeType nodeType) { DiscoveryNode node = new DiscoveryNode(); node.name = name; node.nodeType = nodeType.getKind(); - node.labels = new ArrayList<>(); + node.labels = new HashMap<>(); node.children = new ArrayList<>(); node.target = null; node.persist(); @@ -150,7 +152,7 @@ public static DiscoveryNode target(Target target, NodeType nodeType) { DiscoveryNode node = new DiscoveryNode(); node.name = target.connectUrl.toString(); node.nodeType = nodeType.getKind(); - node.labels = new ArrayList<>(target.labels); + node.labels = new HashMap<>(target.labels); node.children = null; node.target = target; node.persist(); @@ -201,7 +203,7 @@ void prePersist(DiscoveryNode node) { node.children = new ArrayList<>(); } if (node.labels == null) { - node.labels = new ArrayList<>(); + node.labels = new HashMap<>(); } } diff --git a/src/main/java/io/cryostat/discovery/JDPDiscovery.java b/src/main/java/io/cryostat/discovery/JDPDiscovery.java index 1a52958a9..7cc167792 100644 --- a/src/main/java/io/cryostat/discovery/JDPDiscovery.java +++ b/src/main/java/io/cryostat/discovery/JDPDiscovery.java @@ -137,16 +137,15 @@ void handleJdpEvent(JvmDiscoveryEvent evt) { target.annotations = new Annotations( null, - KeyValue.listFromMap( - Map.of( - "REALM", // AnnotationKey.REALM, - REALM, - "JAVA_MAIN", // AnnotationKey.JAVA_MAIN, - evt.getJvmDescriptor().getMainClass(), - "HOST", // AnnotationKey.HOST, - rmiTarget.getHost(), - "PORT", // "AnnotationKey.PORT, - Integer.toString(rmiTarget.getPort())))); + Map.of( + "REALM", // AnnotationKey.REALM, + REALM, + "JAVA_MAIN", // AnnotationKey.JAVA_MAIN, + evt.getJvmDescriptor().getMainClass(), + "HOST", // AnnotationKey.HOST, + rmiTarget.getHost(), + "PORT", // "AnnotationKey.PORT, + Integer.toString(rmiTarget.getPort()))); DiscoveryNode node = DiscoveryNode.target(target, BaseNodeType.JVM); diff --git a/src/main/java/io/cryostat/discovery/KeyValue.java b/src/main/java/io/cryostat/discovery/KeyValue.java deleted file mode 100644 index 522fed537..000000000 --- a/src/main/java/io/cryostat/discovery/KeyValue.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.discovery; - -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -public record KeyValue(String key, String value) implements Comparable { - - public KeyValue { - Objects.requireNonNull(key); - Objects.requireNonNull(value); - } - - public KeyValue(KeyValue other) { - this(other.key(), other.value()); - } - - public KeyValue(Map.Entry entry) { - this(entry.getKey(), entry.getValue()); - } - - public static List listFromMap(Map map) { - return map.entrySet().stream().map(KeyValue::new).toList(); - } - - public static Map mapFromList(List list) { - return list.stream().sorted().collect(Collectors.toMap(KeyValue::key, KeyValue::value)); - } - - @Override - public int compareTo(KeyValue o) { - return Comparator.comparing(KeyValue::key) - .thenComparing(Comparator.comparing(KeyValue::value)) - .compare(this, o); - } -} diff --git a/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java b/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java index a4ce90b98..f6874804c 100644 --- a/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java +++ b/src/main/java/io/cryostat/discovery/KubeApiDiscovery.java @@ -235,8 +235,7 @@ private boolean isTargetUnderRealm(URI connectUrl) throws IllegalStateException // Check for any targets with the same connectUrl in other realms try { Target persistedTarget = Target.getTargetByConnectUrl(connectUrl); - String realmOfTarget = - KeyValue.mapFromList(persistedTarget.annotations.cryostat()).get("REALM"); + String realmOfTarget = persistedTarget.annotations.cryostat().get("REALM"); if (!REALM.equals(realmOfTarget)) { logger.warnv( "Expected persisted target with serviceURL {0} to be under realm" @@ -287,8 +286,7 @@ private void handleObservedEndpoints(String namespace) { .filter( (n) -> namespace.equals( - KeyValue.mapFromList(n.labels) - .get(DISCOVERY_NAMESPACE_LABEL_KEY))) + n.labels.get(DISCOVERY_NAMESPACE_LABEL_KEY))) .collect(Collectors.toList()); Map targetRefMap = new HashMap<>(); @@ -476,8 +474,7 @@ private Pair queryForNode( return nodeType.getKind().equals(n.nodeType) && name.equals(n.name) && namespace.equals( - KeyValue.mapFromList(n.labels) - .get(DISCOVERY_NAMESPACE_LABEL_KEY)); + n.labels.get(DISCOVERY_NAMESPACE_LABEL_KEY)); }) .orElseGet( () -> { @@ -492,7 +489,7 @@ private Pair queryForNode( : new HashMap<>(); // Add namespace to label to retrieve node later labels.put(DISCOVERY_NAMESPACE_LABEL_KEY, namespace); - newNode.labels = KeyValue.listFromMap(labels); + newNode.labels = labels; return newNode; }); return Pair.of(kubeObj, node); @@ -633,27 +630,21 @@ public Target toTarget() { target.activeRecordings = new ArrayList<>(); target.connectUrl = connectUrl; target.alias = objRef.getName(); - target.labels = - KeyValue.listFromMap( - (obj != null ? obj.getMetadata().getLabels() : new HashMap<>())); + target.labels = (obj != null ? obj.getMetadata().getLabels() : new HashMap<>()); target.annotations = new Annotations( - KeyValue.listFromMap( - obj != null - ? obj.getMetadata().getAnnotations() - : Map.of()), - KeyValue.listFromMap( - Map.of( - "REALM", - REALM, - "HOST", - addr.getIp(), - "PORT", - Integer.toString(port.getPort()), - "NAMESPACE", - objRef.getNamespace(), - isPod ? "POD_NAME" : "OBJECT_NAME", - objRef.getName()))); + obj != null ? obj.getMetadata().getAnnotations() : Map.of(), + Map.of( + "REALM", + REALM, + "HOST", + addr.getIp(), + "PORT", + Integer.toString(port.getPort()), + "NAMESPACE", + objRef.getNamespace(), + isPod ? "POD_NAME" : "OBJECT_NAME", + objRef.getName())); return target; } catch (Exception e) { diff --git a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java index 675907a6d..527e12111 100644 --- a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java +++ b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java @@ -23,9 +23,9 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; -import io.cryostat.discovery.KeyValue; import io.cryostat.expressions.MatchExpression.ExpressionEvent; import io.cryostat.targets.Target; +import io.cryostat.targets.Target.Annotations; import io.cryostat.targets.Target.TargetDiscovery; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -226,7 +226,7 @@ private static record SimplifiedTarget( String alias, @Nullable String jvmId, Map labels, - SimplifiedAnnotations annotations) { + Target.Annotations annotations) { SimplifiedTarget { Objects.requireNonNull(connectUrl); Objects.requireNonNull(alias); @@ -234,7 +234,7 @@ private static record SimplifiedTarget( labels = Collections.emptyMap(); } if (annotations == null) { - annotations = new SimplifiedAnnotations(); + annotations = new Annotations(); } } @@ -244,17 +244,8 @@ static SimplifiedTarget from(Target target) { target.connectUrl.toString(), target.alias, target.jvmId, - KeyValue.mapFromList(target.labels), - new SimplifiedAnnotations( - KeyValue.mapFromList(target.annotations.platform()), - KeyValue.mapFromList(target.annotations.cryostat()))); - } - } - - private static record SimplifiedAnnotations( - Map platform, Map cryostat) { - public SimplifiedAnnotations() { - this(Map.of(), Map.of()); + target.labels, + target.annotations); } } } diff --git a/src/main/java/io/cryostat/graphql/Annotations.java b/src/main/java/io/cryostat/graphql/Annotations.java deleted file mode 100644 index c9f1e56a0..000000000 --- a/src/main/java/io/cryostat/graphql/Annotations.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.graphql; - -import java.util.List; -import java.util.Map; - -import io.cryostat.discovery.KeyValue; -import io.cryostat.targets.Target; - -import io.smallrye.graphql.api.Nullable; -import org.eclipse.microprofile.graphql.GraphQLApi; -import org.eclipse.microprofile.graphql.Source; - -@GraphQLApi -public class Annotations { - - public Map cryostat( - @Source Target.Annotations annotations, @Nullable List key) { - return KeyValue.mapFromList( - annotations.cryostat().stream() - .filter(kv -> key == null || key.contains(kv.key())) - .toList()); - } - - public Map platform( - @Source Target.Annotations annotations, @Nullable List key) { - return KeyValue.mapFromList( - annotations.platform().stream() - .filter(kv -> key == null || key.contains(kv.key())) - .toList()); - } -} diff --git a/src/main/java/io/cryostat/graphql/RootNode.java b/src/main/java/io/cryostat/graphql/RootNode.java index 37e546546..670bbb99f 100644 --- a/src/main/java/io/cryostat/graphql/RootNode.java +++ b/src/main/java/io/cryostat/graphql/RootNode.java @@ -17,19 +17,16 @@ import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.function.Predicate; import io.cryostat.discovery.DiscoveryNode; -import io.cryostat.discovery.KeyValue; import io.cryostat.graphql.matchers.LabelSelectorMatcher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.smallrye.graphql.api.Nullable; import org.eclipse.microprofile.graphql.Description; import org.eclipse.microprofile.graphql.GraphQLApi; -import org.eclipse.microprofile.graphql.NonNull; import org.eclipse.microprofile.graphql.Query; import org.eclipse.microprofile.graphql.Source; @@ -55,12 +52,6 @@ public List descendantTargets( .toList(); } - public @NonNull Map labels( - @Source DiscoveryNode node, @Nullable List key) { - return KeyValue.mapFromList( - node.labels.stream().filter(kv -> key == null || key.contains(kv.key())).toList()); - } - static Set recurseChildren( DiscoveryNode node, Predicate predicate) { Set result = new HashSet<>(); @@ -105,9 +96,7 @@ public boolean test(DiscoveryNode t) { .allMatch( label -> LabelSelectorMatcher.parse(label) - .test( - KeyValue.mapFromList( - n.labels))); + .test(n.labels)); Predicate matchesAnnotations = n -> annotations == null @@ -118,12 +107,9 @@ public boolean test(DiscoveryNode t) { LabelSelectorMatcher.parse( annotation) .test( - KeyValue - .mapFromList( - n - .target - .annotations - .merged())))); + n.target + .annotations + .merged()))); return List.of( matchesId, diff --git a/src/main/java/io/cryostat/graphql/TargetNodes.java b/src/main/java/io/cryostat/graphql/TargetNodes.java index 160df045f..afe9eb9f8 100644 --- a/src/main/java/io/cryostat/graphql/TargetNodes.java +++ b/src/main/java/io/cryostat/graphql/TargetNodes.java @@ -17,12 +17,10 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import io.cryostat.core.net.JFRConnection; import io.cryostat.discovery.DiscoveryNode; -import io.cryostat.discovery.KeyValue; import io.cryostat.graphql.ActiveRecordings.ActiveRecordingsFilter; import io.cryostat.graphql.ArchivedRecordings.ArchivedRecordingsFilter; import io.cryostat.graphql.RootNode.DiscoveryNodeFilter; @@ -69,11 +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)); - // } - @Transactional public ActiveRecordings activeRecordings( @Source Target target, @Nullable ActiveRecordingsFilter filter) { @@ -103,13 +96,6 @@ public ArchivedRecordings archivedRecordings( return recordings; } - public @NonNull Map labels(@Source Target target, @Nullable List key) { - return KeyValue.mapFromList( - target.labels.stream() - .filter(kv -> key == null || key.contains(kv.key())) - .toList()); - } - @Transactional @Description("Get the active and archived recordings belonging to this target") public Recordings recordings(@Source Target target, Context context) { diff --git a/src/main/java/io/cryostat/targets/Target.java b/src/main/java/io/cryostat/targets/Target.java index 36d13961a..9bc733033 100644 --- a/src/main/java/io/cryostat/targets/Target.java +++ b/src/main/java/io/cryostat/targets/Target.java @@ -22,8 +22,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -33,7 +35,6 @@ import io.cryostat.core.net.JFRConnection; import io.cryostat.credentials.Credential; import io.cryostat.discovery.DiscoveryNode; -import io.cryostat.discovery.KeyValue; import io.cryostat.expressions.MatchExpressionEvaluator; import io.cryostat.libcryostat.JvmIdentifier; import io.cryostat.recordings.ActiveRecording; @@ -89,7 +90,7 @@ public class Target extends PanacheEntity { @JdbcTypeCode(SqlTypes.JSON) @NotNull - public List labels = new ArrayList<>(); + public Map labels = new HashMap<>(); @JdbcTypeCode(SqlTypes.JSON) @NotNull @@ -146,11 +147,7 @@ public static List findByRealm(String realm) { List targets = findAll().list(); return targets.stream() - .filter( - (t) -> - realm.equals( - KeyValue.mapFromList(t.annotations.cryostat()) - .get("REALM"))) + .filter((t) -> realm.equals(t.annotations.cryostat().get("REALM"))) .collect(Collectors.toList()); } @@ -162,13 +159,13 @@ public ActiveRecording getRecordingById(long remoteId) { } @SuppressFBWarnings("EI_EXPOSE_REP") - public static record Annotations(List platform, List cryostat) { + public static record Annotations(Map platform, Map cryostat) { public Annotations { if (platform == null) { - platform = new ArrayList<>(); + platform = new HashMap<>(); } if (cryostat == null) { - cryostat = new ArrayList<>(); + cryostat = new HashMap<>(); } } @@ -176,10 +173,10 @@ public Annotations() { this(null, null); } - public List merged() { - List merged = new ArrayList<>(platform); - merged.addAll(cryostat); - return Collections.unmodifiableList(merged); + public Map merged() { + Map merged = new HashMap<>(cryostat); + merged.putAll(platform); + return Collections.unmodifiableMap(merged); } } @@ -353,7 +350,7 @@ void prePersist(Target target) { } if (target.labels == null) { - target.labels = new ArrayList<>(); + target.labels = new HashMap<>(); } if (target.annotations == null) { target.annotations = new Annotations(); diff --git a/src/test/java/itest/GraphQLTest.java b/src/test/java/itest/GraphQLTest.java index 152f0a18d..a4f39be8a 100644 --- a/src/test/java/itest/GraphQLTest.java +++ b/src/test/java/itest/GraphQLTest.java @@ -26,7 +26,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -71,7 +70,7 @@ class GraphQLTest extends StandardSelfTest { private final ExecutorService worker = ForkJoinPool.commonPool(); - static final long DELAY = 500L; + static final long DELAY = 5_000L; static final String TEST_RECORDING_NAME = "archivedRecording"; @@ -254,24 +253,29 @@ void testStartRecordingMutationOnSpecificTarget() throws Exception { Matchers.equalTo( String.format( "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", "localhost", 0))); - JsonObject notificationLabels = - notificationRecording.getJsonObject("metadata").getJsonObject("labels"); + JsonArray notificationLabels = + notificationRecording.getJsonObject("metadata").getJsonArray("labels"); Map expectedLabels = Map.of("template.name", "Profiling", "template.type", "TARGET"); - MatcherAssert.assertThat(notificationLabels, Matchers.isA(JsonObject.class)); MatcherAssert.assertThat( notificationLabels, - Matchers.equalTo(new JsonObject(new HashMap<>(expectedLabels)))); + Matchers.containsInAnyOrder( + expectedLabels.entrySet().stream() + .map( + e -> + new JsonObject( + Map.of( + "key", + e.getKey(), + "value", + e.getValue()))) + .toArray())); ActiveRecording recording = new ActiveRecording(); recording.name = "test"; recording.duration = 30_000L; recording.state = "RUNNING"; - recording.metadata = new RecordingMetadata(); - recording.metadata.labels = - expectedLabels.entrySet().stream() - .map(e -> new KeyValue(e.getKey(), e.getValue())) - .toList(); + recording.metadata = RecordingMetadata.of(expectedLabels); MatcherAssert.assertThat(actual.data.recordings, Matchers.equalTo(List.of(recording)));