Skip to content
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

Extra testing and some cleanups for filtering on field caps #85068

Merged
merged 19 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@

package org.elasticsearch.upgrades;

import org.apache.http.HttpHost;
import org.elasticsearch.Version;
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.junit.Before;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
Expand All @@ -27,6 +32,9 @@
* reduce the transport message size between nodes and clusters, and the memory usage to hold these internal responses.
* As the optimization is applied for only field-caps requests without index-filter and nodes on 8.2 or later,
* these BWC tests verify these combinations of field-caps requests: (old|new|mixed indices) and (with|without index filter)
*
* In 8.2 we also added the ability to filter fields by type and metadata, with some post-hoc filtering applied on
* the co-ordinating node if older nodes were included in the system
*/
public class FieldCapsIT extends AbstractRollingTestCase {
private static boolean indicesCreated = false;
Expand All @@ -42,6 +50,7 @@ public void setupIndices() throws Exception {
"red_field": { "type": "keyword" },
"yellow_field": { "type": "integer" },
"blue_field": { "type": "keyword" },
"multi_field" : { "type" : "ip", "fields" : { "keyword" : { "type" : "keyword" } } },
"timestamp": {"type": "date"}
}
""";
Expand All @@ -50,6 +59,7 @@ public void setupIndices() throws Exception {
"green_field": { "type": "keyword" },
"yellow_field": { "type": "long" },
"blue_field": { "type": "keyword" },
"multi_field" : { "type" : "ip", "fields" : { "keyword" : { "type" : "keyword" } } },
"timestamp": {"type": "date"}
}
""";
Expand Down Expand Up @@ -88,7 +98,7 @@ public void setupIndices() throws Exception {

public void testOldIndicesOnly() throws Exception {
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_red_*"), List.of("*"), null);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_red_*"), List.of("*"), null, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "old_red_1", "old_red_2", "old_red_empty" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -98,7 +108,7 @@ public void testOldIndicesOnly() throws Exception {
assertTrue(resp.getField("blue_field").get("keyword").isSearchable());
}
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*"), List.of("*"), null);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*"), List.of("*"), null, null, null);
assertThat(
resp.getIndices(),
equalTo(new String[] { "old_green_1", "old_green_2", "old_green_empty", "old_red_1", "old_red_2", "old_red_empty" })
Expand All @@ -116,7 +126,7 @@ public void testOldIndicesOnly() throws Exception {
public void testOldIndicesWithIndexFilter() throws Exception {
final QueryBuilder indexFilter = QueryBuilders.rangeQuery("timestamp").gte("2020-01-01").lte("2020-12-12");
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_red_*"), List.of("*"), indexFilter);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_red_*"), List.of("*"), indexFilter, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "old_red_1", "old_red_2" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -126,7 +136,7 @@ public void testOldIndicesWithIndexFilter() throws Exception {
assertTrue(resp.getField("blue_field").get("keyword").isSearchable());
}
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*"), List.of("*"), indexFilter);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*"), List.of("*"), indexFilter, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "old_green_1", "old_green_2", "old_red_1", "old_red_2" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -141,7 +151,7 @@ public void testOldIndicesWithIndexFilter() throws Exception {
public void testNewIndicesOnly() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_red_*"), List.of("*"), null);
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_red_*"), List.of("*"), null, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "new_red_1", "new_red_2", "new_red_empty" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -151,7 +161,7 @@ public void testNewIndicesOnly() throws Exception {
assertTrue(resp.getField("blue_field").get("keyword").isSearchable());
}
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_*"), List.of("*"), null);
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_*"), List.of("*"), null, null, null);
assertThat(
resp.getIndices(),
equalTo(new String[] { "new_green_1", "new_green_2", "new_green_empty", "new_red_1", "new_red_2", "new_red_empty" })
Expand All @@ -170,7 +180,7 @@ public void testNewIndicesOnlyWithIndexFilter() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
final QueryBuilder indexFilter = QueryBuilders.rangeQuery("timestamp").gte("2020-01-01").lte("2020-12-12");
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_red_*"), List.of("*"), indexFilter);
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_red_*"), List.of("*"), indexFilter, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "new_red_1", "new_red_2" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -180,7 +190,7 @@ public void testNewIndicesOnlyWithIndexFilter() throws Exception {
assertTrue(resp.getField("blue_field").get("keyword").isSearchable());
}
{
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_*"), List.of("*"), indexFilter);
FieldCapabilitiesResponse resp = fieldCaps(List.of("new_*"), List.of("*"), indexFilter, null, null);
assertThat(resp.getIndices(), equalTo(new String[] { "new_green_1", "new_green_2", "new_red_1", "new_red_2" }));
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertTrue(resp.getField("red_field").get("keyword").isSearchable());
Expand All @@ -194,7 +204,7 @@ public void testNewIndicesOnlyWithIndexFilter() throws Exception {

public void testAllIndices() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*", "new_*"), List.of("*"), null);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*", "new_*"), List.of("*"), null, null, null);
assertThat(
resp.getIndices(),
equalTo(
Expand Down Expand Up @@ -227,7 +237,7 @@ public void testAllIndices() throws Exception {
public void testAllIndicesWithIndexFilter() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
final QueryBuilder indexFilter = QueryBuilders.rangeQuery("timestamp").gte("2020-01-01").lte("2020-12-12");
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*", "new_*"), List.of("*"), indexFilter);
FieldCapabilitiesResponse resp = fieldCaps(List.of("old_*", "new_*"), List.of("*"), indexFilter, null, null);
assertThat(
resp.getIndices(),
equalTo(
Expand All @@ -252,4 +262,46 @@ public void testAllIndicesWithIndexFilter() throws Exception {
assertThat(resp.getField("blue_field").keySet(), contains("keyword"));
assertTrue(resp.getField("blue_field").get("keyword").isSearchable());
}

@SuppressWarnings("unchecked")
private RestClient getUpgradedNodeClient() throws IOException {
for (HttpHost host : getClusterHosts()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can plug in a NodeSelector to the RestClient to have it only point to nodes with specific characteristics. Version can be a factor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frustratingly that doesn't help here, because the RestClientBuilder doesn't know the version of its Nodes up-front, and so we can't get the version in the NodeSelector.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok for my understanding, maybe the problem is that the existing client does not point to the upgraded nodes? I guess it depends on how it is initialized. The point of node selector is to influence node selection when making each request, but then all nodes need to be added to the client rotation. I see that the client gets the nodes from tests.rest.cluster set on build.gradle. Is there a chance that not all nodes are included?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the nodes are included; the problem is that the RestClientBuilder.builder(HttpHost hosts...) that gets called here then builds its Nodes using plain Node::new, which doesn't set any Node metadata (including the version).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. Only the sniffer sets these automatically as it has knowledge about the metadata, but you can set metadata up yourself by using the builder(Node ... nodes) method. That makes this change not so useful and along the lines of what you already have though. Have a look if it makes sense to make the change or not?

RestClient client = RestClient.builder(host).build();
Request nodesRequest = new Request("GET", "_nodes/_local/_none");
Map<String, ?> nodeMap = (Map<String, ?>) entityAsMap(client.performRequest(nodesRequest)).get("nodes");
Map<String, ?> nameMap = (Map<String, ?>) nodeMap.values().iterator().next();
String version = (String) nameMap.get("version");
if (version.equals(Version.CURRENT.toString())) {
return client;
}
client.close();
}
throw new IllegalStateException("Couldn't find node on version " + Version.CURRENT);
}

// Test field type filtering on mixed cluster
public void testAllIndicesWithFieldTypeFilter() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
RestClient restClient = getUpgradedNodeClient();
javanna marked this conversation as resolved.
Show resolved Hide resolved
FieldCapabilitiesResponse resp = fieldCaps(restClient, List.of("old_*", "new_*"), List.of("*"), null, "keyword", null);
assertThat(resp.getField("red_field").keySet(), contains("keyword"));
assertNull(resp.getField("yellow_field"));
restClient.close();
}

// Test multifield exclusion on mixed cluster
public void testAllIndicesWithExclusionFilter() throws Exception {
assumeFalse("required mixed or upgraded cluster", CLUSTER_TYPE == ClusterType.OLD);
RestClient client = getUpgradedNodeClient();
{
FieldCapabilitiesResponse resp = fieldCaps(client, List.of("old_*", "new_*"), List.of("*"), null, null, null);
assertThat(resp.getField("multi_field.keyword").keySet(), contains("keyword"));
}
{
FieldCapabilitiesResponse resp = fieldCaps(client, List.of("old_*", "new_*"), List.of("*"), null, null, "-multifield");
assertThat(resp.getField("multi_field").keySet(), contains("ip"));
assertNull(resp.getField("multi_field.keyword"));
}
client.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ static Map<String, IndexFieldCapabilities> retrieveFieldCaps(

boolean includeParentObjects = checkIncludeParents(filters);

FieldCapsFilter filter = buildFilter(indexFieldfilter, filters, types);
Predicate<MappedFieldType> filter = buildFilter(indexFieldfilter, filters, types, context);
Map<String, IndexFieldCapabilities> responseMap = new HashMap<>();
for (String field : fieldNames) {
MappedFieldType ft = context.getFieldType(field);
if (filter.matches(ft, context)) {
if (filter.test(ft)) {
IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(
field,
ft.familyTypeName(),
Expand Down Expand Up @@ -190,30 +190,27 @@ private boolean canMatchShard(
return SearchService.queryStillMatchesAfterRewrite(searchRequest, searchExecutionContext);
}

private interface FieldCapsFilter {
boolean matches(MappedFieldType fieldType, SearchExecutionContext context);

default FieldCapsFilter and(FieldCapsFilter other) {
return (ft, context) -> matches(ft, context) && other.matches(ft, context);
}
}

private static FieldCapsFilter buildFilter(Predicate<String> fieldFilter, String[] filters, String[] fieldTypes) {
private static Predicate<MappedFieldType> buildFilter(
Predicate<String> fieldFilter,
String[] filters,
String[] fieldTypes,
SearchExecutionContext context
) {
// security filters don't exclude metadata fields
FieldCapsFilter fcf = (ft, c) -> fieldFilter.test(ft.name()) || c.isMetadataField(ft.name());
Predicate<MappedFieldType> fcf = ft -> fieldFilter.test(ft.name()) || context.isMetadataField(ft.name());
if (fieldTypes.length > 0) {
Set<String> acceptedTypes = Set.of(fieldTypes);
fcf = fcf.and((ft, c) -> acceptedTypes.contains(ft.familyTypeName()));
fcf = fcf.and(ft -> acceptedTypes.contains(ft.familyTypeName()));
}
for (String filter : filters) {
if ("parent".equals(filter) || "-parent".equals(filter)) {
continue;
}
FieldCapsFilter next = switch (filter) {
case "+metadata" -> (ft, c) -> c.isMetadataField(ft.name());
case "-metadata" -> (ft, c) -> c.isMetadataField(ft.name()) == false;
case "-nested" -> (ft, c) -> c.nestedLookup().getNestedParent(ft.name()) == null;
case "-multifield" -> (ft, c) -> c.isMultiField(ft.name()) == false;
Predicate<MappedFieldType> next = switch (filter) {
case "+metadata" -> ft -> context.isMetadataField(ft.name());
case "-metadata" -> ft -> context.isMetadataField(ft.name()) == false;
case "-nested" -> ft -> context.nestedLookup().getNestedParent(ft.name()) == null;
case "-multifield" -> ft -> context.isMultiField(ft.name()) == false;
default -> throw new IllegalArgumentException("Unknown field caps filter [" + filter + "]");
};
fcf = fcf.and(next);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public final class FieldCapabilitiesRequest extends ActionRequest implements Ind
private IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS;
private String[] fields = Strings.EMPTY_ARRAY;
private String[] filters = Strings.EMPTY_ARRAY;
private String[] allowedTypes = Strings.EMPTY_ARRAY;
private String[] types = Strings.EMPTY_ARRAY;
private boolean includeUnmapped = false;
// pkg private API mainly for cross cluster search to signal that we do multiple reductions ie. the results should not be merged
private boolean mergeResults = true;
Expand All @@ -57,7 +57,7 @@ public FieldCapabilitiesRequest(StreamInput in) throws IOException {
runtimeFields = in.readMap();
if (in.getVersion().onOrAfter(Version.V_8_2_0)) {
filters = in.readStringArray();
allowedTypes = in.readStringArray();
types = in.readStringArray();
}
}

Expand Down Expand Up @@ -95,7 +95,7 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeGenericMap(runtimeFields);
if (out.getVersion().onOrAfter(Version.V_8_2_0)) {
out.writeStringArray(filters);
out.writeStringArray(allowedTypes);
out.writeStringArray(types);
}
}

Expand Down Expand Up @@ -137,13 +137,13 @@ public String[] filters() {
return filters;
}

public FieldCapabilitiesRequest allowedTypes(String... types) {
this.allowedTypes = types;
public FieldCapabilitiesRequest types(String... types) {
this.types = types;
return this;
}

public String[] allowedTypes() {
return allowedTypes;
public String[] types() {
return types;
}

/**
Expand Down Expand Up @@ -243,7 +243,7 @@ public boolean equals(Object o) {
&& Objects.equals(indexFilter, that.indexFilter)
&& Objects.equals(nowInMillis, that.nowInMillis)
&& Arrays.equals(filters, that.filters)
&& Arrays.equals(allowedTypes, that.allowedTypes)
&& Arrays.equals(types, that.types)
&& Objects.equals(runtimeFields, that.runtimeFields);
}

Expand All @@ -253,7 +253,7 @@ public int hashCode() {
result = 31 * result + Arrays.hashCode(indices);
result = 31 * result + Arrays.hashCode(fields);
result = 31 * result + Arrays.hashCode(filters);
result = 31 * result + Arrays.hashCode(allowedTypes);
result = 31 * result + Arrays.hashCode(types);
return result;
}

Expand All @@ -266,7 +266,7 @@ public String getDescription() {
stringBuilder.append("], filters[");
stringBuilder.append(Strings.collectionToDelimitedString(Arrays.asList(filters), ","));
stringBuilder.append("], types[");
stringBuilder.append(Strings.collectionToDelimitedString(Arrays.asList(allowedTypes), ","));
stringBuilder.append(Strings.collectionToDelimitedString(Arrays.asList(types), ","));
stringBuilder.append("]");
return stringBuilder.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class IndexFieldCapabilities implements Writeable {
private final TimeSeriesParams.MetricType metricType;
private final Map<String, String> meta;

public static IndexFieldCapabilities withMetadata(IndexFieldCapabilities input, boolean isMetadata) {
static IndexFieldCapabilities withMetadata(IndexFieldCapabilities input, boolean isMetadata) {
return new IndexFieldCapabilities(
input.getName(),
input.getType(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private void sendRequestToNode(String nodeId, List<ShardId> shardIds) {
shardIds,
fieldCapsRequest.fields(),
fieldCapsRequest.filters(),
fieldCapsRequest.allowedTypes(),
fieldCapsRequest.types(),
originalIndices,
fieldCapsRequest.indexFilter(),
nowInMillis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Applies field type filters to responses that come from earlier versions of ES
javanna marked this conversation as resolved.
Show resolved Hide resolved
* that do not support filtering directly.
*/
final class ResponseRewriter {

public static Map<String, IndexFieldCapabilities> rewriteOldResponses(
Expand All @@ -26,7 +30,7 @@ public static Map<String, IndexFieldCapabilities> rewriteOldResponses(
String[] allowedTypes,
Predicate<String> isMetadata
) {
if (version.onOrAfter(Version.V_8_1_0)) {
if (version.onOrAfter(Version.V_8_2_0)) {
return input; // nothing needs to be done
}
Function<IndexFieldCapabilities, IndexFieldCapabilities> transformer = buildTransformer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ private static FieldCapabilitiesRequest prepareRemoteRequest(
remoteRequest.indices(originalIndices.indices());
remoteRequest.fields(request.fields());
remoteRequest.filters(request.filters());
remoteRequest.allowedTypes(request.allowedTypes());
remoteRequest.types(request.types());
remoteRequest.runtimeFields(request.runtimeFields());
remoteRequest.indexFilter(request.indexFilter());
remoteRequest.nowInMillis(nowInMillis);
Expand Down Expand Up @@ -281,7 +281,7 @@ private void innerMerge(
response.getOriginVersion(),
response.get(),
request.filters(),
request.allowedTypes(),
request.types(),
metadataFieldPred
);
for (Map.Entry<String, IndexFieldCapabilities> entry : fields.entrySet()) {
Expand Down
Loading