-
Notifications
You must be signed in to change notification settings - Fork 25.7k
allows PIT to be cross project #137966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allows PIT to be cross project #137966
Changes from all commits
2e056e6
c79b1fe
09e73ff
67d8560
ce723b0
32499fb
84057fb
e697a61
40e8063
e5be4f5
22ea646
ea06f5b
93d8441
8370134
8ab1ad6
f789176
ea8ee3c
205a825
0d6a886
10ee519
7f61783
a377c9a
e11424b
bf9c3b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 137966 | ||
| summary: Allows PIT to be cross project | ||
| area: Search | ||
| type: enhancement | ||
| issues: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,15 @@ | |
| import org.elasticsearch.action.ActionType; | ||
| import org.elasticsearch.action.IndicesRequest; | ||
| import org.elasticsearch.action.OriginalIndices; | ||
| import org.elasticsearch.action.ResolvedIndexExpression; | ||
| import org.elasticsearch.action.ResolvedIndexExpressions; | ||
| import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction; | ||
| import org.elasticsearch.action.support.ActionFilters; | ||
| import org.elasticsearch.action.support.ChannelActionListener; | ||
| import org.elasticsearch.action.support.GroupedActionListener; | ||
| import org.elasticsearch.action.support.HandledTransportAction; | ||
| import org.elasticsearch.action.support.IndicesOptions; | ||
| import org.elasticsearch.action.support.SubscribableListener; | ||
| import org.elasticsearch.cluster.ClusterState; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.common.bytes.BytesReference; | ||
|
|
@@ -38,25 +43,36 @@ | |
| import org.elasticsearch.search.SearchPhaseResult; | ||
| import org.elasticsearch.search.SearchService; | ||
| import org.elasticsearch.search.builder.SearchSourceBuilder; | ||
| import org.elasticsearch.search.crossproject.CrossProjectIndexResolutionValidator; | ||
| import org.elasticsearch.search.crossproject.CrossProjectModeDecider; | ||
| import org.elasticsearch.search.internal.AliasFilter; | ||
| import org.elasticsearch.search.internal.ShardSearchContextId; | ||
| import org.elasticsearch.tasks.Task; | ||
| import org.elasticsearch.threadpool.ThreadPool; | ||
| import org.elasticsearch.transport.AbstractTransportRequest; | ||
| import org.elasticsearch.transport.RemoteClusterAware; | ||
| import org.elasticsearch.transport.RemoteClusterService; | ||
| import org.elasticsearch.transport.Transport; | ||
| import org.elasticsearch.transport.TransportActionProxy; | ||
| import org.elasticsearch.transport.TransportChannel; | ||
| import org.elasticsearch.transport.TransportRequestHandler; | ||
| import org.elasticsearch.transport.TransportRequestOptions; | ||
| import org.elasticsearch.transport.TransportResponseHandler; | ||
| import org.elasticsearch.transport.TransportService; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.Collection; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.function.BiFunction; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static org.elasticsearch.core.Strings.format; | ||
| import static org.elasticsearch.search.crossproject.CrossProjectIndexResolutionValidator.indicesOptionsForCrossProjectFanout; | ||
| import static org.elasticsearch.transport.RemoteClusterAware.buildRemoteIndexName; | ||
|
|
||
| public class TransportOpenPointInTimeAction extends HandledTransportAction<OpenPointInTimeRequest, OpenPointInTimeResponse> { | ||
|
|
||
|
|
@@ -72,6 +88,8 @@ public class TransportOpenPointInTimeAction extends HandledTransportAction<OpenP | |
| private final SearchService searchService; | ||
| private final ClusterService clusterService; | ||
| private final SearchResponseMetrics searchResponseMetrics; | ||
| private final CrossProjectModeDecider crossProjectModeDecider; | ||
| private final TimeValue forceConnectTimeoutSecs; | ||
|
|
||
| @Inject | ||
| public TransportOpenPointInTimeAction( | ||
|
|
@@ -92,6 +110,9 @@ public TransportOpenPointInTimeAction( | |
| this.namedWriteableRegistry = namedWriteableRegistry; | ||
| this.clusterService = clusterService; | ||
| this.searchResponseMetrics = searchResponseMetrics; | ||
| this.crossProjectModeDecider = new CrossProjectModeDecider(clusterService.getSettings()); | ||
| this.forceConnectTimeoutSecs = clusterService.getSettings() | ||
| .getAsTime("search.ccs.force_connect_timeout", TimeValue.timeValueSeconds(3L)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (asking to learn) - When is this set vs. taking the default of 3 seconds? I think for CPS we want to always ensure it is 3 seconds, so is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's only being used by CPS because we only call it in executeOpenPitCrossProject, so I don't think we need to have another if statement for CrossProjectModeDecider.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When we manually "inject" the corresponding value from our end for Serverless CPS. I had a brief chat with Matteo about this, and there's scope for improvement/refactoring here: we'll abstract this away and move it elsewhere so that we won't have to:
Currently, it's being used in Search, Field Caps, and PIT (this). |
||
| transportService.registerRequestHandler( | ||
| OPEN_SHARD_READER_CONTEXT_NAME, | ||
| EsExecutors.DIRECT_EXECUTOR_SERVICE, | ||
|
|
@@ -123,6 +144,146 @@ protected void doExecute(Task task, OpenPointInTimeRequest request, ActionListen | |
| ); | ||
| return; | ||
| } | ||
|
|
||
| final boolean resolveCrossProject = crossProjectModeDecider.resolvesCrossProject(request); | ||
| if (resolveCrossProject) { | ||
| executeOpenPitCrossProject((SearchTask) task, request, listener); | ||
| } else { | ||
| executeOpenPit((SearchTask) task, request, listener); | ||
| } | ||
| } | ||
|
|
||
| private void executeOpenPitCrossProject( | ||
| SearchTask task, | ||
| OpenPointInTimeRequest request, | ||
| ActionListener<OpenPointInTimeResponse> listener | ||
| ) { | ||
| String[] indices = request.indices(); | ||
| IndicesOptions originalIndicesOptions = request.indicesOptions(); | ||
| // in CPS before executing the open pit request we need to get index resolution and possibly throw based on merged project view | ||
| // rules. This should happen only if either ignore_unavailable or allow_no_indices is set to false (strict). | ||
| // If instead both are true we can continue with the "normal" pit execution. | ||
| if (originalIndicesOptions.ignoreUnavailable() && originalIndicesOptions.allowNoIndices()) { | ||
| // lenient indicesOptions thus execute standard pit | ||
| executeOpenPit(task, request, listener); | ||
| return; | ||
| } | ||
|
|
||
| // ResolvedIndexExpression for the origin cluster (only) as determined by the Security Action Filter | ||
| final ResolvedIndexExpressions localResolvedIndexExpressions = request.getResolvedIndexExpressions(); | ||
piergm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| RemoteClusterService remoteClusterService = searchTransportService.getRemoteClusterService(); | ||
| final Map<String, OriginalIndices> indicesPerCluster = remoteClusterService.groupIndices( | ||
| indicesOptionsForCrossProjectFanout(originalIndicesOptions), | ||
| indices | ||
| ); | ||
| // local indices resolution was already taken care of by the Security Action Filter | ||
| indicesPerCluster.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); | ||
|
|
||
| if (indicesPerCluster.isEmpty()) { | ||
| // for CPS requests that are targeting origin only, could be because of project_routing or other reasons, execute standard pit. | ||
| final Exception ex = CrossProjectIndexResolutionValidator.validate( | ||
| originalIndicesOptions, | ||
| request.getProjectRouting(), | ||
| localResolvedIndexExpressions, | ||
| Map.of() | ||
| ); | ||
| if (ex != null) { | ||
| listener.onFailure(ex); | ||
| return; | ||
| } | ||
| executeOpenPit(task, request, listener); | ||
| return; | ||
| } | ||
|
|
||
| // CPS | ||
| final int linkedProjectsToQuery = indicesPerCluster.size(); | ||
| ActionListener<Collection<Map.Entry<String, ResolveIndexAction.Response>>> responsesListener = listener.delegateFailureAndWrap( | ||
| (l, responses) -> { | ||
| Map<String, ResolvedIndexExpressions> resolvedRemoteExpressions = responses.stream() | ||
| .filter(e -> e.getValue().getResolvedIndexExpressions() != null) | ||
| .collect( | ||
| Collectors.toMap( | ||
| Map.Entry::getKey, | ||
| e -> e.getValue().getResolvedIndexExpressions() | ||
|
|
||
| ) | ||
| ); | ||
| final Exception ex = CrossProjectIndexResolutionValidator.validate( | ||
| originalIndicesOptions, | ||
| request.getProjectRouting(), | ||
| localResolvedIndexExpressions, | ||
| resolvedRemoteExpressions | ||
| ); | ||
| if (ex != null) { | ||
| listener.onFailure(ex); | ||
| return; | ||
| } | ||
| Set<String> collectedIndices = new HashSet<>(indices.length); | ||
|
|
||
| for (Map.Entry<String, ResolvedIndexExpressions> resolvedRemoteExpressionEntry : resolvedRemoteExpressions.entrySet()) { | ||
| String remoteAlias = resolvedRemoteExpressionEntry.getKey(); | ||
| for (ResolvedIndexExpression expression : resolvedRemoteExpressionEntry.getValue().expressions()) { | ||
| ResolvedIndexExpression.LocalExpressions oneRemoteExpression = expression.localExpressions(); | ||
| if (false == oneRemoteExpression.indices().isEmpty() | ||
| && oneRemoteExpression | ||
| .localIndexResolutionResult() == ResolvedIndexExpression.LocalIndexResolutionResult.SUCCESS) { | ||
| collectedIndices.addAll( | ||
| oneRemoteExpression.indices() | ||
| .stream() | ||
| .map(i -> buildRemoteIndexName(remoteAlias, i)) | ||
| .collect(Collectors.toSet()) | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| if (localResolvedIndexExpressions != null) { // this should never be null in CPS | ||
piergm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| collectedIndices.addAll(localResolvedIndexExpressions.getLocalIndicesList()); | ||
| } | ||
| request.indices(collectedIndices.toArray(String[]::new)); | ||
| executeOpenPit(task, request, listener); | ||
| } | ||
| ); | ||
| ActionListener<Map.Entry<String, ResolveIndexAction.Response>> groupedListener = new GroupedActionListener<>( | ||
| linkedProjectsToQuery, | ||
| responsesListener | ||
| ); | ||
|
|
||
| // make CPS calls | ||
| for (Map.Entry<String, OriginalIndices> remoteClusterIndices : indicesPerCluster.entrySet()) { | ||
| String clusterAlias = remoteClusterIndices.getKey(); | ||
| OriginalIndices originalIndices = remoteClusterIndices.getValue(); | ||
| IndicesOptions relaxedFanoutIdxOptions = originalIndices.indicesOptions(); // from indicesOptionsForCrossProjectFanout | ||
| ResolveIndexAction.Request remoteRequest = new ResolveIndexAction.Request(originalIndices.indices(), relaxedFanoutIdxOptions); | ||
|
|
||
| SubscribableListener<Transport.Connection> connectionListener = new SubscribableListener<>(); | ||
| connectionListener.addTimeout(forceConnectTimeoutSecs, transportService.getThreadPool(), EsExecutors.DIRECT_EXECUTOR_SERVICE); | ||
|
|
||
| connectionListener.addListener(groupedListener.delegateResponse((l, failure) -> { | ||
| logger.info("failed to resolve indices on remote cluster [" + clusterAlias + "]", failure); | ||
| l.onFailure(failure); | ||
| }) | ||
| .delegateFailure( | ||
| (ignored, connection) -> transportService.sendRequest( | ||
| connection, | ||
| ResolveIndexAction.REMOTE_TYPE.name(), | ||
| remoteRequest, | ||
| TransportRequestOptions.EMPTY, | ||
| new ActionListenerResponseHandler<>(groupedListener.delegateResponse((l, failure) -> { | ||
| logger.info("Error occurred on remote cluster [" + clusterAlias + "]", failure); | ||
| l.onFailure(failure); | ||
| }).map(resolveIndexResponse -> Map.entry(clusterAlias, resolveIndexResponse)), | ||
| ResolveIndexAction.Response::new, | ||
| EsExecutors.DIRECT_EXECUTOR_SERVICE | ||
| ) | ||
| ) | ||
| )); | ||
|
|
||
| remoteClusterService.maybeEnsureConnectedAndGetConnection(clusterAlias, true, connectionListener); | ||
pawankartik-elastic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| private void executeOpenPit(SearchTask task, OpenPointInTimeRequest request, ActionListener<OpenPointInTimeResponse> listener) { | ||
| final SearchRequest searchRequest = new SearchRequest().indices(request.indices()) | ||
| .indicesOptions(request.indicesOptions()) | ||
| .preference(request.preference()) | ||
|
|
@@ -132,7 +293,7 @@ protected void doExecute(Task task, OpenPointInTimeRequest request, ActionListen | |
| searchRequest.setMaxConcurrentShardRequests(request.maxConcurrentShardRequests()); | ||
| searchRequest.setCcsMinimizeRoundtrips(false); | ||
|
|
||
| transportSearchAction.executeOpenPit((SearchTask) task, searchRequest, listener.map(r -> { | ||
| transportSearchAction.executeOpenPit(task, searchRequest, listener.map(r -> { | ||
| assert r.pointInTimeId() != null : r; | ||
| return new OpenPointInTimeResponse( | ||
| r.pointInTimeId(), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.