From b0aae230cc498a3ddb623abcd3a8140d54d11db0 Mon Sep 17 00:00:00 2001 From: Robert Niedziela <175605712+robsunday@users.noreply.github.com> Date: Wed, 16 Oct 2024 04:56:39 +0200 Subject: [PATCH] Integration tests for JMX Scraper (#1480) Co-authored-by: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> --- .../jmxscraper/JmxConnectorBuilderTest.java | 10 +++-- .../contrib/jmxscraper/PortSelector.java | 42 +++++++++++++++++++ .../contrib/jmxscraper/TestAppContainer.java | 37 ++++++++++++---- .../TargetSystemIntegrationTest.java | 29 ++++--------- .../jmxscraper/JmxConnectorBuilder.java | 1 + 5 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index 132605242..5766cd890 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -31,23 +31,25 @@ static void afterAll() { @Test void noAuth() { - try (TestAppContainer app = new TestAppContainer().withNetwork(network).withJmxPort(9990)) { + int port = PortSelector.getAvailableRandomPort(); + try (TestAppContainer app = new TestAppContainer().withHostAccessFixedJmxPort(port)) { app.start(); testConnector( - () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(9990)).build()); + () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)).build()); } } @Test void loginPwdAuth() { + int port = PortSelector.getAvailableRandomPort(); String login = "user"; String pwd = "t0p!Secret"; try (TestAppContainer app = - new TestAppContainer().withNetwork(network).withJmxPort(9999).withUserAuth(login, pwd)) { + new TestAppContainer().withHostAccessFixedJmxPort(port).withUserAuth(login, pwd)) { app.start(); testConnector( () -> - JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(9999)) + JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)) .userCredentials(login, pwd) .build()); } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java new file mode 100644 index 000000000..400b7bb5e --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jmxscraper; + +import java.io.IOException; +import java.net.Socket; +import java.util.Random; + +/** Class used for finding random free network port from range 1024-65535 */ +public class PortSelector { + private static final Random random = new Random(System.currentTimeMillis()); + + private static final int MIN_PORT = 1024; + private static final int MAX_PORT = 65535; + + private PortSelector() {} + + /** + * @return random available TCP port + */ + public static synchronized int getAvailableRandomPort() { + int port; + + do { + port = random.nextInt(MAX_PORT - MIN_PORT + 1) + MIN_PORT; + } while (!isPortAvailable(port)); + + return port; + } + + private static boolean isPortAvailable(int port) { + // see https://stackoverflow.com/a/13826145 for the chosen implementation + try (Socket s = new Socket("localhost", port)) { + return false; + } catch (IOException e) { + return true; + } + } +} diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java index a38dd7ace..990a316c9 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java @@ -25,7 +25,6 @@ public class TestAppContainer extends GenericContainer { private final Map properties; - private int port; private String login; private String pwd; @@ -44,11 +43,16 @@ public TestAppContainer() { .withCommand("java", "-jar", "/app.jar"); } + /** + * Configures app container for container-to-container access + * + * @param port mapped port to use + * @return this + */ @CanIgnoreReturnValue public TestAppContainer withJmxPort(int port) { - this.port = port; properties.put("com.sun.management.jmxremote.port", Integer.toString(port)); - return this.withExposedPorts(port); + return this; } @CanIgnoreReturnValue @@ -58,9 +62,30 @@ public TestAppContainer withUserAuth(String login, String pwd) { return this; } + /** + * Configures app container for host-to-container access, port will be used as-is from host to + * work-around JMX in docker. This is optional on Linux as there is a network route and the + * container is accessible, but not on Mac where the container runs in an isolated VM. + * + * @param port port to use, must be available on host. + * @return this + */ + @CanIgnoreReturnValue + public TestAppContainer withHostAccessFixedJmxPort(int port) { + // To get host->container JMX connection working docker must expose JMX/RMI port under the same + // port number. Because of this testcontainers' standard exposed port randomization approach + // can't be used. + // Explanation: + // https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/6 + properties.put("com.sun.management.jmxremote.port", Integer.toString(port)); + properties.put("com.sun.management.jmxremote.rmi.port", Integer.toString(port)); + properties.put("java.rmi.server.hostname", getHost()); + addFixedExposedPort(port, port); + return this; + } + @Override public void start() { - // TODO: add support for ssl properties.put("com.sun.management.jmxremote.ssl", "false"); @@ -92,11 +117,9 @@ public void start() { this.withEnv("JAVA_TOOL_OPTIONS", confArgs); - logger().info("Test application JAVA_TOOL_OPTIONS = " + confArgs); + logger().info("Test application JAVA_TOOL_OPTIONS = {}", confArgs); super.start(); - - logger().info("Test application JMX port mapped to {}:{}", getHost(), getMappedPort(port)); } private static Path createPwdFile(String login, String pwd) { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java index 0552aa3bd..2cce282f9 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java @@ -5,24 +5,19 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; -import static org.assertj.core.api.Assertions.assertThat; - import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; -import io.opentelemetry.contrib.jmxscraper.JmxConnectorBuilder; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingDeque; -import javax.management.remote.JMXConnector; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -35,8 +30,8 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; public abstract class TargetSystemIntegrationTest { - - private static final Logger logger = LoggerFactory.getLogger(TargetSystemIntegrationTest.class); + private static final Logger targetSystemLogger = LoggerFactory.getLogger("TargetSystemContainer"); + private static final Logger jmxScraperLogger = LoggerFactory.getLogger("JmxScraperContainer"); private static final String TARGET_SYSTEM_NETWORK_ALIAS = "targetsystem"; private static String otlpEndpoint; @@ -54,6 +49,9 @@ public abstract class TargetSystemIntegrationTest { private JmxScraperContainer scraper; private static final String OTLP_HOST = "host.testcontainers.internal"; + + // JMX communication only happens between container, and we don't have to use JMX + // from host to container, we can use a fixed port. private static final int JMX_PORT = 9999; @BeforeAll @@ -93,27 +91,14 @@ void endToEndTest() { target = createTargetContainer(JMX_PORT) - .withLogConsumer(new Slf4jLogConsumer(logger)) + .withLogConsumer(new Slf4jLogConsumer(targetSystemLogger)) .withNetwork(network) - .withExposedPorts(JMX_PORT) .withNetworkAliases(TARGET_SYSTEM_NETWORK_ALIAS); target.start(); - String targetHost = target.getHost(); - Integer targetPort = target.getMappedPort(JMX_PORT); - logger.info( - "Target system started, JMX port: {} mapped to {}:{}", JMX_PORT, targetHost, targetPort); - - // TODO : wait for metrics to be sent and add assertions on what is being captured - // for now we just test that we can connect to remote JMX using our client. - try (JMXConnector connector = JmxConnectorBuilder.createNew(targetHost, targetPort).build()) { - assertThat(connector.getMBeanServerConnection()).isNotNull(); - } catch (IOException e) { - throw new RuntimeException(e); - } - scraper = new JmxScraperContainer(otlpEndpoint) + .withLogConsumer(new Slf4jLogConsumer(jmxScraperLogger)) .withNetwork(network) .withService(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java index dd509aa13..eebca5fd4 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java @@ -138,6 +138,7 @@ private Map buildEnv() { @SuppressWarnings("BanJNDI") private static JMXConnector doConnect(JMXServiceURL url, Map env) throws IOException { + logger.info("Connecting to " + url); return JMXConnectorFactory.connect(url, env); }