Skip to content

Commit 092131d

Browse files
authored
Include min transport version into FC response (elastic#136887) (elastic#137088)
(cherry picked from commit 8090c2a)
1 parent 9d433a2 commit 092131d

File tree

6 files changed

+135
-47
lines changed

6 files changed

+135
-47
lines changed

server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/CCSFieldCapabilitiesIT.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.search.fieldcaps;
1111

1212
import org.elasticsearch.ExceptionsHelper;
13+
import org.elasticsearch.TransportVersion;
1314
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesFailure;
1415
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
1516
import org.elasticsearch.client.internal.Client;
@@ -266,4 +267,12 @@ public void testReturnAllLocal() {
266267
}
267268
}
268269
}
270+
271+
public void testIncludesMinTransportVersion() {
272+
if (randomBoolean()) {
273+
assertAcked(client().admin().indices().prepareCreate("index"));
274+
}
275+
var response = client().prepareFieldCaps("_all").setFields("*").get();
276+
assertThat(response.minTransportVersion(), equalTo(TransportVersion.current()));
277+
}
269278
}

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponse.java

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99

1010
package org.elasticsearch.action.fieldcaps;
1111

12+
import org.elasticsearch.TransportVersion;
1213
import org.elasticsearch.action.ActionResponse;
1314
import org.elasticsearch.common.Strings;
1415
import org.elasticsearch.common.collect.Iterators;
1516
import org.elasticsearch.common.io.stream.StreamInput;
1617
import org.elasticsearch.common.io.stream.StreamOutput;
1718
import org.elasticsearch.common.xcontent.ChunkedToXContentObject;
19+
import org.elasticsearch.core.Nullable;
1820
import org.elasticsearch.xcontent.ParseField;
1921
import org.elasticsearch.xcontent.ToXContent;
2022

@@ -30,6 +32,9 @@
3032
* Response for {@link FieldCapabilitiesRequest} requests.
3133
*/
3234
public class FieldCapabilitiesResponse extends ActionResponse implements ChunkedToXContentObject {
35+
36+
private static final TransportVersion MIN_TRANSPORT_VERSION = TransportVersion.fromName("min_transport_version");
37+
3338
public static final ParseField INDICES_FIELD = new ParseField("indices");
3439
public static final ParseField FIELDS_FIELD = new ParseField("fields");
3540
private static final ParseField FAILED_INDICES_FIELD = new ParseField("failed_indices");
@@ -39,40 +44,46 @@ public class FieldCapabilitiesResponse extends ActionResponse implements Chunked
3944
private final Map<String, Map<String, FieldCapabilities>> fields;
4045
private final List<FieldCapabilitiesFailure> failures;
4146
private final List<FieldCapabilitiesIndexResponse> indexResponses;
47+
private final TransportVersion minTransportVersion;
4248

4349
public FieldCapabilitiesResponse(
4450
String[] indices,
4551
Map<String, Map<String, FieldCapabilities>> fields,
4652
List<FieldCapabilitiesFailure> failures
4753
) {
48-
this(indices, fields, Collections.emptyList(), failures);
54+
this(indices, fields, Collections.emptyList(), failures, null);
4955
}
5056

5157
public FieldCapabilitiesResponse(String[] indices, Map<String, Map<String, FieldCapabilities>> fields) {
52-
this(indices, fields, Collections.emptyList(), Collections.emptyList());
58+
this(indices, fields, Collections.emptyList(), Collections.emptyList(), null);
5359
}
5460

5561
public FieldCapabilitiesResponse(List<FieldCapabilitiesIndexResponse> indexResponses, List<FieldCapabilitiesFailure> failures) {
56-
this(Strings.EMPTY_ARRAY, Collections.emptyMap(), indexResponses, failures);
62+
this(Strings.EMPTY_ARRAY, Collections.emptyMap(), indexResponses, failures, null);
5763
}
5864

5965
private FieldCapabilitiesResponse(
6066
String[] indices,
6167
Map<String, Map<String, FieldCapabilities>> fields,
6268
List<FieldCapabilitiesIndexResponse> indexResponses,
63-
List<FieldCapabilitiesFailure> failures
69+
List<FieldCapabilitiesFailure> failures,
70+
TransportVersion minTransportVersion
6471
) {
6572
this.fields = Objects.requireNonNull(fields);
6673
this.indexResponses = Objects.requireNonNull(indexResponses);
6774
this.indices = indices;
6875
this.failures = failures;
76+
this.minTransportVersion = minTransportVersion;
6977
}
7078

7179
public FieldCapabilitiesResponse(StreamInput in) throws IOException {
7280
this.indices = in.readStringArray();
7381
this.fields = in.readMap(FieldCapabilitiesResponse::readField);
7482
this.indexResponses = FieldCapabilitiesIndexResponse.readList(in);
7583
this.failures = in.readCollectionAsList(FieldCapabilitiesFailure::new);
84+
this.minTransportVersion = in.getTransportVersion().supports(MIN_TRANSPORT_VERSION)
85+
? in.readOptional(TransportVersion::readVersion)
86+
: null;
7687
}
7788

7889
/**
@@ -116,13 +127,20 @@ public List<FieldCapabilitiesIndexResponse> getIndexResponses() {
116127
}
117128

118129
/**
119-
*
120130
* Get the field capabilities per type for the provided {@code field}.
121131
*/
122132
public Map<String, FieldCapabilities> getField(String field) {
123133
return fields.get(field);
124134
}
125135

136+
/**
137+
* @return the minTransportVersion across all clusters involved in resolution
138+
*/
139+
@Nullable
140+
public TransportVersion minTransportVersion() {
141+
return minTransportVersion;
142+
}
143+
126144
/**
127145
* Returns <code>true</code> if the provided field is a metadata field.
128146
*/
@@ -144,6 +162,9 @@ public void writeTo(StreamOutput out) throws IOException {
144162
out.writeMap(fields, FieldCapabilitiesResponse::writeField);
145163
FieldCapabilitiesIndexResponse.writeList(out, indexResponses);
146164
out.writeCollection(failures);
165+
if (out.getTransportVersion().supports(MIN_TRANSPORT_VERSION)) {
166+
out.writeOptional((Writer<TransportVersion>) (o, v) -> TransportVersion.writeVersion(v, o), minTransportVersion);
167+
}
147168
}
148169

149170
private static void writeField(StreamOutput out, Map<String, FieldCapabilities> map) throws IOException {
@@ -184,21 +205,60 @@ public boolean equals(Object o) {
184205
return Arrays.equals(indices, that.indices)
185206
&& Objects.equals(fields, that.fields)
186207
&& Objects.equals(indexResponses, that.indexResponses)
187-
&& Objects.equals(failures, that.failures);
208+
&& Objects.equals(failures, that.failures)
209+
&& Objects.equals(minTransportVersion, that.minTransportVersion);
188210
}
189211

190212
@Override
191213
public int hashCode() {
192-
int result = Objects.hash(fields, indexResponses, failures);
193-
result = 31 * result + Arrays.hashCode(indices);
194-
return result;
214+
return Objects.hash(fields, indexResponses, failures, minTransportVersion) * 31 + Arrays.hashCode(indices);
195215
}
196216

197217
@Override
198218
public String toString() {
199-
if (indexResponses.size() > 0) {
200-
return "FieldCapabilitiesResponse{unmerged}";
219+
return indexResponses.isEmpty() ? Strings.toString(this) : "FieldCapabilitiesResponse{unmerged}";
220+
}
221+
222+
public static Builder builder() {
223+
return new Builder();
224+
}
225+
226+
public static class Builder {
227+
private String[] indices = Strings.EMPTY_ARRAY;
228+
private Map<String, Map<String, FieldCapabilities>> fields = Collections.emptyMap();
229+
private List<FieldCapabilitiesIndexResponse> indexResponses = Collections.emptyList();
230+
private List<FieldCapabilitiesFailure> failures = Collections.emptyList();
231+
private TransportVersion minTransportVersion = null;
232+
233+
private Builder() {}
234+
235+
public Builder withIndices(String[] indices) {
236+
this.indices = indices;
237+
return this;
238+
}
239+
240+
public Builder withFields(Map<String, Map<String, FieldCapabilities>> fields) {
241+
this.fields = fields;
242+
return this;
243+
}
244+
245+
public Builder withIndexResponses(List<FieldCapabilitiesIndexResponse> indexResponses) {
246+
this.indexResponses = indexResponses;
247+
return this;
248+
}
249+
250+
public Builder withFailures(List<FieldCapabilitiesFailure> failures) {
251+
this.failures = failures;
252+
return this;
253+
}
254+
255+
public Builder withMinTransportVersion(TransportVersion minTransportVersion) {
256+
this.minTransportVersion = minTransportVersion;
257+
return this;
258+
}
259+
260+
public FieldCapabilitiesResponse build() {
261+
return new FieldCapabilitiesResponse(indices, fields, indexResponses, failures, minTransportVersion);
201262
}
202-
return Strings.toString(this);
203263
}
204264
}

server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
1414
import org.elasticsearch.ElasticsearchTimeoutException;
1515
import org.elasticsearch.ExceptionsHelper;
16+
import org.elasticsearch.TransportVersion;
1617
import org.elasticsearch.action.ActionListener;
1718
import org.elasticsearch.action.ActionListenerResponseHandler;
1819
import org.elasticsearch.action.ActionRunnable;
@@ -69,6 +70,7 @@
6970
import java.util.Set;
7071
import java.util.concurrent.Executor;
7172
import java.util.concurrent.atomic.AtomicBoolean;
73+
import java.util.concurrent.atomic.AtomicReference;
7274
import java.util.function.BiConsumer;
7375
import java.util.function.Consumer;
7476
import java.util.function.Function;
@@ -144,22 +146,20 @@ private void doExecuteForked(Task task, FieldCapabilitiesRequest request, Action
144146
final Executor singleThreadedExecutor = buildSingleThreadedExecutor(searchCoordinationExecutor, LOGGER);
145147
assert task instanceof CancellableTask;
146148
final CancellableTask fieldCapTask = (CancellableTask) task;
147-
// retrieve the initial timestamp in case the action is a cross cluster search
149+
// retrieve the initial timestamp in case the action is a cross-cluster search
148150
long nowInMillis = request.nowInMillis() == null ? System.currentTimeMillis() : request.nowInMillis();
149151
final ProjectState projectState = projectResolver.getProjectState(clusterService.state());
152+
final var minTransportVersion = new AtomicReference<>(clusterService.state().getMinTransportVersion());
150153
final Map<String, OriginalIndices> remoteClusterIndices = transportService.getRemoteClusterService()
151154
.groupIndices(request.indicesOptions(), request.indices(), request.returnLocalAll());
152155
final OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY);
153-
final String[] concreteIndices;
154-
if (localIndices == null) {
155-
// in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices
156-
concreteIndices = Strings.EMPTY_ARRAY;
157-
} else {
158-
concreteIndices = indexNameExpressionResolver.concreteIndexNames(projectState.metadata(), localIndices);
159-
}
156+
// in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices
157+
final String[] concreteIndices = localIndices != null
158+
? indexNameExpressionResolver.concreteIndexNames(projectState.metadata(), localIndices)
159+
: Strings.EMPTY_ARRAY;
160160

161161
if (concreteIndices.length == 0 && remoteClusterIndices.isEmpty()) {
162-
listener.onResponse(new FieldCapabilitiesResponse(new String[0], Collections.emptyMap()));
162+
listener.onResponse(FieldCapabilitiesResponse.builder().withMinTransportVersion(minTransportVersion.get()).build());
163163
return;
164164
}
165165

@@ -235,7 +235,7 @@ private void doExecuteForked(Task task, FieldCapabilitiesRequest request, Action
235235
if (fieldCapTask.notifyIfCancelled(listener)) {
236236
releaseResourcesOnCancel.run();
237237
} else {
238-
mergeIndexResponses(request, fieldCapTask, indexResponses, indexFailures, listener);
238+
mergeIndexResponses(request, fieldCapTask, indexResponses, indexFailures, minTransportVersion, listener);
239239
}
240240
})) {
241241
// local cluster
@@ -281,6 +281,12 @@ private void doExecuteForked(Task task, FieldCapabilitiesRequest request, Action
281281
handleIndexFailure.accept(RemoteClusterAware.buildRemoteIndexName(clusterAlias, index), ex);
282282
}
283283
}
284+
minTransportVersion.accumulateAndGet(response.minTransportVersion(), (lhs, rhs) -> {
285+
if (lhs == null || rhs == null) {
286+
return null;
287+
}
288+
return TransportVersion.min(lhs, rhs);
289+
});
284290
}, ex -> {
285291
for (String index : originalIndices.indices()) {
286292
handleIndexFailure.accept(RemoteClusterAware.buildRemoteIndexName(clusterAlias, index), ex);
@@ -360,35 +366,41 @@ private static void mergeIndexResponses(
360366
CancellableTask task,
361367
Map<String, FieldCapabilitiesIndexResponse> indexResponses,
362368
FailureCollector indexFailures,
369+
AtomicReference<TransportVersion> minTransportVersion,
363370
ActionListener<FieldCapabilitiesResponse> listener
364371
) {
365372
List<FieldCapabilitiesFailure> failures = indexFailures.build(indexResponses.keySet());
366-
if (indexResponses.size() > 0) {
373+
if (indexResponses.isEmpty() == false) {
367374
if (request.isMergeResults()) {
368-
ActionListener.completeWith(listener, () -> merge(indexResponses, task, request, failures));
375+
ActionListener.completeWith(listener, () -> merge(indexResponses, task, request, failures, minTransportVersion));
369376
} else {
370-
listener.onResponse(new FieldCapabilitiesResponse(new ArrayList<>(indexResponses.values()), failures));
377+
listener.onResponse(
378+
FieldCapabilitiesResponse.builder()
379+
.withIndexResponses(new ArrayList<>(indexResponses.values()))
380+
.withFailures(failures)
381+
.withMinTransportVersion(minTransportVersion.get())
382+
.build()
383+
);
371384
}
372-
} else {
373-
// we have no responses at all, maybe because of errors
374-
if (indexFailures.isEmpty() == false) {
375-
/*
376-
* Under no circumstances are we to pass timeout errors originating from SubscribableListener as top-level errors.
377-
* Instead, they should always be passed through the response object, as part of "failures".
378-
*/
379-
if (failures.stream()
380-
.anyMatch(
381-
failure -> failure.getException() instanceof IllegalStateException ise
382-
&& ise.getCause() instanceof ElasticsearchTimeoutException
383-
)) {
384-
listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), failures));
385-
} else {
386-
// throw back the first exception
387-
listener.onFailure(failures.get(0).getException());
388-
}
385+
} else if (indexFailures.isEmpty() == false) {
386+
/*
387+
* Under no circumstances are we to pass timeout errors originating from SubscribableListener as top-level errors.
388+
* Instead, they should always be passed through the response object, as part of "failures".
389+
*/
390+
if (failures.stream()
391+
.anyMatch(
392+
failure -> failure.getException() instanceof IllegalStateException ise
393+
&& ise.getCause() instanceof ElasticsearchTimeoutException
394+
)) {
395+
listener.onResponse(
396+
FieldCapabilitiesResponse.builder().withFailures(failures).withMinTransportVersion(minTransportVersion.get()).build()
397+
);
389398
} else {
390-
listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), Collections.emptyList()));
399+
// throw back the first exception
400+
listener.onFailure(failures.get(0).getException());
391401
}
402+
} else {
403+
listener.onResponse(FieldCapabilitiesResponse.builder().withMinTransportVersion(minTransportVersion.get()).build());
392404
}
393405
}
394406

@@ -423,7 +435,8 @@ private static FieldCapabilitiesResponse merge(
423435
Map<String, FieldCapabilitiesIndexResponse> indexResponsesMap,
424436
CancellableTask task,
425437
FieldCapabilitiesRequest request,
426-
List<FieldCapabilitiesFailure> failures
438+
List<FieldCapabilitiesFailure> failures,
439+
AtomicReference<TransportVersion> minTransportVersion
427440
) {
428441
assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SEARCH_COORDINATION); // too expensive to run this on a transport worker
429442
task.ensureNotCancelled();
@@ -464,7 +477,12 @@ private static FieldCapabilitiesResponse merge(
464477
);
465478
}
466479
}
467-
return new FieldCapabilitiesResponse(indices, Collections.unmodifiableMap(fields), failures);
480+
return FieldCapabilitiesResponse.builder()
481+
.withIndices(indices)
482+
.withFields(Collections.unmodifiableMap(fields))
483+
.withFailures(failures)
484+
.withMinTransportVersion(minTransportVersion.get())
485+
.build();
468486
}
469487

470488
private static boolean shouldLogException(Exception e) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
9202000,9185004
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
initial_9.2.1,9185003
1+
min_transport_version,9185004
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dimension_values,9197000
1+
min_transport_version,9202000

0 commit comments

Comments
 (0)