Skip to content

Commit e5c510c

Browse files
authored
Add alias support to fleet search API (#79285)
Currently the fleet search and msearch APIs do not support aliases. This PR adds support if the alias resolves to a single concrete index.
1 parent bd01654 commit e5c510c

File tree

8 files changed

+89
-29
lines changed

8 files changed

+89
-29
lines changed

docs/reference/fleet/fleet-multi-search.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ without prior notice.
2424
[[fleet-multi-search-api-path-params]]
2525
==== {api-path-parms-title}
2626

27-
`<index>`::
27+
`<target>`::
2828
(Optional, string)
29-
A single index. Index aliases are not supported.
29+
A single target to search. If the target is an index alias, it must resolve to a single index.
3030

3131
[role="child_attributes"]
3232
[[fleet-multi-search-api-query-parms]]

docs/reference/fleet/fleet-search.asciidoc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ refresh. The checkpoints are indexed by shard.
2626
If a timeout occurs before the the checkpoint has been refreshed into Elasticsearch,
2727
the search request will timeout.
2828

29-
The fleet search API only supports searches against a single index.
29+
The fleet search API only supports searches against a single target. If an index alias
30+
is supplied as the search target, it must resolve to a single concrete index.
3031

3132
[discrete]
3233
[[fleet-search-partial-responses]]
@@ -41,14 +42,14 @@ timed out.
4142
[[fleet-search-api-request]]
4243
==== {api-request-title}
4344

44-
`GET /<index>/_fleet/_search`
45+
`GET /<target>/_fleet/_search`
4546

4647
[[fleet-search-api-path-params]]
4748
==== {api-path-parms-title}
4849

49-
`<index>`::
50+
`<target>`::
5051
(Required, string)
51-
A single index. Index aliases are not supported.
52+
A single target to search. If the target is an index alias, it must resolve to a single index.
5253

5354
[role="child_attributes"]
5455
[[fleet-search-api-query-parms]]

docs/reference/fleet/index.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ agent and action data. These APIs are experimental and for internal use by
1515
// top-level
1616
include::get-global-checkpoints.asciidoc[]
1717
include::fleet-search.asciidoc[]
18+
include::fleet-multi-search.asciidoc[]

rest-api-spec/src/main/resources/rest-api-spec/api/fleet.msearch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
],
2828
"parts":{
2929
"index":{
30-
"type":"list",
31-
"description":"A comma-separated list of index names to use as default"
30+
"type":"string",
31+
"description":"The index name to use as the default"
3232
}
3333
}
3434
}

rest-api-spec/src/main/resources/rest-api-spec/api/fleet.search.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"parts":{
2222
"index":{
2323
"type":"string",
24-
"description":"The name of the index."
24+
"description":"The index name to search."
2525
}
2626
}
2727
}

server/src/internalClusterTest/java/org/elasticsearch/action/search/TransportSearchIT.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.common.settings.Settings;
2727
import org.elasticsearch.core.TimeValue;
2828
import org.elasticsearch.common.util.concurrent.AtomicArray;
29+
import org.elasticsearch.index.seqno.SequenceNumbers;
2930
import org.elasticsearch.xcontent.ObjectParser;
3031
import org.elasticsearch.xcontent.XContentBuilder;
3132
import org.elasticsearch.index.IndexSettings;
@@ -59,6 +60,7 @@
5960
import org.elasticsearch.test.ESIntegTestCase;
6061

6162
import java.io.IOException;
63+
import java.util.Arrays;
6264
import java.util.Collection;
6365
import java.util.Collections;
6466
import java.util.List;
@@ -257,20 +259,47 @@ public void testFinalReduce() {
257259
public void testWaitForRefreshIndexValidation() throws Exception {
258260
int numberOfShards = randomIntBetween(3, 10);
259261
assertAcked(prepareCreate("test1").setSettings(Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, numberOfShards)));
262+
assertAcked(prepareCreate("test2").setSettings(Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, numberOfShards)));
263+
assertAcked(prepareCreate("test3").setSettings(Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, numberOfShards)));
260264
client().admin().indices().prepareAliases().addAlias("test1", "testAlias").get();
265+
client().admin().indices().prepareAliases().addAlias(new String[] {"test2", "test3"}, "testFailedAlias").get();
266+
267+
long[] validCheckpoints = new long[numberOfShards];
268+
Arrays.fill(validCheckpoints, SequenceNumbers.UNASSIGNED_SEQ_NO);
261269

262270
// no exception
263-
client().prepareSearch("testAlias").get();
271+
client().prepareSearch("testAlias").setWaitForCheckpoints(Collections.singletonMap("testAlias", validCheckpoints)).get();
272+
264273

265274
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
266-
() -> client().prepareSearch("testAlias").setWaitForCheckpoints(Collections.singletonMap("testAlias", new long[0])).get());
267-
assertThat(e.getMessage(), containsString("Index configured with wait_for_checkpoints must be a concrete index resolved in this " +
268-
"search. Index [testAlias] is not a concrete index resolved in this search."));
275+
() -> client().prepareSearch("testFailedAlias")
276+
.setWaitForCheckpoints(Collections.singletonMap("testFailedAlias", validCheckpoints))
277+
.get());
278+
assertThat(e.getMessage(), containsString("Failed to resolve wait_for_checkpoints target [testFailedAlias]. Configured target " +
279+
"must resolve to a single open index."));
269280

270281
IllegalArgumentException e2 = expectThrows(IllegalArgumentException.class,
271-
() -> client().prepareSearch("test1").setWaitForCheckpoints(Collections.singletonMap("test1", new long[2])).get());
272-
assertThat(e2.getMessage(), containsString("Index configured with wait_for_checkpoints must search the same number of shards as " +
273-
"checkpoints provided. [2] checkpoints provided. Index [test1] has [" + numberOfShards + "] shards."));
282+
() -> client().prepareSearch("test1")
283+
.setWaitForCheckpoints(Collections.singletonMap("test1", new long[2]))
284+
.get());
285+
assertThat(e2.getMessage(), containsString("Target configured with wait_for_checkpoints must search the same number of shards as " +
286+
"checkpoints provided. [2] checkpoints provided. Target [test1] which resolved to index [test1] has [" + numberOfShards +
287+
"] shards."));
288+
289+
IllegalArgumentException e3 = expectThrows(IllegalArgumentException.class,
290+
() -> client().prepareSearch("testAlias")
291+
.setWaitForCheckpoints(Collections.singletonMap("testAlias", new long[2]))
292+
.get());
293+
assertThat(e3.getMessage(), containsString("Target configured with wait_for_checkpoints must search the same number of shards as " +
294+
"checkpoints provided. [2] checkpoints provided. Target [testAlias] which resolved to index [test1] has [" + numberOfShards +
295+
"] shards."));
296+
297+
IllegalArgumentException e4 = expectThrows(IllegalArgumentException.class,
298+
() -> client().prepareSearch("testAlias")
299+
.setWaitForCheckpoints(Collections.singletonMap("test2", validCheckpoints))
300+
.get());
301+
assertThat(e4.getMessage(), containsString("Target configured with wait_for_checkpoints must be a concrete index resolved in " +
302+
"this search. Target [test2] is not a concrete index resolved in this search."));
274303
}
275304

276305
public void testShardCountLimit() throws Exception {

server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package org.elasticsearch.action.search;
1010

1111
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.IndicesRequest;
1213
import org.elasticsearch.action.OriginalIndices;
1314
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsGroup;
1415
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest;
@@ -711,7 +712,7 @@ private void executeSearch(SearchTask task, SearchTimeProvider timeProvider, Sea
711712
if (remoteShardIterators.isEmpty() == false) {
712713
throw new IllegalArgumentException("Cannot use wait_for_checkpoints parameter with cross-cluster searches.");
713714
} else {
714-
validateWaitForCheckpoint(clusterState, searchRequest, concreteLocalIndices);
715+
validateAndResolveWaitForCheckpoint(clusterState, indexNameExpressionResolver, searchRequest, concreteLocalIndices);
715716
}
716717
}
717718

@@ -889,24 +890,46 @@ public void run() {
889890
}
890891
}
891892

892-
private static void validateWaitForCheckpoint(ClusterState clusterState, SearchRequest searchRequest, String[] concreteLocalIndices) {
893+
private static void validateAndResolveWaitForCheckpoint(ClusterState clusterState, IndexNameExpressionResolver resolver,
894+
SearchRequest searchRequest, String[] concreteLocalIndices) {
893895
HashSet<String> searchedIndices = new HashSet<>(Arrays.asList(concreteLocalIndices));
896+
HashMap<String, long[]> newWaitForCheckpoints = new HashMap<>(searchRequest.getWaitForCheckpoints().size());
894897
for (Map.Entry<String, long[]> waitForCheckpointIndex : searchRequest.getWaitForCheckpoints().entrySet()) {
895-
int checkpointsProvided = waitForCheckpointIndex.getValue().length;
896-
String index = waitForCheckpointIndex.getKey();
898+
long[] checkpoints = waitForCheckpointIndex.getValue();
899+
int checkpointsProvided = checkpoints.length;
900+
String target = waitForCheckpointIndex.getKey();
901+
Index resolved;
902+
try {
903+
resolved = resolver.concreteSingleIndex(clusterState, new IndicesRequest() {
904+
@Override
905+
public String[] indices() {
906+
return new String[] { target };
907+
}
908+
909+
@Override
910+
public IndicesOptions indicesOptions() {
911+
return IndicesOptions.strictSingleIndexNoExpandForbidClosed();
912+
}
913+
});
914+
} catch (Exception e) {
915+
throw new IllegalArgumentException("Failed to resolve wait_for_checkpoints target [" + target + "]. Configured target " +
916+
"must resolve to a single open index.", e);
917+
}
918+
String index = resolved.getName();
897919
IndexMetadata indexMetadata = clusterState.metadata().index(index);
898920
if (searchedIndices.contains(index) == false) {
899-
throw new IllegalArgumentException("Index configured with wait_for_checkpoints must be a concrete index resolved in " +
900-
"this search. Index [" + index + "] is not a concrete index resolved in this search.");
921+
throw new IllegalArgumentException("Target configured with wait_for_checkpoints must be a concrete index resolved in " +
922+
"this search. Target [" + target + "] is not a concrete index resolved in this search.");
901923
} else if (indexMetadata == null) {
902924
throw new IllegalArgumentException("Cannot find index configured for wait_for_checkpoints parameter [" + index + "].");
903925
} else if (indexMetadata.getNumberOfShards() != checkpointsProvided) {
904-
throw new IllegalArgumentException("Index configured with wait_for_checkpoints must search the same number of shards as " +
905-
"checkpoints provided. [" + checkpointsProvided + "] checkpoints provided. Index [" + index + "] has " +
906-
"[" + indexMetadata.getNumberOfShards() + "] shards.");
907-
926+
throw new IllegalArgumentException("Target configured with wait_for_checkpoints must search the same number of shards as " +
927+
"checkpoints provided. [" + checkpointsProvided + "] checkpoints provided. Target [" + target + "] which resolved to " +
928+
"index [" + index + "] has " + "[" + indexMetadata.getNumberOfShards() + "] shards.");
908929
}
930+
newWaitForCheckpoints.put(index, checkpoints);
909931
}
932+
searchRequest.setWaitForCheckpoints(Collections.unmodifiableMap(newWaitForCheckpoints));
910933
}
911934

912935
private static void failIfOverShardCountLimit(ClusterService clusterService, int shardCount) {

x-pack/plugin/fleet/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/fleet/20_wait_for_checkpoints.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ setup:
4545
- match: { hits.total.value: 2 }
4646

4747
---
48-
"Cannot use alias":
48+
"Can use alias":
4949
- do:
50-
catch: bad_request
5150
fleet.search:
5251
index: "test-alias"
5352
allow_partial_search_results: false
5453
wait_for_checkpoints: 1
5554
body: { query: { match_all: {} } }
5655

56+
- match: { _shards.successful: 1 }
57+
- match: { hits.total.value: 2 }
58+
5759
---
5860
"Must provide correct number of checkpoints":
5961
- do:
@@ -102,9 +104,13 @@ setup:
102104
body:
103105
- {"index": "test-after-refresh", "allow_partial_search_results" : false, wait_for_checkpoints: 1}
104106
- {query: { match_all: {} } }
107+
- { "index": "test-alias", "allow_partial_search_results": false, wait_for_checkpoints: 1 }
108+
- { query: { match_all: { } } }
105109
- {"index": "test-refresh-disabled", "allow_partial_search_results": false, wait_for_checkpoints: 2}
106110
- {query: { match_all: {} } }
107111

108112
- match: { responses.0._shards.successful: 1 }
109113
- match: { responses.0.hits.total.value: 2 }
110-
- match: { responses.1.error.caused_by.type: "illegal_argument_exception" }
114+
- match: { responses.1._shards.successful: 1 }
115+
- match: { responses.1.hits.total.value: 2 }
116+
- match: { responses.2.error.caused_by.type: "illegal_argument_exception" }

0 commit comments

Comments
 (0)