diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/10_basic.yml index 6338598de05d0..f3dc3ac86bc9f 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/10_basic.yml @@ -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: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/30_wildcards.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/30_wildcards.yml new file mode 100644 index 0000000000000..08b3009be0e88 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/30_wildcards.yml @@ -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 diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java index 0d6d46e95b602..8cdf9e62b1096 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java @@ -25,11 +25,11 @@ 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.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 String getName() { return "get_aliases_action"; } + static RestResponse buildRestResponse(boolean aliasesExplicitlyRequested, String[] requestedAliases, + ImmutableOpenMap> responseAliasMap, XContentBuilder builder) throws Exception { + final Set indicesToDisplay = new HashSet<>(); + final Set returnedAliasNames = new HashSet<>(); + for (final ObjectObjectCursor> 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 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> 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 RestChannelConsumer prepareRequest(final RestRequest request, final NodeC return channel -> client.admin().indices().getAliases(getAliasesRequest, new RestBuilderListener(channel) { @Override public RestResponse buildResponse(GetAliasesResponse response, XContentBuilder builder) throws Exception { - final ImmutableOpenMap> aliasMap = response.getAliases(); - - final Set aliasNames = new HashSet<>(); - final Set indicesToDisplay = new HashSet<>(); - for (final ObjectObjectCursor> cursor : aliasMap) { - for (final AliasMetaData aliasMetaData : cursor.value) { - aliasNames.add(aliasMetaData.alias()); - if (namesProvided) { - indicesToDisplay.add(cursor.key); - } - } - } - - // first remove requested aliases that are exact matches - final SortedSet difference = Sets.sortedDifference(Arrays.stream(aliases).collect(Collectors.toSet()), aliasNames); - - // now remove requested aliases that contain wildcards that are simple matches - final List 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> 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); + return buildRestResponse(namesProvided, aliases, response.getAliases(), builder); } - }); } diff --git a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesActionTests.java new file mode 100644 index 0000000000000..ced52096687a2 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesActionTests.java @@ -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> 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> 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> 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> 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> 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> 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> 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("{}")); + } +}