Get Aliases with wildcard exclusion expression (#34230)

This commit adds the code in the HTTP layer that will parse exclusion wildcard
expressions.
The existing code issues 404s for wildcards as well as explicit indices.
But, in general, in an expression with exclude wildcards (-...*) following other
include wildcards, there is no way to tell if the include wildcard produced no
results or they were subsequently excluded.
Therefore, the proposed change is breaking the behavior of 404s for
wildcards. Specifically, no 404s will be returned for wildcards, even
if they are not followed by exclude wildcards or the exclude wildcards
could not possibly exclude what has previously been included.
Only explicitly requested aliases will be called out as missing.
This commit is contained in:
Albert Zaharovits 2019-01-29 18:56:20 +02:00 committed by GitHub
parent 1579ac032b
commit d05a4b9d14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 367 additions and 75 deletions

View File

@ -59,7 +59,7 @@ setup:
- do:
indices.get_alias:
name: _all
name: '*'
- match: {test_index.aliases.test_alias: {}}
- match: {test_index.aliases.test_blias: {}}
@ -220,7 +220,7 @@ setup:
- is_false: test_index_2.aliases.test_blias
---
"Get aliases via /pref*/_alias/{name}":
"Get aliases via /*suf/_alias/{name}":
- do:
indices.get_alias:

View File

@ -0,0 +1,140 @@
---
setup:
- do:
indices.create:
index: test_index
body:
aliases:
test_alias_1: {}
test_alias_2: {}
test_blias_1: {}
test_blias_2: {}
test: {}
---
"Get aliases wildcard and inclusion":
- do:
indices.get_alias:
name: test_alias*,test_blias_1
- match: {test_index.aliases.test_alias_1: {}}
- match: {test_index.aliases.test_alias_2: {}}
- match: {test_index.aliases.test_blias_1: {}}
- is_false: test_index.aliases.test_blias_2
- is_false: test_index.aliases.test
---
"Get aliases wildcard and simple exclusion":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
indices.get_alias:
name: test_blias_2,test_alias*,-test_alias_1
- is_false: test_index.aliases.test_alias_1
- match: {test_index.aliases.test_alias_2: {}}
- is_false: test_index.aliases.test_blias_1
- match: {test_index.aliases.test_blias_2: {}}
- is_false: test_index.aliases.test
---
"Get aliases and wildcard exclusion":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
indices.get_alias:
name: test_alias_1,test_blias_1,-test_alias*
- is_false: test_index.aliases.test_alias_1
- is_false: test_index.aliases.test_alias_2
- match: {test_index.aliases.test_blias_1: {}}
- is_false: test_index.aliases.test_blias_2
- is_false: test_index.aliases.test
- do:
indices.get_alias:
name: test_blias_2,tes*,-test_alias*
- is_false: test_index.aliases.test_alias_1
- is_false: test_index.aliases.test_alias_2
- match: {test_index.aliases.test_blias_1: {}}
- match: {test_index.aliases.test_blias_2: {}}
- match: {test_index.aliases.test: {}}
---
"Non-existent exclusion alias before wildcard returns 404":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
catch: missing
indices.get_alias:
name: -test_alias_1,test_alias*,-test_alias_2
- match: { 'status': 404}
- match: { 'error': 'alias [-test_alias_1] missing' }
- match: {test_index.aliases.test_alias_1: {}}
- is_false: test_index.aliases.test_alias_2
- is_false: test_index.aliases.test_blias_1
- is_false: test_index.aliases.test_blias_2
- is_false: test_index.aliases.test
- do:
catch: missing
indices.get_alias:
name: -test_alias_1,-non-existing,test_alias*,-test
- match: { 'status': 404}
- match: { 'error': 'aliases [-non-existing,-test_alias_1] missing' }
- match: {test_index.aliases.test_alias_1: {}}
- match: {test_index.aliases.test_alias_2: {}}
- is_false: test_index.aliases.test_blias_1
- is_false: test_index.aliases.test_blias_2
- is_false: test_index.aliases.test
---
"Missing exclusions does not fire 404":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
indices.get_alias:
name: test_alias*,-non-existent,test_blias*,-test
- match: {test_index.aliases.test_alias_1: {}}
- match: {test_index.aliases.test_alias_2: {}}
- match: {test_index.aliases.test_blias_1: {}}
- match: {test_index.aliases.test_blias_2: {}}
- is_false: test_index.aliases.test
---
"Exclusion of non wildcarded aliases":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
indices.get_alias:
name: test_alias_1,test_blias_2,-test_alias*,-test_blias_2
- match: { '': {}}
---
"Wildcard exclusions does not trigger 404":
- skip:
version: " - 6.99.99"
reason: Exclusions in the alias expression are not handled
- do:
catch: missing
indices.get_alias:
name: -non-existent,-non-existent*,-another
- match: { 'status': 404}
- match: { 'error': 'alias [-non-existent] missing' }
- is_false: test_index.aliases.test_alias_1
- is_false: test_index.aliases.test_alias_2
- is_false: test_index.aliases.test_blias_1
- is_false: test_index.aliases.test_blias_2
- is_false: test_index.aliases.test

View File

@ -25,11 +25,11 @@ import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
@ -41,14 +41,12 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.SortedSet;
import java.util.stream.Collectors;
import java.util.TreeSet;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
@ -75,6 +73,94 @@ public class RestGetAliasesAction extends BaseRestHandler {
return "get_aliases_action";
}
static RestResponse buildRestResponse(boolean aliasesExplicitlyRequested, String[] requestedAliases,
ImmutableOpenMap<String, List<AliasMetaData>> responseAliasMap, XContentBuilder builder) throws Exception {
final Set<String> indicesToDisplay = new HashSet<>();
final Set<String> returnedAliasNames = new HashSet<>();
for (final ObjectObjectCursor<String, List<AliasMetaData>> cursor : responseAliasMap) {
for (final AliasMetaData aliasMetaData : cursor.value) {
if (aliasesExplicitlyRequested) {
// only display indices that have aliases
indicesToDisplay.add(cursor.key);
}
returnedAliasNames.add(aliasMetaData.alias());
}
}
// compute explicitly requested aliases that have are not returned in the result
final SortedSet<String> missingAliases = new TreeSet<>();
// first wildcard index, leading "-" as an alias name after this index means
// that it is an exclusion
int firstWildcardIndex = requestedAliases.length;
for (int i = 0; i < requestedAliases.length; i++) {
if (Regex.isSimpleMatchPattern(requestedAliases[i])) {
firstWildcardIndex = i;
break;
}
}
for (int i = 0; i < requestedAliases.length; i++) {
if (MetaData.ALL.equals(requestedAliases[i]) || Regex.isSimpleMatchPattern(requestedAliases[i])
|| (i > firstWildcardIndex && requestedAliases[i].charAt(0) == '-')) {
// only explicitly requested aliases will be called out as missing (404)
continue;
}
// check if aliases[i] is subsequently excluded
int j = Math.max(i + 1, firstWildcardIndex);
for (; j < requestedAliases.length; j++) {
if (requestedAliases[j].charAt(0) == '-') {
// this is an exclude pattern
if (Regex.simpleMatch(requestedAliases[j].substring(1), requestedAliases[i])
|| MetaData.ALL.equals(requestedAliases[j].substring(1))) {
// aliases[i] is excluded by aliases[j]
break;
}
}
}
if (j == requestedAliases.length) {
// explicitly requested aliases[i] is not excluded by any subsequent "-" wildcard in expression
if (false == returnedAliasNames.contains(requestedAliases[i])) {
// aliases[i] is not in the result set
missingAliases.add(requestedAliases[i]);
}
}
}
final RestStatus status;
builder.startObject();
{
if (missingAliases.isEmpty()) {
status = RestStatus.OK;
} else {
status = RestStatus.NOT_FOUND;
final String message;
if (missingAliases.size() == 1) {
message = String.format(Locale.ROOT, "alias [%s] missing", Strings.collectionToCommaDelimitedString(missingAliases));
} else {
message = String.format(Locale.ROOT, "aliases [%s] missing", Strings.collectionToCommaDelimitedString(missingAliases));
}
builder.field("error", message);
builder.field("status", status.getStatus());
}
for (final ObjectObjectCursor<String, List<AliasMetaData>> entry : responseAliasMap) {
if (aliasesExplicitlyRequested == false || (aliasesExplicitlyRequested && indicesToDisplay.contains(entry.key))) {
builder.startObject(entry.key);
{
builder.startObject("aliases");
{
for (final AliasMetaData alias : entry.value) {
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
}
}
builder.endObject();
}
builder.endObject();
}
}
}
builder.endObject();
return new BytesRestResponse(status, builder);
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
// The TransportGetAliasesAction was improved do the same post processing as is happening here.
@ -94,76 +180,8 @@ public class RestGetAliasesAction extends BaseRestHandler {
return channel -> client.admin().indices().getAliases(getAliasesRequest, new RestBuilderListener<GetAliasesResponse>(channel) {
@Override
public RestResponse buildResponse(GetAliasesResponse response, XContentBuilder builder) throws Exception {
final ImmutableOpenMap<String, List<AliasMetaData>> aliasMap = response.getAliases();
final Set<String> aliasNames = new HashSet<>();
final Set<String> indicesToDisplay = new HashSet<>();
for (final ObjectObjectCursor<String, List<AliasMetaData>> cursor : aliasMap) {
for (final AliasMetaData aliasMetaData : cursor.value) {
aliasNames.add(aliasMetaData.alias());
if (namesProvided) {
indicesToDisplay.add(cursor.key);
return buildRestResponse(namesProvided, aliases, response.getAliases(), builder);
}
}
}
// first remove requested aliases that are exact matches
final SortedSet<String> difference = Sets.sortedDifference(Arrays.stream(aliases).collect(Collectors.toSet()), aliasNames);
// now remove requested aliases that contain wildcards that are simple matches
final List<String> matches = new ArrayList<>();
outer:
for (final String pattern : difference) {
if (pattern.contains("*")) {
for (final String aliasName : aliasNames) {
if (Regex.simpleMatch(pattern, aliasName)) {
matches.add(pattern);
continue outer;
}
}
}
}
difference.removeAll(matches);
final RestStatus status;
builder.startObject();
{
if (difference.isEmpty()) {
status = RestStatus.OK;
} else {
status = RestStatus.NOT_FOUND;
final String message;
if (difference.size() == 1) {
message = String.format(Locale.ROOT, "alias [%s] missing",
Strings.collectionToCommaDelimitedString(difference));
} else {
message = String.format(Locale.ROOT, "aliases [%s] missing",
Strings.collectionToCommaDelimitedString(difference));
}
builder.field("error", message);
builder.field("status", status.getStatus());
}
for (final ObjectObjectCursor<String, List<AliasMetaData>> entry : response.getAliases()) {
if (namesProvided == false || (namesProvided && indicesToDisplay.contains(entry.key))) {
builder.startObject(entry.key);
{
builder.startObject("aliases");
{
for (final AliasMetaData alias : entry.value) {
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
}
}
builder.endObject();
}
builder.endObject();
}
}
}
builder.endObject();
return new BytesRestResponse(status, builder);
}
});
}

View File

@ -0,0 +1,134 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.rest.action.admin.indices;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.test.ESTestCase;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.rest.RestStatus.OK;
import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
import static org.hamcrest.Matchers.equalTo;
public class RestGetAliasesActionTests extends ESTestCase {
// # Assumes the following setup
// curl -X PUT "localhost:9200/index" -H "Content-Type: application/json" -d'
// {
// "aliases": {
// "foo": {},
// "foobar": {}
// }
// }'
public void testBareRequest() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final AliasMetaData foobarAliasMetaData = AliasMetaData.builder("foobar").build();
final AliasMetaData fooAliasMetaData = AliasMetaData.builder("foo").build();
openMapBuilder.put("index", Arrays.asList(fooAliasMetaData, foobarAliasMetaData));
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(false, new String[0], openMapBuilder.build(),
xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{\"index\":{\"aliases\":{\"foo\":{},\"foobar\":{}}}}"));
}
public void testSimpleAliasWildcardMatchingNothing() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, new String[] { "baz*" }, openMapBuilder.build(),
xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{}"));
}
public void testMultipleAliasWildcardsSomeMatching() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final AliasMetaData aliasMetaData = AliasMetaData.builder("foobar").build();
openMapBuilder.put("index", Arrays.asList(aliasMetaData));
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, new String[] { "baz*", "foobar*" },
openMapBuilder.build(), xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{\"index\":{\"aliases\":{\"foobar\":{}}}}"));
}
public void testAliasWildcardsIncludeAndExcludeAll() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, new String[] { "foob*", "-foo*" },
openMapBuilder.build(), xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{}"));
}
public void testAliasWildcardsIncludeAndExcludeSome() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final AliasMetaData aliasMetaData = AliasMetaData.builder("foo").build();
openMapBuilder.put("index", Arrays.asList(aliasMetaData));
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, new String[] { "foo*", "-foob*" },
openMapBuilder.build(), xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{\"index\":{\"aliases\":{\"foo\":{}}}}"));
}
public void testAliasWildcardsIncludeAndExcludeSomeAndExplicitMissing() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final AliasMetaData aliasMetaData = AliasMetaData.builder("foo").build();
openMapBuilder.put("index", Arrays.asList(aliasMetaData));
final String[] aliasPattern;
if (randomBoolean()) {
aliasPattern = new String[] { "missing", "foo*", "-foob*" };
} else {
aliasPattern = new String[] { "foo*", "-foob*", "missing" };
}
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, aliasPattern, openMapBuilder.build(),
xContentBuilder);
assertThat(restResponse.status(), equalTo(NOT_FOUND));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(),
equalTo("{\"error\":\"alias [missing] missing\",\"status\":404,\"index\":{\"aliases\":{\"foo\":{}}}}"));
}
public void testAliasWildcardsExcludeExplicitMissing() throws Exception {
final XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
final ImmutableOpenMap.Builder<String, List<AliasMetaData>> openMapBuilder = ImmutableOpenMap.builder();
final RestResponse restResponse = RestGetAliasesAction.buildRestResponse(true, new String[] { "foo", "foofoo", "-foo*" },
openMapBuilder.build(), xContentBuilder);
assertThat(restResponse.status(), equalTo(OK));
assertThat(restResponse.contentType(), equalTo("application/json; charset=UTF-8"));
assertThat(restResponse.content().utf8ToString(), equalTo("{}"));
}
}