diff --git a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java index 4930f71adf..e3c5917803 100644 --- a/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java +++ b/src/main/java/io/cryostat/net/web/http/api/v2/TargetsPostHandler.java @@ -40,6 +40,8 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import javax.inject.Inject; @@ -49,6 +51,7 @@ import io.cryostat.net.web.http.api.ApiVersion; import io.cryostat.platform.PlatformClient; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.platform.internal.CustomTargetPlatformClient; import io.cryostat.util.URIUtil; @@ -128,7 +131,18 @@ public IntermediateResponse handle(RequestParameters params) throws throw new ApiException(400, "Duplicate connectUrl"); } } + Map cryostatAnnotations = new HashMap<>(); ServiceRef serviceRef = new ServiceRef(uri, alias); + for (AnnotationKey ak : AnnotationKey.values()) { + // TODO is there a good way to determine this prefix from the structure of the + // ServiceRef's serialized form? + String formKey = "annotations.cryostat." + ak.name(); + if (attrs.contains(formKey)) { + cryostatAnnotations.put(ak, attrs.get(formKey)); + } + } + serviceRef.setCryostatAnnotations(cryostatAnnotations); + boolean v = customTargetPlatformClient.addTarget(serviceRef); if (!v) { throw new ApiException(400, "Duplicate connectUrl"); diff --git a/src/main/java/io/cryostat/platform/ServiceRef.java b/src/main/java/io/cryostat/platform/ServiceRef.java index ed8066f198..e9e32f1481 100644 --- a/src/main/java/io/cryostat/platform/ServiceRef.java +++ b/src/main/java/io/cryostat/platform/ServiceRef.java @@ -38,6 +38,10 @@ package io.cryostat.platform; import java.net.URI; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import com.google.gson.annotations.SerializedName; @@ -49,6 +53,8 @@ public class ServiceRef { private final @SerializedName("connectUrl") URI serviceUri; private final String alias; + private final Map labels = new HashMap<>(); + private final Annotations annotations = new Annotations(); public ServiceRef(URI uri, String alias) { this.serviceUri = uri; @@ -63,18 +69,111 @@ public Optional getAlias() { return Optional.ofNullable(alias); } + public void setLabels(Map labels) { + this.labels.clear(); + this.labels.putAll(labels); + } + + public Map getLabels() { + return Collections.unmodifiableMap(labels); + } + + public void setPlatformAnnotations(Map annotations) { + this.annotations.platform.clear(); + this.annotations.platform.putAll(annotations); + } + + public Map getPlatformAnnotations() { + return new HashMap<>(annotations.platform); + } + + public void setCryostatAnnotations(Map annotations) { + this.annotations.cryostat.clear(); + this.annotations.cryostat.putAll(annotations); + } + + public Map getCryostatAnnotations() { + return new HashMap<>(annotations.cryostat); + } + @Override - public boolean equals(Object o) { - return EqualsBuilder.reflectionEquals(this, o); + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (other == this) { + return true; + } + if (!(other instanceof ServiceRef)) { + return false; + } + ServiceRef sr = (ServiceRef) other; + return new EqualsBuilder() + .append(serviceUri, sr.serviceUri) + .append(alias, sr.alias) + .append(labels, sr.labels) + .append(annotations, sr.annotations) + .build(); } @Override public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + return new HashCodeBuilder() + .append(serviceUri) + .append(alias) + .append(labels) + .append(annotations) + .toHashCode(); } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } + + private static class Annotations { + private final Map platform = new HashMap<>(); + private final Map cryostat = new EnumMap<>(AnnotationKey.class); + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (other == this) { + return true; + } + if (!(other instanceof Annotations)) { + return false; + } + Annotations o = (Annotations) other; + return new EqualsBuilder() + .append(platform, o.platform) + .append(cryostat, o.cryostat) + .build(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(platform).append(cryostat).toHashCode(); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + } + + public enum AnnotationKey { + HOST, + PORT, + JAVA_MAIN, + PID, + START_TIME, + NAMESPACE, + SERVICE_NAME, + CONTAINER_NAME, + POD_NAME, + ; + } } diff --git a/src/main/java/io/cryostat/platform/internal/DefaultPlatformClient.java b/src/main/java/io/cryostat/platform/internal/DefaultPlatformClient.java index df634aa9de..055819bae2 100644 --- a/src/main/java/io/cryostat/platform/internal/DefaultPlatformClient.java +++ b/src/main/java/io/cryostat/platform/internal/DefaultPlatformClient.java @@ -39,15 +39,21 @@ import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.management.remote.JMXServiceURL; + import io.cryostat.core.log.Logger; +import io.cryostat.core.net.discovery.DiscoveredJvmDescriptor; import io.cryostat.core.net.discovery.JvmDiscoveryClient; import io.cryostat.core.net.discovery.JvmDiscoveryClient.JvmDiscoveryEvent; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.util.URIUtil; class DefaultPlatformClient extends AbstractPlatformClient implements Consumer { @@ -69,11 +75,7 @@ public void start() throws IOException { @Override public void accept(JvmDiscoveryEvent evt) { try { - ServiceRef serviceRef = - new ServiceRef( - URIUtil.convert(evt.getJvmDescriptor().getJmxServiceUrl()), - evt.getJvmDescriptor().getMainClass()); - notifyAsyncTargetDiscovery(evt.getEventKind(), serviceRef); + notifyAsyncTargetDiscovery(evt.getEventKind(), convert(evt.getJvmDescriptor())); } catch (MalformedURLException | URISyntaxException e) { logger.warn(e); } @@ -83,10 +85,9 @@ public void accept(JvmDiscoveryEvent evt) { public List listDiscoverableServices() { return discoveryClient.getDiscoveredJvmDescriptors().stream() .map( - u -> { + desc -> { try { - return new ServiceRef( - URIUtil.convert(u.getJmxServiceUrl()), u.getMainClass()); + return convert(desc); } catch (MalformedURLException | URISyntaxException e) { logger.warn(e); return null; @@ -95,4 +96,17 @@ public List listDiscoverableServices() { .filter(s -> s != null) .collect(Collectors.toList()); } + + private static ServiceRef convert(DiscoveredJvmDescriptor desc) + throws MalformedURLException, URISyntaxException { + JMXServiceURL serviceUrl = desc.getJmxServiceUrl(); + ServiceRef serviceRef = new ServiceRef(URIUtil.convert(serviceUrl), desc.getMainClass()); + URI rmiTarget = URIUtil.getRmiTarget(serviceUrl); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.JAVA_MAIN, desc.getMainClass(), + AnnotationKey.HOST, rmiTarget.getHost(), + AnnotationKey.PORT, Integer.toString(rmiTarget.getPort()))); + return serviceRef; + } } diff --git a/src/main/java/io/cryostat/platform/internal/KubeApiPlatformClient.java b/src/main/java/io/cryostat/platform/internal/KubeApiPlatformClient.java index 81bbc63747..df22ffbb50 100644 --- a/src/main/java/io/cryostat/platform/internal/KubeApiPlatformClient.java +++ b/src/main/java/io/cryostat/platform/internal/KubeApiPlatformClient.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -48,6 +49,7 @@ import io.cryostat.core.net.JFRConnectionToolkit; import io.cryostat.core.net.discovery.JvmDiscoveryClient.EventKind; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.util.URIUtil; import dagger.Lazy; @@ -171,13 +173,25 @@ private List createServiceRefs(EndpointSubset subset, EndpointPort p .map( addr -> { try { - return new ServiceRef( - URIUtil.convert( - connectionToolkit - .get() - .createServiceURL( - addr.getIp(), port.getPort())), - addr.getTargetRef().getName()); + ServiceRef serviceRef = + new ServiceRef( + URIUtil.convert( + connectionToolkit + .get() + .createServiceURL( + addr.getIp(), + port.getPort())), + addr.getTargetRef().getName()); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.HOST, addr.getIp(), + AnnotationKey.PORT, + Integer.toString(port.getPort()), + AnnotationKey.NAMESPACE, + addr.getTargetRef().getNamespace(), + AnnotationKey.POD_NAME, + addr.getTargetRef().getName())); + return serviceRef; } catch (Exception e) { logger.warn(e); return null; diff --git a/src/main/java/io/cryostat/util/URIUtil.java b/src/main/java/io/cryostat/util/URIUtil.java index 4a041e90e3..0d7c54756c 100644 --- a/src/main/java/io/cryostat/util/URIUtil.java +++ b/src/main/java/io/cryostat/util/URIUtil.java @@ -56,4 +56,13 @@ public static URI createAbsolute(String uri) throws URISyntaxException, Relative public static URI convert(JMXServiceURL serviceUrl) throws URISyntaxException { return new URI(serviceUrl.toString()); } + + public static URI getRmiTarget(JMXServiceURL serviceUrl) throws URISyntaxException { + String rmiPart = "/jndi/rmi://"; + String pathPart = serviceUrl.getURLPath(); + if (!pathPart.startsWith(rmiPart)) { + throw new IllegalArgumentException(serviceUrl.getURLPath()); + } + return new URI(pathPart.substring("/jndi/".length(), pathPart.length())); + } } diff --git a/src/test/java/io/cryostat/net/web/http/api/v2/TargetsPostHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v2/TargetsPostHandlerTest.java index f108b53877..cc63a05d82 100644 --- a/src/test/java/io/cryostat/net/web/http/api/v2/TargetsPostHandlerTest.java +++ b/src/test/java/io/cryostat/net/web/http/api/v2/TargetsPostHandlerTest.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.net.URI; import java.util.List; +import java.util.Map; import java.util.Optional; import io.cryostat.MainModule; @@ -49,6 +50,7 @@ import io.cryostat.net.web.http.api.ApiVersion; import io.cryostat.platform.PlatformClient; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.platform.internal.CustomTargetPlatformClient; import com.google.gson.Gson; @@ -140,6 +142,8 @@ void testSuccessfulRequest() throws Exception { ServiceRef captured = refCaptor.getValue(); MatcherAssert.assertThat(captured.getServiceUri(), Matchers.equalTo(new URI(connectUrl))); MatcherAssert.assertThat(captured.getAlias(), Matchers.equalTo(Optional.of(alias))); + MatcherAssert.assertThat(captured.getPlatformAnnotations(), Matchers.equalTo(Map.of())); + MatcherAssert.assertThat(captured.getCryostatAnnotations(), Matchers.equalTo(Map.of())); MatcherAssert.assertThat(response.getBody(), Matchers.equalTo(captured)); } @@ -186,6 +190,38 @@ void testRequestWithDuplicateTarget() throws IOException { MatcherAssert.assertThat(ex.getStatusCode(), Matchers.equalTo(400)); } + @Test + void testRequestWithAdditionalAnnotations() throws Exception { + MultiMap attrs = MultiMap.caseInsensitiveMultiMap(); + RequestParameters params = Mockito.mock(RequestParameters.class); + Mockito.when(params.getFormAttributes()).thenReturn(attrs); + String connectUrl = "service:jmx:rmi:///jndi/rmi://cryostat:9099/jmxrmi"; + String alias = "TestTarget"; + + attrs.set("connectUrl", connectUrl); + attrs.set("alias", alias); + attrs.set("annotations.cryostat.HOST", "app.example.com"); + attrs.set("annotations.cryostat.PID", "1234"); + attrs.set("annotations.cryostat.MADEUPKEY", "should not appear"); + + Mockito.when(customTargetPlatformClient.addTarget(Mockito.any())).thenReturn(true); + + IntermediateResponse response = handler.handle(params); + MatcherAssert.assertThat(response.getStatusCode(), Matchers.equalTo(200)); + + ArgumentCaptor refCaptor = ArgumentCaptor.forClass(ServiceRef.class); + Mockito.verify(customTargetPlatformClient).addTarget(refCaptor.capture()); + ServiceRef captured = refCaptor.getValue(); + MatcherAssert.assertThat(captured.getServiceUri(), Matchers.equalTo(new URI(connectUrl))); + MatcherAssert.assertThat(captured.getAlias(), Matchers.equalTo(Optional.of(alias))); + MatcherAssert.assertThat(captured.getPlatformAnnotations(), Matchers.equalTo(Map.of())); + MatcherAssert.assertThat( + captured.getCryostatAnnotations(), + Matchers.equalTo( + Map.of(AnnotationKey.HOST, "app.example.com", AnnotationKey.PID, "1234"))); + MatcherAssert.assertThat(response.getBody(), Matchers.equalTo(captured)); + } + @Test void testRequestWithIOException() throws IOException { MultiMap attrs = MultiMap.caseInsensitiveMultiMap(); diff --git a/src/test/java/io/cryostat/platform/internal/DefaultPlatformClientTest.java b/src/test/java/io/cryostat/platform/internal/DefaultPlatformClientTest.java index 21b8354877..a39c22b665 100644 --- a/src/test/java/io/cryostat/platform/internal/DefaultPlatformClientTest.java +++ b/src/test/java/io/cryostat/platform/internal/DefaultPlatformClientTest.java @@ -45,6 +45,7 @@ import java.net.MalformedURLException; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -56,6 +57,7 @@ import io.cryostat.core.net.discovery.JvmDiscoveryClient.EventKind; import io.cryostat.core.net.discovery.JvmDiscoveryClient.JvmDiscoveryEvent; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.platform.TargetDiscoveryEvent; import io.cryostat.util.URIUtil; @@ -116,8 +118,18 @@ void testDiscoverableServiceMapping() throws Exception { ServiceRef exp1 = new ServiceRef(URIUtil.convert(desc1.getJmxServiceUrl()), desc1.getMainClass()); + exp1.setCryostatAnnotations( + Map.of( + AnnotationKey.JAVA_MAIN, desc1.getMainClass(), + AnnotationKey.HOST, "cryostat", + AnnotationKey.PORT, "9091")); ServiceRef exp2 = new ServiceRef(URIUtil.convert(desc3.getJmxServiceUrl()), desc3.getMainClass()); + exp2.setCryostatAnnotations( + Map.of( + AnnotationKey.JAVA_MAIN, desc3.getMainClass(), + AnnotationKey.HOST, "cryostat", + AnnotationKey.PORT, "9092")); assertThat(results, equalTo(List.of(exp1, exp2))); } @@ -125,9 +137,9 @@ void testDiscoverableServiceMapping() throws Exception { @Test void testAcceptDiscoveryEvent() throws Exception { JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi"); - String mainClass = "com.example.Main"; + String javaMain = "com.example.Main"; DiscoveredJvmDescriptor desc = mock(DiscoveredJvmDescriptor.class); - when(desc.getMainClass()).thenReturn(mainClass); + when(desc.getMainClass()).thenReturn(javaMain); when(desc.getJmxServiceUrl()).thenReturn(url); JvmDiscoveryEvent evt = mock(JvmDiscoveryEvent.class); when(evt.getEventKind()).thenReturn(EventKind.FOUND); @@ -139,10 +151,15 @@ void testAcceptDiscoveryEvent() throws Exception { client.accept(evt); verifyNoInteractions(discoveryClient); + TargetDiscoveryEvent event = future.get(1, TimeUnit.SECONDS); MatcherAssert.assertThat(event.getEventKind(), Matchers.equalTo(EventKind.FOUND)); - MatcherAssert.assertThat( - event.getServiceRef(), - Matchers.equalTo(new ServiceRef(URIUtil.convert(url), mainClass))); + ServiceRef serviceRef = new ServiceRef(URIUtil.convert(url), javaMain); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.JAVA_MAIN, "com.example.Main", + AnnotationKey.HOST, "cryostat", + AnnotationKey.PORT, "9091")); + MatcherAssert.assertThat(event.getServiceRef(), Matchers.equalTo(serviceRef)); } } diff --git a/src/test/java/io/cryostat/platform/internal/KubeApiPlatformClientTest.java b/src/test/java/io/cryostat/platform/internal/KubeApiPlatformClientTest.java index e1f6509658..46301200d6 100644 --- a/src/test/java/io/cryostat/platform/internal/KubeApiPlatformClientTest.java +++ b/src/test/java/io/cryostat/platform/internal/KubeApiPlatformClientTest.java @@ -40,6 +40,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -51,6 +52,7 @@ import io.cryostat.core.net.discovery.JvmDiscoveryClient.EventKind; import io.cryostat.core.sys.Environment; import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; import io.cryostat.platform.TargetDiscoveryEvent; import io.cryostat.util.URIUtil; @@ -131,10 +133,13 @@ void shouldReturnListOfMatchingEndpointRefs() throws Exception { // Mockito.when(objRef1.getName()).thenReturn("targetA"); ObjectReference objRef2 = Mockito.mock(ObjectReference.class); Mockito.when(objRef2.getName()).thenReturn("targetB"); + Mockito.when(objRef2.getNamespace()).thenReturn("myproject"); ObjectReference objRef3 = Mockito.mock(ObjectReference.class); Mockito.when(objRef3.getName()).thenReturn("targetC"); + Mockito.when(objRef3.getNamespace()).thenReturn("myproject"); ObjectReference objRef4 = Mockito.mock(ObjectReference.class); Mockito.when(objRef4.getName()).thenReturn("targetD"); + Mockito.when(objRef4.getNamespace()).thenReturn("myproject"); EndpointAddress address1 = Mockito.mock(EndpointAddress.class); // Mockito.when(address1.getIp()).thenReturn("127.0.0.1"); @@ -197,18 +202,36 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { connectionToolkit.createServiceURL( address2.getIp(), port2.getPort())), address2.getTargetRef().getName()); + serv1.setCryostatAnnotations( + Map.of( + AnnotationKey.HOST, address2.getIp(), + AnnotationKey.PORT, Integer.toString(port2.getPort()), + AnnotationKey.NAMESPACE, address2.getTargetRef().getNamespace(), + AnnotationKey.POD_NAME, address2.getTargetRef().getName())); ServiceRef serv2 = new ServiceRef( URIUtil.convert( connectionToolkit.createServiceURL( address3.getIp(), port2.getPort())), address3.getTargetRef().getName()); + serv2.setCryostatAnnotations( + Map.of( + AnnotationKey.HOST, address3.getIp(), + AnnotationKey.PORT, Integer.toString(port2.getPort()), + AnnotationKey.NAMESPACE, address3.getTargetRef().getNamespace(), + AnnotationKey.POD_NAME, address3.getTargetRef().getName())); ServiceRef serv3 = new ServiceRef( URIUtil.convert( connectionToolkit.createServiceURL( address4.getIp(), port3.getPort())), address4.getTargetRef().getName()); + serv3.setCryostatAnnotations( + Map.of( + AnnotationKey.HOST, address4.getIp(), + AnnotationKey.PORT, Integer.toString(port3.getPort()), + AnnotationKey.NAMESPACE, address4.getTargetRef().getNamespace(), + AnnotationKey.POD_NAME, address4.getTargetRef().getName())); MatcherAssert.assertThat(result, Matchers.equalTo(Arrays.asList(serv1, serv2, serv3))); } @@ -254,6 +277,7 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { ObjectReference objRef = Mockito.mock(ObjectReference.class); Mockito.when(objRef.getName()).thenReturn("targetA"); + Mockito.when(objRef.getNamespace()).thenReturn("myproject"); EndpointAddress address = Mockito.mock(EndpointAddress.class); Mockito.when(address.getIp()).thenReturn("127.0.0.1"); Mockito.when(address.getTargetRef()).thenReturn(objRef); @@ -285,6 +309,12 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { connectionToolkit.createServiceURL( address.getIp(), port.getPort())), address.getTargetRef().getName()); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.NAMESPACE, "myproject", + AnnotationKey.POD_NAME, "targetA", + AnnotationKey.PORT, "9999", + AnnotationKey.HOST, "127.0.0.1")); TargetDiscoveryEvent event = future.get(1, TimeUnit.SECONDS); MatcherAssert.assertThat(event.getEventKind(), Matchers.equalTo(EventKind.FOUND)); MatcherAssert.assertThat(event.getServiceRef(), Matchers.equalTo(serviceRef)); @@ -298,7 +328,7 @@ public void shouldNotifyOnAsyncDeleted() throws Exception { Mockito.when(connectionToolkit.createServiceURL(Mockito.anyString(), Mockito.anyInt())) .thenAnswer( - new Answer<>() { + new Answer() { @Override public JMXServiceURL answer(InvocationOnMock args) throws Throwable { String host = args.getArgument(0); @@ -313,6 +343,7 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { ObjectReference objRef = Mockito.mock(ObjectReference.class); Mockito.when(objRef.getName()).thenReturn("targetA"); + Mockito.when(objRef.getNamespace()).thenReturn("myproject"); EndpointAddress address = Mockito.mock(EndpointAddress.class); Mockito.when(address.getIp()).thenReturn("127.0.0.1"); Mockito.when(address.getTargetRef()).thenReturn(objRef); @@ -344,6 +375,12 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { connectionToolkit.createServiceURL( address.getIp(), port.getPort())), address.getTargetRef().getName()); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.NAMESPACE, "myproject", + AnnotationKey.POD_NAME, "targetA", + AnnotationKey.PORT, "9999", + AnnotationKey.HOST, "127.0.0.1")); TargetDiscoveryEvent event = future.get(1, TimeUnit.SECONDS); MatcherAssert.assertThat(event.getEventKind(), Matchers.equalTo(EventKind.LOST)); MatcherAssert.assertThat(event.getServiceRef(), Matchers.equalTo(serviceRef)); @@ -372,6 +409,7 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { ObjectReference objRef = Mockito.mock(ObjectReference.class); Mockito.when(objRef.getName()).thenReturn("targetA"); + Mockito.when(objRef.getNamespace()).thenReturn("myproject"); EndpointAddress address = Mockito.mock(EndpointAddress.class); Mockito.when(address.getIp()).thenReturn("127.0.0.1"); Mockito.when(address.getTargetRef()).thenReturn(objRef); @@ -403,6 +441,12 @@ public JMXServiceURL answer(InvocationOnMock args) throws Throwable { connectionToolkit.createServiceURL( address.getIp(), port.getPort())), address.getTargetRef().getName()); + serviceRef.setCryostatAnnotations( + Map.of( + AnnotationKey.HOST, address.getIp(), + AnnotationKey.PORT, Integer.toString(port.getPort()), + AnnotationKey.NAMESPACE, address.getTargetRef().getNamespace(), + AnnotationKey.POD_NAME, address.getTargetRef().getName())); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(TargetDiscoveryEvent.class); diff --git a/src/test/java/io/cryostat/util/URIUtilTest.java b/src/test/java/io/cryostat/util/URIUtilTest.java new file mode 100644 index 0000000000..abdfe92921 --- /dev/null +++ b/src/test/java/io/cryostat/util/URIUtilTest.java @@ -0,0 +1,58 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.cryostat.util; + +import java.net.URI; + +import javax.management.remote.JMXServiceURL; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +class URIUtilTest { + + @Test + void testGetRmiTarget() throws Exception { + String serviceUrl = "service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi"; + URI converted = URIUtil.getRmiTarget(new JMXServiceURL(serviceUrl)); + MatcherAssert.assertThat(converted.getHost(), Matchers.equalTo("cryostat")); + MatcherAssert.assertThat(converted.getPort(), Matchers.equalTo(9091)); + MatcherAssert.assertThat(converted.getPath(), Matchers.equalTo("/jmxrmi")); + } +} diff --git a/src/test/java/itest/InterleavedExternalTargetRequestsIT.java b/src/test/java/itest/InterleavedExternalTargetRequestsIT.java index c4d393c991..6fe005946e 100644 --- a/src/test/java/itest/InterleavedExternalTargetRequestsIT.java +++ b/src/test/java/itest/InterleavedExternalTargetRequestsIT.java @@ -37,6 +37,7 @@ */ package itest; +import java.net.URI; import java.util.ArrayList; import java.util.Base64; import java.util.HashSet; @@ -47,6 +48,13 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import io.cryostat.MainModule; +import io.cryostat.core.log.Logger; +import io.cryostat.platform.ServiceRef; +import io.cryostat.platform.ServiceRef.AnnotationKey; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import io.vertx.core.MultiMap; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -64,6 +72,8 @@ @TestMethodOrder(OrderAnnotation.class) class InterleavedExternalTargetRequestsIT extends ExternalTargetsTest { + private static final Gson gson = MainModule.provideGson(Logger.INSTANCE); + static final int NUM_EXT_CONTAINERS = 8; static final List CONTAINERS = new ArrayList<>(); @@ -98,27 +108,55 @@ static void cleanup() throws Exception { @Test @Order(1) void testOtherContainersFound() throws Exception { - JsonArray listResp = queryTargets().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - Set> actual = new HashSet<>(listResp.getList()); + // FIXME don't use ServiceRefs or Gson (de)serialization in these tests. This should be + // as independent as possible from Cryostat internal implementation details. + CompletableFuture> resp = new CompletableFuture<>(); + webClient + .get("/api/v1/targets") + .send( + ar -> { + if (assertRequestStatus(ar, resp)) { + resp.complete( + gson.fromJson( + ar.result().bodyAsString(), + new TypeToken>() {}.getType())); + } + }); + Set actual = resp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); // ordering may not be guaranteed so use a Set, but there should be no duplicates and so // size should not change - Set> expected = new HashSet<>(); - expected.add( + MatcherAssert.assertThat(actual.size(), Matchers.equalTo(NUM_EXT_CONTAINERS + 1)); + Set expected = new HashSet<>(); + ServiceRef cryostat = + new ServiceRef( + new URI( + String.format( + "service:jmx:rmi:///jndi/rmi://%s:9091/jmxrmi", + Podman.POD_NAME)), + "io.cryostat.Cryostat"); + cryostat.setCryostatAnnotations( Map.of( - "connectUrl", - String.format( - "service:jmx:rmi:///jndi/rmi://%s:9091/jmxrmi", Podman.POD_NAME), - "alias", - "io.cryostat.Cryostat")); + AnnotationKey.JAVA_MAIN, "io.cryostat.Cryostat", + AnnotationKey.HOST, Podman.POD_NAME, + AnnotationKey.PORT, "9091")); + expected.add(cryostat); for (int i = 0; i < NUM_EXT_CONTAINERS; i++) { - expected.add( + ServiceRef ext = + new ServiceRef( + new URI( + String.format( + "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", + Podman.POD_NAME, 9093 + i)), + "es.andrewazor.demo.Main"); + ext.setCryostatAnnotations( Map.of( - "connectUrl", - String.format( - "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", - Podman.POD_NAME, 9093 + i), - "alias", - "es.andrewazor.demo.Main")); + AnnotationKey.JAVA_MAIN, + "es.andrewazor.demo.Main", + AnnotationKey.HOST, + Podman.POD_NAME, + AnnotationKey.PORT, + Integer.toString(9093 + i))); + expected.add(ext); } MatcherAssert.assertThat(actual, Matchers.equalTo(expected)); }