1111
1212import org .apache .logging .log4j .LogManager ;
1313import org .apache .logging .log4j .Logger ;
14+ import org .elasticsearch .ElasticsearchException ;
1415import org .elasticsearch .ExceptionsHelper ;
1516import org .elasticsearch .TransportVersions ;
1617import org .elasticsearch .action .ActionListener ;
2021import org .elasticsearch .action .IndicesRequest ;
2122import org .elasticsearch .action .OriginalIndices ;
2223import org .elasticsearch .action .RemoteClusterActionType ;
24+ import org .elasticsearch .action .ResolvedIndexExpressions ;
2325import org .elasticsearch .action .ResolvedIndices ;
2426import org .elasticsearch .action .ShardOperationFailedException ;
2527import org .elasticsearch .action .admin .cluster .shards .ClusterSearchShardsRequest ;
7981import org .elasticsearch .search .aggregations .AggregationReduceContext ;
8082import org .elasticsearch .search .builder .PointInTimeBuilder ;
8183import org .elasticsearch .search .builder .SearchSourceBuilder ;
84+ import org .elasticsearch .search .crossproject .CrossProjectIndexResolutionValidator ;
85+ import org .elasticsearch .search .crossproject .CrossProjectModeDecider ;
8286import org .elasticsearch .search .internal .AliasFilter ;
8387import org .elasticsearch .search .internal .SearchContext ;
8488import org .elasticsearch .search .internal .ShardSearchContextId ;
120124import static org .elasticsearch .action .search .SearchType .DFS_QUERY_THEN_FETCH ;
121125import static org .elasticsearch .action .search .SearchType .QUERY_THEN_FETCH ;
122126import static org .elasticsearch .action .search .TransportSearchHelper .checkCCSVersionCompatibility ;
127+ import static org .elasticsearch .search .crossproject .CrossProjectIndexResolutionValidator .indicesOptionsForCrossProjectFanout ;
123128import static org .elasticsearch .search .sort .FieldSortBuilder .hasPrimaryFieldSort ;
124129import static org .elasticsearch .threadpool .ThreadPool .Names .SYSTEM_CRITICAL_READ ;
125130import static org .elasticsearch .threadpool .ThreadPool .Names .SYSTEM_READ ;
@@ -170,6 +175,7 @@ public class TransportSearchAction extends HandledTransportAction<SearchRequest,
170175 private final UsageService usageService ;
171176 private final boolean collectCCSTelemetry ;
172177 private final TimeValue forceConnectTimeoutSecs ;
178+ private final CrossProjectModeDecider crossProjectModeDecider ;
173179
174180 @ Inject
175181 public TransportSearchAction (
@@ -218,7 +224,8 @@ public TransportSearchAction(
218224 this .searchResponseMetrics = searchResponseMetrics ;
219225 this .client = client ;
220226 this .usageService = usageService ;
221- forceConnectTimeoutSecs = settings .getAsTime ("search.ccs.force_connect_timeout" , null );
227+ this .forceConnectTimeoutSecs = settings .getAsTime ("search.ccs.force_connect_timeout" , null );
228+ this .crossProjectModeDecider = new CrossProjectModeDecider (settings );
222229 }
223230
224231 private Map <String , OriginalIndices > buildPerIndexOriginalIndices (
@@ -353,6 +360,7 @@ private void executeRequest(
353360 Function <ActionListener <SearchResponse >, SearchPhaseProvider > searchPhaseProvider ,
354361 boolean collectSearchTelemetry
355362 ) {
363+ boolean resolvesCrossProject = crossProjectModeDecider .resolvesCrossProject (original );
356364 final long relativeStartNanos = System .nanoTime ();
357365 final SearchTimeProvider timeProvider = new SearchTimeProvider (
358366 original .getOrCreateAbsoluteStartMillis (),
@@ -365,16 +373,28 @@ private void executeRequest(
365373
366374 ProjectState projectState = projectResolver .getProjectState (clusterState );
367375 final ResolvedIndices resolvedIndices ;
376+
377+ /*
378+ * Irrespective of whether this is the origin project or a linked project, it's wiser to relax
379+ * the index options to prevent the index resolution APIs from throwing index not found errors.
380+ * Also, we do not replace the indices options on the SearchRequest because we'd be needing it
381+ * downstream when validating the indices from both the origin and the linked projects.
382+ */
383+ IndicesOptions resolutionIdxOpts = crossProjectModeDecider .resolvesCrossProject (original )
384+ ? indicesOptionsForCrossProjectFanout (original .indicesOptions ())
385+ : original .indicesOptions ();
386+
368387 if (original .pointInTimeBuilder () != null ) {
369388 resolvedIndices = ResolvedIndices .resolveWithPIT (
370389 original .pointInTimeBuilder (),
371- original . indicesOptions () ,
390+ resolutionIdxOpts ,
372391 projectState .metadata (),
373392 namedWriteableRegistry
374393 );
375394 } else {
376- resolvedIndices = ResolvedIndices .resolveWithIndicesRequest (
377- original ,
395+ resolvedIndices = ResolvedIndices .resolveWithIndexNamesAndOptions (
396+ original .indices (),
397+ resolutionIdxOpts ,
378398 projectState .metadata (),
379399 indexNameExpressionResolver ,
380400 remoteClusterService ,
@@ -589,7 +609,9 @@ public void onFailure(Exception e) {
589609 searchPhaseProvider .apply (finalDelegate )
590610 );
591611 }),
592- forceConnectTimeoutSecs
612+ forceConnectTimeoutSecs ,
613+ resolvesCrossProject ,
614+ rewritten .getResolvedIndexExpressions ()
593615 );
594616 }
595617 }
@@ -939,7 +961,7 @@ static SearchResponseMerger createSearchResponseMerger(
939961 * Used for ccs_minimize_roundtrips=false
940962 */
941963 static void collectSearchShards (
942- IndicesOptions indicesOptions ,
964+ IndicesOptions originalIdxOpts ,
943965 String preference ,
944966 String routing ,
945967 QueryBuilder query ,
@@ -950,7 +972,9 @@ static void collectSearchShards(
950972 SearchTimeProvider timeProvider ,
951973 TransportService transportService ,
952974 ActionListener <Map <String , SearchShardsResponse >> listener ,
953- TimeValue forceConnectTimeoutSecs
975+ TimeValue forceConnectTimeoutSecs ,
976+ boolean resolvesCrossProject ,
977+ ResolvedIndexExpressions originResolvedIdxExpressions
954978 ) {
955979 RemoteClusterService remoteClusterService = transportService .getRemoteClusterService ();
956980 final CountDown responsesCountDown = new CountDown (remoteIndicesByCluster .size ());
@@ -976,6 +1000,27 @@ void innerOnResponse(SearchShardsResponse searchShardsResponse) {
9761000
9771001 @ Override
9781002 Map <String , SearchShardsResponse > createFinalResponse () {
1003+ // TODO: Perhaps, it's wiser to check for resolvesCrossProject too.
1004+ if (originResolvedIdxExpressions != null ) {
1005+ Map <String , ResolvedIndexExpressions > resolvedIndexExpressions = new HashMap <>();
1006+ for (Map .Entry <String , SearchShardsResponse > entry : searchShardsResponses .entrySet ()) {
1007+ if (entry .getValue ().getResolvedIndexExpressions () == null ) {
1008+ throw new IllegalArgumentException (
1009+ "Failed to get resolved index expressions for cluster [" + entry .getKey () + "]"
1010+ );
1011+ }
1012+ resolvedIndexExpressions .put (entry .getKey (), entry .getValue ().getResolvedIndexExpressions ());
1013+ }
1014+ // We do not use the related index options here when validating indices' existence.
1015+ ElasticsearchException validationEx = CrossProjectIndexResolutionValidator .validate (
1016+ originalIdxOpts ,
1017+ originResolvedIdxExpressions ,
1018+ resolvedIndexExpressions
1019+ );
1020+ if (validationEx != null ) {
1021+ throw validationEx ;
1022+ }
1023+ }
9791024 return searchShardsResponses ;
9801025 }
9811026 };
@@ -988,13 +1033,24 @@ Map<String, SearchShardsResponse> createFinalResponse() {
9881033 );
9891034
9901035 connectionListener .addListener (singleListener .delegateFailure ((responseListener , connection ) -> {
1036+ /*
1037+ * It may be possible that indices do not exist on the project that SearchShards API is targeting.
1038+ * In such cases, it throws an error because it calls the index resolution APIs underneath. We relax
1039+ * the index options to prevent this from happening. Also, it's fine to pass in these relaxed options
1040+ * to it because SearchShardsRequest#allowsCrossProject() returns false anyway and the index rewriting
1041+ * does not happen downstream.
1042+ */
1043+ IndicesOptions searchShardsIdxOpts = resolvesCrossProject
1044+ ? indicesOptionsForCrossProjectFanout (originalIdxOpts )
1045+ : originalIdxOpts ;
1046+
9911047 final String [] indices = entry .getValue ().indices ();
9921048 final Executor responseExecutor = transportService .getThreadPool ().executor (ThreadPool .Names .SEARCH_COORDINATION );
9931049 // TODO: support point-in-time
9941050 if (searchContext == null && connection .getTransportVersion ().onOrAfter (TransportVersions .V_8_9_X )) {
9951051 SearchShardsRequest searchShardsRequest = new SearchShardsRequest (
9961052 indices ,
997- indicesOptions ,
1053+ searchShardsIdxOpts ,
9981054 query ,
9991055 routing ,
10001056 preference ,
@@ -1013,7 +1069,7 @@ Map<String, SearchShardsResponse> createFinalResponse() {
10131069 ClusterSearchShardsRequest searchShardsRequest = new ClusterSearchShardsRequest (
10141070 MasterNodeRequest .INFINITE_MASTER_NODE_TIMEOUT ,
10151071 indices
1016- ).indicesOptions (indicesOptions ).local (true ).preference (preference ).routing (routing );
1072+ ).indicesOptions (searchShardsIdxOpts ).local (true ).preference (preference ).routing (routing );
10171073 transportService .sendRequest (
10181074 connection ,
10191075 TransportClusterSearchShardsAction .TYPE .name (),
0 commit comments