Skip to content

Commit d19f761

Browse files
committed
jmx-scraper test config sources
1 parent 5c6be9d commit d19f761

File tree

2 files changed

+191
-46
lines changed

2 files changed

+191
-46
lines changed

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
import java.util.function.Function;
1313
import org.junit.jupiter.api.AfterAll;
1414
import org.junit.jupiter.api.BeforeAll;
15+
import org.junit.jupiter.api.BeforeEach;
1516
import org.junit.jupiter.api.Test;
1617
import org.junit.jupiter.api.io.TempDir;
18+
import org.junit.jupiter.params.ParameterizedTest;
19+
import org.junit.jupiter.params.provider.EnumSource;
1720
import org.slf4j.Logger;
1821
import org.slf4j.LoggerFactory;
1922
import org.testcontainers.containers.GenericContainer;
@@ -43,6 +46,9 @@ public class JmxConnectionTest {
4346

4447
private static Network network;
4548

49+
// temporary folder for files that are copied to container
50+
@TempDir private Path tempDir;
51+
4652
@BeforeAll
4753
static void beforeAll() {
4854
network = Network.newNetwork();
@@ -53,6 +59,12 @@ static void afterAll() {
5359
network.close();
5460
}
5561

62+
@BeforeEach
63+
void beforeEach() {
64+
// extra safety to ensure temp folder is empty before each test method
65+
assertThat(tempDir).isEmptyDirectory();
66+
}
67+
5668
@Test
5769
void connectionError() {
5870
try (JmxScraperContainer scraper = scraperContainer().withRmiServiceUrl("unknown_host", 1234)) {
@@ -62,32 +74,43 @@ void connectionError() {
6274
}
6375
}
6476

65-
@Test
66-
void connectNoAuth() {
77+
@ParameterizedTest
78+
@EnumSource
79+
void connectNoAuth(JmxScraperContainer.ConfigSource configSource) {
6780
connectionTest(
68-
app -> app.withJmxPort(JMX_PORT), scraper -> scraper.withRmiServiceUrl(APP_HOST, JMX_PORT));
81+
app -> app.withJmxPort(JMX_PORT),
82+
scraper -> scraper.withRmiServiceUrl(APP_HOST, JMX_PORT).withConfigSource(configSource));
6983
}
7084

71-
@Test
72-
void userPassword() {
85+
@ParameterizedTest
86+
@EnumSource
87+
void userPassword(JmxScraperContainer.ConfigSource configSource) {
7388
String login = "user";
7489
String pwd = "t0p!Secret";
7590
connectionTest(
7691
app -> app.withJmxPort(JMX_PORT).withUserAuth(login, pwd),
77-
scraper -> scraper.withRmiServiceUrl(APP_HOST, JMX_PORT).withUser(login).withPassword(pwd));
92+
scraper ->
93+
scraper
94+
.withRmiServiceUrl(APP_HOST, JMX_PORT)
95+
.withUser(login)
96+
.withPassword(pwd)
97+
.withConfigSource(configSource));
7898
}
7999

80-
@Test
81-
void serverSsl(@TempDir Path tempDir) {
82-
testServerSsl(tempDir, /* sslRmiRegistry= */ false);
100+
@ParameterizedTest
101+
@EnumSource
102+
void serverSsl(JmxScraperContainer.ConfigSource configSource) {
103+
testServerSsl(/* sslRmiRegistry= */ false, configSource);
83104
}
84105

85-
@Test
86-
void serverSslWithSslRmiRegistry(@TempDir Path tempDir) {
87-
testServerSsl(tempDir, /* sslRmiRegistry= */ true);
106+
@ParameterizedTest
107+
@EnumSource
108+
void serverSslWithSslRmiRegistry(JmxScraperContainer.ConfigSource configSource) {
109+
testServerSsl(/* sslRmiRegistry= */ true, configSource);
88110
}
89111

90-
private static void testServerSsl(Path tempDir, boolean sslRmiRegistry) {
112+
private void testServerSsl(
113+
boolean sslRmiRegistry, JmxScraperContainer.ConfigSource configSource) {
91114
// two keystores:
92115
// server keystore with public/private key pair
93116
// client trust store with certificate from server
@@ -109,11 +132,13 @@ private static void testServerSsl(Path tempDir, boolean sslRmiRegistry) {
109132
scraper ->
110133
(sslRmiRegistry ? scraper.withSslRmiRegistry() : scraper)
111134
.withRmiServiceUrl(APP_HOST, JMX_PORT)
112-
.withTrustStore(clientTrustStore));
135+
.withTrustStore(clientTrustStore)
136+
.withConfigSource(configSource));
113137
}
114138

115-
@Test
116-
void serverSslClientSsl(@TempDir Path tempDir) {
139+
@ParameterizedTest
140+
@EnumSource(value = JmxScraperContainer.ConfigSource.class)
141+
void serverSslClientSsl(JmxScraperContainer.ConfigSource configSource) {
117142
// Note: this could have been made simpler by relying on the fact that keystore could be used
118143
// as a trust store, but having clear split provides also some extra clarity
119144
//
@@ -152,7 +177,8 @@ void serverSslClientSsl(@TempDir Path tempDir) {
152177
scraper
153178
.withRmiServiceUrl(APP_HOST, JMX_PORT)
154179
.withKeyStore(clientKeyStore)
155-
.withTrustStore(clientTrustStore));
180+
.withTrustStore(clientTrustStore)
181+
.withConfigSource(configSource));
156182
}
157183

158184
private static void connectionTest(

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java

Lines changed: 148 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
import static org.assertj.core.api.Assertions.assertThat;
99

1010
import com.google.errorprone.annotations.CanIgnoreReturnValue;
11+
import java.io.IOException;
12+
import java.nio.file.Files;
1113
import java.nio.file.Path;
1214
import java.time.Duration;
1315
import java.util.ArrayList;
14-
import java.util.Arrays;
15-
import java.util.Collections;
16+
import java.util.HashMap;
1617
import java.util.HashSet;
1718
import java.util.List;
1819
import java.util.Locale;
20+
import java.util.Map;
1921
import java.util.Set;
22+
import java.util.stream.Collectors;
2023
import org.testcontainers.containers.GenericContainer;
2124
import org.testcontainers.containers.wait.strategy.Wait;
2225
import org.testcontainers.utility.MountableFile;
@@ -35,6 +38,19 @@ public class JmxScraperContainer extends GenericContainer<JmxScraperContainer> {
3538
private TestKeyStore keyStore;
3639
private TestKeyStore trustStore;
3740
private boolean sslRmiRegistry;
41+
private ConfigSource configSource;
42+
43+
/** Defines different strategies to provide scraper configuration */
44+
public enum ConfigSource {
45+
/** system properties with "-D" prefix in JVM command */
46+
SYSTEM_PROPERTIES,
47+
/** properties file */
48+
PROPERTIES_FILE,
49+
/** standard input */
50+
STDIN,
51+
/** environment variables with "OTEL_" prefix, non-otel options as system properties */
52+
ENVIRONMENT_VARIABLES;
53+
}
3854

3955
public JmxScraperContainer(String otlpEndpoint, String baseImage) {
4056
super(baseImage);
@@ -48,6 +64,7 @@ public JmxScraperContainer(String otlpEndpoint, String baseImage) {
4864
this.targetSystems = new HashSet<>();
4965
this.customYamlFiles = new HashSet<>();
5066
this.extraJars = new ArrayList<>();
67+
this.configSource = ConfigSource.SYSTEM_PROPERTIES;
5168
}
5269

5370
/**
@@ -182,83 +199,185 @@ public JmxScraperContainer withSslRmiRegistry() {
182199
return this;
183200
}
184201

202+
/**
203+
* Sets how configuration is provided to scraper
204+
*
205+
* @param source configuration source
206+
* @return this
207+
*/
208+
@CanIgnoreReturnValue
209+
public JmxScraperContainer withConfigSource(ConfigSource source) {
210+
this.configSource = source;
211+
return this;
212+
}
213+
185214
@Override
186215
public void start() {
187-
// for now only configure through JVM args
188-
List<String> arguments = new ArrayList<>();
189-
arguments.add("java");
190-
arguments.add("-Dotel.metrics.exporter=otlp");
191-
arguments.add("-Dotel.exporter.otlp.endpoint=" + endpoint);
216+
217+
Map<String, String> config = new HashMap<>();
218+
config.put("otel.metrics.exporter", "otlp");
219+
config.put("otel.exporter.otlp.endpoint", endpoint);
192220

193221
if (!targetSystems.isEmpty()) {
194-
arguments.add("-Dotel.jmx.target.system=" + String.join(",", targetSystems));
222+
config.put("otel.jmx.target.system", String.join(",", targetSystems));
195223
}
196224

197225
if (serviceUrl == null) {
198226
throw new IllegalStateException("Missing service URL");
199227
}
200-
arguments.add("-Dotel.jmx.service.url=" + serviceUrl);
228+
config.put("otel.jmx.service.url", serviceUrl);
229+
201230
// always use a very short export interval for testing
202-
arguments.add("-Dotel.metric.export.interval=1s");
231+
config.put("otel.metric.export.interval", "1s");
203232

204233
if (user != null) {
205-
arguments.add("-Dotel.jmx.username=" + user);
234+
config.put("otel.jmx.username", user);
206235
}
207236
if (password != null) {
208-
arguments.add("-Dotel.jmx.password=" + password);
237+
config.put("otel.jmx.password", password);
209238
}
210239

211-
arguments.addAll(addSecureStore(keyStore, /* isKeyStore= */ true));
212-
arguments.addAll(addSecureStore(trustStore, /* isKeyStore= */ false));
240+
addSecureStore(keyStore, /* isKeyStore= */ true, config);
241+
addSecureStore(trustStore, /* isKeyStore= */ false, config);
213242

214243
if (sslRmiRegistry) {
215-
arguments.add("-Dotel.jmx.remote.registry.ssl=true");
244+
config.put("otel.jmx.remote.registry.ssl", "true");
216245
}
217246

218247
if (!customYamlFiles.isEmpty()) {
219248
for (String yaml : customYamlFiles) {
220249
this.withCopyFileToContainer(MountableFile.forClasspathResource(yaml), yaml);
221250
}
222-
arguments.add("-Dotel.jmx.config=" + String.join(",", customYamlFiles));
251+
config.put("otel.jmx.config", String.join(",", customYamlFiles));
252+
}
253+
254+
List<String> cmd = new ArrayList<>();
255+
cmd.add("java");
256+
257+
switch (configSource) {
258+
case SYSTEM_PROPERTIES:
259+
cmd.addAll(
260+
toKeyValueString(config).stream().map(s -> "-D" + s).collect(Collectors.toList()));
261+
break;
262+
case PROPERTIES_FILE:
263+
try {
264+
Path configFile = Files.createTempFile("config", ".properties");
265+
Files.write(configFile, toKeyValueString(config));
266+
this.withCopyFileToContainer(MountableFile.forHostPath(configFile), "/config.properties");
267+
} catch (IOException e) {
268+
throw new IllegalStateException(e);
269+
}
270+
break;
271+
case STDIN:
272+
// nothing needed here
273+
break;
274+
case ENVIRONMENT_VARIABLES:
275+
Map<String, String> env = new HashMap<>();
276+
Map<String, String> other = new HashMap<>();
277+
config.forEach(
278+
(k, v) -> {
279+
if (k.startsWith("otel.")) {
280+
env.put(k.toUpperCase(Locale.ROOT).replace(".", "_"), v);
281+
} else {
282+
other.put(k, v);
283+
}
284+
});
285+
286+
if (!other.isEmpty()) {
287+
env.put(
288+
"JAVA_TOOL_OPTIONS",
289+
toKeyValueString(other).stream().map(s -> "-D" + s).collect(Collectors.joining(" ")));
290+
}
291+
this.withEnv(env);
292+
env.forEach((k, v) -> logger().info("Using environment variable {} = {} ", k, v));
293+
294+
break;
223295
}
224296

225297
if (extraJars.isEmpty()) {
226298
// using "java -jar" to start
227-
arguments.add("-jar");
228-
arguments.add("/scraper.jar");
299+
cmd.add("-jar");
300+
cmd.add("/scraper.jar");
229301
} else {
230302
// using "java -cp" to start
231-
arguments.add("-cp");
232-
arguments.add("/scraper.jar:" + String.join(":", extraJars));
233-
arguments.add("io.opentelemetry.contrib.jmxscraper.JmxScraper");
303+
cmd.add("-cp");
304+
cmd.add("/scraper.jar:" + String.join(":", extraJars));
305+
cmd.add("io.opentelemetry.contrib.jmxscraper.JmxScraper");
306+
}
307+
308+
switch (configSource) {
309+
case SYSTEM_PROPERTIES:
310+
case ENVIRONMENT_VARIABLES:
311+
// no extra program argument needed
312+
break;
313+
case PROPERTIES_FILE:
314+
cmd.add("-config");
315+
cmd.add("/config.properties");
316+
break;
317+
case STDIN:
318+
cmd.add("-config");
319+
cmd.add("-");
320+
break;
234321
}
235322

236323
if (testJmx) {
237-
arguments.add("-test");
324+
cmd.add("-test");
238325
this.waitingFor(Wait.forLogMessage(".*JMX connection test.*", 1));
239326
} else {
240327
this.waitingFor(
241328
Wait.forLogMessage(".*JMX scraping started.*", 1)
242329
.withStartupTimeout(Duration.ofSeconds(10)));
243330
}
244331

245-
this.withCommand(arguments.toArray(new String[0]));
332+
if (configSource != ConfigSource.STDIN) {
333+
this.withCommand(cmd.toArray(new String[0]));
334+
} else {
335+
// generate shell script to feed standard input with config
336+
List<String> lines = new ArrayList<>();
337+
lines.add("#!/bin/bash");
338+
lines.add(String.join(" ", cmd) + "<<EOF");
339+
lines.addAll(toKeyValueString(config));
340+
lines.add("EOF");
341+
342+
Path script;
343+
try {
344+
script = Files.createTempFile("scraper", ".sh");
345+
Files.write(script, lines);
346+
} catch (IOException e) {
347+
throw new IllegalStateException(e);
348+
}
246349

247-
logger().info("Starting scraper with command: " + String.join(" ", arguments));
350+
logger().info("Scraper executed with /scraper.sh shell script");
351+
for (int i = 0; i < lines.size(); i++) {
352+
logger().info("/scrapper.sh:{} {}", i, lines.get(i));
353+
}
248354

355+
this.withCopyFileToContainer(MountableFile.forHostPath(script, 500), "/scraper.sh");
356+
this.withCommand("/scraper.sh");
357+
}
358+
359+
logger().info("Starting scraper with command: " + String.join(" ", this.getCommandParts()));
249360
super.start();
250361
}
251362

252-
private List<String> addSecureStore(TestKeyStore keyStore, boolean isKeyStore) {
363+
private void addSecureStore(
364+
TestKeyStore keyStore, boolean isKeyStore, Map<String, String> config) {
253365
if (keyStore == null) {
254-
return Collections.emptyList();
366+
return;
255367
}
256368
Path path = keyStore.getPath();
257369
String containerPath = "/" + path.getFileName().toString();
258370
this.withCopyFileToContainer(MountableFile.forHostPath(path), containerPath);
259371

260-
String prefix = String.format("-Djavax.net.ssl.%sStore", isKeyStore ? "key" : "trust");
261-
return Arrays.asList(
262-
prefix + "=" + containerPath, prefix + "Password=" + keyStore.getPassword());
372+
String prefix = String.format("javax.net.ssl.%sStore", isKeyStore ? "key" : "trust");
373+
374+
config.put(prefix, containerPath);
375+
config.put(prefix + "Password", keyStore.getPassword());
376+
}
377+
378+
private static List<String> toKeyValueString(Map<String, String> options) {
379+
return options.entrySet().stream()
380+
.map(e -> String.format("%s=%s", e.getKey(), e.getValue()))
381+
.collect(Collectors.toList());
263382
}
264383
}

0 commit comments

Comments
 (0)