Skip to content

Commit d05a4b9

Browse files
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.
1 parent 1579ac0 commit d05a4b9

File tree

4 files changed

+367
-75
lines changed

4 files changed

+367
-75
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/indices.get_alias/10_basic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ setup:
5959

6060
- do:
6161
indices.get_alias:
62-
name: _all
62+
name: '*'
6363

6464
- match: {test_index.aliases.test_alias: {}}
6565
- match: {test_index.aliases.test_blias: {}}
@@ -220,7 +220,7 @@ setup:
220220
- is_false: test_index_2.aliases.test_blias
221221

222222
---
223-
"Get aliases via /pref*/_alias/{name}":
223+
"Get aliases via /*suf/_alias/{name}":
224224

225225
- do:
226226
indices.get_alias:
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
setup:
3+
4+
- do:
5+
indices.create:
6+
index: test_index
7+
body:
8+
aliases:
9+
test_alias_1: {}
10+
test_alias_2: {}
11+
test_blias_1: {}
12+
test_blias_2: {}
13+
test: {}
14+
15+
---
16+
"Get aliases wildcard and inclusion":
17+
- do:
18+
indices.get_alias:
19+
name: test_alias*,test_blias_1
20+
21+
- match: {test_index.aliases.test_alias_1: {}}
22+
- match: {test_index.aliases.test_alias_2: {}}
23+
- match: {test_index.aliases.test_blias_1: {}}
24+
- is_false: test_index.aliases.test_blias_2
25+
- is_false: test_index.aliases.test
26+
27+
---
28+
"Get aliases wildcard and simple exclusion":
29+
- skip:
30+
version: " - 6.99.99"
31+
reason: Exclusions in the alias expression are not handled
32+
- do:
33+
indices.get_alias:
34+
name: test_blias_2,test_alias*,-test_alias_1
35+
36+
- is_false: test_index.aliases.test_alias_1
37+
- match: {test_index.aliases.test_alias_2: {}}
38+
- is_false: test_index.aliases.test_blias_1
39+
- match: {test_index.aliases.test_blias_2: {}}
40+
- is_false: test_index.aliases.test
41+
42+
---
43+
"Get aliases and wildcard exclusion":
44+
- skip:
45+
version: " - 6.99.99"
46+
reason: Exclusions in the alias expression are not handled
47+
- do:
48+
indices.get_alias:
49+
name: test_alias_1,test_blias_1,-test_alias*
50+
51+
- is_false: test_index.aliases.test_alias_1
52+
- is_false: test_index.aliases.test_alias_2
53+
- match: {test_index.aliases.test_blias_1: {}}
54+
- is_false: test_index.aliases.test_blias_2
55+
- is_false: test_index.aliases.test
56+
57+
- do:
58+
indices.get_alias:
59+
name: test_blias_2,tes*,-test_alias*
60+
61+
- is_false: test_index.aliases.test_alias_1
62+
- is_false: test_index.aliases.test_alias_2
63+
- match: {test_index.aliases.test_blias_1: {}}
64+
- match: {test_index.aliases.test_blias_2: {}}
65+
- match: {test_index.aliases.test: {}}
66+
67+
---
68+
"Non-existent exclusion alias before wildcard returns 404":
69+
- skip:
70+
version: " - 6.99.99"
71+
reason: Exclusions in the alias expression are not handled
72+
- do:
73+
catch: missing
74+
indices.get_alias:
75+
name: -test_alias_1,test_alias*,-test_alias_2
76+
77+
- match: { 'status': 404}
78+
- match: { 'error': 'alias [-test_alias_1] missing' }
79+
- match: {test_index.aliases.test_alias_1: {}}
80+
- is_false: test_index.aliases.test_alias_2
81+
- is_false: test_index.aliases.test_blias_1
82+
- is_false: test_index.aliases.test_blias_2
83+
- is_false: test_index.aliases.test
84+
85+
- do:
86+
catch: missing
87+
indices.get_alias:
88+
name: -test_alias_1,-non-existing,test_alias*,-test
89+
90+
- match: { 'status': 404}
91+
- match: { 'error': 'aliases [-non-existing,-test_alias_1] missing' }
92+
- match: {test_index.aliases.test_alias_1: {}}
93+
- match: {test_index.aliases.test_alias_2: {}}
94+
- is_false: test_index.aliases.test_blias_1
95+
- is_false: test_index.aliases.test_blias_2
96+
- is_false: test_index.aliases.test
97+
98+
---
99+
"Missing exclusions does not fire 404":
100+
- skip:
101+
version: " - 6.99.99"
102+
reason: Exclusions in the alias expression are not handled
103+
- do:
104+
indices.get_alias:
105+
name: test_alias*,-non-existent,test_blias*,-test
106+
107+
- match: {test_index.aliases.test_alias_1: {}}
108+
- match: {test_index.aliases.test_alias_2: {}}
109+
- match: {test_index.aliases.test_blias_1: {}}
110+
- match: {test_index.aliases.test_blias_2: {}}
111+
- is_false: test_index.aliases.test
112+
113+
---
114+
"Exclusion of non wildcarded aliases":
115+
- skip:
116+
version: " - 6.99.99"
117+
reason: Exclusions in the alias expression are not handled
118+
- do:
119+
indices.get_alias:
120+
name: test_alias_1,test_blias_2,-test_alias*,-test_blias_2
121+
122+
- match: { '': {}}
123+
124+
---
125+
"Wildcard exclusions does not trigger 404":
126+
- skip:
127+
version: " - 6.99.99"
128+
reason: Exclusions in the alias expression are not handled
129+
- do:
130+
catch: missing
131+
indices.get_alias:
132+
name: -non-existent,-non-existent*,-another
133+
134+
- match: { 'status': 404}
135+
- match: { 'error': 'alias [-non-existent] missing' }
136+
- is_false: test_index.aliases.test_alias_1
137+
- is_false: test_index.aliases.test_alias_2
138+
- is_false: test_index.aliases.test_blias_1
139+
- is_false: test_index.aliases.test_blias_2
140+
- is_false: test_index.aliases.test

server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
import org.elasticsearch.action.support.IndicesOptions;
2626
import org.elasticsearch.client.node.NodeClient;
2727
import org.elasticsearch.cluster.metadata.AliasMetaData;
28+
import org.elasticsearch.cluster.metadata.MetaData;
2829
import org.elasticsearch.common.Strings;
2930
import org.elasticsearch.common.collect.ImmutableOpenMap;
3031
import org.elasticsearch.common.regex.Regex;
3132
import org.elasticsearch.common.settings.Settings;
32-
import org.elasticsearch.common.util.set.Sets;
3333
import org.elasticsearch.common.xcontent.ToXContent;
3434
import org.elasticsearch.common.xcontent.XContentBuilder;
3535
import org.elasticsearch.rest.BaseRestHandler;
@@ -41,14 +41,12 @@
4141
import org.elasticsearch.rest.action.RestBuilderListener;
4242

4343
import java.io.IOException;
44-
import java.util.ArrayList;
45-
import java.util.Arrays;
4644
import java.util.HashSet;
4745
import java.util.List;
4846
import java.util.Locale;
4947
import java.util.Set;
5048
import java.util.SortedSet;
51-
import java.util.stream.Collectors;
49+
import java.util.TreeSet;
5250

5351
import static org.elasticsearch.rest.RestRequest.Method.GET;
5452
import static org.elasticsearch.rest.RestRequest.Method.HEAD;
@@ -75,6 +73,94 @@ public String getName() {
7573
return "get_aliases_action";
7674
}
7775

76+
static RestResponse buildRestResponse(boolean aliasesExplicitlyRequested, String[] requestedAliases,
77+
ImmutableOpenMap<String, List<AliasMetaData>> responseAliasMap, XContentBuilder builder) throws Exception {
78+
final Set<String> indicesToDisplay = new HashSet<>();
79+
final Set<String> returnedAliasNames = new HashSet<>();
80+
for (final ObjectObjectCursor<String, List<AliasMetaData>> cursor : responseAliasMap) {
81+
for (final AliasMetaData aliasMetaData : cursor.value) {
82+
if (aliasesExplicitlyRequested) {
83+
// only display indices that have aliases
84+
indicesToDisplay.add(cursor.key);
85+
}
86+
returnedAliasNames.add(aliasMetaData.alias());
87+
}
88+
}
89+
// compute explicitly requested aliases that have are not returned in the result
90+
final SortedSet<String> missingAliases = new TreeSet<>();
91+
// first wildcard index, leading "-" as an alias name after this index means
92+
// that it is an exclusion
93+
int firstWildcardIndex = requestedAliases.length;
94+
for (int i = 0; i < requestedAliases.length; i++) {
95+
if (Regex.isSimpleMatchPattern(requestedAliases[i])) {
96+
firstWildcardIndex = i;
97+
break;
98+
}
99+
}
100+
for (int i = 0; i < requestedAliases.length; i++) {
101+
if (MetaData.ALL.equals(requestedAliases[i]) || Regex.isSimpleMatchPattern(requestedAliases[i])
102+
|| (i > firstWildcardIndex && requestedAliases[i].charAt(0) == '-')) {
103+
// only explicitly requested aliases will be called out as missing (404)
104+
continue;
105+
}
106+
// check if aliases[i] is subsequently excluded
107+
int j = Math.max(i + 1, firstWildcardIndex);
108+
for (; j < requestedAliases.length; j++) {
109+
if (requestedAliases[j].charAt(0) == '-') {
110+
// this is an exclude pattern
111+
if (Regex.simpleMatch(requestedAliases[j].substring(1), requestedAliases[i])
112+
|| MetaData.ALL.equals(requestedAliases[j].substring(1))) {
113+
// aliases[i] is excluded by aliases[j]
114+
break;
115+
}
116+
}
117+
}
118+
if (j == requestedAliases.length) {
119+
// explicitly requested aliases[i] is not excluded by any subsequent "-" wildcard in expression
120+
if (false == returnedAliasNames.contains(requestedAliases[i])) {
121+
// aliases[i] is not in the result set
122+
missingAliases.add(requestedAliases[i]);
123+
}
124+
}
125+
}
126+
127+
final RestStatus status;
128+
builder.startObject();
129+
{
130+
if (missingAliases.isEmpty()) {
131+
status = RestStatus.OK;
132+
} else {
133+
status = RestStatus.NOT_FOUND;
134+
final String message;
135+
if (missingAliases.size() == 1) {
136+
message = String.format(Locale.ROOT, "alias [%s] missing", Strings.collectionToCommaDelimitedString(missingAliases));
137+
} else {
138+
message = String.format(Locale.ROOT, "aliases [%s] missing", Strings.collectionToCommaDelimitedString(missingAliases));
139+
}
140+
builder.field("error", message);
141+
builder.field("status", status.getStatus());
142+
}
143+
144+
for (final ObjectObjectCursor<String, List<AliasMetaData>> entry : responseAliasMap) {
145+
if (aliasesExplicitlyRequested == false || (aliasesExplicitlyRequested && indicesToDisplay.contains(entry.key))) {
146+
builder.startObject(entry.key);
147+
{
148+
builder.startObject("aliases");
149+
{
150+
for (final AliasMetaData alias : entry.value) {
151+
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
152+
}
153+
}
154+
builder.endObject();
155+
}
156+
builder.endObject();
157+
}
158+
}
159+
}
160+
builder.endObject();
161+
return new BytesRestResponse(status, builder);
162+
}
163+
78164
@Override
79165
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
80166
// 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
94180
return channel -> client.admin().indices().getAliases(getAliasesRequest, new RestBuilderListener<GetAliasesResponse>(channel) {
95181
@Override
96182
public RestResponse buildResponse(GetAliasesResponse response, XContentBuilder builder) throws Exception {
97-
final ImmutableOpenMap<String, List<AliasMetaData>> aliasMap = response.getAliases();
98-
99-
final Set<String> aliasNames = new HashSet<>();
100-
final Set<String> indicesToDisplay = new HashSet<>();
101-
for (final ObjectObjectCursor<String, List<AliasMetaData>> cursor : aliasMap) {
102-
for (final AliasMetaData aliasMetaData : cursor.value) {
103-
aliasNames.add(aliasMetaData.alias());
104-
if (namesProvided) {
105-
indicesToDisplay.add(cursor.key);
106-
}
107-
}
108-
}
109-
110-
// first remove requested aliases that are exact matches
111-
final SortedSet<String> difference = Sets.sortedDifference(Arrays.stream(aliases).collect(Collectors.toSet()), aliasNames);
112-
113-
// now remove requested aliases that contain wildcards that are simple matches
114-
final List<String> matches = new ArrayList<>();
115-
outer:
116-
for (final String pattern : difference) {
117-
if (pattern.contains("*")) {
118-
for (final String aliasName : aliasNames) {
119-
if (Regex.simpleMatch(pattern, aliasName)) {
120-
matches.add(pattern);
121-
continue outer;
122-
}
123-
}
124-
}
125-
}
126-
difference.removeAll(matches);
127-
128-
final RestStatus status;
129-
builder.startObject();
130-
{
131-
if (difference.isEmpty()) {
132-
status = RestStatus.OK;
133-
} else {
134-
status = RestStatus.NOT_FOUND;
135-
final String message;
136-
if (difference.size() == 1) {
137-
message = String.format(Locale.ROOT, "alias [%s] missing",
138-
Strings.collectionToCommaDelimitedString(difference));
139-
} else {
140-
message = String.format(Locale.ROOT, "aliases [%s] missing",
141-
Strings.collectionToCommaDelimitedString(difference));
142-
}
143-
builder.field("error", message);
144-
builder.field("status", status.getStatus());
145-
}
146-
147-
for (final ObjectObjectCursor<String, List<AliasMetaData>> entry : response.getAliases()) {
148-
if (namesProvided == false || (namesProvided && indicesToDisplay.contains(entry.key))) {
149-
builder.startObject(entry.key);
150-
{
151-
builder.startObject("aliases");
152-
{
153-
for (final AliasMetaData alias : entry.value) {
154-
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
155-
}
156-
}
157-
builder.endObject();
158-
}
159-
builder.endObject();
160-
}
161-
}
162-
}
163-
builder.endObject();
164-
return new BytesRestResponse(status, builder);
183+
return buildRestResponse(namesProvided, aliases, response.getAliases(), builder);
165184
}
166-
167185
});
168186
}
169187

0 commit comments

Comments
 (0)