Skip to content

Commit cf7860a

Browse files
ywangdalbertzaharovits
authored andcommitted
[Test] Move helper mthods for multi-project rest test (elastic#124285)
This PR moves the helper methods up to the base ESRestTestCase class so that they can be reused by other subclasses, e.g. the ones on the serverless side. Relates: ES-10292
1 parent 2c4ccdf commit cf7860a

File tree

3 files changed

+137
-142
lines changed

3 files changed

+137
-142
lines changed

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

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.elasticsearch.client.RestClient;
4949
import org.elasticsearch.client.RestClientBuilder;
5050
import org.elasticsearch.client.WarningsHandler;
51+
import org.elasticsearch.cluster.metadata.Metadata;
5152
import org.elasticsearch.common.Strings;
5253
import org.elasticsearch.common.bytes.BytesArray;
5354
import org.elasticsearch.common.bytes.BytesReference;
@@ -74,6 +75,7 @@
7475
import org.elasticsearch.index.query.QueryBuilder;
7576
import org.elasticsearch.index.seqno.ReplicationTracker;
7677
import org.elasticsearch.rest.RestStatus;
78+
import org.elasticsearch.tasks.Task;
7779
import org.elasticsearch.test.AbstractBroadcastResponseTestCase;
7880
import org.elasticsearch.test.ESTestCase;
7981
import org.elasticsearch.test.MapMatcher;
@@ -142,7 +144,9 @@
142144
import static org.elasticsearch.test.rest.TestFeatureService.ALL_FEATURES;
143145
import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS;
144146
import static org.hamcrest.Matchers.anyOf;
147+
import static org.hamcrest.Matchers.containsInAnyOrder;
145148
import static org.hamcrest.Matchers.containsString;
149+
import static org.hamcrest.Matchers.empty;
146150
import static org.hamcrest.Matchers.equalTo;
147151
import static org.hamcrest.Matchers.everyItem;
148152
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -2702,4 +2706,137 @@ protected static void assertResultMap(
27022706
) {
27032707
assertMap(result, mapMatcher.entry("columns", columnMatcher).entry("values", valuesMatcher));
27042708
}
2709+
2710+
protected void createProject(String project) throws IOException {
2711+
assert multiProjectEnabled;
2712+
RestClient client = adminClient();
2713+
final Request request = new Request("PUT", "/_project/" + project);
2714+
try {
2715+
final Response response = client.performRequest(request);
2716+
logger.info("Created project {} : {}", project, response.getStatusLine());
2717+
} catch (ResponseException e) {
2718+
logger.error("Failed to create project: {}", project);
2719+
throw e;
2720+
}
2721+
}
2722+
2723+
protected void assertProjectIds(RestClient client, List<String> expectedProjects) throws IOException {
2724+
assert multiProjectEnabled;
2725+
final Collection<String> actualProjects = getProjectIds(client);
2726+
assertThat(
2727+
"Cluster returned project ids: " + actualProjects,
2728+
actualProjects,
2729+
containsInAnyOrder(expectedProjects.toArray(String[]::new))
2730+
);
2731+
}
2732+
2733+
private Collection<String> getProjectIds(RestClient client) throws IOException {
2734+
assert multiProjectEnabled;
2735+
final Request request = new Request("GET", "/_cluster/state/routing_table?multi_project=true");
2736+
try {
2737+
final ObjectPath response = ObjectPath.createFromResponse(client.performRequest(request));
2738+
final List<Map<String, Object>> projectRouting = response.evaluate("routing_table.projects");
2739+
return projectRouting.stream().map(obj -> (String) obj.get("id")).toList();
2740+
} catch (ResponseException e) {
2741+
logger.error("Failed to retrieve cluster state");
2742+
throw e;
2743+
}
2744+
}
2745+
2746+
protected void assertEmptyProject(String projectId) throws IOException {
2747+
assert multiProjectEnabled;
2748+
final Request request = new Request("GET", "_cluster/state/metadata,routing_table,customs");
2749+
request.setOptions(request.getOptions().toBuilder().addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, projectId).build());
2750+
2751+
var response = responseAsMap(adminClient().performRequest(request));
2752+
ObjectPath state = new ObjectPath(response);
2753+
2754+
final var indexNames = ((Map<?, ?>) state.evaluate("metadata.indices")).keySet();
2755+
final var routingTableEntries = ((Map<?, ?>) state.evaluate("routing_table.indices")).keySet();
2756+
if (indexNames.isEmpty() == false || routingTableEntries.isEmpty() == false) {
2757+
// Only the default project is allowed to have the security index after tests complete.
2758+
// The security index could show up in the indices, routing table, or both.
2759+
// If that happens, we need to check that it hasn't been modified by any leaking API calls.
2760+
if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())
2761+
&& (indexNames.isEmpty() || (indexNames.size() == 1 && indexNames.contains(".security-7")))
2762+
&& (routingTableEntries.isEmpty() || (routingTableEntries.size() == 1 && routingTableEntries.contains(".security-7")))) {
2763+
checkSecurityIndex();
2764+
} else {
2765+
// If there are any other indices or if this is for a non-default project, we fail the test.
2766+
assertThat("Project [" + projectId + "] should not have indices", indexNames, empty());
2767+
assertThat("Project [" + projectId + "] should not have routing entries", routingTableEntries, empty());
2768+
}
2769+
}
2770+
assertThat(
2771+
"Project [" + projectId + "] should not have graveyard entries",
2772+
state.evaluate("metadata.index-graveyard.tombstones"),
2773+
empty()
2774+
);
2775+
2776+
final Map<String, ?> legacyTemplates = state.evaluate("metadata.templates");
2777+
if (legacyTemplates != null) {
2778+
var templateNames = legacyTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
2779+
assertThat("Project [" + projectId + "] should not have legacy templates", templateNames, empty());
2780+
}
2781+
2782+
final Map<String, Object> indexTemplates = state.evaluate("metadata.index_template.index_template");
2783+
if (indexTemplates != null) {
2784+
var templateNames = indexTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
2785+
assertThat("Project [" + projectId + "] should not have index templates", templateNames, empty());
2786+
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2787+
fail("Expected default project to have standard templates, but was null");
2788+
}
2789+
2790+
final Map<String, Object> componentTemplates = state.evaluate("metadata.component_template.component_template");
2791+
if (componentTemplates != null) {
2792+
var templateNames = componentTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
2793+
assertThat("Project [" + projectId + "] should not have component templates", templateNames, empty());
2794+
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2795+
fail("Expected default project to have standard component templates, but was null");
2796+
}
2797+
2798+
final List<Map<String, ?>> pipelines = state.evaluate("metadata.ingest.pipeline");
2799+
if (pipelines != null) {
2800+
var pipelineNames = pipelines.stream()
2801+
.map(pipeline -> String.valueOf(pipeline.get("id")))
2802+
.filter(id -> isXPackIngestPipeline(id) == false)
2803+
.toList();
2804+
assertThat("Project [" + projectId + "] should not have ingest pipelines", pipelineNames, empty());
2805+
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2806+
fail("Expected default project to have standard ingest pipelines, but was null");
2807+
}
2808+
2809+
if (has(ProductFeature.ILM)) {
2810+
final Map<String, Object> ilmPolicies = state.evaluate("metadata.index_lifecycle.policies");
2811+
if (ilmPolicies != null) {
2812+
var policyNames = new HashSet<>(ilmPolicies.keySet());
2813+
policyNames.removeAll(preserveILMPolicyIds());
2814+
assertThat("Project [" + projectId + "] should not have ILM Policies", policyNames, empty());
2815+
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
2816+
fail("Expected default project to have standard ILM policies, but was null");
2817+
}
2818+
}
2819+
}
2820+
2821+
private void checkSecurityIndex() throws IOException {
2822+
assert multiProjectEnabled;
2823+
final Request request = new Request("GET", "/_security/_query/role");
2824+
request.setJsonEntity("""
2825+
{
2826+
"query": {
2827+
"bool": {
2828+
"must_not": {
2829+
"term": {
2830+
"metadata._reserved": true
2831+
}
2832+
}
2833+
}
2834+
}
2835+
}""");
2836+
request.setOptions(
2837+
request.getOptions().toBuilder().addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, Metadata.DEFAULT_PROJECT_ID.id()).build()
2838+
);
2839+
final var response = responseAsMap(adminClient().performRequest(request));
2840+
assertThat("Security index should not contain any non-reserved roles", (Collection<?>) response.get("roles"), empty());
2841+
}
27052842
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,6 @@ private Map<String, Object> authenticate(String projectId, RequestOptions reques
136136
return entityAsMap(client().performRequest(request));
137137
}
138138

139-
private void createProject(String project) throws IOException {
140-
final Request request = new Request("PUT", "/_project/" + project);
141-
client().performRequest(request);
142-
}
143-
144139
private void deleteProject(String project) throws IOException {
145140
final Request request = new Request("DELETE", "/_project/" + project);
146141
client().performRequest(request);

x-pack/qa/multi-project/yaml-test-framework/src/main/java/org/elasticsearch/multiproject/test/MultipleProjectsClientYamlSuiteTestCase.java

Lines changed: 0 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,24 @@
77

88
package org.elasticsearch.multiproject.test;
99

10-
import org.elasticsearch.client.Request;
11-
import org.elasticsearch.client.Response;
12-
import org.elasticsearch.client.ResponseException;
1310
import org.elasticsearch.client.RestClient;
1411
import org.elasticsearch.cluster.metadata.Metadata;
1512
import org.elasticsearch.common.settings.SecureString;
1613
import org.elasticsearch.common.settings.Settings;
1714
import org.elasticsearch.common.util.CollectionUtils;
1815
import org.elasticsearch.common.util.concurrent.ThreadContext;
1916
import org.elasticsearch.tasks.Task;
20-
import org.elasticsearch.test.rest.ObjectPath;
2117
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
2218
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
2319
import org.junit.After;
2420
import org.junit.Before;
2521
import org.junit.BeforeClass;
2622

27-
import java.io.IOException;
28-
import java.util.Collection;
29-
import java.util.HashSet;
3023
import java.util.List;
3124
import java.util.Locale;
32-
import java.util.Map;
3325
import java.util.Objects;
3426
import java.util.Set;
3527

36-
import static org.hamcrest.Matchers.containsInAnyOrder;
37-
import static org.hamcrest.Matchers.empty;
38-
3928
/**
4029
* Base class for running YAML Rest tests against a cluster with multiple projects
4130
*/
@@ -99,132 +88,6 @@ public final void assertEmptyProjects() throws Exception {
9988
}
10089
}
10190

102-
private void createProject(String project) throws IOException {
103-
RestClient client = adminClient();
104-
final Request request = new Request("PUT", "/_project/" + project);
105-
try {
106-
final Response response = client.performRequest(request);
107-
logger.info("Created project {} : {}", project, response.getStatusLine());
108-
} catch (ResponseException e) {
109-
logger.error("Failed to create project: {}", project);
110-
throw e;
111-
}
112-
}
113-
114-
private void assertEmptyProject(String projectId) throws IOException {
115-
final Request request = new Request("GET", "_cluster/state/metadata,routing_table,customs");
116-
request.setOptions(request.getOptions().toBuilder().addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, projectId).build());
117-
118-
var response = responseAsMap(adminClient().performRequest(request));
119-
ObjectPath state = new ObjectPath(response);
120-
121-
final var indexNames = ((Map<?, ?>) state.evaluate("metadata.indices")).keySet();
122-
final var routingTableEntries = ((Map<?, ?>) state.evaluate("routing_table.indices")).keySet();
123-
if (indexNames.isEmpty() == false || routingTableEntries.isEmpty() == false) {
124-
// Only the default project is allowed to have the security index after tests complete.
125-
// The security index could show up in the indices, routing table, or both.
126-
// If that happens, we need to check that it hasn't been modified by any leaking API calls.
127-
if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())
128-
&& (indexNames.isEmpty() || (indexNames.size() == 1 && indexNames.contains(".security-7")))
129-
&& (routingTableEntries.isEmpty() || (routingTableEntries.size() == 1 && routingTableEntries.contains(".security-7")))) {
130-
checkSecurityIndex();
131-
} else {
132-
// If there are any other indices or if this is for a non-default project, we fail the test.
133-
assertThat("Project [" + projectId + "] should not have indices", indexNames, empty());
134-
assertThat("Project [" + projectId + "] should not have routing entries", routingTableEntries, empty());
135-
}
136-
}
137-
assertThat(
138-
"Project [" + projectId + "] should not have graveyard entries",
139-
state.evaluate("metadata.index-graveyard.tombstones"),
140-
empty()
141-
);
142-
143-
final Map<String, ?> legacyTemplates = state.evaluate("metadata.templates");
144-
if (legacyTemplates != null) {
145-
var templateNames = legacyTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
146-
assertThat("Project [" + projectId + "] should not have legacy templates", templateNames, empty());
147-
}
148-
149-
final Map<String, Object> indexTemplates = state.evaluate("metadata.index_template.index_template");
150-
if (indexTemplates != null) {
151-
var templateNames = indexTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
152-
assertThat("Project [" + projectId + "] should not have index templates", templateNames, empty());
153-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
154-
fail("Expected default project to have standard templates, but was null");
155-
}
156-
157-
final Map<String, Object> componentTemplates = state.evaluate("metadata.component_template.component_template");
158-
if (componentTemplates != null) {
159-
var templateNames = componentTemplates.keySet().stream().filter(name -> isXPackTemplate(name) == false).toList();
160-
assertThat("Project [" + projectId + "] should not have component templates", templateNames, empty());
161-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
162-
fail("Expected default project to have standard component templates, but was null");
163-
}
164-
165-
final List<Map<String, ?>> pipelines = state.evaluate("metadata.ingest.pipeline");
166-
if (pipelines != null) {
167-
var pipelineNames = pipelines.stream()
168-
.map(pipeline -> String.valueOf(pipeline.get("id")))
169-
.filter(id -> isXPackIngestPipeline(id) == false)
170-
.toList();
171-
assertThat("Project [" + projectId + "] should not have ingest pipelines", pipelineNames, empty());
172-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
173-
fail("Expected default project to have standard ingest pipelines, but was null");
174-
}
175-
176-
final Map<String, Object> ilmPolicies = state.evaluate("metadata.index_lifecycle.policies");
177-
if (ilmPolicies != null) {
178-
var policyNames = new HashSet<>(ilmPolicies.keySet());
179-
policyNames.removeAll(preserveILMPolicyIds());
180-
assertThat("Project [" + projectId + "] should not have ILM Policies", policyNames, empty());
181-
} else if (projectId.equals(Metadata.DEFAULT_PROJECT_ID.id())) {
182-
fail("Expected default project to have standard ILM policies, but was null");
183-
}
184-
}
185-
186-
private void checkSecurityIndex() throws IOException {
187-
final Request request = new Request("GET", "/_security/_query/role");
188-
request.setJsonEntity("""
189-
{
190-
"query": {
191-
"bool": {
192-
"must_not": {
193-
"term": {
194-
"metadata._reserved": true
195-
}
196-
}
197-
}
198-
}
199-
}""");
200-
request.setOptions(
201-
request.getOptions().toBuilder().addHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, Metadata.DEFAULT_PROJECT_ID.id()).build()
202-
);
203-
final var response = responseAsMap(adminClient().performRequest(request));
204-
assertThat("Security index should not contain any non-reserved roles", (Collection<?>) response.get("roles"), empty());
205-
}
206-
207-
private void assertProjectIds(RestClient client, List<String> expectedProjects) throws IOException {
208-
final Collection<String> actualProjects = getProjectIds(client);
209-
assertThat(
210-
"Cluster returned project ids: " + actualProjects,
211-
actualProjects,
212-
containsInAnyOrder(expectedProjects.toArray(String[]::new))
213-
);
214-
}
215-
216-
protected Collection<String> getProjectIds(RestClient client) throws IOException {
217-
final Request request = new Request("GET", "/_cluster/state/routing_table?multi_project=true");
218-
try {
219-
final ObjectPath response = ObjectPath.createFromResponse(client.performRequest(request));
220-
final List<Map<String, Object>> projectRouting = response.evaluate("routing_table.projects");
221-
return projectRouting.stream().map(obj -> (String) obj.get("id")).toList();
222-
} catch (ResponseException e) {
223-
logger.error("Failed to retrieve cluster state");
224-
throw e;
225-
}
226-
}
227-
22891
@Override
22992
protected Settings restClientSettings() {
23093
return clientSettings(true);

0 commit comments

Comments
 (0)