Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration tests for JMX Scraper #1480

Merged
merged 12 commits into from
Oct 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
public class TestAppContainer extends GenericContainer<TestAppContainer> {

private final Map<String, String> properties;
private int port;
private String login;
private String pwd;

Expand All @@ -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
Expand All @@ -58,9 +62,33 @@ 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() {

// properties.put("com.sun.management.jmxremote.local.only", "false");
// properties.put("java.rmi.server.logCalls", "true");
//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest either remove or include a code comment explaining why you may want to uncomment

Suggested change
// properties.put("com.sun.management.jmxremote.local.only", "false");
// properties.put("java.rmi.server.logCalls", "true");
//

// TODO: add support for ssl
properties.put("com.sun.management.jmxremote.ssl", "false");

Expand Down Expand Up @@ -92,11 +120,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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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);
}

Comment on lines -102 to -114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] this part had to go anyway as it's now replaced by actual integration tests in #1485 , here it only checked the connection from the host as a proxy of "target JVM is working with JMX settings".

scraper =
new JmxScraperContainer(otlpEndpoint)
.withLogConsumer(new Slf4jLogConsumer(jmxScraperLogger))
.withNetwork(network)
.withService(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ private Map<String, Object> buildEnv() {
@SuppressWarnings("BanJNDI")
private static JMXConnector doConnect(JMXServiceURL url, Map<String, Object> env)
throws IOException {
logger.info("Connecting to " + url);
robsunday marked this conversation as resolved.
Show resolved Hide resolved
return JMXConnectorFactory.connect(url, env);
}

Expand Down
Loading