Skip to content

Commit 483a9ae

Browse files
authored
Allow REST tests to run in MP mode (elastic#126906)
Mainly moves project setup from `MultipleProjectsClientYamlSuiteTestCase` to `ESRestTestCase`. This allows both Java REST tests and YAML tests to be run in MP mode by passing the system property `-Dtests.multi_project.enabled=true`. Future work will add the required gradle changes to be able to run any REST test in MP mode more easily.
1 parent d4045e2 commit 483a9ae

File tree

16 files changed

+179
-195
lines changed

16 files changed

+179
-195
lines changed
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask
2-
31
apply plugin: 'elasticsearch.internal-es-plugin'
42
apply plugin: 'elasticsearch.test-with-dependencies'
53
apply plugin: 'elasticsearch.internal-java-rest-test'
@@ -12,12 +10,10 @@ esplugin {
1210
dependencies {
1311
testImplementation project(path: ':test:test-clusters')
1412
clusterModules project(':test:external-modules:test-multi-project')
15-
}
16-
17-
tasks.withType(StandaloneRestIntegTestTask).configureEach {
18-
usesDefaultDistribution("to be triaged")
13+
clusterModules project(':modules:analysis-common')
1914
}
2015

2116
tasks.named("javaRestTest").configure {
2217
enabled = buildParams.snapshotBuild
18+
systemProperty "tests.multi_project.enabled", true
2319
}

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/admin/indices/IndexMultiProjectCRUDIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class IndexMultiProjectCRUDIT extends MultiProjectRestTestCase {
4646
private static ElasticsearchCluster createCluster() {
4747
LocalClusterSpecBuilder<ElasticsearchCluster> clusterBuilder = ElasticsearchCluster.local()
4848
.nodes(NODE_NUM)
49-
.distribution(DistributionType.INTEG_TEST) // TODO multi-project: make this test suite work under the default distrib
49+
.distribution(DistributionType.INTEG_TEST)
5050
.module("test-multi-project")
5151
.setting("test.multi_project.enabled", "true")
5252
.setting("xpack.security.enabled", "false") // TODO multi-project: make this test suite work with Security enabled

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/index/IndexDocumentMultiProjectIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class IndexDocumentMultiProjectIT extends MultiProjectRestTestCase {
4242
private static ElasticsearchCluster createCluster() {
4343
LocalClusterSpecBuilder<ElasticsearchCluster> clusterBuilder = ElasticsearchCluster.local()
4444
.nodes(NODE_NUM)
45-
.distribution(DistributionType.INTEG_TEST) // TODO multi-project: make this test suite work under the default distrib
45+
.distribution(DistributionType.INTEG_TEST)
4646
.module("test-multi-project")
4747
.setting("test.multi_project.enabled", "true")
4848
.setting("xpack.security.enabled", "false") // TODO multi-project: make this test suite work with Security enabled

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/multiproject/MultiProjectClusterStateActionIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class MultiProjectClusterStateActionIT extends MultiProjectRestTestCase {
2525

2626
@ClassRule
2727
public static ElasticsearchCluster CLUSTER = ElasticsearchCluster.local()
28-
.distribution(DistributionType.DEFAULT)
28+
.distribution(DistributionType.INTEG_TEST)
2929
.setting("test.multi_project.enabled", "true")
3030
.setting("xpack.security.http.ssl.enabled", "false")
3131
.setting("xpack.security.enabled", "false")

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/multiproject/MultiProjectRestTestCase.java

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,77 +11,32 @@
1111

1212
import org.elasticsearch.client.Request;
1313
import org.elasticsearch.client.RequestOptions;
14-
import org.elasticsearch.client.Response;
15-
import org.elasticsearch.client.ResponseException;
16-
import org.elasticsearch.cluster.metadata.Metadata;
1714
import org.elasticsearch.tasks.Task;
1815
import org.elasticsearch.test.rest.ESRestTestCase;
19-
import org.elasticsearch.xcontent.ObjectPath;
2016
import org.junit.After;
2117

2218
import java.io.IOException;
23-
import java.util.List;
24-
import java.util.Map;
25-
import java.util.Set;
26-
import java.util.stream.Collectors;
2719

2820
public abstract class MultiProjectRestTestCase extends ESRestTestCase {
29-
protected static Request setRequestProjectId(Request request, String projectId) {
30-
RequestOptions.Builder options = request.getOptions().toBuilder();
31-
options.removeHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER);
32-
options.addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, projectId);
33-
request.setOptions(options);
34-
return request;
35-
}
36-
37-
protected static void clearRequestProjectId(Request request) {
38-
RequestOptions options = request.getOptions();
39-
if (options.containsHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER)) {
40-
request.setOptions(options.toBuilder().removeHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER).build());
41-
}
42-
}
43-
44-
protected void createProject(String projectId) throws IOException {
45-
Request request = new Request("PUT", "/_project/" + projectId);
46-
try {
47-
Response response = adminClient().performRequest(request);
48-
assertOK(response);
49-
logger.info("Created project {} : {}", projectId, response.getStatusLine());
50-
} catch (ResponseException e) {
51-
logger.error("Failed to create project: {}", projectId);
52-
throw e;
53-
}
54-
}
55-
56-
protected void deleteProject(String projectId) throws IOException {
57-
final Request request = new Request("DELETE", "/_project/" + projectId);
58-
try {
59-
final Response response = adminClient().performRequest(request);
60-
logger.info("Deleted project {} : {}", projectId, response.getStatusLine());
61-
} catch (ResponseException e) {
62-
logger.error("Failed to delete project: {}", projectId);
63-
throw e;
64-
}
65-
}
6621

67-
protected Set<String> listProjects() throws IOException {
68-
final Request request = new Request("GET", "/_cluster/state/metadata?multi_project");
69-
final Response response = adminClient().performRequest(request);
70-
final List<Map<String, ?>> projects = ObjectPath.eval("metadata.projects", entityAsMap(response));
71-
return projects.stream().map(m -> String.valueOf(m.get("id"))).collect(Collectors.toSet());
22+
@Override
23+
protected boolean shouldConfigureProjects() {
24+
return false;
7225
}
7326

7427
@After
7528
public void removeNonDefaultProjects() throws IOException {
7629
if (preserveClusterUponCompletion() == false) {
77-
final Set<String> projects = listProjects();
78-
logger.info("Removing non-default projects from {}", projects);
79-
for (String projectId : projects) {
80-
if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id()) == false) {
81-
deleteProject(projectId);
82-
}
83-
}
30+
cleanUpProjects();
8431
}
8532
}
8633

34+
protected static Request setRequestProjectId(Request request, String projectId) {
35+
RequestOptions.Builder options = request.getOptions().toBuilder();
36+
options.removeHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER);
37+
options.addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, projectId);
38+
request.setOptions(options);
39+
return request;
40+
}
41+
8742
}

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/multiproject/MultiProjectRestartIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class MultiProjectRestartIT extends MultiProjectRestTestCase {
2323

2424
@ClassRule
2525
public static ElasticsearchCluster CLUSTER = ElasticsearchCluster.local()
26-
.distribution(DistributionType.DEFAULT)
26+
.distribution(DistributionType.INTEG_TEST)
2727
.setting("test.multi_project.enabled", "true")
2828
.setting("xpack.security.http.ssl.enabled", "false")
2929
.setting("xpack.security.enabled", "false")

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/multiproject/action/ProjectCrudActionIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
import org.elasticsearch.client.Request;
1313
import org.elasticsearch.client.Response;
1414
import org.elasticsearch.client.ResponseException;
15+
import org.elasticsearch.multiproject.MultiProjectRestTestCase;
1516
import org.elasticsearch.test.cluster.ElasticsearchCluster;
1617
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
17-
import org.elasticsearch.test.rest.ESRestTestCase;
1818
import org.elasticsearch.test.rest.ObjectPath;
1919
import org.junit.ClassRule;
2020

@@ -31,11 +31,11 @@
3131
import static org.hamcrest.CoreMatchers.hasItem;
3232
import static org.hamcrest.CoreMatchers.not;
3333

34-
public class ProjectCrudActionIT extends ESRestTestCase {
34+
public class ProjectCrudActionIT extends MultiProjectRestTestCase {
3535

3636
@ClassRule
3737
public static ElasticsearchCluster CLUSTER = ElasticsearchCluster.local()
38-
.distribution(DistributionType.DEFAULT)
38+
.distribution(DistributionType.INTEG_TEST)
3939
.setting("test.multi_project.enabled", "true")
4040
.setting("xpack.security.enabled", "false")
4141
.build();

test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/xpack/security/SecurityRolesMultiProjectIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public class SecurityRolesMultiProjectIT extends MultiProjectRestTestCase {
4040
@ClassRule
4141
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
4242
.nodes(1)
43-
.distribution(DistributionType.DEFAULT)
44-
.module("test-multi-project")
43+
.distribution(DistributionType.INTEG_TEST)
44+
.module("analysis-common")
4545
.setting("test.multi_project.enabled", "true")
4646
.setting("xpack.security.enabled", "true")
4747
.user("admin", PASSWORD)

test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@
4949
import org.elasticsearch.client.RestClientBuilder;
5050
import org.elasticsearch.client.WarningsHandler;
5151
import org.elasticsearch.cluster.metadata.Metadata;
52+
import org.elasticsearch.cluster.metadata.ProjectId;
5253
import org.elasticsearch.common.Strings;
5354
import org.elasticsearch.common.bytes.BytesArray;
5455
import org.elasticsearch.common.bytes.BytesReference;
5556
import org.elasticsearch.common.settings.SecureString;
5657
import org.elasticsearch.common.settings.Settings;
5758
import org.elasticsearch.common.ssl.PemUtils;
59+
import org.elasticsearch.common.util.CollectionUtils;
5860
import org.elasticsearch.common.util.concurrent.ThreadContext;
5961
import org.elasticsearch.common.util.set.Sets;
6062
import org.elasticsearch.common.xcontent.XContentHelper;
@@ -92,6 +94,7 @@
9294
import org.junit.After;
9395
import org.junit.AfterClass;
9496
import org.junit.Before;
97+
import org.junit.BeforeClass;
9598

9699
import java.io.BufferedReader;
97100
import java.io.IOException;
@@ -118,6 +121,7 @@
118121
import java.util.HashMap;
119122
import java.util.HashSet;
120123
import java.util.List;
124+
import java.util.Locale;
121125
import java.util.Map;
122126
import java.util.Objects;
123127
import java.util.Optional;
@@ -266,6 +270,9 @@ public static boolean hasXPack() {
266270
private static RestClient cleanupClient;
267271

268272
private static boolean multiProjectEnabled;
273+
private static String activeProject;
274+
private static Set<String> extraProjects;
275+
private static boolean projectsConfigured = false;
269276

270277
public enum ProductFeature {
271278
XPACK,
@@ -357,6 +364,14 @@ protected static boolean testFeatureServiceInitialized() {
357364
return testFeatureService != ALL_FEATURES;
358365
}
359366

367+
@BeforeClass
368+
public static void initializeProjectIds() {
369+
// The active project-id is slightly longer, and has a fixed prefix so that it's easier to pick in error messages etc.
370+
activeProject = "active00" + randomAlphaOfLength(8).toLowerCase(Locale.ROOT);
371+
extraProjects = randomSet(1, 3, () -> randomAlphaOfLength(12).toLowerCase(Locale.ROOT));
372+
multiProjectEnabled = Boolean.parseBoolean(System.getProperty("tests.multi_project.enabled"));
373+
}
374+
360375
@Before
361376
public void initClient() throws IOException {
362377
if (client == null) {
@@ -367,17 +382,19 @@ public void initClient() throws IOException {
367382
assert testFeatureServiceInitialized() == false;
368383
clusterHosts = parseClusterHosts(getTestRestCluster());
369384
logger.info("initializing REST clients against {}", clusterHosts);
370-
var clientSettings = restClientSettings();
385+
// We add the project ID to the client settings afterward because a lot of subclasses don't call super.restClientSettings(),
386+
// meaning the project ID would be removed from the settings.
387+
var clientSettings = addProjectIdToSettings(restClientSettings());
371388
var adminSettings = restAdminSettings();
389+
var cleanupSettings = cleanupClientSettings();
372390
var hosts = clusterHosts.toArray(new HttpHost[0]);
373391
client = buildClient(clientSettings, hosts);
374392
adminClient = clientSettings.equals(adminSettings) ? client : buildClient(adminSettings, hosts);
375-
cleanupClient = getCleanupClient();
393+
cleanupClient = adminSettings.equals(cleanupSettings) ? adminClient : buildClient(cleanupSettings, hosts);
376394

377395
availableFeatures = EnumSet.of(ProductFeature.LEGACY_TEMPLATES);
378396
Set<String> versions = new HashSet<>();
379397
boolean serverless = false;
380-
String multiProjectPluginVariant = null;
381398

382399
for (Map<?, ?> nodeInfo : getNodesInfo(adminClient).values()) {
383400
var nodeVersion = nodeInfo.get("version").toString();
@@ -407,11 +424,6 @@ public void initClient() throws IOException {
407424
if (moduleName.startsWith("serverless-")) {
408425
serverless = true;
409426
}
410-
if (moduleName.contains("test-multi-project")) {
411-
multiProjectPluginVariant = "test";
412-
} else if (moduleName.contains("serverless-multi-project")) {
413-
multiProjectPluginVariant = "serverless";
414-
}
415427
}
416428
if (serverless) {
417429
availableFeatures.removeAll(
@@ -432,20 +444,9 @@ public void initClient() throws IOException {
432444
.flatMap(Optional::stream)
433445
.collect(Collectors.toSet());
434446
assert semanticNodeVersions.isEmpty() == false || serverless;
435-
436-
if (multiProjectPluginVariant != null) {
437-
final Request settingRequest = new Request(
438-
"GET",
439-
"/_cluster/settings?include_defaults&filter_path=*." + multiProjectPluginVariant + ".multi_project.enabled"
440-
);
441-
settingRequest.setOptions(RequestOptions.DEFAULT.toBuilder().setWarningsHandler(WarningsHandler.PERMISSIVE));
442-
final var response = entityAsMap(adminClient.performRequest(settingRequest));
443-
multiProjectEnabled = Boolean.parseBoolean(
444-
ObjectPath.evaluate(response, "defaults." + multiProjectPluginVariant + ".multi_project.enabled")
445-
);
446-
}
447-
448447
testFeatureService = createTestFeatureService(getClusterStateFeatures(adminClient), semanticNodeVersions);
448+
449+
configureProjects();
449450
}
450451

451452
assert testFeatureServiceInitialized();
@@ -1621,9 +1622,21 @@ protected Settings restAdminSettings() {
16211622
/**
16221623
* Returns the REST client used for cleaning up the cluster.
16231624
*/
1624-
protected RestClient getCleanupClient() {
1625-
assert adminClient != null;
1626-
return adminClient;
1625+
protected Settings cleanupClientSettings() {
1626+
if (multiProjectEnabled == false || shouldConfigureProjects() == false) {
1627+
return restAdminSettings();
1628+
}
1629+
return addProjectIdToSettings(restAdminSettings());
1630+
}
1631+
1632+
private Settings addProjectIdToSettings(Settings settings) {
1633+
if (multiProjectEnabled == false || shouldConfigureProjects() == false) {
1634+
return settings;
1635+
}
1636+
return Settings.builder()
1637+
.put(settings)
1638+
.put(ThreadContext.PREFIX + "." + Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, activeProject)
1639+
.build();
16271640
}
16281641

16291642
/**
@@ -2741,12 +2754,50 @@ protected static void assertResultMap(
27412754
assertMap(result, mapMatcher.entry("columns", columnMatcher).entry("values", valuesMatcher));
27422755
}
27432756

2757+
/**
2758+
* Whether the test framework should configure an active projects and some extra projects. This is true by default (when multi-project
2759+
* is enabled). Subclasses can override this method to avoid configuring projects - e.g. when they configure projects themselves.
2760+
*/
2761+
protected boolean shouldConfigureProjects() {
2762+
assert multiProjectEnabled;
2763+
return true;
2764+
}
2765+
2766+
private void configureProjects() throws IOException {
2767+
if (projectsConfigured || multiProjectEnabled == false || shouldConfigureProjects() == false) {
2768+
return;
2769+
}
2770+
projectsConfigured = true;
2771+
createProject(activeProject);
2772+
for (var project : extraProjects) {
2773+
createProject(project);
2774+
}
2775+
2776+
// The admin client does not set a project id, and can see all projects
2777+
assertProjectIds(
2778+
adminClient(),
2779+
CollectionUtils.concatLists(List.of(Metadata.DEFAULT_PROJECT_ID.id(), activeProject), extraProjects)
2780+
);
2781+
// The test client can only see the project it targets
2782+
assertProjectIds(client(), List.of(activeProject));
2783+
}
2784+
2785+
@After
2786+
public final void assertEmptyProjects() throws Exception {
2787+
if (projectsConfigured == false) {
2788+
return;
2789+
}
2790+
assertEmptyProject(Metadata.DEFAULT_PROJECT_ID.id());
2791+
for (var project : extraProjects) {
2792+
assertEmptyProject(project);
2793+
}
2794+
}
2795+
27442796
protected void createProject(String project) throws IOException {
27452797
assert multiProjectEnabled;
2746-
RestClient client = adminClient();
27472798
final Request request = new Request("PUT", "/_project/" + project);
27482799
try {
2749-
final Response response = client.performRequest(request);
2800+
final Response response = adminClient().performRequest(request);
27502801
logger.info("Created project {} : {}", project, response.getStatusLine());
27512802
} catch (ResponseException e) {
27522803
logger.error("Failed to create project: {}", project);
@@ -2777,6 +2828,23 @@ private Collection<String> getProjectIds(RestClient client) throws IOException {
27772828
}
27782829
}
27792830

2831+
protected void cleanUpProjects() throws IOException {
2832+
assert multiProjectEnabled;
2833+
final var projectIds = getProjectIds(adminClient());
2834+
for (String projectId : projectIds) {
2835+
if (projectId.equals(ProjectId.DEFAULT.id())) {
2836+
continue;
2837+
}
2838+
deleteProject(projectId);
2839+
}
2840+
}
2841+
2842+
private void deleteProject(String project) throws IOException {
2843+
assert multiProjectEnabled;
2844+
final Request request = new Request("DELETE", "/_project/" + project);
2845+
cleanupClient().performRequest(request);
2846+
}
2847+
27802848
protected void assertEmptyProject(String projectId) throws IOException {
27812849
assert multiProjectEnabled;
27822850
final Request request = new Request("GET", "_cluster/state/metadata,routing_table,customs");

0 commit comments

Comments
 (0)