Watcher understands hidden expand wildcard value (#65372)

Watcher has a search template that stores indices options to be used as
part of a search during watch execution, but this was not updated to be
aware of hidden indices and the `hidden` expand_wildcards option. This
change makes use of the `IndicesOptions#toXContent` method in Watcher,
which already handles the new value. Additionally, the XContent parsing
is moved to the IndicesOptions class so that we will be less likely to
miss updating this in the future.

Closes #65148
Backport of #65332
This commit is contained in:
Jay Modi 2020-11-23 09:17:49 -07:00 committed by GitHub
parent 1828a8b6cc
commit 1a13a0b10f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 193 additions and 93 deletions

View File

@ -19,15 +19,18 @@
package org.elasticsearch.action.support;
import org.elasticsearch.Version;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.rest.RestRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Map;
@ -59,25 +62,7 @@ public class IndicesOptions implements ToXContentFragment {
// TODO why do we let patterns like "none,all" or "open,none,closed" get used. The location of 'none' in the array changes the
// meaning of the resulting value
for (String wildcard : wildcards) {
switch (wildcard) {
case "open":
states.add(OPEN);
break;
case "closed":
states.add(CLOSED);
break;
case "hidden":
states.add(HIDDEN);
break;
case "none":
states.clear();
break;
case "all":
states = EnumSet.allOf(WildcardStates.class);
break;
default:
throw new IllegalArgumentException("No valid expand wildcard value [" + wildcard + "]");
}
updateSetForValue(states, wildcard);
}
return states;
@ -94,6 +79,28 @@ public class IndicesOptions implements ToXContentFragment {
}
return builder;
}
private static void updateSetForValue(EnumSet<WildcardStates> states, String wildcard) {
switch (wildcard) {
case "open":
states.add(OPEN);
break;
case "closed":
states.add(CLOSED);
break;
case "hidden":
states.add(HIDDEN);
break;
case "none":
states.clear();
break;
case "all":
states.addAll(EnumSet.allOf(WildcardStates.class));
break;
default:
throw new IllegalArgumentException("No valid expand wildcard value [" + wildcard + "]");
}
}
}
public enum Option {
@ -148,11 +155,6 @@ public class IndicesOptions implements ToXContentFragment {
this.expandWildcards = expandWildcards;
}
private IndicesOptions(Collection<Option> options, Collection<WildcardStates> expandWildcards) {
this(options.isEmpty() ? Option.NONE : EnumSet.copyOf(options),
expandWildcards.isEmpty() ? WildcardStates.NONE : EnumSet.copyOf(expandWildcards));
}
/**
* @return Whether specified concrete indices should be ignored when unavailable (missing or closed)
*/
@ -380,6 +382,84 @@ public class IndicesOptions implements ToXContentFragment {
return builder;
}
private static final ParseField EXPAND_WILDCARDS_FIELD = new ParseField("expand_wildcards");
private static final ParseField IGNORE_UNAVAILABLE_FIELD = new ParseField("ignore_unavailable");
private static final ParseField IGNORE_THROTTLED_FIELD = new ParseField("ignore_throttled");
private static final ParseField ALLOW_NO_INDICES_FIELD = new ParseField("allow_no_indices");
public static IndicesOptions fromXContent(XContentParser parser) throws IOException {
EnumSet<WildcardStates> wildcardStates = null;
Boolean allowNoIndices = null;
Boolean ignoreUnavailable = null;
boolean ignoreThrottled = false;
Token token = parser.currentToken() == Token.START_OBJECT ? parser.currentToken() : parser.nextToken();
String currentFieldName = null;
if (token != Token.START_OBJECT) {
throw new ElasticsearchParseException("expected START_OBJECT as the token but was " + token);
}
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == Token.START_ARRAY) {
if (EXPAND_WILDCARDS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
if (wildcardStates == null) {
wildcardStates = EnumSet.noneOf(WildcardStates.class);
while ((token = parser.nextToken()) != Token.END_ARRAY) {
if (token.isValue()) {
WildcardStates.updateSetForValue(wildcardStates, parser.text());
} else {
throw new ElasticsearchParseException("expected values within array for " +
EXPAND_WILDCARDS_FIELD.getPreferredName());
}
}
} else {
throw new ElasticsearchParseException("already parsed expand_wildcards");
}
} else {
throw new ElasticsearchParseException(EXPAND_WILDCARDS_FIELD.getPreferredName() +
" is the only field that is an array in IndicesOptions");
}
} else if (token.isValue()) {
if (EXPAND_WILDCARDS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
if (wildcardStates == null) {
wildcardStates = EnumSet.noneOf(WildcardStates.class);
WildcardStates.updateSetForValue(wildcardStates, parser.text());
} else {
throw new ElasticsearchParseException("already parsed expand_wildcards");
}
} else if (IGNORE_UNAVAILABLE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
ignoreUnavailable = parser.booleanValue();
} else if (ALLOW_NO_INDICES_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
allowNoIndices = parser.booleanValue();
} else if (IGNORE_THROTTLED_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
ignoreThrottled = parser.booleanValue();
} else {
throw new ElasticsearchParseException("could not read indices options. unexpected index option [" +
currentFieldName + "]");
}
} else {
throw new ElasticsearchParseException("could not read indices options. unexpected object field [" +
currentFieldName + "]");
}
}
if (wildcardStates == null) {
throw new ElasticsearchParseException("indices options xcontent did not contain " + EXPAND_WILDCARDS_FIELD.getPreferredName());
}
if (ignoreUnavailable == null) {
throw new ElasticsearchParseException("indices options xcontent did not contain " +
IGNORE_UNAVAILABLE_FIELD.getPreferredName());
}
if (allowNoIndices == null) {
throw new ElasticsearchParseException("indices options xcontent did not contain " +
ALLOW_NO_INDICES_FIELD.getPreferredName());
}
return IndicesOptions.fromOptions(ignoreUnavailable, allowNoIndices, wildcardStates.contains(WildcardStates.OPEN),
wildcardStates.contains(WildcardStates.CLOSED), wildcardStates.contains(WildcardStates.HIDDEN), true, false, false,
ignoreThrottled);
}
/**
* @return indices options that requires every specified index to exist, expands wildcards only to open indices and
* allows that no indices are resolved from wildcard expressions (not returning an error).

View File

@ -324,13 +324,13 @@ public class IndicesOptionsTests extends ESTestCase {
options.isEmpty() ? Option.NONE : EnumSet.copyOf(options),
wildcardStates.isEmpty() ? WildcardStates.NONE : EnumSet.copyOf(wildcardStates));
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
indicesOptions.toXContent(builder, new MapParams(Collections.emptyMap()));
builder.endObject();
XContentParser parser = XContentType.JSON.xContent().createParser(
NamedXContentRegistry.EMPTY, null, BytesReference.bytes(builder).streamInput());
Map<String, Object> map = parser.mapOrdered();
XContentType type = randomFrom(XContentType.values());
BytesReference xContentBytes = toXContentBytes(indicesOptions, type);
Map<String, Object> map;
try (XContentParser parser = type.xContent().createParser(
NamedXContentRegistry.EMPTY, null, xContentBytes.streamInput())) {
map = parser.mapOrdered();
}
boolean open = wildcardStates.contains(WildcardStates.OPEN);
if (open) {
@ -349,4 +349,83 @@ public class IndicesOptionsTests extends ESTestCase {
assertEquals(map.get("allow_no_indices"), options.contains(Option.ALLOW_NO_INDICES));
assertEquals(map.get("ignore_throttled"), options.contains(Option.IGNORE_THROTTLED));
}
public void testFromXContent() throws IOException {
Collection<WildcardStates> wildcardStates = randomSubsetOf(Arrays.asList(WildcardStates.values()));
Collection<Option> options = randomSubsetOf(Arrays.asList(Option.values()));
IndicesOptions indicesOptions = new IndicesOptions(
options.isEmpty() ? Option.NONE : EnumSet.copyOf(options),
wildcardStates.isEmpty() ? WildcardStates.NONE : EnumSet.copyOf(wildcardStates));
XContentType type = randomFrom(XContentType.values());
BytesReference xContentBytes = toXContentBytes(indicesOptions, type);
IndicesOptions fromXContentOptions;
try (XContentParser parser = type.xContent().createParser(
NamedXContentRegistry.EMPTY, null, xContentBytes.streamInput())) {
fromXContentOptions = IndicesOptions.fromXContent(parser);
}
assertEquals(indicesOptions.expandWildcardsClosed(), fromXContentOptions.expandWildcardsClosed());
assertEquals(indicesOptions.expandWildcardsHidden(), fromXContentOptions.expandWildcardsHidden());
assertEquals(indicesOptions.expandWildcardsOpen(), fromXContentOptions.expandWildcardsOpen());
assertEquals(indicesOptions.ignoreUnavailable(), fromXContentOptions.ignoreUnavailable());
assertEquals(indicesOptions.allowNoIndices(), fromXContentOptions.allowNoIndices());
assertEquals(indicesOptions.ignoreThrottled(), fromXContentOptions.ignoreThrottled());
}
public void testFromXContentWithWildcardSpecialValues() throws IOException {
XContentType type = randomFrom(XContentType.values());
final boolean ignoreUnavailable = randomBoolean();
final boolean allowNoIndices = randomBoolean();
BytesReference xContentBytes;
try (XContentBuilder builder = XContentFactory.contentBuilder(type)) {
builder.startObject();
builder.field("expand_wildcards", "all");
builder.field("ignore_unavailable", ignoreUnavailable);
builder.field("allow_no_indices", allowNoIndices);
builder.endObject();
xContentBytes = BytesReference.bytes(builder);
}
IndicesOptions fromXContentOptions;
try (XContentParser parser = type.xContent().createParser(
NamedXContentRegistry.EMPTY, null, xContentBytes.streamInput())) {
fromXContentOptions = IndicesOptions.fromXContent(parser);
}
assertEquals(ignoreUnavailable, fromXContentOptions.ignoreUnavailable());
assertEquals(allowNoIndices, fromXContentOptions.allowNoIndices());
assertTrue(fromXContentOptions.expandWildcardsClosed());
assertTrue(fromXContentOptions.expandWildcardsHidden());
assertTrue(fromXContentOptions.expandWildcardsOpen());
try (XContentBuilder builder = XContentFactory.contentBuilder(type)) {
builder.startObject();
builder.field("expand_wildcards", "none");
builder.field("ignore_unavailable", ignoreUnavailable);
builder.field("allow_no_indices", allowNoIndices);
builder.endObject();
xContentBytes = BytesReference.bytes(builder);
}
try (XContentParser parser = type.xContent().createParser(
NamedXContentRegistry.EMPTY, null, xContentBytes.streamInput())) {
fromXContentOptions = IndicesOptions.fromXContent(parser);
}
assertEquals(ignoreUnavailable, fromXContentOptions.ignoreUnavailable());
assertEquals(allowNoIndices, fromXContentOptions.allowNoIndices());
assertFalse(fromXContentOptions.expandWildcardsClosed());
assertFalse(fromXContentOptions.expandWildcardsHidden());
assertFalse(fromXContentOptions.expandWildcardsOpen());
}
private BytesReference toXContentBytes(IndicesOptions indicesOptions, XContentType type) throws IOException {
try (XContentBuilder builder = XContentFactory.contentBuilder(type)) {
builder.startObject();
indicesOptions.toXContent(builder, new MapParams(Collections.emptyMap()));
builder.endObject();
return BytesReference.bytes(builder);
}
}
}

View File

@ -158,19 +158,7 @@ public class WatcherSearchTemplateRequest implements ToXContentObject {
}
if (indicesOptions != DEFAULT_INDICES_OPTIONS) {
builder.startObject(INDICES_OPTIONS_FIELD.getPreferredName());
String value;
if (indicesOptions.expandWildcardsClosed() && indicesOptions.expandWildcardsOpen()) {
value = "all";
} else if (indicesOptions.expandWildcardsOpen()) {
value = "open";
} else if (indicesOptions.expandWildcardsClosed()) {
value = "closed";
} else {
value = "none";
}
builder.field(EXPAND_WILDCARDS_FIELD.getPreferredName(), value);
builder.field(IGNORE_UNAVAILABLE_FIELD.getPreferredName(), indicesOptions.ignoreUnavailable());
builder.field(ALLOW_NO_INDICES_FIELD.getPreferredName(), indicesOptions.allowNoIndices());
indicesOptions.toXContent(builder, params);
builder.endObject();
}
if (template != null) {
@ -228,51 +216,7 @@ public class WatcherSearchTemplateRequest implements ToXContentObject {
searchSource = BytesReference.bytes(builder);
}
} else if (INDICES_OPTIONS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
boolean expandOpen = DEFAULT_INDICES_OPTIONS.expandWildcardsOpen();
boolean expandClosed = DEFAULT_INDICES_OPTIONS.expandWildcardsClosed();
boolean allowNoIndices = DEFAULT_INDICES_OPTIONS.allowNoIndices();
boolean ignoreUnavailable = DEFAULT_INDICES_OPTIONS.ignoreUnavailable();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (EXPAND_WILDCARDS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
switch (parser.text()) {
case "all":
expandOpen = true;
expandClosed = true;
break;
case "open":
expandOpen = true;
expandClosed = false;
break;
case "closed":
expandOpen = false;
expandClosed = true;
break;
case "none":
expandOpen = false;
expandClosed = false;
break;
default:
throw new ElasticsearchParseException("could not read search request. unknown value [" +
parser.text() + "] for [" + currentFieldName + "] field ");
}
} else if (IGNORE_UNAVAILABLE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
ignoreUnavailable = parser.booleanValue();
} else if (ALLOW_NO_INDICES_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
allowNoIndices = parser.booleanValue();
} else {
throw new ElasticsearchParseException("could not read search request. unexpected index option [" +
currentFieldName + "]");
}
} else {
throw new ElasticsearchParseException("could not read search request. unexpected object field [" +
currentFieldName + "]");
}
}
indicesOptions = IndicesOptions.fromOptions(ignoreUnavailable, allowNoIndices, expandOpen, expandClosed,
DEFAULT_INDICES_OPTIONS);
indicesOptions = IndicesOptions.fromXContent(parser);
} else if (TEMPLATE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
template = Script.parse(parser, Script.DEFAULT_TEMPLATE_LANG);
} else {
@ -343,9 +287,6 @@ public class WatcherSearchTemplateRequest implements ToXContentObject {
private static final ParseField BODY_FIELD = new ParseField("body");
private static final ParseField SEARCH_TYPE_FIELD = new ParseField("search_type");
private static final ParseField INDICES_OPTIONS_FIELD = new ParseField("indices_options");
private static final ParseField EXPAND_WILDCARDS_FIELD = new ParseField("expand_wildcards");
private static final ParseField IGNORE_UNAVAILABLE_FIELD = new ParseField("ignore_unavailable");
private static final ParseField ALLOW_NO_INDICES_FIELD = new ParseField("allow_no_indices");
private static final ParseField TEMPLATE_FIELD = new ParseField("template");
private static final ParseField REST_TOTAL_HITS_AS_INT_FIELD = new ParseField("rest_total_hits_as_int");