Skip to content

Commit 5837f9d

Browse files
authored
Return error when remote indices are locally resolved (#74762)
We support the cluster:index syntax in all the API that support cross-cluster calls. Those API will extract remote indices, properly resolve them, and resolve locally the local indices. API that don't support this syntax though end up attempting to resolve such indices locally, which in most cases leads to an index not found exception depending on how ignore_unavailable is configured for the API. The reason for treating these index names as local is that we used to support ':' in index names, but that is no longer supported since 7.x. That means that 7.x may still have indices with ':' in their names from 6.x though. Silently failing makes it hard for users to know that they are even relying on a feature that is not supported, hence we'd like to start throwing error also in 7.x, similarly to what we did in #74556. This commit introduces a check for remote indices that are locally resolved, which is an indication of cross cluster syntax used in API that don't support cross cluster calls. We then check if that index exists in the local cluster, and if so we proceed to resolve it as usual. If not, we throw a specific error that makes it clear to users that they are relying on cross cluster calls calling API that does not support them. relates to #26247
1 parent d96bd54 commit 5837f9d

File tree

3 files changed

+161
-7
lines changed

3 files changed

+161
-7
lines changed

server/src/internalClusterTest/java/org/elasticsearch/get/GetActionIT.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,13 @@ void indexSingleDocumentWithStringFieldsGeneratedFromText(boolean stored, boolea
783783
index("test", "_doc", "1", doc);
784784
}
785785

786+
public void testGetRemoteIndex() {
787+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
788+
() -> client().prepareGet("cluster:index", "_doc", "id").get());
789+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:index]",
790+
iae.getMessage());
791+
}
792+
786793
private void assertGetFieldsAlwaysWorks(String index, String type, String docId, String[] fields) {
787794
assertGetFieldsAlwaysWorks(index, type, docId, fields, null);
788795
}

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

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public Index[] concreteIndices(ClusterState state, IndicesRequest request) {
113113
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
114114
* contains no indices and the indices options in the context don't allow such a case.
115115
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
116-
* indices options in the context don't allow such a case.
116+
* indices options in the context don't allow such a case; if a remote index is requested.
117117
*/
118118
public String[] concreteIndexNames(ClusterState state, IndicesOptions options, String... indexExpressions) {
119119
Context context = new Context(state, options, getSystemIndexAccessLevel(),
@@ -168,7 +168,7 @@ public List<String> dataStreamNames(ClusterState state, IndicesOptions options,
168168
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
169169
* contains no indices and the indices options in the context don't allow such a case.
170170
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
171-
* indices options in the context don't allow such a case.
171+
* indices options in the context don't allow such a case; if a remote index is requested.
172172
*/
173173
public Index[] concreteIndices(ClusterState state, IndicesOptions options, String... indexExpressions) {
174174
return concreteIndices(state, options, false, indexExpressions);
@@ -190,7 +190,7 @@ public Index[] concreteIndices(ClusterState state, IndicesOptions options, boole
190190
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
191191
* contains no indices and the indices options in the context don't allow such a case.
192192
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
193-
* indices options in the context don't allow such a case.
193+
* indices options in the context don't allow such a case; if a remote index is requested.
194194
*/
195195
public Index[] concreteIndices(ClusterState state, IndicesRequest request, long startTime) {
196196
Context context = new Context(state, request.indicesOptions(), startTime, false, false, request.includeDataStreams(), false,
@@ -208,11 +208,45 @@ String[] concreteIndexNames(Context context, String... indexExpressions) {
208208
}
209209

210210
Index[] concreteIndices(Context context, String... indexExpressions) {
211+
Metadata metadata = context.getState().metadata();
212+
IndicesOptions options = context.getOptions();
211213
if (indexExpressions == null || indexExpressions.length == 0) {
212214
indexExpressions = new String[]{Metadata.ALL};
215+
} else {
216+
if (options.ignoreUnavailable() == false) {
217+
Set<String> crossClusterIndices = new HashSet<>();
218+
for (String indexExpression : indexExpressions) {
219+
if (indexExpression.contains(":")) {
220+
List<String> resolved;
221+
try {
222+
resolved = wildcardExpressionResolver.resolve(context, Collections.singletonList(indexExpression));
223+
} catch(IndexNotFoundException e) {
224+
resolved = Collections.emptyList();
225+
}
226+
if (resolved.isEmpty()) {
227+
crossClusterIndices.add(indexExpression);
228+
} else {
229+
boolean found = false;
230+
for (String index : resolved) {
231+
if (metadata.getIndicesLookup().containsKey(index)) {
232+
found = true;
233+
break;
234+
}
235+
}
236+
if (found == false) {
237+
crossClusterIndices.add(indexExpression);
238+
}
239+
}
240+
241+
}
242+
}
243+
if (crossClusterIndices.size() > 0) {
244+
throw new IllegalArgumentException("Cross-cluster calls are not supported in this context but remote indices " +
245+
"were requested: " + crossClusterIndices);
246+
}
247+
}
213248
}
214-
Metadata metadata = context.getState().metadata();
215-
IndicesOptions options = context.getOptions();
249+
216250
// If only one index is specified then whether we fail a request if an index is missing depends on the allow_no_indices
217251
// option. At some point we should change this, because there shouldn't be a reason why whether a single index
218252
// or multiple indices are specified yield different behaviour.
@@ -397,7 +431,7 @@ private static IllegalArgumentException aliasesNotSupportedException(String expr
397431
* @param state the cluster state containing all the data to resolve to expression to a concrete index
398432
* @param request The request that defines how the an alias or an index need to be resolved to a concrete index
399433
* and the expression that can be resolved to an alias or an index name.
400-
* @throws IllegalArgumentException if the index resolution lead to more than one index
434+
* @throws IllegalArgumentException if the index resolution returns more than one index; if a remote index is requested.
401435
* @return the concrete index obtained as a result of the index resolution
402436
*/
403437
public Index concreteSingleIndex(ClusterState state, IndicesRequest request) {
@@ -416,7 +450,8 @@ public Index concreteSingleIndex(ClusterState state, IndicesRequest request) {
416450
* @param state the cluster state containing all the data to resolve to expression to a concrete index
417451
* @param request The request that defines how the an alias or an index need to be resolved to a concrete index
418452
* and the expression that can be resolved to an alias or an index name.
419-
* @throws IllegalArgumentException if the index resolution does not lead to an index, or leads to more than one index
453+
* @throws IllegalArgumentException if the index resolution does not lead to an index, or leads to more than one index, as well as
454+
* if a remote index is requested.
420455
* @return the write index obtained as a result of the index resolution
421456
*/
422457
public Index concreteWriteIndex(ClusterState state, IndicesRequest request) {

server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,118 @@ private ClusterState systemIndexTestClusterState() {
24412441
return ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build();
24422442
}
24432443

2444+
public void testRemoteIndex() {
2445+
Metadata.Builder mdBuilder = Metadata.builder();
2446+
ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build();
2447+
2448+
{
2449+
IndicesOptions options = IndicesOptions.fromOptions(false, randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean());
2450+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2451+
state, options, SystemIndexAccessLevel.NONE);
2452+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
2453+
() -> indexNameExpressionResolver.concreteIndexNames(context, "cluster:index", "local"));
2454+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:index]",
2455+
iae.getMessage());
2456+
}
2457+
{
2458+
IndicesOptions options = IndicesOptions.fromOptions(false, randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean());
2459+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2460+
state, options, SystemIndexAccessLevel.NONE);
2461+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
2462+
() -> indexNameExpressionResolver.concreteIndexNames(context, "cluster:*", "local"));
2463+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:*]",
2464+
iae.getMessage());
2465+
}
2466+
{
2467+
IndicesOptions options = IndicesOptions.fromOptions(false, randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean());
2468+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2469+
state, options, SystemIndexAccessLevel.NONE);
2470+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
2471+
() -> indexNameExpressionResolver.concreteIndexNames(context, "cluster:i*", "local"));
2472+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:i*]",
2473+
iae.getMessage());
2474+
}
2475+
{
2476+
IndicesOptions options = IndicesOptions.fromOptions(true, true, randomBoolean(), randomBoolean(), randomBoolean());
2477+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2478+
state, options, SystemIndexAccessLevel.NONE);
2479+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:index", "local");
2480+
assertEquals(0, indexNames.length);
2481+
}
2482+
{
2483+
IndicesOptions options = IndicesOptions.fromOptions(true, true, randomBoolean(), randomBoolean(), randomBoolean());
2484+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2485+
state, options, SystemIndexAccessLevel.NONE);
2486+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:i*", "local");
2487+
assertEquals(0, indexNames.length);
2488+
}
2489+
}
2490+
2491+
public void testColonWithinIndexName() {
2492+
Settings settings = Settings.builder().build();
2493+
Metadata.Builder mdBuilder = Metadata.builder()
2494+
.put(indexBuilder("cluster:index", settings).state(State.OPEN));
2495+
ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build();
2496+
2497+
{
2498+
IndicesOptions options = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(),
2499+
randomBoolean(), randomBoolean(), randomBoolean());
2500+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2501+
state, options, SystemIndexAccessLevel.NONE);
2502+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:index");
2503+
assertThat(indexNames, arrayContaining("cluster:index"));
2504+
}
2505+
//Using wildcards, expand wildcards to open indices: true -> index locally resolved
2506+
{
2507+
IndicesOptions options = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, randomBoolean(), randomBoolean());
2508+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2509+
state, options, SystemIndexAccessLevel.NONE);
2510+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:*");
2511+
assertThat(indexNames, arrayContaining("cluster:index"));
2512+
}
2513+
{
2514+
IndicesOptions options = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, randomBoolean(), randomBoolean());
2515+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2516+
state, options, SystemIndexAccessLevel.NONE);
2517+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:in*");
2518+
assertThat(indexNames, arrayContaining("cluster:index"));
2519+
}
2520+
//With wildcards, ignore_unavailable: false, expand wildcards to open indices: false -> error about cross cluster indices
2521+
{
2522+
IndicesOptions options = IndicesOptions.fromOptions(false, randomBoolean(), false, randomBoolean(), randomBoolean());
2523+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2524+
state, options, SystemIndexAccessLevel.NONE);
2525+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
2526+
() -> indexNameExpressionResolver.concreteIndexNames(context, "cluster:*"));
2527+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:*]",
2528+
iae.getMessage());
2529+
}
2530+
{
2531+
IndicesOptions options = IndicesOptions.fromOptions(false, randomBoolean(), false, randomBoolean(), randomBoolean());
2532+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2533+
state, options, SystemIndexAccessLevel.NONE);
2534+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
2535+
() -> indexNameExpressionResolver.concreteIndexNames(context, "cluster:in*"));
2536+
assertEquals("Cross-cluster calls are not supported in this context but remote indices were requested: [cluster:in*]",
2537+
iae.getMessage());
2538+
}
2539+
//With wildcards: ignore_unavailable: true, allow_no_indices: true, expand wildcards to open indices: false -> empty list of indices
2540+
{
2541+
IndicesOptions options = IndicesOptions.fromOptions(true, true, false, randomBoolean(), randomBoolean());
2542+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2543+
state, options, SystemIndexAccessLevel.NONE);
2544+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:*");
2545+
assertEquals(0, indexNames.length);
2546+
}
2547+
{
2548+
IndicesOptions options = IndicesOptions.fromOptions(true, true, false, randomBoolean(), randomBoolean());
2549+
IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context(
2550+
state, options, SystemIndexAccessLevel.NONE);
2551+
String[] indexNames = indexNameExpressionResolver.concreteIndexNames(context, "cluster:in*");
2552+
assertEquals(0, indexNames.length);
2553+
}
2554+
}
2555+
24442556
private List<String> resolveConcreteIndexNameList(ClusterState state, SearchRequest request) {
24452557
return Arrays
24462558
.stream(indexNameExpressionResolver.concreteIndices(state, request))

0 commit comments

Comments
 (0)