diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexAction.java index 0146fe6697ae0..5433ebffe5c42 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexAction.java @@ -57,13 +57,20 @@ public Set supportedCapabilities() { protected BaseRestHandler.RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { String[] indices = Strings.splitStringByCommaToArray(request.param("name")); String modeParam = request.param("mode"); - if (settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false)) { + final boolean crossProjectEnabled = settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false); + if (crossProjectEnabled) { // accept but drop project_routing param until fully supported request.param("project_routing"); } + IndicesOptions indicesOptions = IndicesOptions.fromRequest(request, ResolveIndexAction.Request.DEFAULT_INDICES_OPTIONS); + if (crossProjectEnabled) { + indicesOptions = IndicesOptions.builder(indicesOptions) + .crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true)) + .build(); + } ResolveIndexAction.Request resolveRequest = new ResolveIndexAction.Request( indices, - IndicesOptions.fromRequest(request, ResolveIndexAction.Request.DEFAULT_INDICES_OPTIONS), + indicesOptions, modeParam == null ? null : Arrays.stream(modeParam.split(",")) diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java index a31640fab8ebd..2c52b92dfda7e 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java @@ -109,7 +109,8 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC // this might be set by old clients request.param("min_compatible_shard_node"); - if (settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false)) { + final boolean crossProjectEnabled = settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false); + if (crossProjectEnabled) { // accept but drop project_routing param until fully supported request.param("project_routing"); } @@ -128,7 +129,15 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC */ IntConsumer setSize = size -> searchRequest.source().size(size); request.withContentOrSourceParamParserOrNull( - parser -> parseSearchRequest(searchRequest, request, parser, clusterSupportsFeature, setSize, searchUsageHolder) + parser -> parseSearchRequest( + searchRequest, + request, + parser, + clusterSupportsFeature, + setSize, + searchUsageHolder, + crossProjectEnabled + ) ); return channel -> { @@ -157,6 +166,17 @@ public static void parseSearchRequest( parseSearchRequest(searchRequest, request, requestContentParser, clusterSupportsFeature, setSize, null); } + public static void parseSearchRequest( + SearchRequest searchRequest, + RestRequest request, + @Nullable XContentParser requestContentParser, + Predicate clusterSupportsFeature, + IntConsumer setSize, + @Nullable SearchUsageHolder searchUsageHolder + ) throws IOException { + parseSearchRequest(searchRequest, request, requestContentParser, clusterSupportsFeature, setSize, searchUsageHolder, false); + } + /** * Parses the rest request on top of the SearchRequest, preserving values that are not overridden by the rest request. * @@ -167,6 +187,7 @@ public static void parseSearchRequest( * @param clusterSupportsFeature used to check if certain features are available in this cluster * @param setSize how the size url parameter is handled. {@code udpate_by_query} and regular search differ here. * @param searchUsageHolder the holder of search usage stats + * @param crossProjectEnabled whether serverless.cross_project.enabled is set to true */ public static void parseSearchRequest( SearchRequest searchRequest, @@ -174,7 +195,8 @@ public static void parseSearchRequest( @Nullable XContentParser requestContentParser, Predicate clusterSupportsFeature, IntConsumer setSize, - @Nullable SearchUsageHolder searchUsageHolder + @Nullable SearchUsageHolder searchUsageHolder, + boolean crossProjectEnabled ) throws IOException { if (searchRequest.source() == null) { searchRequest.source(new SearchSourceBuilder()); @@ -222,7 +244,13 @@ public static void parseSearchRequest( } searchRequest.routing(request.param("routing")); searchRequest.preference(request.param("preference")); - searchRequest.indicesOptions(IndicesOptions.fromRequest(request, searchRequest.indicesOptions())); + IndicesOptions indicesOptions = IndicesOptions.fromRequest(request, searchRequest.indicesOptions()); + if (crossProjectEnabled) { + indicesOptions = IndicesOptions.builder(indicesOptions) + .crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true)) + .build(); + } + searchRequest.indicesOptions(indicesOptions); validateSearchRequest(request, searchRequest); diff --git a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectIndexResolutionValidator.java b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectIndexResolutionValidator.java index 92322b771cd67..f928ec9096e7d 100644 --- a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectIndexResolutionValidator.java +++ b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectIndexResolutionValidator.java @@ -167,10 +167,10 @@ public static ElasticsearchException validate( } public static IndicesOptions indicesOptionsForCrossProjectFanout(IndicesOptions indicesOptions) { - // TODO set resolveCrossProject=false here once we have an IndicesOptions flag for that return IndicesOptions.builder(indicesOptions) .concreteTargetOptions(new IndicesOptions.ConcreteTargetOptions(true)) .wildcardOptions(IndicesOptions.WildcardOptions.builder(indicesOptions.wildcardOptions()).allowEmptyExpressions(true).build()) + .crossProjectModeOptions(IndicesOptions.CrossProjectModeOptions.DEFAULT) .build(); } diff --git a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java index 7afdfd181f661..ef47446bca54b 100644 --- a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java +++ b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java @@ -11,7 +11,6 @@ import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.core.Booleans; /** * Utility class to determine whether Cross-Project Search (CPS) applies to an inbound request. @@ -49,8 +48,7 @@ public boolean resolvesCrossProject(IndicesRequest.Replaceable request) { if (crossProjectEnabled == false) { return false; } - // TODO this needs to be based on the IndicesOptions flag instead, once available - final boolean indicesOptionsResolveCrossProject = Booleans.parseBoolean(System.getProperty("cps.resolve_cross_project", "false")); - return request.allowsCrossProject() && indicesOptionsResolveCrossProject; + // TODO: The following check can be an method on the request itself + return request.allowsCrossProject() && request.indicesOptions().resolveCrossProjectIndexExpression(); } } diff --git a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexActionTests.java new file mode 100644 index 0000000000000..7e325a48dba0c --- /dev/null +++ b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexActionTests.java @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +package org.elasticsearch.rest.action.admin.indices; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction; +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.cluster.project.TestProjectResolvers; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.FakeRestChannel; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.threadpool.ThreadPool; + +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; + +public class RestResolveIndexActionTests extends ESTestCase { + + public void testAddResolveCrossProjectBasedOnSettingValue() throws Exception { + final boolean cpsEnabled = randomBoolean(); + final Settings settings = Settings.builder().put("serverless.cross_project.enabled", cpsEnabled).build(); + final var action = new RestResolveIndexAction(settings); + final var request = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/_resolve/index/foo") + .build(); + + final NodeClient nodeClient = new NodeClient(settings, mock(ThreadPool.class), TestProjectResolvers.DEFAULT_PROJECT_ONLY) { + @SuppressWarnings("unchecked") + @Override + public void doExecute( + ActionType action, + Request request, + ActionListener listener + ) { + final var resolveIndexRequest = asInstanceOf(ResolveIndexAction.Request.class, request); + assertThat(resolveIndexRequest.indicesOptions().resolveCrossProjectIndexExpression(), equalTo(cpsEnabled)); + listener.onResponse((Response) new ResolveIndexAction.Response(List.of(), List.of(), List.of())); + } + }; + + final var restChannel = new FakeRestChannel(request, true, 1); + action.handleRequest(request, restChannel, nodeClient); + assertThat(restChannel.responses().get(), equalTo(1)); + } +}