Skip to content

Commit 6eaa9ba

Browse files
authored
Add support for addition configuration files to test clusters framework (#92579) (#92581)
This adds the ability to supply arbitrary files to the config directory of cluster nodes. Typically, this is used for security use cases, such as providing for SSL certificates and trust stores. This commit adds a few other features to enable more testing ues cases as well, such as the ability to restart a cluster, as well as explicit ordering of test cases withing a test class. This is needed for test suites that need to execute some tests, restart the cluster, then execute more in a particular order.
1 parent ed8aebe commit 6eaa9ba

File tree

20 files changed

+286
-170
lines changed

20 files changed

+286
-170
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.test;
10+
11+
import com.carrotsearch.randomizedtesting.TestMethodAndParams;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Inherited;
15+
import java.lang.annotation.Retention;
16+
import java.lang.annotation.RetentionPolicy;
17+
import java.lang.annotation.Target;
18+
import java.util.Comparator;
19+
20+
/**
21+
* Test case ordering to be used in conjunction with {@link com.carrotsearch.randomizedtesting.annotations.TestCaseOrdering}. Tests are
22+
* ordered with respect to ordinals defined with {@link Order} annotations placed on individual test methods.
23+
*/
24+
public class AnnotationTestOrdering implements Comparator<TestMethodAndParams> {
25+
@Override
26+
public int compare(TestMethodAndParams o1, TestMethodAndParams o2) {
27+
return Integer.compare(
28+
o1.getTestMethod().getAnnotation(Order.class).value(),
29+
o2.getTestMethod().getAnnotation(Order.class).value()
30+
);
31+
}
32+
33+
@Retention(RetentionPolicy.RUNTIME)
34+
@Target(ElementType.METHOD)
35+
@Inherited
36+
public @interface Order {
37+
int value();
38+
}
39+
}

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/ClusterHandle.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ public interface ClusterHandle extends Closeable {
2727
*/
2828
void stop(boolean forcibly);
2929

30+
/**
31+
* Restarts the cluster. Effectively the same as calling {@link #stop(boolean)} followed by {@link #start()}
32+
*
33+
* @param forcibly whether to ficibly terminate the cluster
34+
*/
35+
void restart(boolean forcibly);
36+
3037
/**
3138
* Whether the cluster is started or not. This method makes no guarantees on cluster availability, only that the node processes have
3239
* been started.

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalSpecBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.test.cluster.FeatureFlag;
1313
import org.elasticsearch.test.cluster.SettingsProvider;
1414
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
15+
import org.elasticsearch.test.cluster.util.resource.Resource;
1516

1617
import java.util.ArrayList;
1718
import java.util.HashMap;
@@ -31,6 +32,7 @@ public abstract class AbstractLocalSpecBuilder<T extends LocalSpecBuilder<?>> im
3132
private final Set<String> plugins = new HashSet<>();
3233
private final Set<FeatureFlag> features = new HashSet<>();
3334
private final Map<String, String> keystoreSettings = new HashMap<>();
35+
private final Map<String, Resource> extraConfigFiles = new HashMap<>();
3436
private DistributionType distributionType;
3537

3638
protected AbstractLocalSpecBuilder(AbstractLocalSpecBuilder<?> parent) {
@@ -134,6 +136,16 @@ public Map<String, String> getKeystoreSettings() {
134136
return inherit(() -> parent.getKeystoreSettings(), keystoreSettings);
135137
}
136138

139+
@Override
140+
public T configFile(String fileName, Resource configFile) {
141+
this.extraConfigFiles.put(fileName, configFile);
142+
return cast(this);
143+
}
144+
145+
public Map<String, Resource> getExtraConfigFiles() {
146+
return inherit(() -> parent.getExtraConfigFiles(), extraConfigFiles);
147+
}
148+
137149
private <T> List<T> inherit(Supplier<List<T>> parent, List<T> child) {
138150
List<T> combinedList = new ArrayList<>();
139151
if (this.parent != null) {

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/DefaultLocalClusterSpecBuilder.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
1414
import org.elasticsearch.test.cluster.local.model.User;
1515
import org.elasticsearch.test.cluster.util.Version;
16-
import org.elasticsearch.test.cluster.util.resource.TextResource;
16+
import org.elasticsearch.test.cluster.util.resource.Resource;
1717

1818
import java.util.ArrayList;
1919
import java.util.List;
@@ -24,13 +24,13 @@ public class DefaultLocalClusterSpecBuilder extends AbstractLocalSpecBuilder<Loc
2424
private String name = "test-cluster";
2525
private final List<DefaultLocalNodeSpecBuilder> nodeBuilders = new ArrayList<>();
2626
private final List<User> users = new ArrayList<>();
27-
private final List<TextResource> roleFiles = new ArrayList<>();
27+
private final List<Resource> roleFiles = new ArrayList<>();
2828

2929
public DefaultLocalClusterSpecBuilder() {
3030
super(null);
3131
this.settings(new DefaultSettingsProvider());
3232
this.environment(new DefaultEnvironmentProvider());
33-
this.rolesFile(TextResource.fromClasspath("default_test_roles.yml"));
33+
this.rolesFile(Resource.fromClasspath("default_test_roles.yml"));
3434
}
3535

3636
@Override
@@ -95,7 +95,7 @@ public DefaultLocalClusterSpecBuilder user(String username, String password, Str
9595
}
9696

9797
@Override
98-
public DefaultLocalClusterSpecBuilder rolesFile(TextResource rolesFile) {
98+
public DefaultLocalClusterSpecBuilder rolesFile(Resource rolesFile) {
9999
this.roleFiles.add(rolesFile);
100100
return this;
101101
}
@@ -146,7 +146,8 @@ private LocalNodeSpec build(LocalClusterSpec cluster) {
146146
getPlugins(),
147147
Optional.ofNullable(getDistributionType()).orElse(DistributionType.INTEG_TEST),
148148
getFeatures(),
149-
getKeystoreSettings()
149+
getKeystoreSettings(),
150+
getExtraConfigFiles()
150151
);
151152
}
152153
}

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterFactory.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@
2525
import org.elasticsearch.test.cluster.util.Version;
2626

2727
import java.io.BufferedInputStream;
28+
import java.io.BufferedReader;
2829
import java.io.File;
2930
import java.io.FileInputStream;
3031
import java.io.IOException;
3132
import java.io.InputStream;
33+
import java.io.InputStreamReader;
34+
import java.io.Reader;
3235
import java.io.UncheckedIOException;
36+
import java.io.Writer;
3337
import java.nio.charset.StandardCharsets;
3438
import java.nio.file.Files;
3539
import java.nio.file.Path;
@@ -100,17 +104,25 @@ public synchronized void start() {
100104
distributionDescriptor = resolveDistribution();
101105
LOGGER.info("Distribution for node '{}': {}", spec.getName(), distributionDescriptor);
102106
initializeWorkingDirectory();
103-
writeConfiguration();
104-
createKeystore();
105-
addKeystoreSettings();
106-
configureSecurity();
107107
installPlugins();
108108
if (spec.getDistributionType() == DistributionType.INTEG_TEST) {
109109
installModules();
110110
}
111111
initialized = true;
112112
}
113113

114+
try {
115+
IOUtils.deleteWithRetry(configDir);
116+
Files.createDirectories(configDir);
117+
} catch (IOException e) {
118+
throw new UncheckedIOException("An error occurred creating config directory", e);
119+
}
120+
writeConfiguration();
121+
createKeystore();
122+
addKeystoreSettings();
123+
configureSecurity();
124+
copyExtraConfigFiles();
125+
114126
startElasticsearch();
115127
}
116128

@@ -192,7 +204,6 @@ private void initializeWorkingDirectory() {
192204
IOUtils.deleteWithRetry(distributionDir);
193205
IOUtils.syncWithCopy(distributionDescriptor.getDistributionDir(), distributionDir);
194206
}
195-
Files.createDirectories(configDir);
196207
Files.createDirectories(snapshotsDir);
197208
Files.createDirectories(dataDir);
198209
Files.createDirectories(logsDir);
@@ -254,6 +265,10 @@ private void writeConfiguration() {
254265
}
255266
}
256267

268+
private void copyExtraConfigFiles() {
269+
spec.getExtraConfigFiles().forEach((fileName, resource) -> resource.writeTo(configDir.resolve(fileName)));
270+
}
271+
257272
private void createKeystore() {
258273
try {
259274
ProcessUtils.exec(
@@ -300,13 +315,11 @@ private void configureSecurity() {
300315

301316
Path destination = workingDir.resolve("config").resolve("roles.yml");
302317
spec.getRolesFiles().forEach(rolesFile -> {
303-
try {
304-
Files.writeString(
305-
destination,
306-
rolesFile.getText() + System.lineSeparator(),
307-
StandardCharsets.UTF_8,
308-
StandardOpenOption.APPEND
309-
);
318+
try (
319+
Writer writer = Files.newBufferedWriter(destination, StandardOpenOption.APPEND);
320+
Reader reader = new BufferedReader(new InputStreamReader(rolesFile.asStream()))
321+
) {
322+
reader.transferTo(writer);
310323
} catch (IOException e) {
311324
throw new UncheckedIOException("Failed to append roles file " + rolesFile + " to " + destination, e);
312325
}

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterHandle.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.io.UncheckedIOException;
21+
import java.net.MalformedURLException;
2122
import java.nio.file.Files;
2223
import java.nio.file.Path;
2324
import java.time.Duration;
@@ -75,12 +76,19 @@ public void stop(boolean forcibly) {
7576
if (started.getAndSet(false)) {
7677
LOGGER.info("Stopping Elasticsearch test cluster '{}', forcibly: {}", name, forcibly);
7778
execute(() -> nodes.forEach(n -> n.stop(forcibly)));
79+
deletePortFiles();
7880
} else {
7981
// Make sure the process is stopped, otherwise wait
8082
execute(() -> nodes.forEach(n -> n.waitForExit()));
8183
}
8284
}
8385

86+
@Override
87+
public void restart(boolean forcibly) {
88+
stop(forcibly);
89+
start();
90+
}
91+
8492
@Override
8593
public boolean isStarted() {
8694
return started.get();
@@ -124,18 +132,7 @@ private void waitUntilReady() {
124132
writeUnicastHostsFile();
125133
try {
126134
Retry.retryUntilTrue(CLUSTER_UP_TIMEOUT, Duration.ZERO, () -> {
127-
Node node = nodes.get(0);
128-
boolean securityEnabled = Boolean.parseBoolean(node.getSpec().getSetting("xpack.security.enabled", "true"));
129-
boolean sslEnabled = Boolean.parseBoolean(node.getSpec().getSetting("xpack.security.http.ssl.enabled", "false"));
130-
boolean securityAutoConfigured = isSecurityAutoConfigured(node);
131-
String scheme = securityEnabled && (sslEnabled || securityAutoConfigured) ? "https" : "http";
132-
WaitForHttpResource wait = new WaitForHttpResource(scheme, node.getHttpAddress(), nodes.size());
133-
User credentials = node.getSpec().getUsers().get(0);
134-
wait.setUsername(credentials.getUsername());
135-
wait.setPassword(credentials.getPassword());
136-
if (securityAutoConfigured) {
137-
wait.setCertificateAuthorities(node.getWorkingDir().resolve("config/certs/http_ca.crt").toFile());
138-
}
135+
WaitForHttpResource wait = configureWaitForReady();
139136
return wait.wait(500);
140137
});
141138
} catch (TimeoutException e) {
@@ -145,6 +142,23 @@ private void waitUntilReady() {
145142
}
146143
}
147144

145+
private WaitForHttpResource configureWaitForReady() throws MalformedURLException {
146+
Node node = nodes.get(0);
147+
boolean securityEnabled = Boolean.parseBoolean(node.getSpec().getSetting("xpack.security.enabled", "true"));
148+
boolean sslEnabled = Boolean.parseBoolean(node.getSpec().getSetting("xpack.security.http.ssl.enabled", "false"));
149+
boolean securityAutoConfigured = isSecurityAutoConfigured(node);
150+
String scheme = securityEnabled && (sslEnabled || securityAutoConfigured) ? "https" : "http";
151+
WaitForHttpResource wait = new WaitForHttpResource(scheme, node.getHttpAddress(), nodes.size());
152+
User credentials = node.getSpec().getUsers().get(0);
153+
wait.setUsername(credentials.getUsername());
154+
wait.setPassword(credentials.getPassword());
155+
if (securityAutoConfigured) {
156+
wait.setCertificateAuthorities(node.getWorkingDir().resolve("config/certs/http_ca.crt").toFile());
157+
}
158+
159+
return wait;
160+
}
161+
148162
private boolean isSecurityAutoConfigured(Node node) {
149163
Path configFile = node.getWorkingDir().resolve("config").resolve("elasticsearch.yml");
150164
try (Stream<String> lines = Files.lines(configFile)) {
@@ -168,6 +182,22 @@ private void writeUnicastHostsFile() {
168182
});
169183
}
170184

185+
private void deletePortFiles() {
186+
nodes.forEach(node -> {
187+
try {
188+
Path hostsFile = node.getWorkingDir().resolve("config").resolve("unicast_hosts.txt");
189+
Path httpPortsFile = node.getWorkingDir().resolve("logs").resolve("http.ports");
190+
Path tranportPortsFile = node.getWorkingDir().resolve("logs").resolve("transport.ports");
191+
192+
Files.deleteIfExists(hostsFile);
193+
Files.deleteIfExists(httpPortsFile);
194+
Files.deleteIfExists(tranportPortsFile);
195+
} catch (IOException e) {
196+
throw new UncheckedIOException("Failed to write unicast_hosts for: " + node, e);
197+
}
198+
});
199+
}
200+
171201
private <T> T execute(Callable<T> task) {
172202
try {
173203
return executor.submit(task).get();

test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalClusterSpec.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
1616
import org.elasticsearch.test.cluster.local.model.User;
1717
import org.elasticsearch.test.cluster.util.Version;
18-
import org.elasticsearch.test.cluster.util.resource.TextResource;
18+
import org.elasticsearch.test.cluster.util.resource.Resource;
1919

2020
import java.util.HashMap;
2121
import java.util.List;
@@ -26,10 +26,10 @@
2626
public class LocalClusterSpec implements ClusterSpec {
2727
private final String name;
2828
private final List<User> users;
29-
private final List<TextResource> roleFiles;
29+
private final List<Resource> roleFiles;
3030
private List<LocalNodeSpec> nodes;
3131

32-
public LocalClusterSpec(String name, List<User> users, List<TextResource> roleFiles) {
32+
public LocalClusterSpec(String name, List<User> users, List<Resource> roleFiles) {
3333
this.name = name;
3434
this.users = users;
3535
this.roleFiles = roleFiles;
@@ -43,7 +43,7 @@ public List<User> getUsers() {
4343
return users;
4444
}
4545

46-
public List<TextResource> getRoleFiles() {
46+
public List<Resource> getRoleFiles() {
4747
return roleFiles;
4848
}
4949

@@ -79,6 +79,7 @@ public static class LocalNodeSpec {
7979
private final DistributionType distributionType;
8080
private final Set<FeatureFlag> features;
8181
private final Map<String, String> keystoreSettings;
82+
private final Map<String, Resource> extraConfigFiles;
8283

8384
public LocalNodeSpec(
8485
LocalClusterSpec cluster,
@@ -92,7 +93,8 @@ public LocalNodeSpec(
9293
Set<String> plugins,
9394
DistributionType distributionType,
9495
Set<FeatureFlag> features,
95-
Map<String, String> keystoreSettings
96+
Map<String, String> keystoreSettings,
97+
Map<String, Resource> extraConfigFiles
9698
) {
9799
this.cluster = cluster;
98100
this.name = name;
@@ -106,6 +108,7 @@ public LocalNodeSpec(
106108
this.distributionType = distributionType;
107109
this.features = features;
108110
this.keystoreSettings = keystoreSettings;
111+
this.extraConfigFiles = extraConfigFiles;
109112
}
110113

111114
public LocalClusterSpec getCluster() {
@@ -124,7 +127,7 @@ public List<User> getUsers() {
124127
return cluster.getUsers();
125128
}
126129

127-
public List<TextResource> getRolesFiles() {
130+
public List<Resource> getRolesFiles() {
128131
return cluster.getRoleFiles();
129132
}
130133

@@ -148,6 +151,10 @@ public Map<String, String> getKeystoreSettings() {
148151
return keystoreSettings;
149152
}
150153

154+
public Map<String, Resource> getExtraConfigFiles() {
155+
return extraConfigFiles;
156+
}
157+
151158
public boolean isSecurityEnabled() {
152159
return Boolean.parseBoolean(
153160
resolveSettings().getOrDefault("xpack.security.enabled", getVersion().onOrAfter("8.0.0") ? "true" : "false")

0 commit comments

Comments
 (0)