Skip to content

Commit fbee351

Browse files
authored
Improve error message for NoMatchingProjectException (#138141)
Add project routing information to the error message when applicable. Resolves: ES-12966
1 parent 1f62936 commit fbee351

File tree

5 files changed

+69
-27
lines changed

5 files changed

+69
-27
lines changed

server/src/main/java/org/elasticsearch/cluster/metadata/IndexAbstractionResolver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public ResolvedIndexExpressions resolveIndexAbstractions(
7474
final Function<IndexComponentSelector, Set<String>> allAuthorizedAndAvailableBySelector,
7575
final BiPredicate<String, IndexComponentSelector> isAuthorized,
7676
final TargetProjects targetProjects,
77-
final boolean includeDataStreams
77+
final boolean includeDataStreams,
78+
@Nullable final String projectRouting
7879
) {
7980
if (targetProjects == TargetProjects.LOCAL_ONLY_FOR_CPS_DISABLED) {
8081
final String message = "cannot resolve indices cross project if target set is local only";
@@ -88,7 +89,7 @@ public ResolvedIndexExpressions resolveIndexAbstractions(
8889
boolean wildcardSeen = false;
8990
for (String originalIndexExpression : indices) {
9091
final CrossProjectIndexExpressionsRewriter.IndexRewriteResult indexRewriteResult = CrossProjectIndexExpressionsRewriter
91-
.rewriteIndexExpression(originalIndexExpression, originProjectAlias, linkedProjectAliases);
92+
.rewriteIndexExpression(originalIndexExpression, originProjectAlias, linkedProjectAliases, projectRouting);
9293

9394
final String localIndexExpression = indexRewriteResult.localExpression();
9495
if (localIndexExpression == null) {

server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectIndexExpressionsRewriter.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ public static Map<String, IndexRewriteResult> rewriteIndexExpressions(
7272
if (canonicalExpressionsMap.containsKey(indexExpression)) {
7373
continue;
7474
}
75-
canonicalExpressionsMap.put(indexExpression, rewriteIndexExpression(indexExpression, originProjectAlias, allProjectAliases));
75+
canonicalExpressionsMap.put(
76+
indexExpression,
77+
rewriteIndexExpression(indexExpression, originProjectAlias, allProjectAliases, null)
78+
);
7679
}
7780
return canonicalExpressionsMap;
7881
}
@@ -85,27 +88,30 @@ public static Map<String, IndexRewriteResult> rewriteIndexExpressions(
8588
* it can match on its actual alias and on the special alias "_origin". Any expression matched by the origin
8689
* project also cannot be qualified with its actual alias in the final rewritten expression.
8790
* @param allProjectAliases the list of all project aliases (linked and origin) consider for a request
91+
* @param projectRouting the project routing that was applied to determine the origin and linked projects.
92+
* {@code null} if no project routing was applied.
8893
* @throws IllegalArgumentException if exclusions, date math or selectors are present in the index expressions
8994
* @throws NoMatchingProjectException if a qualified resource cannot be resolved because a project is missing
9095
*/
9196
public static IndexRewriteResult rewriteIndexExpression(
9297
String indexExpression,
9398
@Nullable String originProjectAlias,
94-
Set<String> allProjectAliases
99+
Set<String> allProjectAliases,
100+
@Nullable String projectRouting
95101
) {
96102
maybeThrowOnUnsupportedResource(indexExpression);
97103

98104
// Always 404 when no project is available for index resolution. This is matching error handling behaviour for resolving
99105
// projects with qualified index patterns such as "missing-*:index".
100106
if (originProjectAlias == null && allProjectAliases.isEmpty()) {
101-
// TODO: add project_routing string to the exception message
102-
throw new NoMatchingProjectException("no matching project after applying project routing");
107+
assert projectRouting != null;
108+
throw new NoMatchingProjectException("no matching project after applying project routing [" + projectRouting + "]");
103109
}
104110

105111
final boolean isQualified = RemoteClusterAware.isRemoteIndexName(indexExpression);
106112
final IndexRewriteResult rewrittenExpression;
107113
if (isQualified) {
108-
rewrittenExpression = rewriteQualifiedExpression(indexExpression, originProjectAlias, allProjectAliases);
114+
rewrittenExpression = rewriteQualifiedExpression(indexExpression, originProjectAlias, allProjectAliases, projectRouting);
109115
logger.debug("Rewrote qualified expression [{}] to [{}]", indexExpression, rewrittenExpression);
110116
} else {
111117
rewrittenExpression = rewriteUnqualifiedExpression(indexExpression, originProjectAlias, allProjectAliases);
@@ -146,7 +152,8 @@ private static IndexRewriteResult rewriteUnqualifiedExpression(
146152
private static IndexRewriteResult rewriteQualifiedExpression(
147153
String resource,
148154
@Nullable String originProjectAlias,
149-
Set<String> allProjectAliases
155+
Set<String> allProjectAliases,
156+
@Nullable String projectRouting
150157
) {
151158
String[] splitResource = RemoteClusterAware.splitIndexName(resource);
152159
assert splitResource.length == 2
@@ -167,7 +174,7 @@ private static IndexRewriteResult rewriteQualifiedExpression(
167174

168175
if (originProjectAlias == null && ProjectRoutingResolver.ORIGIN.equals(requestedProjectAlias)) {
169176
// handling case where we have a qualified expression like: _origin:indexName but no _origin project is set
170-
throw new NoMatchingProjectException(requestedProjectAlias);
177+
throw new NoMatchingProjectException(requestedProjectAlias, projectRouting);
171178
}
172179

173180
try {
@@ -177,7 +184,7 @@ private static IndexRewriteResult rewriteQualifiedExpression(
177184
);
178185

179186
if (allProjectsMatchingAlias.isEmpty()) {
180-
throw new NoMatchingProjectException(requestedProjectAlias);
187+
throw new NoMatchingProjectException(requestedProjectAlias, projectRouting);
181188
}
182189

183190
String localExpression = null;
@@ -193,7 +200,7 @@ private static IndexRewriteResult rewriteQualifiedExpression(
193200
return new IndexRewriteResult(localExpression, resourcesMatchingLinkedProjectAliases);
194201
} catch (NoSuchRemoteClusterException ex) {
195202
logger.debug(ex.getMessage(), ex);
196-
throw new NoMatchingProjectException(requestedProjectAlias);
203+
throw new NoMatchingProjectException(requestedProjectAlias, projectRouting);
197204
}
198205
}
199206

server/src/main/java/org/elasticsearch/search/crossproject/NoMatchingProjectException.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.elasticsearch.ResourceNotFoundException;
1313
import org.elasticsearch.common.io.stream.StreamInput;
14+
import org.elasticsearch.core.Nullable;
1415

1516
import java.io.IOException;
1617

@@ -19,8 +20,12 @@
1920
*/
2021
public final class NoMatchingProjectException extends ResourceNotFoundException {
2122

22-
public NoMatchingProjectException(String projectName) {
23-
super("No such project: [" + projectName + "]");
23+
public NoMatchingProjectException(String message) {
24+
super(message);
25+
}
26+
27+
public NoMatchingProjectException(String projectAlias, @Nullable String projectRouting) {
28+
super("No such project: [" + projectAlias + "]" + (projectRouting != null ? " with project routing [" + projectRouting + "]" : ""));
2429
}
2530

2631
public NoMatchingProjectException(StreamInput in) throws IOException {

server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectIndexExpressionsRewriterTests.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
import java.util.ArrayList;
1818
import java.util.List;
1919
import java.util.Map;
20+
import java.util.Set;
21+
import java.util.stream.Collectors;
2022

2123
import static org.hamcrest.Matchers.containsInAnyOrder;
24+
import static org.hamcrest.Matchers.equalTo;
2225

2326
public class CrossProjectIndexExpressionsRewriterTests extends ESTestCase {
2427

@@ -354,19 +357,43 @@ public void test_ALLExpressionShouldMatchAll() {
354357
}
355358

356359
public void testRewritingShouldThrowIfNotProjectMatchExpression() {
357-
ProjectRoutingInfo origin = createRandomProjectWithAlias("P0");
358-
List<ProjectRoutingInfo> linked = List.of(
359-
createRandomProjectWithAlias("P1"),
360-
createRandomProjectWithAlias("P2"),
361-
createRandomProjectWithAlias("Q1"),
362-
createRandomProjectWithAlias("Q2")
363-
);
364-
String[] requestedResources = new String[] { "X*:metrics" };
360+
{
361+
final var projectRouting = "_alias:" + randomAlphaOfLengthBetween(1, 10);
362+
363+
final var e = expectThrows(
364+
NoMatchingProjectException.class,
365+
() -> CrossProjectIndexExpressionsRewriter.rewriteIndexExpression(randomIdentifier(), null, Set.of(), projectRouting)
366+
);
367+
assertThat(e.getMessage(), equalTo("no matching project after applying project routing [" + projectRouting + "]"));
368+
}
365369

366-
expectThrows(
367-
NoMatchingProjectException.class,
368-
() -> CrossProjectIndexExpressionsRewriter.rewriteIndexExpressions(origin, linked, requestedResources)
369-
);
370+
{
371+
ProjectRoutingInfo origin = createRandomProjectWithAlias("P0");
372+
List<ProjectRoutingInfo> linked = List.of(
373+
createRandomProjectWithAlias("P1"),
374+
createRandomProjectWithAlias("P2"),
375+
createRandomProjectWithAlias("Q1"),
376+
createRandomProjectWithAlias("Q2")
377+
);
378+
String indexExpression = "X*:metrics";
379+
final var projectRouting = randomBoolean() ? "_alias:" + randomAlphaOfLengthBetween(1, 10) : null;
380+
381+
final var e = expectThrows(
382+
NoMatchingProjectException.class,
383+
() -> CrossProjectIndexExpressionsRewriter.rewriteIndexExpression(
384+
indexExpression,
385+
origin.projectAlias(),
386+
linked.stream().map(ProjectRoutingInfo::projectAlias).collect(Collectors.toUnmodifiableSet()),
387+
projectRouting
388+
)
389+
);
390+
391+
if (projectRouting != null) {
392+
assertThat(e.getMessage(), equalTo("No such project: [X*] with project routing [" + projectRouting + "]"));
393+
} else {
394+
assertThat(e.getMessage(), equalTo("No such project: [X*]"));
395+
}
396+
}
370397
}
371398

372399
private ProjectRoutingInfo createRandomProjectWithAlias(String alias) {

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ ResolvedIndices resolveIndicesAndAliases(
399399
final var rewritten = CrossProjectIndexExpressionsRewriter.rewriteIndexExpression(
400400
indexExpression,
401401
resolvedProjects.originProjectAlias(),
402-
resolvedProjects.allProjectAliases()
402+
resolvedProjects.allProjectAliases(),
403+
replaceable.getProjectRouting()
403404
);
404405
remoteIndices = rewritten.remoteExpressions();
405406
if (resolvedProjects.originProject() == null || rewritten.localExpression() == null) {
@@ -448,7 +449,8 @@ ResolvedIndices resolveIndicesAndAliases(
448449
authorizedIndices::all,
449450
authorizedIndices::check,
450451
resolvedProjects,
451-
indicesRequest.includeDataStreams()
452+
indicesRequest.includeDataStreams(),
453+
replaceable.getProjectRouting()
452454
);
453455
setResolvedIndexExpressionsIfUnset(replaceable, resolved);
454456
resolvedIndicesBuilder.addLocal(resolved.getLocalIndicesList());

0 commit comments

Comments
 (0)