diff --git a/server/src/main/java/org/elasticsearch/action/search/RestOpenPointInTimeAction.java b/server/src/main/java/org/elasticsearch/action/search/RestOpenPointInTimeAction.java index 5d50f4ce58eb9..6d6b2666e0ad2 100644 --- a/server/src/main/java/org/elasticsearch/action/search/RestOpenPointInTimeAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/RestOpenPointInTimeAction.java @@ -55,7 +55,6 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC final OpenPointInTimeRequest openRequest = new OpenPointInTimeRequest(indices); final boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled(); if (crossProjectEnabled) { - openRequest.projectRouting(request.param("project_routing", null)); openRequest.indicesOptions(IndicesOptions.fromRequest(request, OpenPointInTimeRequest.DEFAULT_CPS_INDICES_OPTIONS)); } else { openRequest.indicesOptions(IndicesOptions.fromRequest(request, OpenPointInTimeRequest.DEFAULT_INDICES_OPTIONS)); diff --git a/server/src/main/java/org/elasticsearch/rest/action/RestFieldCapabilitiesAction.java b/server/src/main/java/org/elasticsearch/rest/action/RestFieldCapabilitiesAction.java index a4b01f3722145..92c09d7ccca50 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/RestFieldCapabilitiesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/RestFieldCapabilitiesAction.java @@ -62,7 +62,6 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC final boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled(); if (crossProjectEnabled) { - fieldRequest.projectRouting(request.param("project_routing", null)); // Setting includeResolvedTo to always include index resolution data structure in the linked project responses, // in order to allow the coordinating node to call CrossProjectIndexResolutionValidator#validate fieldRequest.includeResolvedTo(true); 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 2a5064579d09c..353ca95ce42c8 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 @@ -9,9 +9,11 @@ package org.elasticsearch.rest.action.admin.indices; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexMode; @@ -20,22 +22,26 @@ import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.search.crossproject.CrossProjectModeDecider; +import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; @ServerlessScope(Scope.PUBLIC) public class RestResolveIndexAction extends BaseRestHandler { private static final Set CAPABILITIES = Set.of("mode_filter"); - private final Settings settings; + private final CrossProjectModeDecider crossProjectModeDecider; public RestResolveIndexAction(Settings settings) { - this.settings = settings; + this.crossProjectModeDecider = new CrossProjectModeDecider(settings); } @Override @@ -45,7 +51,7 @@ public String getName() { @Override public List routes() { - return List.of(new Route(GET, "/_resolve/index/{name}")); + return List.of(new Route(GET, "/_resolve/index/{name}"), new Route(POST, "/_resolve/index/{name}")); } @Override @@ -57,17 +63,28 @@ 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"); - final boolean crossProjectEnabled = settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false); - String projectRouting = null; - if (crossProjectEnabled) { - projectRouting = request.param("project_routing"); - } + + final boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled(); + AtomicReference projectRouting = new AtomicReference<>(); IndicesOptions indicesOptions = IndicesOptions.fromRequest(request, ResolveIndexAction.Request.DEFAULT_INDICES_OPTIONS); + if (crossProjectEnabled) { + request.withContentOrSourceParamParserOrNull(parser -> { + try { + // If parser is null, there's no request body. projectRouting will then yield `null`. + if (parser != null) { + projectRouting.set(parseProjectRouting(parser)); + } + } catch (Exception e) { + throw new ElasticsearchException("Couldn't parse request body", e); + } + }); + indicesOptions = IndicesOptions.builder(indicesOptions) .crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true)) .build(); } + ResolveIndexAction.Request resolveRequest = new ResolveIndexAction.Request( indices, indicesOptions, @@ -76,8 +93,44 @@ protected BaseRestHandler.RestChannelConsumer prepareRequest(RestRequest request : Arrays.stream(modeParam.split(",")) .map(IndexMode::fromString) .collect(() -> EnumSet.noneOf(IndexMode.class), EnumSet::add, EnumSet::addAll), - projectRouting + projectRouting.get() ); return channel -> client.admin().indices().resolveIndex(resolveRequest, new RestToXContentListener<>(channel)); } + + private static String parseProjectRouting(XContentParser parser) throws ParsingException { + try { + XContentParser.Token first = parser.nextToken(); + if (first == null) { + return null; + } + + if (first != XContentParser.Token.START_OBJECT) { + throw new ParsingException( + parser.getTokenLocation(), + "Expected [" + XContentParser.Token.START_OBJECT + "] but found [" + first + "]", + parser.getTokenLocation() + ); + } + + String projectRouting = null; + for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { + if (token == XContentParser.Token.FIELD_NAME) { + String currentName = parser.currentName(); + if ("project_routing".equals(currentName)) { + parser.nextToken(); + projectRouting = parser.text(); + } else { + throw new ParsingException(parser.getTokenLocation(), "request does not support [" + parser.currentName() + "]"); + } + } + } + + return projectRouting; + } catch (ParsingException e) { + throw e; + } catch (Exception e) { + throw new ParsingException(parser == null ? null : parser.getTokenLocation(), "Failed to parse", e); + } + } } 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 0c51f4a886a0e..ec6ce815b333c 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 @@ -108,9 +108,6 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC request.param("min_compatible_shard_node"); final boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled(); - if (crossProjectEnabled) { - searchRequest.setProjectRouting(request.param("project_routing")); - } /* * We have to pull out the call to `source().size(size)` because diff --git a/x-pack/plugin/async-search/src/main/java/org/elasticsearch/xpack/search/RestSubmitAsyncSearchAction.java b/x-pack/plugin/async-search/src/main/java/org/elasticsearch/xpack/search/RestSubmitAsyncSearchAction.java index 711e68e3908c0..aa5417e542c6a 100644 --- a/x-pack/plugin/async-search/src/main/java/org/elasticsearch/xpack/search/RestSubmitAsyncSearchAction.java +++ b/x-pack/plugin/async-search/src/main/java/org/elasticsearch/xpack/search/RestSubmitAsyncSearchAction.java @@ -72,7 +72,6 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled(); if (crossProjectEnabled) { submit.getSearchRequest().setCcsMinimizeRoundtrips(true); - submit.getSearchRequest().setProjectRouting(request.param("project_routing")); } IntConsumer setSize = size -> submit.getSearchRequest().source().size(size); diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlSearchAction.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlSearchAction.java index 7cdb29fcc5c62..d28846fc82dcb 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlSearchAction.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlSearchAction.java @@ -69,7 +69,6 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli indicesOptions = IndicesOptions.builder(indicesOptions) .crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true)) .build(); - eqlRequest.projectRouting(request.param("project_routing")); } eqlRequest.indicesOptions(indicesOptions); if (request.hasParam("wait_for_completion_timeout")) { diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java index 8a5889254080d..b0a1c1ed091c4 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java @@ -56,11 +56,6 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli sqlRequest = SqlQueryRequest.fromXContent(parser); } - String routingParam = request.param("project_routing"); - if (routingParam != null) { - // takes precedence on the parameter in the body - sqlRequest.projectRouting(routingParam); - } if (sqlRequest.projectRouting() != null && crossProjectModeDecider.crossProjectEnabled() == false) { throw new InvalidArgumentException("[project_routing] is only allowed when cross-project search is enabled"); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlTranslateAction.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlTranslateAction.java index cca2cfcf9e111..fb4a272a9ab7e 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlTranslateAction.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlTranslateAction.java @@ -49,11 +49,6 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli try (XContentParser parser = request.contentOrSourceParamParser()) { sqlRequest = SqlTranslateRequest.fromXContent(parser); } - String routingParam = request.param("project_routing"); - if (routingParam != null) { - // takes precedence on the parameter in the body - sqlRequest.projectRouting(routingParam); - } if (sqlRequest.projectRouting() != null && crossProjectModeDecider.crossProjectEnabled() == false) { throw new InvalidArgumentException("[project_routing] is only allowed when cross-project search is enabled"); }