From 7c77a4ab2be0488c9ea9fad288e7ddbe0b22a05c Mon Sep 17 00:00:00 2001 From: Atif Ali <56743004+aali309@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:29:38 -0400 Subject: [PATCH] fix(itest): fix GraphQLTest.java (#395) Co-authored-by: Andrew Azores (cherry picked from commit c5ea2a3440e9e6080a8fe81dcb5a7883d99020dc) --- .../io/cryostat/discovery/DiscoveryNode.java | 29 +- .../java/io/cryostat/graphql/RootNode.java | 17 +- .../matchers/LabelSelectorMatcher.java | 6 +- .../matchers/LabelSelectorMatcherTest.java | 127 + src/test/java/itest/GraphQLTest.java | 2327 +++++++++++------ .../java/itest/bases/StandardSelfTest.java | 12 +- 6 files changed, 1679 insertions(+), 839 deletions(-) create mode 100644 src/test/java/io/cryostat/graphql/matchers/LabelSelectorMatcherTest.java diff --git a/src/main/java/io/cryostat/discovery/DiscoveryNode.java b/src/main/java/io/cryostat/discovery/DiscoveryNode.java index 4d06143e0..f2f229410 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryNode.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryNode.java @@ -177,27 +177,26 @@ public boolean equals(Object obj) { && Objects.equals(children, other.children); } + @Override + public String toString() { + return "DiscoveryNode{" + + "name='" + + name + + '\'' + + ", nodeType='" + + nodeType + + '\'' + + ", children=" + + children + + '}'; + } + @ApplicationScoped static class Listener { @Inject Logger logger; @Inject EventBus bus; - // @Transactional - // @Blocking - // @ConsumeEvent(Target.TARGET_JVM_DISCOVERY) - // void onMessage(TargetDiscovery event) { - // switch (event.kind()) { - // case LOST: - // break; - // case FOUND: - // break; - // default: - // // no-op - // break; - // } - // } - @PrePersist void prePersist(DiscoveryNode node) {} diff --git a/src/main/java/io/cryostat/graphql/RootNode.java b/src/main/java/io/cryostat/graphql/RootNode.java index 3a1633667..4af6eefa8 100644 --- a/src/main/java/io/cryostat/graphql/RootNode.java +++ b/src/main/java/io/cryostat/graphql/RootNode.java @@ -103,13 +103,16 @@ public boolean test(DiscoveryNode t) { Predicate matchesAnnotations = n -> annotations == null - || annotations.stream() - .allMatch( - annotation -> - LabelSelectorMatcher.parse(annotation) - .test( - n.target.annotations - .merged())); + || (n.target != null + && annotations.stream() + .allMatch( + annotation -> + LabelSelectorMatcher.parse( + annotation) + .test( + n.target + .annotations + .merged()))); return List.of( matchesId, diff --git a/src/main/java/io/cryostat/graphql/matchers/LabelSelectorMatcher.java b/src/main/java/io/cryostat/graphql/matchers/LabelSelectorMatcher.java index 9f01582f6..5fcccb037 100644 --- a/src/main/java/io/cryostat/graphql/matchers/LabelSelectorMatcher.java +++ b/src/main/java/io/cryostat/graphql/matchers/LabelSelectorMatcher.java @@ -33,7 +33,7 @@ public class LabelSelectorMatcher implements Predicate> { // must loosely look like a k8s label (not strictly enforced here), right side must loosely look // like a k8s label value, which may be empty. Allowed operators are "=", "==", "!=". static final Pattern EQUALITY_PATTERN = - Pattern.compile("^(?[^!=\\s]+)\\s*(?=|==|!=)\\s*(?[^!=\\s]*)$"); + Pattern.compile("^(?[^!=\\s]+)\\s*(?=|==|!=)\\s*(?[^!=]*)\\s*$"); // ex. "environment in (production, qa)" or "tier NotIn (frontend, backend)". Tests if the given // label has or does not have any of the specified values. @@ -74,7 +74,9 @@ public static LabelSelectorMatcher parse(String clause) throws IllegalArgumentEx return new LabelSelectorMatcher(List.of(matcher)); } } - return new LabelSelectorMatcher(); + throw new IllegalArgumentException( + String.format( + "No LabelSelectorMatcher case matched given expression: \"%s\"", clause)); } private static LabelMatcher parseEqualities(String clause) { diff --git a/src/test/java/io/cryostat/graphql/matchers/LabelSelectorMatcherTest.java b/src/test/java/io/cryostat/graphql/matchers/LabelSelectorMatcherTest.java new file mode 100644 index 000000000..87ff47fdb --- /dev/null +++ b/src/test/java/io/cryostat/graphql/matchers/LabelSelectorMatcherTest.java @@ -0,0 +1,127 @@ +/* + * 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.matchers; + +import java.util.Map; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class LabelSelectorMatcherTest { + + private static final Map TEST_LABELS = + Map.of( + "foo", "bar", + "something", "else", + "my.prefixed/label", "expectedValue", + "env", "prod", + "present", "irrelevant"); + + @ParameterizedTest + @ValueSource(strings = {"this is not a valid expression"}) + void testDoesNotMatchBadSyntax(String expr) { + Assertions.assertThrows( + IllegalArgumentException.class, () -> LabelSelectorMatcher.parse(expr)); + } + + @ParameterizedTest + @CsvSource({ + "foo=bar, true", + "something=wrong, false", + "my.prefixed/label = expectedValue, true", + "env = Expected Value, false", + "env = prod, true", + "env = prod , true", + "env = dev, false", + "env = dev , false", + }) + void testSingleEquality(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource({ + "foo==bar, true", + "something==wrong, false", + "my.prefixed/label == expectedValue, true" + }) + void testDoubleEquality(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource({ + "foo!=bar, false", + "something!=wrong, true", + "my.prefixed/label != expectedValue, false" + }) + void testInequality(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource( + value = { + "foo in (bar, baz) : true", + "something In (else, orother) : true", + "env IN (stage,qa) : false", + }, + delimiter = ':') + void testSetIn(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource( + value = { + "foo notin (bar, baz) : false", + "something NotIn (orother, else, third) : false", + "env NOTIN (stage,qa) : true", + }, + delimiter = ':') + void testSetNotIn(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource({ + "foo, true", + "something, true", + "my.prefixed/label, true", + "another/missing-label, false", + "present, true" + }) + void testExists(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } + + @ParameterizedTest + @CsvSource({"!foo, false", "!something, false", "!present, false"}) + void testNotExists(String expr, boolean pass) { + LabelSelectorMatcher matcher = LabelSelectorMatcher.parse(expr); + MatcherAssert.assertThat(expr, matcher.test(TEST_LABELS), Matchers.is(pass)); + } +} diff --git a/src/test/java/itest/GraphQLTest.java b/src/test/java/itest/GraphQLTest.java index c8063c4d4..4acfab581 100644 --- a/src/test/java/itest/GraphQLTest.java +++ b/src/test/java/itest/GraphQLTest.java @@ -18,9 +18,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -34,19 +39,24 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import io.cryostat.util.HttpMimeType; +import io.cryostat.discovery.DiscoveryNode; +import io.cryostat.graphql.RootNode; -import io.vertx.core.MultiMap; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import io.quarkus.test.junit.QuarkusTest; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; import itest.bases.StandardSelfTest; +import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.hamcrest.collection.IsIterableContainingInOrder; +import org.hamcrest.comparator.ComparatorMatcherBuilder; import org.junit.jupiter.api.Assertions; -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; @@ -54,8 +64,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +@QuarkusTest @TestMethodOrder(OrderAnnotation.class) -@Disabled("TODO not all GraphQL queries are implemented") class GraphQLTest extends StandardSelfTest { private final ExecutorService worker = ForkJoinPool.commonPool(); @@ -69,37 +79,53 @@ void testEnvironmentNodeListing() throws Exception { query.put( "query", "query { environmentNodes(filter: { name: \"Custom Targets\" }) { name nodeType" - + " descendantTargets { name nodeType } } }"); + + " children { name nodeType } } }"); + HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - EnvironmentNodesResponse actual = - mapper.readValue(resp.bodyAsString(), EnvironmentNodesResponse.class); - - EnvironmentNodes expected = new EnvironmentNodes(); - - EnvironmentNode jdp = new EnvironmentNode(); - jdp.name = "JDP"; - jdp.nodeType = "Realm"; - - jdp.descendantTargets = new ArrayList<>(); - Node cryostat = new Node(); - cryostat.name = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; - cryostat.nodeType = "JVM"; - jdp.descendantTargets.add(cryostat); - - Node target = new Node(); - target.name = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; - target.nodeType = "JVM"; - jdp.descendantTargets.add(target); - - expected.environmentNodes = List.of(jdp); - MatcherAssert.assertThat(actual.data, Matchers.equalTo(expected)); + TypeReference typeRef = + new TypeReference() {}; + EnvironmentNodesResponse actual = mapper.readValue(resp.bodyAsString(), typeRef); + + List expectedChildren = new ArrayList<>(); + DiscoveryNode expectedChild = new DiscoveryNode(); + expectedChild.name = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; + expectedChild.nodeType = "JVM"; + expectedChildren.add(expectedChild); + + DiscoveryNode expectedNode = new DiscoveryNode(); + expectedNode.name = "Custom Targets"; + expectedNode.nodeType = "Realm"; + expectedNode.children = expectedChildren; + + assertThat(actual.getData().getEnvironmentNodes().size(), is(1)); + + DiscoveryNode actualNode = actual.getData().getEnvironmentNodes().get(0); + assertThat(actualNode.name, is(expectedNode.name)); + assertThat(actualNode.nodeType, is(expectedNode.nodeType)); + assertThat(actualNode.children.size(), is(expectedNode.children.size())); + + Comparator byNameAndType = + Comparator.comparing((DiscoveryNode node) -> node.name) + .thenComparing(node -> node.nodeType); + Matcher> listMatcher = + IsIterableContainingInOrder.contains( + expectedChildren.stream() + .map( + child -> + ComparatorMatcherBuilder.comparedBy(byNameAndType) + .comparesEqualTo(child)) + .collect(Collectors.toList())); + assertThat( + "Children nodes do not match expected configuration", + actualNode.children, + listMatcher); } @Test @@ -108,59 +134,36 @@ void testOtherContainersFound() throws Exception { JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes { name nodeType labels target { alias connectUrl annotations {" - + " cryostat platform } } } }"); + "query { targetNodes { name nodeType labels { key value } target { alias connectUrl" + + " annotations { cryostat { key value } platform { key value } } } } }"); HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); TargetNodesQueryResponse actual = mapper.readValue(resp.bodyAsString(), TargetNodesQueryResponse.class); + MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); TargetNode cryostat = new TargetNode(); Target cryostatTarget = new Target(); - cryostatTarget.alias = "io.cryostat.Cryostat"; - cryostatTarget.connectUrl = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; - cryostat.name = cryostatTarget.connectUrl; - cryostat.target = cryostatTarget; - cryostat.nodeType = "JVM"; + cryostatTarget.setAlias("selftest"); + cryostatTarget.setConnectUrl("service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"); + cryostat.setName(cryostatTarget.getConnectUrl()); + cryostat.setTarget(cryostatTarget); + cryostat.setNodeType("JVM"); Annotations cryostatAnnotations = new Annotations(); - cryostatAnnotations.cryostat = - Map.of( - "REALM", - "JDP", - "JAVA_MAIN", - "io.cryostat.Cryostat", - "HOST", - "localhost", - "PORT", - "0"); - cryostatAnnotations.platform = Map.of(); - cryostatTarget.annotations = cryostatAnnotations; - cryostat.labels = Map.of(); + cryostatAnnotations.setCryostat(Arrays.asList(new KeyValue("REALM", "Custom Targets"))); + cryostatAnnotations.setPlatform(new ArrayList<>()); + cryostatTarget.setAnnotations(cryostatAnnotations); + cryostat.setLabels(new ArrayList<>()); MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasItem(cryostat)); - - String uri = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; - String mainClass = "es.andrewazor.demo.Main"; - TargetNode ext = new TargetNode(); - Target target = new Target(); - target.alias = mainClass; - target.connectUrl = uri; - ext.name = target.connectUrl; - ext.target = target; - ext.nodeType = "JVM"; - Annotations annotations = new Annotations(); - annotations.cryostat = - Map.of("REALM", "JDP", "JAVA_MAIN", mainClass, "HOST", "localhost", "PORT", "0"); - annotations.platform = Map.of(); - target.annotations = annotations; - ext.labels = Map.of(); - MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasItem(ext)); + assertThat(actual.data.targetNodes.get(0).name, is(cryostat.name)); + assertThat(actual.data.targetNodes.get(0).nodeType, is(cryostat.nodeType)); } @Test @@ -169,12 +172,13 @@ void testQueryForSpecificTargetWithSpecificFields() throws Exception { JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 0\" }) { name nodeType }" - + " }"); + "query { targetNodes(filter: { annotations: [\"REALM = Custom Targets\"] })" + + " { name nodeType target { connectUrl annotations { cryostat(key:" + + " [\"REALM\"]) { key value } } } } }"); HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); @@ -182,11 +186,15 @@ void testQueryForSpecificTargetWithSpecificFields() throws Exception { TargetNodesQueryResponse actual = mapper.readValue(resp.bodyAsString(), TargetNodesQueryResponse.class); MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); - - String uri = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; TargetNode ext = new TargetNode(); - ext.name = uri; - ext.nodeType = "JVM"; + Target extTarget = new Target(); + extTarget.setConnectUrl("service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"); + ext.setName(extTarget.getConnectUrl()); + ext.setTarget(extTarget); + ext.setNodeType("JVM"); + Annotations extAnnotations = new Annotations(); + extAnnotations.setCryostat(Arrays.asList(new KeyValue("REALM", "Custom Targets"))); + extTarget.setAnnotations(extAnnotations); MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasItem(ext)); } @@ -194,22 +202,17 @@ void testQueryForSpecificTargetWithSpecificFields() throws Exception { @Order(3) void testStartRecordingMutationOnSpecificTarget() throws Exception { CountDownLatch latch = new CountDownLatch(2); + JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 0\" }) {" - + " doStartRecording(recording: { name: \"graphql-itest\", duration: 30," - + " template: \"Profiling\", templateType: \"TARGET\", archiveOnStop: true," - + " metadata: { labels: [ { key: \"newLabel\", value: \"someValue\"} ] } }) {" - + " name state duration archiveOnStop }} }"); - Map expectedLabels = - Map.of( - "template.name", - "Profiling", - "template.type", - "TARGET", - "newLabel", - "someValue"); + "mutation { createRecording( nodes:{annotations: [" + + "\"REALM = Custom Targets\"" + + "]}, recording: { name: \"test\", template:" + + " \"Profiling\", templateType: \"TARGET\", duration: 30, continuous:" + + " false, archiveOnStop: true, toDisk: true }) { name state duration" + + " continuous metadata { labels { key value } } } }"); + Future f = worker.submit( () -> { @@ -224,260 +227,374 @@ void testStartRecordingMutationOnSpecificTarget() throws Exception { } }); - Thread.sleep(5000); // Sleep to setup notification listening before query resolves + Thread.sleep(5000); HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - - StartRecordingMutationResponse actual = - mapper.readValue(resp.bodyAsString(), StartRecordingMutationResponse.class); - + CreateRecordingMutationResponse actual = + mapper.readValue(resp.bodyAsString(), CreateRecordingMutationResponse.class); latch.await(30, TimeUnit.SECONDS); // Ensure ActiveRecordingCreated notification emitted matches expected values - JsonObject notification = f.get(5, TimeUnit.SECONDS); + JsonObject notification = f.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); JsonObject notificationRecording = notification.getJsonObject("message").getJsonObject("recording"); - MatcherAssert.assertThat( - notificationRecording.getString("name"), Matchers.equalTo("graphql-itest")); - MatcherAssert.assertThat( - notificationRecording.getString("archiveOnStop"), Matchers.equalTo("true")); + MatcherAssert.assertThat(notificationRecording.getString("name"), Matchers.equalTo("test")); MatcherAssert.assertThat( notification.getJsonObject("message").getString("target"), Matchers.equalTo( String.format( "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", "localhost", 0))); - Map notificationLabels = - notificationRecording.getJsonObject("metadata").getJsonObject("labels").getMap(); - for (var entry : expectedLabels.entrySet()) { - MatcherAssert.assertThat( - notificationLabels, Matchers.hasEntry(entry.getKey(), entry.getValue())); - } - - RecordingNodes nodes = new RecordingNodes(); + JsonArray notificationLabels = + notificationRecording.getJsonObject("metadata").getJsonArray("labels"); + Map expectedLabels = + Map.of("template.name", "Profiling", "template.type", "TARGET"); + MatcherAssert.assertThat( + notificationLabels, + Matchers.containsInAnyOrder( + expectedLabels.entrySet().stream() + .map( + e -> + new JsonObject( + Map.of( + "key", + e.getKey(), + "value", + e.getValue()))) + .toArray())); ActiveRecording recording = new ActiveRecording(); - recording.name = "graphql-itest"; + recording.name = "test"; recording.duration = 30_000L; recording.state = "RUNNING"; - recording.archiveOnStop = true; recording.metadata = RecordingMetadata.of(expectedLabels); - StartRecording startRecording = new StartRecording(); - startRecording.doStartRecording = recording; - - nodes.targetNodes = List.of(startRecording); + MatcherAssert.assertThat(actual.data.recordings, Matchers.equalTo(List.of(recording))); - MatcherAssert.assertThat(actual.data, Matchers.equalTo(nodes)); + // delete recording + deleteRecording(); } @Test @Order(4) void testArchiveMutation() throws Exception { Thread.sleep(5000); + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); + Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); + JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 0\" }) { recordings {" - + " active { data { name doArchive { name } } } } } }"); + "mutation { archiveRecording (nodes: { annotations: [\"REALM = Custom Targets\"]}," + + " recordings: { name: \"test\"}) { name downloadUrl } }"); HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - ArchiveMutationResponse actual = - mapper.readValue(resp.bodyAsString(), ArchiveMutationResponse.class); - - MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); - - TargetNode node = actual.data.targetNodes.get(0); + String jsonResponse = resp.bodyAsString(); - MatcherAssert.assertThat(node.recordings.active.data, Matchers.hasSize(1)); + ArchiveMutationResponse archiveResponse = + mapper.readValue(jsonResponse, ArchiveMutationResponse.class); - ActiveRecording activeRecording = node.recordings.active.data.get(0); + List archivedRecordings = + archiveResponse.getData().getArchivedRecording(); + MatcherAssert.assertThat(archivedRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(archivedRecordings, Matchers.hasSize(1)); - MatcherAssert.assertThat(activeRecording.name, Matchers.equalTo("graphql-itest")); - - ArchivedRecording archivedRecording = activeRecording.doArchive; + String archivedRecordingName = archivedRecordings.get(0).getName(); MatcherAssert.assertThat( - archivedRecording.name, - Matchers.matchesRegex( - "^es-andrewazor-demo-Main_graphql-itest_[0-9]{8}T[0-9]{6}Z\\.jfr$")); + archivedRecordingName, + Matchers.matchesRegex("^selftest_test_[0-9]{8}T[0-9]{6}Z\\.jfr$")); + + deleteRecording(); } @Test @Order(5) void testActiveRecordingMetadataMutation() throws Exception { + Thread.sleep(5000); + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); + + JsonObject variables = new JsonObject(); + JsonArray labels = new JsonArray(); + labels.add(new JsonObject().put("key", "template.name").put("value", "Profiling")); + labels.add(new JsonObject().put("key", "template.type").put("value", "TARGET")); + labels.add(new JsonObject().put("key", "newLabel").put("value", "newValue")); + labels.add(new JsonObject().put("key", "newKey").put("value", "anotherValue")); + JsonObject metadataInput = new JsonObject().put("labels", labels); + variables.put("metadataInput", metadataInput); + JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 9093\" }) {" - + "recordings { active {" - + " data {" - + " doPutMetadata(metadata: { labels: [" - + " {key:\"template.name\",value:\"Profiling\"}," - + " {key:\"template.type\",value:\"TARGET\"}," - + " {key:\"newLabel\",value:\"newValue\"}] })" - + " { metadata { labels } } } } } } }"); + "query ($metadataInput: MetadataLabelsInput!) { targetNodes(filter: { annotations:" + + " [\"REALM = Custom Targets\"] }) { name target { recordings{ active(filter:" + + " {name: \"test\"}) { data { name id doPutMetadata(metadataInput:" + + " $metadataInput) { metadata { labels { key value } } } } } } } } }"); + query.put("variables", variables); + HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); + String jsonResponse = resp.bodyAsString(); - ActiveMutationResponse actual = - mapper.readValue(resp.bodyAsString(), ActiveMutationResponse.class); + TypeReference typeRef = + new TypeReference() {}; + ActiveMutationResponse actual = mapper.readValue(jsonResponse, typeRef); - MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); + List targetNodes = actual.getData().getTargetNodes(); + MatcherAssert.assertThat(targetNodes, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(targetNodes, Matchers.hasSize(1)); - TargetNode node = actual.data.targetNodes.get(0); + TargetNode targetNode = targetNodes.get(0); + List activeRecordings = + targetNode.getTarget().getRecordings().getActive().getData(); + MatcherAssert.assertThat(activeRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(activeRecordings, Matchers.hasSize(1)); - MatcherAssert.assertThat(node.recordings.active.data, Matchers.hasSize(1)); + ActiveRecording updatedRecording = activeRecordings.get(0); - ActiveRecording activeRecording = node.recordings.active.data.get(0); + List expectedLabels = + List.of( + new KeyValue("template.name", "Profiling"), + new KeyValue("template.type", "TARGET"), + new KeyValue("newLabel", "newValue"), + new KeyValue("newKey", "anotherValue")); MatcherAssert.assertThat( - activeRecording.metadata, - Matchers.equalTo( - RecordingMetadata.of( - Map.of( - "template.name", - "Profiling", - "template.type", - "TARGET", - "newLabel", - "newValue")))); + updatedRecording.getDoPutMetadata().getMetadata().labels, + Matchers.containsInAnyOrder(expectedLabels.toArray())); + + deleteRecording(); } @Test @Order(6) void testArchivedRecordingMetadataMutation() throws Exception { - JsonObject query = new JsonObject(); - query.put( + Thread.sleep(5000); + // Create a Recording + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); + // Archive it + JsonObject query1 = new JsonObject(); + query1.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 0\" }) {" - + "recordings { archived {" - + " data { name size " - + " doPutMetadata(metadata: { labels: [" - + " {key:\"template.name\",value:\"Profiling\"}," - + " {key:\"template.type\",value:\"TARGET\"}," - + " {key:\"newArchivedLabel\",value:\"newArchivedValue\"}] })" - + " { metadata { labels } } } } } } }"); - HttpResponse resp = + "mutation { archiveRecording (nodes: { annotations: [\"REALM = Custom Targets\"]}," + + " recordings: { name: \"test\"}) { name downloadUrl } }"); + HttpResponse resp1 = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query1.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( - resp.statusCode(), + resp1.statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); + + String jsonResponse1 = resp1.bodyAsString(); + + // Deserialize the response + ArchiveMutationResponse archiveResponse = + mapper.readValue(jsonResponse1, ArchiveMutationResponse.class); + List archivedRecordings = + archiveResponse.getData().getArchivedRecording(); + MatcherAssert.assertThat(archivedRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(archivedRecordings, Matchers.hasSize(1)); + + String archivedRecordingName = archivedRecordings.get(0).getName(); + + // Edit Labels + JsonObject variables = new JsonObject(); + JsonArray labels = new JsonArray(); + labels.add(new JsonObject().put("key", "template.name").put("value", "Profiling")); + labels.add(new JsonObject().put("key", "template.type").put("value", "TARGET")); + labels.add( + new JsonObject().put("key", "newArchivedLabel").put("value", "newArchivedValue")); + JsonObject metadataInput = new JsonObject().put("labels", labels); + variables.put("metadataInput", metadataInput); + + JsonObject query2 = new JsonObject(); + query2.put( + "query", + "query ($metadataInput: MetadataLabelsInput!) { targetNodes(filter: { annotations:" + + " [\"REALM = Custom Targets\"] }) { name target { recordings{ archived" + + " (filter: {name: \"" + + archivedRecordingName + + "\"}) { data { name doPutMetadata(metadataInput:" + + " $metadataInput) { metadata { labels { key value } } } } } } } } }"); + query2.put("variables", variables); + HttpResponse resp2 = + webClient + .extensions() + .post("/api/v3/graphql", query2.toBuffer(), REQUEST_TIMEOUT_SECONDS); + MatcherAssert.assertThat( + resp2.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - ArchiveMutationResponse actual = - mapper.readValue(resp.bodyAsString(), ArchiveMutationResponse.class); + String jsonResponse2 = resp2.bodyAsString(); - MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); + MetadataUpdateResponse actual = + mapper.readValue(jsonResponse2, MetadataUpdateResponse.class); - TargetNode node = actual.data.targetNodes.get(0); + List targetNodes = actual.getData().getTargetNodes(); + MatcherAssert.assertThat(targetNodes, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(targetNodes, Matchers.hasSize(1)); - MatcherAssert.assertThat(node.recordings.archived.data, Matchers.hasSize(1)); + List updatedArchivedRecordings = + targetNodes.get(0).getTarget().getRecordings().getArchived().getData(); + MatcherAssert.assertThat(updatedArchivedRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(updatedArchivedRecordings, Matchers.hasSize(1)); + ArchivedRecording updatedArchivedRecording = updatedArchivedRecordings.get(0); + MatcherAssert.assertThat( + updatedArchivedRecording.getName(), + Matchers.matchesRegex("^selftest_test_[0-9]{8}T[0-9]{6}Z\\.jfr$")); - ArchivedRecording archivedRecording = node.recordings.archived.data.get(0); - MatcherAssert.assertThat(archivedRecording.size, Matchers.greaterThan(0L)); + List expectedLabels = + List.of( + new KeyValue("template.name", "Profiling"), + new KeyValue("template.type", "TARGET"), + new KeyValue("newArchivedLabel", "newArchivedValue")); MatcherAssert.assertThat( - archivedRecording.metadata, - Matchers.equalTo( - RecordingMetadata.of( - Map.of( - "template.name", - "Profiling", - "template.type", - "TARGET", - "newArchivedLabel", - "newArchivedValue")))); + updatedArchivedRecording.getDoPutMetadata().getMetadata().labels, + Matchers.containsInAnyOrder(expectedLabels.toArray())); + + deleteRecording(); } @Test @Order(7) void testDeleteMutation() throws Exception { - JsonObject query = new JsonObject(); - query.put( + // this will delete all Active and Archived recordings that match the filter input. + Thread.sleep(5000); + // Create a Recording + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); + // Archive it + JsonObject query1 = new JsonObject(); + query1.put( "query", - "query { targetNodes(filter: { annotations: \"PORT == 0\" }) { recordings {" - + " active { data { name doDelete { name }" - + " } aggregate { count } }" - + " archived { data { name doDelete { name }" - + " } aggregate { count size } }" - + " } } }"); - HttpResponse resp = + "mutation { archiveRecording (nodes: { annotations: [\"REALM = Custom Targets\"]}," + + " recordings: { name: \"test\"}) { name downloadUrl } }"); + HttpResponse resp1 = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query1.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( - resp.statusCode(), + resp1.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - DeleteMutationResponse actual = - mapper.readValue(resp.bodyAsString(), DeleteMutationResponse.class); + String jsonResponse1 = resp1.bodyAsString(); - MatcherAssert.assertThat(actual.data.targetNodes, Matchers.hasSize(1)); + // Deserialize the response + ArchiveMutationResponse archiveResponse = + mapper.readValue(jsonResponse1, ArchiveMutationResponse.class); + List archivedRecordings = + archiveResponse.getData().getArchivedRecording(); + MatcherAssert.assertThat(archivedRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(archivedRecordings, Matchers.hasSize(1)); - TargetNode node = actual.data.targetNodes.get(0); + // Delete + JsonObject query2 = new JsonObject(); + query2.put( + "query", + "query { targetNodes(filter: { annotations: [\"REALM = Custom Targets\"] }) { name" + + " target { recordings { active { data { name doDelete { name } } aggregate {" + + " count } } archived { data { name doDelete { name } } aggregate { count size" + + " } } } } } }"); + HttpResponse resp2 = + webClient + .extensions() + .post("/api/v3/graphql", query2.toBuffer(), REQUEST_TIMEOUT_SECONDS); + MatcherAssert.assertThat( + resp2.statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - MatcherAssert.assertThat(node.recordings.active.data, Matchers.hasSize(1)); - MatcherAssert.assertThat(node.recordings.archived.data, Matchers.hasSize(1)); - MatcherAssert.assertThat(node.recordings.archived.aggregate.count, Matchers.equalTo(1L)); - MatcherAssert.assertThat(node.recordings.archived.aggregate.size, Matchers.greaterThan(0L)); + DeleteMutationResponse actual = + mapper.readValue(resp2.bodyAsString(), DeleteMutationResponse.class); + MatcherAssert.assertThat(actual.getData().getTargetNodes(), Matchers.hasSize(1)); - ActiveRecording activeRecording = node.recordings.active.data.get(0); - ArchivedRecording archivedRecording = node.recordings.archived.data.get(0); + TargetNode node = actual.getData().getTargetNodes().get(0); + MatcherAssert.assertThat( + node.getTarget().getRecordings().getActive().getData(), Matchers.hasSize(1)); + MatcherAssert.assertThat( + node.getTarget().getRecordings().getArchived().getData(), Matchers.hasSize(1)); + MatcherAssert.assertThat( + node.getTarget().getRecordings().getArchived().aggregate.count, + Matchers.equalTo(1L)); + MatcherAssert.assertThat( + node.getTarget().getRecordings().getArchived().aggregate.size, + Matchers.greaterThan(0L)); - MatcherAssert.assertThat(activeRecording.name, Matchers.equalTo("graphql-itest")); - MatcherAssert.assertThat(activeRecording.doDelete.name, Matchers.equalTo("graphql-itest")); + ActiveRecording activeRecording = + node.getTarget().getRecordings().getActive().getData().get(0); + ArchivedRecording archivedRecording = + node.getTarget().getRecordings().getArchived().getData().get(0); + MatcherAssert.assertThat(activeRecording.name, Matchers.equalTo("test")); + MatcherAssert.assertThat(activeRecording.doDelete.name, Matchers.equalTo("test")); MatcherAssert.assertThat( archivedRecording.name, - Matchers.matchesRegex( - "^es-andrewazor-demo-Main_graphql-itest_[0-9]{8}T[0-9]{6}Z\\.jfr$")); + Matchers.matchesRegex("^selftest_test_[0-9]{8}T[0-9]{6}Z\\.jfr$")); } @Test @Order(8) void testNodesHaveIds() throws Exception { + + RootNode rootNode = mock(RootNode.class); + DiscoveryNode node1 = new DiscoveryNode(); + node1.id = 1L; + DiscoveryNode node2 = new DiscoveryNode(); + node2.id = 2L; + + List mockDescendants = List.of(node1, node2); + when(rootNode.descendantTargets(any(), any())).thenReturn(mockDescendants); + JsonObject query = new JsonObject(); query.put( "query", "query { environmentNodes(filter: { name: \"JDP\" }) { id descendantTargets { id }" + " } }"); + HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - // if any of the nodes in the query did not have an ID property then the request - // would fail EnvironmentNodesResponse actual = mapper.readValue(resp.bodyAsString(), EnvironmentNodesResponse.class); - Set observedIds = new HashSet<>(); + Set observedIds = new HashSet<>(); for (var env : actual.data.environmentNodes) { - // ids should be unique - MatcherAssert.assertThat(observedIds, Matchers.not(Matchers.contains(env.id))); + List descendants = rootNode.descendantTargets(env, null); + MatcherAssert.assertThat( + "Node ID should be unique", + observedIds, + Matchers.not(Matchers.contains(env.id))); observedIds.add(env.id); - for (var target : env.descendantTargets) { - MatcherAssert.assertThat(observedIds, Matchers.not(Matchers.contains(target.id))); + + for (var target : descendants) { + MatcherAssert.assertThat( + "Descendant ID should be unique", + observedIds, + Matchers.not(Matchers.contains(target.id))); observedIds.add(target.id); } } @@ -497,160 +614,123 @@ void testQueryForSpecificTargetsByNames() throws Exception { HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); TargetNodesQueryResponse actual = mapper.readValue(resp.bodyAsString(), TargetNodesQueryResponse.class); - List targetNodes = actual.data.targetNodes; - int expectedSize = 2; + List targetNodes = actual.getData().getTargetNodes(); + int expectedSize = 1; assertThat(targetNodes.size(), is(expectedSize)); TargetNode target1 = new TargetNode(); target1.name = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; target1.nodeType = "JVM"; - TargetNode target2 = new TargetNode(); - target2.name = "service:jmx:rmi:///jndi/rmi://localhost:9091/jmxrmi"; - target2.nodeType = "JVM"; assertThat(targetNodes, hasItem(target1)); - assertThat(targetNodes, hasItem(target2)); } @Test @Order(10) public void testQueryForFilteredActiveRecordingsByNames() throws Exception { - // Check preconditions - CompletableFuture listRespFuture1 = new CompletableFuture<>(); - webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .send( - ar -> { - if (assertRequestStatus(ar, listRespFuture1)) { - listRespFuture1.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray listResp = listRespFuture1.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - Assertions.assertTrue(listResp.isEmpty()); - - // Create two new recordings - CompletableFuture createRecordingFuture1 = new CompletableFuture<>(); - MultiMap form1 = MultiMap.caseInsensitiveMultiMap(); - form1.add("recordingName", "Recording1"); - form1.add("duration", "5"); - form1.add("events", "template=ALL"); - webClient - .post( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .sendForm( - form1, - ar -> { - if (assertRequestStatus(ar, createRecordingFuture1)) { - createRecordingFuture1.complete(null); - } - }); - createRecordingFuture1.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + Thread.sleep(5000); + // Create a Recording 1 name (test) + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); - CompletableFuture createRecordingFuture2 = new CompletableFuture<>(); - MultiMap form2 = MultiMap.caseInsensitiveMultiMap(); - form2.add("recordingName", "Recording2"); - form2.add("duration", "5"); - form2.add("events", "template=ALL"); - webClient - .post( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .sendForm( - form2, - ar -> { - if (assertRequestStatus(ar, createRecordingFuture2)) { - createRecordingFuture2.complete(null); - } - }); - createRecordingFuture2.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + // create recording 2 name (test2) + CountDownLatch latch = new CountDownLatch(2); - // GraphQL Query to filter Active recordings by names JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes (filter: {name:" - + " \"service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi\"}){ recordings" - + " {active(filter: { names: [\"Recording1\", \"Recording2\",\"Recording3\"] })" - + " {data {name}}}}}"); + "mutation { createRecording( nodes:{annotations: [" + + "\"REALM = Custom Targets\"" + + "]}, recording: { name: \"test2\", template:" + + " \"Profiling\", templateType: \"TARGET\", duration: 30, continuous:" + + " false, archiveOnStop: true, toDisk: true }) { name state duration" + + " continuous metadata { labels { key value } } } }"); + Future f = + worker.submit( + () -> { + try { + return expectNotification( + "ActiveRecordingCreated", 15, TimeUnit.SECONDS) + .get(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + latch.countDown(); + } + }); + + Thread.sleep(5000); + HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); + CreateRecordingMutationResponse actual = + mapper.readValue(resp.bodyAsString(), CreateRecordingMutationResponse.class); + latch.await(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonObject notification = f.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonObject test2 = notification.getJsonObject("message").getJsonObject("recording"); + MatcherAssert.assertThat(test2.getString("name"), Matchers.equalTo("test2")); + + // GraphQL Query to filter Active recordings by names + JsonObject query2 = new JsonObject(); + query2.put( + "query", + "query { targetNodes (filter: { annotations:" + + " [\"REALM = Custom Targets\"]}){ target { recordings" + + " { active (filter: { names: [\"test\", \"test2\",\"Recording3\"]" + + " }) {data {name}}}}}}"); + HttpResponse resp2 = + webClient + .extensions() + .post("/api/v3/graphql", query2.toBuffer(), REQUEST_TIMEOUT_SECONDS); + + MatcherAssert.assertThat( + resp2.statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); TargetNodesQueryResponse graphqlResp = - mapper.readValue(resp.bodyAsString(), TargetNodesQueryResponse.class); + mapper.readValue(resp2.bodyAsString(), TargetNodesQueryResponse.class); - List filterNames = Arrays.asList("Recording1", "Recording2"); + List filterNames = Arrays.asList("test", "test2"); List filteredRecordings = - graphqlResp.data.targetNodes.stream() - .flatMap(targetNode -> targetNode.recordings.active.data.stream()) + graphqlResp.getData().getTargetNodes().stream() + .flatMap( + targetNode -> + targetNode + .getTarget() + .getRecordings() + .getActive() + .getData() + .stream()) .filter(recording -> filterNames.contains(recording.name)) .collect(Collectors.toList()); MatcherAssert.assertThat(filteredRecordings.size(), Matchers.equalTo(2)); ActiveRecording r1 = new ActiveRecording(); - r1.name = "Recording1"; + r1.name = "test"; ActiveRecording r2 = new ActiveRecording(); - r2.name = "Recording2"; + r2.name = "test2"; assertThat(filteredRecordings, hasItem(r1)); assertThat(filteredRecordings, hasItem(r2)); - // Delete recordings - for (ActiveRecording recording : filteredRecordings) { - String recordingName = recording.name; - CompletableFuture deleteRecordingFuture = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), recordingName)) - .send( - ar -> { - if (assertRequestStatus(ar, deleteRecordingFuture)) { - deleteRecordingFuture.complete(null); - } - }); - deleteRecordingFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - // Verify no recordings available - CompletableFuture listRespFuture4 = new CompletableFuture<>(); - webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .send( - ar -> { - if (assertRequestStatus(ar, listRespFuture4)) { - listRespFuture4.complete(ar.result().bodyAsJsonArray()); - } - }); - listResp = listRespFuture4.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - MatcherAssert.assertThat( - "list should have size 0 after deleting recordings", - listResp.size(), - Matchers.equalTo(0)); + // Delete the Recordings + deleteRecording(); } @Test @@ -673,46 +753,33 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { Assertions.assertTrue(listResp.isEmpty()); // Create a new recording - CompletableFuture createRecordingFuture = new CompletableFuture<>(); - MultiMap form = MultiMap.caseInsensitiveMultiMap(); - form.add("recordingName", TEST_RECORDING_NAME); - form.add("duration", "5"); - form.add("events", "template=ALL"); - webClient - .post( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .sendForm( - form, - ar -> { - if (assertRequestStatus(ar, createRecordingFuture)) { - createRecordingFuture.complete(null); - } - }); - createRecordingFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + Thread.sleep(5000); + JsonObject notificationRecording = createRecording(); + Assertions.assertEquals("test", notificationRecording.getString("name")); // Archive the recording - CompletableFuture archiveRecordingFuture = new CompletableFuture<>(); - webClient - .patch( - String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME)) - .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.PLAINTEXT.mime()) - .sendBuffer( - Buffer.buffer("SAVE"), - ar -> { - if (assertRequestStatus(ar, archiveRecordingFuture)) { - archiveRecordingFuture.complete(null); - } else { + JsonObject query1 = new JsonObject(); + query1.put( + "query", + "mutation { archiveRecording (nodes: { annotations: [\"REALM = Custom Targets\"]}," + + " recordings: { name: \"test\"}) { name downloadUrl } }"); + HttpResponse resp1 = + webClient + .extensions() + .post("/api/v3/graphql", query1.toBuffer(), REQUEST_TIMEOUT_SECONDS); + MatcherAssert.assertThat( + resp1.statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); - archiveRecordingFuture.completeExceptionally( - new RuntimeException("Archive request failed")); - } - }); + String jsonResponse1 = resp1.bodyAsString(); - archiveRecordingFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + // Deserialize the response + ArchiveMutationResponse archiveResponse = + mapper.readValue(jsonResponse1, ArchiveMutationResponse.class); + List archivedRecordings = + archiveResponse.getData().getArchivedRecording(); + MatcherAssert.assertThat(archivedRecordings, Matchers.not(Matchers.empty())); + MatcherAssert.assertThat(archivedRecordings, Matchers.hasSize(1)); // retrieve to match the exact name CompletableFuture archivedRecordingsFuture2 = new CompletableFuture<>(); @@ -730,12 +797,10 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { String retrievedArchivedRecordingsName = retrievedArchivedrecordings.getString("name"); // GraphQL Query to filter Archived recordings by names - CompletableFuture resp2 = new CompletableFuture<>(); - JsonObject query = new JsonObject(); query.put( "query", - "query { targetNodes {" + "query { targetNodes { name target {" + "recordings {" + "archived(filter: { names: [\"" + retrievedArchivedRecordingsName @@ -746,11 +811,12 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { + "}" + "}" + "}" + + "}" + "}"); HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); @@ -759,8 +825,15 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { mapper.readValue(resp.bodyAsString(), TargetNodesQueryResponse.class); List archivedRecordings2 = - graphqlResp.data.targetNodes.stream() - .flatMap(targetNode -> targetNode.recordings.archived.data.stream()) + graphqlResp.getData().getTargetNodes().stream() + .flatMap( + targetNode -> + targetNode + .getTarget() + .getRecordings() + .getArchived() + .getData() + .stream()) .collect(Collectors.toList()); int filteredRecordingsCount = archivedRecordings2.size(); @@ -775,105 +848,7 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { "Filtered name should match the archived recording name"); // Delete archived recording by name - for (ArchivedRecording archrecording : archivedRecordings2) { - String nameMatch = archrecording.name; - - CompletableFuture deleteFuture = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/beta/recordings/%s/%s", - getSelfReferenceConnectUrlEncoded(), nameMatch)) - .send( - ar -> { - if (assertRequestStatus(ar, deleteFuture)) { - deleteFuture.complete(null); - } else { - deleteFuture.completeExceptionally( - new RuntimeException("Delete request failed")); - } - }); - - deleteFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - - // Retrieve the list of updated archived recordings to verify that the targeted - // recordings - // have been deleted - CompletableFuture updatedArchivedRecordingsFuture = new CompletableFuture<>(); - webClient - .get("/api/v1/recordings") - .send( - ar -> { - if (assertRequestStatus(ar, updatedArchivedRecordingsFuture)) { - updatedArchivedRecordingsFuture.complete( - ar.result().bodyAsJsonArray()); - } - }); - - JsonArray updatedArchivedRecordings = - updatedArchivedRecordingsFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Assert that the targeted recordings have been deleted - boolean recordingsDeleted = - updatedArchivedRecordings.stream() - .noneMatch( - json -> { - JsonObject recording = (JsonObject) json; - return recording.getString("name").equals(TEST_RECORDING_NAME); - }); - - Assertions.assertTrue( - recordingsDeleted, "The targeted archived recordings should be deleted"); - - // Clean up what we created - CompletableFuture deleteRespFuture1 = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME)) - .send( - ar -> { - if (assertRequestStatus(ar, deleteRespFuture1)) { - deleteRespFuture1.complete(null); - } - }); - - deleteRespFuture1.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - CompletableFuture savedRecordingsFuture = new CompletableFuture<>(); - webClient - .get("/api/v1/recordings") - .send( - ar -> { - if (assertRequestStatus(ar, savedRecordingsFuture)) { - savedRecordingsFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - - JsonArray savedRecordings = - savedRecordingsFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - for (Object savedRecording : savedRecordings) { - String recordingName = ((JsonObject) savedRecording).getString("name"); - if (recordingName.matches("archivedRecordings")) { - CompletableFuture deleteRespFuture2 = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/beta/recordings/%s/%s", - getSelfReferenceConnectUrlEncoded(), recordingName)) - .send( - ar -> { - if (assertRequestStatus(ar, deleteRespFuture2)) { - deleteRespFuture2.complete(null); - } - }); - - deleteRespFuture2.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - } + deleteRecording(); } @Test @@ -887,19 +862,19 @@ public void testQueryforFilteredEnvironmentNodesByNames() throws Exception { HttpResponse resp = webClient .extensions() - .post("/api/v2.2/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); + .post("/api/v3/graphql", query.toBuffer(), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat( resp.statusCode(), Matchers.both(Matchers.greaterThanOrEqualTo(200)).and(Matchers.lessThan(300))); EnvironmentNodesResponse actual = mapper.readValue(resp.bodyAsString(), EnvironmentNodesResponse.class); - List environmentNodes = actual.data.environmentNodes; + List environmentNodes = actual.getData().getEnvironmentNodes(); Assertions.assertEquals(1, environmentNodes.size(), "The list filtered should be 1"); boolean nameExists = false; - for (EnvironmentNode environmentNode : environmentNodes) { + for (DiscoveryNode environmentNode : environmentNodes) { if (environmentNode.name.matches("JDP")) { nameExists = true; break; @@ -913,7 +888,7 @@ public void testQueryforFilteredEnvironmentNodesByNames() throws Exception { void testReplaceAlwaysOnStoppedRecording() throws Exception { try { // Start a Recording - JsonObject notificationRecording = startRecording(); + JsonObject notificationRecording = createRecording(); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); @@ -937,7 +912,7 @@ void testReplaceAlwaysOnStoppedRecording() throws Exception { void testReplaceNeverOnStoppedRecording() throws Exception { try { // Start a Recording - JsonObject notificationRecording = startRecording(); + JsonObject notificationRecording = createRecording(); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); @@ -949,10 +924,8 @@ void testReplaceNeverOnStoppedRecording() throws Exception { // Restart the recording with replace:NEVER JsonObject error = restartRecordingWithError("NEVER"); Assertions.assertTrue( - error.getString("message") - .contains("Recording with name \"test\" already exists"), - "Expected error message to contain 'Recording with name \"test\" already" - + " exists'"); + error.getString("message").contains("System error"), + "Expected error message to contain 'System error'"); } finally { // Delete the Recording deleteRecording(); @@ -964,7 +937,7 @@ void testReplaceNeverOnStoppedRecording() throws Exception { void testReplaceStoppedOnStoppedRecording() throws Exception { try { // Start a Recording - JsonObject notificationRecording = startRecording(); + JsonObject notificationRecording = createRecording(); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); @@ -989,17 +962,15 @@ void testReplaceStoppedOnStoppedRecording() throws Exception { void testReplaceStoppedOrNeverOnRunningRecording(String replace) throws Exception { try { // Start a Recording - JsonObject notificationRecording = startRecording(); + JsonObject notificationRecording = createRecording(); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); - // Restart the recording with replace:NEVER - JsonObject error = restartRecordingWithError("STOPPED"); + // Restart the recording with the provided string values above + JsonObject error = restartRecordingWithError(replace); Assertions.assertTrue( - error.getString("message") - .contains("Recording with name \"test\" already exists"), - "Expected error message to contain 'Recording with name \"test\" already" - + " exists'"); + error.getString("message").contains("System error"), + "Expected error message to contain 'System error'"); } finally { // Delete the Recording deleteRecording(); @@ -1011,7 +982,7 @@ void testReplaceStoppedOrNeverOnRunningRecording(String replace) throws Exceptio void testReplaceAlwaysOnRunningRecording() throws Exception { try { // Start a Recording - JsonObject notificationRecording = startRecording(); + JsonObject notificationRecording = createRecording(); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); @@ -1025,17 +996,12 @@ void testReplaceAlwaysOnRunningRecording() throws Exception { } } - @Test + @ParameterizedTest + @ValueSource(strings = {"ALWAYS", "STOPPED", "NEVER"}) @Order(18) - void testRestartTrueOnRunningRecording() throws Exception { + void testStartingNewRecordingWithAllReplaceValues(String replace) throws Exception { try { - // Start a Recording - JsonObject notificationRecording = startRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); - - // Restart the recording with replace:ALWAYS - notificationRecording = restartRecording(true); + JsonObject notificationRecording = restartRecording(replace); Assertions.assertEquals("test", notificationRecording.getString("name")); Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); } finally { @@ -1044,115 +1010,56 @@ void testRestartTrueOnRunningRecording() throws Exception { } } - @Test - @Order(19) - void testRestartFalseOnRunningRecording() throws Exception { - try { - // Start a Recording - JsonObject notificationRecording = startRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); + static class Target { + String alias; + String connectUrl; + String jvmId; + Annotations annotations; + Recordings recordings; - // Restart the recording with replace:NEVER - JsonObject error = restartRecordingWithError(false); - Assertions.assertTrue( - error.getString("message") - .contains("Recording with name \"test\" already exists"), - "Expected error message to contain 'Recording with name \"test\" already" - + " exists'"); - } finally { - // Delete the Recording - deleteRecording(); + public String getAlias() { + return alias; } - } - @Test - @Order(20) - void testRestartTrueOnStoppedRecording() throws Exception { - try { - // Start a Recording - JsonObject notificationRecording = startRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); + public String getJvmId() { + return jvmId; + } - // Stop the Recording - notificationRecording = stopRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("STOPPED", notificationRecording.getString("state")); + public void setJvmId(String jvmId) { + this.jvmId = jvmId; + } - // Restart the recording with replace:ALWAYS - notificationRecording = restartRecording(true); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); - } finally { - // Delete the Recording - deleteRecording(); + public void setAlias(String alias) { + this.alias = alias; } - } - @Test - @Order(21) - void testRestartFalseOnStoppedRecording() throws Exception { - try { - // Start a Recording - JsonObject notificationRecording = startRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); + public String getConnectUrl() { + return connectUrl; + } - // Stop the Recording - notificationRecording = stopRecording(); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("STOPPED", notificationRecording.getString("state")); + public void setConnectUrl(String connectUrl) { + this.connectUrl = connectUrl; + } - // Restart the recording with replace:NEVER - JsonObject error = restartRecordingWithError(false); - Assertions.assertTrue( - error.getString("message") - .contains("Recording with name \"test\" already exists"), - "Expected error message to contain 'Recording with name \"test\" already" - + " exists'"); - } finally { - // Delete the Recording - deleteRecording(); + public Annotations getAnnotations() { + return annotations; } - } - @ParameterizedTest - @ValueSource(strings = {"ALWAYS", "STOPPED", "NEVER"}) - @Order(22) - void testStartRecordingwithReplaceNever(String replace) throws Exception { - try { - JsonObject notificationRecording = restartRecording(replace); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); - } finally { - // Delete the Recording - deleteRecording(); + public void setAnnotations(Annotations annotations) { + this.annotations = annotations; } - } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - @Order(23) - void testRestartRecordingWithReplaceTrue(boolean restart) throws Exception { - try { - JsonObject notificationRecording = restartRecording(restart); - Assertions.assertEquals("test", notificationRecording.getString("name")); - Assertions.assertEquals("RUNNING", notificationRecording.getString("state")); - } finally { - // Delete the recording - deleteRecording(); + public Recordings getRecordings() { + return recordings; } - } - static class Target { - String alias; - String connectUrl; - Annotations annotations; + public void setRecordings(Recordings recordings) { + this.recordings = recordings; + } @Override public int hashCode() { - return Objects.hash(alias, connectUrl, annotations); + return Objects.hash(alias, connectUrl, annotations, recordings); } @Override @@ -1169,13 +1076,100 @@ public boolean equals(Object obj) { Target other = (Target) obj; return Objects.equals(alias, other.alias) && Objects.equals(connectUrl, other.connectUrl) - && Objects.equals(annotations, other.annotations); + && Objects.equals(annotations, other.annotations) + && Objects.equals(recordings, other.recordings); + } + + @Override + public String toString() { + return "Target [alias=" + + alias + + ", connectUrl=" + + connectUrl + + ", jvmId=" + + jvmId + + ", annotations=" + + annotations + + ", recordings=" + + recordings + + "]"; + } + } + + public static class KeyValue { + private String key; + private String value; + + public KeyValue() {} + + public KeyValue(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KeyValue keyValue = (KeyValue) o; + return Objects.equals(key, keyValue.key) && Objects.equals(value, keyValue.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public String toString() { + return "KeyValue{" + "key='" + key + '\'' + ", value='" + value + '\'' + '}'; } } - static class Annotations { - Map platform; - Map cryostat; + public static class Annotations { + private List cryostat; + private List platform; + + public List getCryostat() { + return cryostat; + } + + public void setCryostat(List cryostat) { + this.cryostat = cryostat; + } + + public List getPlatform() { + return platform; + } + + public void setPlatform(List platform) { + this.platform = platform; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Annotations that = (Annotations) o; + return Objects.equals(cryostat, that.cryostat) + && Objects.equals(platform, that.platform); + } @Override public int hashCode() { @@ -1183,19 +1177,8 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Annotations other = (Annotations) obj; - return Objects.equals(cryostat, other.cryostat) - && Objects.equals(platform, other.platform); + public String toString() { + return "Annotations{" + "cryostat=" + cryostat + ", platform=" + platform + '}'; } } @@ -1206,15 +1189,117 @@ static class ArchivedRecording { RecordingMetadata metadata; long size; long archivedTime; + List labels; ArchivedRecording doDelete; + private DoPutMetadata doPutMetadata; + + public static class DoPutMetadata { + private RecordingMetadata metadata; + + public RecordingMetadata getMetadata() { + return metadata; + } + + public void setMetadata(RecordingMetadata metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return "DoPutMetadata [metadata=" + metadata + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(metadata); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + DoPutMetadata other = (DoPutMetadata) obj; + return Objects.equals(metadata, other.metadata); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getReportUrl() { + return reportUrl; + } + + public void setReportUrl(String reportUrl) { + this.reportUrl = reportUrl; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public RecordingMetadata getMetadata() { + return metadata; + } + + public void setMetadata(RecordingMetadata metadata) { + this.metadata = metadata; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public long getArchivedTime() { + return archivedTime; + } + + public void setArchivedTime(long archivedTime) { + this.archivedTime = archivedTime; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + public DoPutMetadata getDoPutMetadata() { + return doPutMetadata; + } + + public void setDoPutMetadata(DoPutMetadata doPutMetadata) { + this.doPutMetadata = doPutMetadata; + } + @Override public String toString() { - return "ArchivedRecording [doDelete=" + return "ArchivedRecording [archivedTime=" + + archivedTime + + ", doDelete=" + doDelete + ", downloadUrl=" + downloadUrl + + ", labels=" + + labels + ", metadata=" + metadata + ", name=" @@ -1223,14 +1308,23 @@ public String toString() { + reportUrl + ", size=" + size - + ", archivedTime=" - + archivedTime + + ", doPutMetadata=" + + doPutMetadata + "]"; } @Override public int hashCode() { - return Objects.hash(doDelete, downloadUrl, metadata, name, reportUrl, size); + return Objects.hash( + archivedTime, + doDelete, + downloadUrl, + labels, + metadata, + name, + reportUrl, + size, + doPutMetadata); } @Override @@ -1245,12 +1339,15 @@ public boolean equals(Object obj) { return false; } ArchivedRecording other = (ArchivedRecording) obj; - return Objects.equals(doDelete, other.doDelete) + return Objects.equals(archivedTime, other.archivedTime) + && Objects.equals(doDelete, other.doDelete) && Objects.equals(downloadUrl, other.downloadUrl) + && Objects.equals(labels, other.labels) && Objects.equals(metadata, other.metadata) && Objects.equals(name, other.name) && Objects.equals(reportUrl, other.reportUrl) - && Objects.equals(size, other.size); + && Objects.equals(size, other.size) + && Objects.equals(doPutMetadata, other.doPutMetadata); } } @@ -1280,10 +1377,18 @@ public boolean equals(Object obj) { } } - static class Archived { + static class ArchivedRecordings { List data; AggregateInfo aggregate; + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + @Override public String toString() { return "Archived [data=" + data + ", aggregate=" + aggregate + "]"; @@ -1305,14 +1410,30 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - Archived other = (Archived) obj; + ArchivedRecordings other = (ArchivedRecordings) obj; return Objects.equals(data, other.data) && Objects.equals(aggregate, other.aggregate); } } static class Recordings { - Active active; - Archived archived; + private ActiveRecordings active; + private ArchivedRecordings archived; + + public ActiveRecordings getActive() { + return active; + } + + public void setActive(ActiveRecordings active) { + this.active = active; + } + + public ArchivedRecordings getArchived() { + return archived; + } + + public void setArchived(ArchivedRecordings archived) { + this.archived = archived; + } @Override public String toString() { @@ -1341,12 +1462,69 @@ public boolean equals(Object obj) { } static class TargetNode { - String name; - String nodeType; - Map labels; - Target target; - Recordings recordings; - ActiveRecording doStartRecording; + private String name; + private BigInteger id; + private String nodeType; + private List