Skip to content

Commit b38bc7f

Browse files
committed
Allow REST tests to run in MP mode
1 parent 92148cf commit b38bc7f

File tree

3 files changed

+107
-126
lines changed

3 files changed

+107
-126
lines changed

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

+86-38
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,15 @@ 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+
// TODO do this in a different way
373+
multiProjectEnabled = Objects.equals(System.getProperty("test.multi_project.enabled"), "true");
374+
}
375+
360376
@Before
361377
public void initClient() throws IOException {
362378
if (client == null) {
@@ -367,17 +383,17 @@ public void initClient() throws IOException {
367383
assert testFeatureServiceInitialized() == false;
368384
clusterHosts = parseClusterHosts(getTestRestCluster());
369385
logger.info("initializing REST clients against {}", clusterHosts);
370-
var clientSettings = restClientSettings();
386+
var clientSettings = addProjectIdToSettings(restClientSettings());
371387
var adminSettings = restAdminSettings();
388+
var cleanupSettings = cleanupClientSettings();
372389
var hosts = clusterHosts.toArray(new HttpHost[0]);
373390
client = buildClient(clientSettings, hosts);
374391
adminClient = clientSettings.equals(adminSettings) ? client : buildClient(adminSettings, hosts);
375-
cleanupClient = getCleanupClient();
392+
cleanupClient = adminSettings.equals(cleanupSettings) ? adminClient : buildClient(cleanupSettings, hosts);
376393

377394
availableFeatures = EnumSet.of(ProductFeature.LEGACY_TEMPLATES);
378395
Set<String> versions = new HashSet<>();
379396
boolean serverless = false;
380-
String multiProjectPluginVariant = null;
381397

382398
for (Map<?, ?> nodeInfo : getNodesInfo(adminClient).values()) {
383399
var nodeVersion = nodeInfo.get("version").toString();
@@ -407,11 +423,6 @@ public void initClient() throws IOException {
407423
if (moduleName.startsWith("serverless-")) {
408424
serverless = true;
409425
}
410-
if (moduleName.contains("test-multi-project")) {
411-
multiProjectPluginVariant = "test";
412-
} else if (moduleName.contains("serverless-multi-project")) {
413-
multiProjectPluginVariant = "serverless";
414-
}
415426
}
416427
if (serverless) {
417428
availableFeatures.removeAll(
@@ -432,22 +443,11 @@ public void initClient() throws IOException {
432443
.flatMap(Optional::stream)
433444
.collect(Collectors.toSet());
434445
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-
448446
testFeatureService = createTestFeatureService(getClusterStateFeatures(adminClient), semanticNodeVersions);
449447
}
450448

449+
configureProjects();
450+
451451
assert testFeatureServiceInitialized();
452452
assert client != null;
453453
assert adminClient != null;
@@ -456,6 +456,40 @@ public void initClient() throws IOException {
456456
assert nodesVersions != null;
457457
}
458458

459+
private void configureProjects() throws IOException {
460+
if (projectsConfigured || multiProjectEnabled == false) {
461+
return;
462+
}
463+
projectsConfigured = true;
464+
createProject(activeProject);
465+
for (var project : extraProjects) {
466+
createProject(project);
467+
}
468+
469+
// The admin client does not set a project id, and can see all projects
470+
assertProjectIds(
471+
adminClient(),
472+
CollectionUtils.concatLists(List.of(Metadata.DEFAULT_PROJECT_ID.id(), activeProject), extraProjects)
473+
);
474+
// The test client can only see the project it targets
475+
assertProjectIds(client(), List.of(activeProject));
476+
}
477+
478+
@After
479+
public final void assertEmptyProjects() throws Exception {
480+
if (multiProjectEnabled == false) {
481+
return;
482+
}
483+
assertEmptyProject(Metadata.DEFAULT_PROJECT_ID.id());
484+
for (var project : extraProjects) {
485+
assertEmptyProject(project);
486+
}
487+
}
488+
489+
public static String activeProject() {
490+
return activeProject;
491+
}
492+
459493
protected final TestFeatureService createTestFeatureService(
460494
Map<String, Set<String>> clusterStateFeatures,
461495
Set<Version> semanticNodeVersions
@@ -1604,10 +1638,6 @@ protected Settings restClientSettings() {
16041638
String token = basicAuthHeaderValue(username, new SecureString(password.toCharArray()));
16051639
builder.put(ThreadContext.PREFIX + ".Authorization", token);
16061640
}
1607-
if (System.getProperty("tests.rest.project.id") != null) {
1608-
final var projectId = System.getProperty("tests.rest.project.id");
1609-
builder.put(ThreadContext.PREFIX + ".X-Elastic-Project-Id", projectId);
1610-
}
16111641
return builder.build();
16121642
}
16131643

@@ -1621,9 +1651,21 @@ protected Settings restAdminSettings() {
16211651
/**
16221652
* Returns the REST client used for cleaning up the cluster.
16231653
*/
1624-
protected RestClient getCleanupClient() {
1625-
assert adminClient != null;
1626-
return adminClient;
1654+
protected Settings cleanupClientSettings() {
1655+
if (multiProjectEnabled == false) {
1656+
return restAdminSettings();
1657+
}
1658+
return addProjectIdToSettings(restAdminSettings());
1659+
}
1660+
1661+
private Settings addProjectIdToSettings(Settings settings) {
1662+
if (multiProjectEnabled == false) {
1663+
return settings;
1664+
}
1665+
return Settings.builder()
1666+
.put(settings)
1667+
.put(ThreadContext.PREFIX + "." + Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, activeProject)
1668+
.build();
16271669
}
16281670

16291671
/**
@@ -2716,10 +2758,9 @@ protected static void assertResultMap(
27162758

27172759
protected void createProject(String project) throws IOException {
27182760
assert multiProjectEnabled;
2719-
RestClient client = adminClient();
27202761
final Request request = new Request("PUT", "/_project/" + project);
27212762
try {
2722-
final Response response = client.performRequest(request);
2763+
final Response response = adminClient().performRequest(request);
27232764
logger.info("Created project {} : {}", project, response.getStatusLine());
27242765
} catch (ResponseException e) {
27252766
logger.error("Failed to create project: {}", project);
@@ -2750,6 +2791,21 @@ private Collection<String> getProjectIds(RestClient client) throws IOException {
27502791
}
27512792
}
27522793

2794+
protected void cleanUpProjects() throws IOException {
2795+
final var projectIds = getProjectIds(adminClient());
2796+
for (String projectId : projectIds) {
2797+
if (projectId.equals(ProjectId.DEFAULT.id())) {
2798+
continue;
2799+
}
2800+
deleteProject(projectId);
2801+
}
2802+
}
2803+
2804+
private void deleteProject(String project) throws IOException {
2805+
final Request request = new Request("DELETE", "/_project/" + project);
2806+
cleanupClient().performRequest(request);
2807+
}
2808+
27532809
protected void assertEmptyProject(String projectId) throws IOException {
27542810
assert multiProjectEnabled;
27552811
final Request request = new Request("GET", "_cluster/state/metadata,routing_table,customs");
@@ -2790,16 +2846,12 @@ protected void assertEmptyProject(String projectId) throws IOException {
27902846
if (indexTemplates != null) {
27912847
var templateNames = indexTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
27922848
assertThat("Project [" + projectId + "] should not have index templates", templateNames, empty());
2793-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2794-
fail("Expected default project to have standard templates, but was null");
27952849
}
27962850

27972851
final Map<String, Object> componentTemplates = state.evaluate("metadata.component_template.component_template");
27982852
if (componentTemplates != null) {
27992853
var templateNames = componentTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
28002854
assertThat("Project [" + projectId + "] should not have component templates", templateNames, empty());
2801-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2802-
fail("Expected default project to have standard component templates, but was null");
28032855
}
28042856

28052857
final List<Map<String, ?>> pipelines = state.evaluate("metadata.ingest.pipeline");
@@ -2809,8 +2861,6 @@ protected void assertEmptyProject(String projectId) throws IOException {
28092861
.filter(id -> isXPackIngestPipeline(id) == false)
28102862
.toList();
28112863
assertThat("Project [" + projectId + "] should not have ingest pipelines", pipelineNames, empty());
2812-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2813-
fail("Expected default project to have standard ingest pipelines, but was null");
28142864
}
28152865

28162866
if (has(ProductFeature.ILM)) {
@@ -2819,8 +2869,6 @@ protected void assertEmptyProject(String projectId) throws IOException {
28192869
var policyNames = new HashSet<>(ilmPolicies.keySet());
28202870
policyNames.removeAll(preserveILMPolicyIds());
28212871
assertThat("Project [" + projectId + "] should not have ILM Policies", policyNames, empty());
2822-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2823-
fail("Expected default project to have standard ILM policies, but was null");
28242872
}
28252873
}
28262874
}

x-pack/plugin/security/qa/multi-project/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/jwt/JwtMultiProjectIT.java

+19-23
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.elasticsearch.test.cluster.util.resource.Resource;
3030
import org.elasticsearch.test.rest.ESRestTestCase;
3131
import org.hamcrest.Matchers;
32+
import org.junit.After;
3233
import org.junit.ClassRule;
3334

3435
import java.io.IOException;
@@ -81,33 +82,33 @@ protected Settings restClientSettings() {
8182
return builder.build();
8283
}
8384

85+
@After
86+
public void cleanup() throws IOException {
87+
cleanUpProjects();
88+
}
89+
8490
@FixForMultiProject(description = "This should also test role mappings from file-based-settings (when they are project-scoped)")
8591
public void testSameJwtAuthenticatesToMultipleProjects() throws Exception {
8692
final String project1 = randomIdentifier();
8793
final String project2 = randomIdentifier();
8894

89-
try {
90-
createProject(project1);
91-
createProject(project2);
95+
createProject(project1);
96+
createProject(project2);
9297

93-
final JWTClaimsSet.Builder claims = buildJwtClaims();
94-
final JWSHeader jwtHeader = new JWSHeader.Builder(JWSAlgorithm.parse("RS256")).build();
95-
final SignedJWT jwt = signJwt(jwtHeader, claims.build());
98+
final JWTClaimsSet.Builder claims = buildJwtClaims();
99+
final JWSHeader jwtHeader = new JWSHeader.Builder(JWSAlgorithm.parse("RS256")).build();
100+
final SignedJWT jwt = signJwt(jwtHeader, claims.build());
96101

97-
RequestOptions requestOptions = RequestOptions.DEFAULT.toBuilder()
98-
.addHeader("Authorization", "Bearer " + jwt.serialize())
99-
.addHeader("ES-Client-Authentication", "SharedSecret " + CLIENT_SECRET)
100-
.build();
102+
RequestOptions requestOptions = RequestOptions.DEFAULT.toBuilder()
103+
.addHeader("Authorization", "Bearer " + jwt.serialize())
104+
.addHeader("ES-Client-Authentication", "SharedSecret " + CLIENT_SECRET)
105+
.build();
101106

102-
final Map<String, Object> authProject1 = authenticate(project1, requestOptions);
103-
assertThat(authProject1, Matchers.hasEntry("username", "tester"));
107+
final Map<String, Object> authProject1 = authenticate(project1, requestOptions);
108+
assertThat(authProject1, Matchers.hasEntry("username", "tester"));
104109

105-
final Map<String, Object> authProject2 = authenticate(project2, requestOptions);
106-
assertThat(authProject2, Matchers.hasEntry("username", "tester"));
107-
} finally {
108-
deleteProject(project1);
109-
deleteProject(project2);
110-
}
110+
final Map<String, Object> authProject2 = authenticate(project2, requestOptions);
111+
assertThat(authProject2, Matchers.hasEntry("username", "tester"));
111112
}
112113

113114
private JWTClaimsSet.Builder buildJwtClaims() {
@@ -135,9 +136,4 @@ private Map<String, Object> authenticate(String projectId, RequestOptions reques
135136
request.setOptions(requestOptions.toBuilder().addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, projectId));
136137
return entityAsMap(client().performRequest(request));
137138
}
138-
139-
private void deleteProject(String project) throws IOException {
140-
final Request request = new Request("DELETE", "/_project/" + project);
141-
client().performRequest(request);
142-
}
143139
}

0 commit comments

Comments
 (0)