|
@@ -11,8 +11,10 @@ package org.elasticsearch.action.fieldcaps;
|
|
|
|
|
|
import org.apache.lucene.util.ArrayUtil;
|
|
|
import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
|
|
|
+import org.elasticsearch.ElasticsearchTimeoutException;
|
|
|
import org.elasticsearch.ExceptionsHelper;
|
|
|
import org.elasticsearch.action.ActionListener;
|
|
|
+import org.elasticsearch.action.ActionListenerResponseHandler;
|
|
|
import org.elasticsearch.action.ActionRunnable;
|
|
|
import org.elasticsearch.action.ActionType;
|
|
|
import org.elasticsearch.action.OriginalIndices;
|
|
@@ -22,7 +24,7 @@ import org.elasticsearch.action.support.ActionFilters;
|
|
|
import org.elasticsearch.action.support.ChannelActionListener;
|
|
|
import org.elasticsearch.action.support.HandledTransportAction;
|
|
|
import org.elasticsearch.action.support.RefCountingRunnable;
|
|
|
-import org.elasticsearch.client.internal.RemoteClusterClient;
|
|
|
+import org.elasticsearch.action.support.SubscribableListener;
|
|
|
import org.elasticsearch.cluster.ProjectState;
|
|
|
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
@@ -37,6 +39,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors;
|
|
|
import org.elasticsearch.common.util.concurrent.ThrottledTaskRunner;
|
|
|
import org.elasticsearch.core.Nullable;
|
|
|
import org.elasticsearch.core.Releasable;
|
|
|
+import org.elasticsearch.core.TimeValue;
|
|
|
import org.elasticsearch.core.Tuple;
|
|
|
import org.elasticsearch.index.shard.ShardId;
|
|
|
import org.elasticsearch.indices.IndicesService;
|
|
@@ -48,9 +51,10 @@ import org.elasticsearch.tasks.CancellableTask;
|
|
|
import org.elasticsearch.tasks.Task;
|
|
|
import org.elasticsearch.threadpool.ThreadPool;
|
|
|
import org.elasticsearch.transport.RemoteClusterAware;
|
|
|
-import org.elasticsearch.transport.RemoteClusterService;
|
|
|
+import org.elasticsearch.transport.Transport;
|
|
|
import org.elasticsearch.transport.TransportChannel;
|
|
|
import org.elasticsearch.transport.TransportRequestHandler;
|
|
|
+import org.elasticsearch.transport.TransportRequestOptions;
|
|
|
import org.elasticsearch.transport.TransportService;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
@@ -91,6 +95,8 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
|
|
|
private final IndicesService indicesService;
|
|
|
private final boolean ccsCheckCompatibility;
|
|
|
+ private final ThreadPool threadPool;
|
|
|
+ private final TimeValue forceConnectTimeoutSecs;
|
|
|
|
|
|
@Inject
|
|
|
public TransportFieldCapabilitiesAction(
|
|
@@ -117,6 +123,8 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
new NodeTransportHandler()
|
|
|
);
|
|
|
this.ccsCheckCompatibility = SearchService.CCS_VERSION_CHECK_SETTING.get(clusterService.getSettings());
|
|
|
+ this.threadPool = threadPool;
|
|
|
+ this.forceConnectTimeoutSecs = clusterService.getSettings().getAsTime("search.ccs.force_connect_timeout", null);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -124,7 +132,13 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
executeRequest(
|
|
|
task,
|
|
|
request,
|
|
|
- (remoteClient, remoteRequest, remoteListener) -> remoteClient.execute(REMOTE_TYPE, remoteRequest, remoteListener),
|
|
|
+ (transportService, conn, fieldCapabilitiesRequest, responseHandler) -> transportService.sendRequest(
|
|
|
+ conn,
|
|
|
+ REMOTE_TYPE.name(),
|
|
|
+ fieldCapabilitiesRequest,
|
|
|
+ TransportRequestOptions.EMPTY,
|
|
|
+ responseHandler
|
|
|
+ ),
|
|
|
listener
|
|
|
);
|
|
|
}
|
|
@@ -132,17 +146,17 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
public void executeRequest(
|
|
|
Task task,
|
|
|
FieldCapabilitiesRequest request,
|
|
|
- RemoteRequestExecutor remoteRequestExecutor,
|
|
|
+ LinkedRequestExecutor linkedRequestExecutor,
|
|
|
ActionListener<FieldCapabilitiesResponse> listener
|
|
|
) {
|
|
|
// workaround for https://github.com/elastic/elasticsearch/issues/97916 - TODO remove this when we can
|
|
|
- searchCoordinationExecutor.execute(ActionRunnable.wrap(listener, l -> doExecuteForked(task, request, remoteRequestExecutor, l)));
|
|
|
+ searchCoordinationExecutor.execute(ActionRunnable.wrap(listener, l -> doExecuteForked(task, request, linkedRequestExecutor, l)));
|
|
|
}
|
|
|
|
|
|
private void doExecuteForked(
|
|
|
Task task,
|
|
|
FieldCapabilitiesRequest request,
|
|
|
- RemoteRequestExecutor remoteRequestExecutor,
|
|
|
+ LinkedRequestExecutor linkedRequestExecutor,
|
|
|
ActionListener<FieldCapabilitiesResponse> listener
|
|
|
) {
|
|
|
if (ccsCheckCompatibility) {
|
|
@@ -268,12 +282,6 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
for (Map.Entry<String, OriginalIndices> remoteIndices : remoteClusterIndices.entrySet()) {
|
|
|
String clusterAlias = remoteIndices.getKey();
|
|
|
OriginalIndices originalIndices = remoteIndices.getValue();
|
|
|
- var remoteClusterClient = transportService.getRemoteClusterService()
|
|
|
- .getRemoteClusterClient(
|
|
|
- clusterAlias,
|
|
|
- singleThreadedExecutor,
|
|
|
- RemoteClusterService.DisconnectedStrategy.RECONNECT_UNLESS_SKIP_UNAVAILABLE
|
|
|
- );
|
|
|
FieldCapabilitiesRequest remoteRequest = prepareRemoteRequest(clusterAlias, request, originalIndices, nowInMillis);
|
|
|
ActionListener<FieldCapabilitiesResponse> remoteListener = ActionListener.wrap(response -> {
|
|
|
for (FieldCapabilitiesIndexResponse resp : response.getIndexResponses()) {
|
|
@@ -299,9 +307,13 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
handleIndexFailure.accept(RemoteClusterAware.buildRemoteIndexName(clusterAlias, index), ex);
|
|
|
}
|
|
|
});
|
|
|
- remoteRequestExecutor.executeRemoteRequest(
|
|
|
- remoteClusterClient,
|
|
|
- remoteRequest,
|
|
|
+
|
|
|
+ SubscribableListener<Transport.Connection> connectionListener = new SubscribableListener<>();
|
|
|
+ if (forceConnectTimeoutSecs != null) {
|
|
|
+ connectionListener.addTimeout(forceConnectTimeoutSecs, threadPool, singleThreadedExecutor);
|
|
|
+ }
|
|
|
+
|
|
|
+ connectionListener.addListener(
|
|
|
// The underlying transport service may call onFailure with a thread pool other than search_coordinator.
|
|
|
// This fork is a workaround to ensure that the merging of field-caps always occurs on the search_coordinator.
|
|
|
// TODO: remove this workaround after we fixed https://github.com/elastic/elasticsearch/issues/107439
|
|
@@ -309,8 +321,20 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
singleThreadedExecutor,
|
|
|
true,
|
|
|
ActionListener.releaseAfter(remoteListener, refs.acquire())
|
|
|
+ ).delegateFailure(
|
|
|
+ (responseListener, conn) -> linkedRequestExecutor.executeRemoteRequest(
|
|
|
+ transportService,
|
|
|
+ conn,
|
|
|
+ remoteRequest,
|
|
|
+ new ActionListenerResponseHandler<>(responseListener, FieldCapabilitiesResponse::new, singleThreadedExecutor)
|
|
|
+ )
|
|
|
)
|
|
|
);
|
|
|
+
|
|
|
+ boolean ensureConnected = forceConnectTimeoutSecs != null
|
|
|
+ || transportService.getRemoteClusterService().isSkipUnavailable(clusterAlias) == false;
|
|
|
+ transportService.getRemoteClusterService()
|
|
|
+ .maybeEnsureConnectedAndGetConnection(clusterAlias, ensureConnected, connectionListener);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -338,11 +362,12 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- public interface RemoteRequestExecutor {
|
|
|
+ public interface LinkedRequestExecutor {
|
|
|
void executeRemoteRequest(
|
|
|
- RemoteClusterClient remoteClient,
|
|
|
+ TransportService transportService,
|
|
|
+ Transport.Connection conn,
|
|
|
FieldCapabilitiesRequest remoteRequest,
|
|
|
- ActionListener<FieldCapabilitiesResponse> remoteListener
|
|
|
+ ActionListenerResponseHandler<FieldCapabilitiesResponse> responseHandler
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -376,8 +401,20 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
} else {
|
|
|
// we have no responses at all, maybe because of errors
|
|
|
if (indexFailures.isEmpty() == false) {
|
|
|
- // throw back the first exception
|
|
|
- listener.onFailure(failures.get(0).getException());
|
|
|
+ /*
|
|
|
+ * Under no circumstances are we to pass timeout errors originating from SubscribableListener as top-level errors.
|
|
|
+ * Instead, they should always be passed through the response object, as part of "failures".
|
|
|
+ */
|
|
|
+ if (failures.stream()
|
|
|
+ .anyMatch(
|
|
|
+ failure -> failure.getException() instanceof IllegalStateException ise
|
|
|
+ && ise.getCause() instanceof ElasticsearchTimeoutException
|
|
|
+ )) {
|
|
|
+ listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), failures));
|
|
|
+ } else {
|
|
|
+ // throw back the first exception
|
|
|
+ listener.onFailure(failures.get(0).getException());
|
|
|
+ }
|
|
|
} else {
|
|
|
listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), Collections.emptyList()));
|
|
|
}
|
|
@@ -585,15 +622,24 @@ public class TransportFieldCapabilitiesAction extends HandledTransportAction<Fie
|
|
|
for (Map.Entry<String, Exception> failure : failuresByIndex.entrySet()) {
|
|
|
String index = failure.getKey();
|
|
|
Exception e = failure.getValue();
|
|
|
+ /*
|
|
|
+ * The listener we use to briefly try, and connect to a linked cluster can throw an ElasticsearchTimeoutException
|
|
|
+ * error if it cannot be reached. To make sure we correctly recognise this scenario via
|
|
|
+ * ExceptionsHelper.isRemoteUnavailableException(), we wrap this error appropriately.
|
|
|
+ */
|
|
|
+ if (e instanceof ElasticsearchTimeoutException ete) {
|
|
|
+ e = new IllegalStateException("Unable to open any connections", ete);
|
|
|
+ }
|
|
|
|
|
|
if (successfulIndices.contains(index) == false) {
|
|
|
// we deduplicate exceptions on the underlying causes message and classname
|
|
|
// we unwrap the cause to e.g. group RemoteTransportExceptions coming from different nodes if the cause is the same
|
|
|
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
|
|
Tuple<String, String> groupingKey = new Tuple<>(cause.getMessage(), cause.getClass().getName());
|
|
|
+ Exception ex = e;
|
|
|
indexFailures.compute(
|
|
|
groupingKey,
|
|
|
- (k, v) -> v == null ? new FieldCapabilitiesFailure(new String[] { index }, e) : v.addIndex(index)
|
|
|
+ (k, v) -> v == null ? new FieldCapabilitiesFailure(new String[] { index }, ex) : v.addIndex(index)
|
|
|
);
|
|
|
}
|
|
|
}
|