Include all aliases including non-filtering in `_search_shards` response (#24489)

`_search_shards`API today only returns aliases names if there is an alias
filter associated with one of them. Now it can be useful to see which aliases
have been expanded for an index given the index expressions. This change also includes non-filtering aliases even without a filtering alias being present.
This commit is contained in:
Simon Willnauer 2017-05-05 09:34:12 +02:00 committed by GitHub
parent c8712e9531
commit 6b67e0bf2f
6 changed files with 92 additions and 36 deletions

View File

@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.internal.AliasFilter; import org.elasticsearch.search.internal.AliasFilter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -117,10 +118,14 @@ public class ClusterSearchShardsResponse extends ActionResponse implements ToXCo
String index = entry.getKey(); String index = entry.getKey();
builder.startObject(index); builder.startObject(index);
AliasFilter aliasFilter = entry.getValue(); AliasFilter aliasFilter = entry.getValue();
if (aliasFilter.getAliases().length > 0) { String[] aliases = aliasFilter.getAliases();
builder.array("aliases", aliasFilter.getAliases()); if (aliases.length > 0) {
builder.field("filter"); Arrays.sort(aliases); // we want consistent ordering here and these values might be generated from a set / map
aliasFilter.getQueryBuilder().toXContent(builder, params); builder.array("aliases", aliases);
if (aliasFilter.getQueryBuilder() != null) { // might be null if we include non-filtering aliases
builder.field("filter");
aliasFilter.getQueryBuilder().toXContent(builder, params);
}
} }
builder.endObject(); builder.endObject();
} }

View File

@ -83,8 +83,10 @@ public class TransportClusterSearchShardsAction extends
Map<String, Set<String>> routingMap = indexNameExpressionResolver.resolveSearchRouting(state, request.routing(), request.indices()); Map<String, Set<String>> routingMap = indexNameExpressionResolver.resolveSearchRouting(state, request.routing(), request.indices());
Map<String, AliasFilter> indicesAndFilters = new HashMap<>(); Map<String, AliasFilter> indicesAndFilters = new HashMap<>();
for (String index : concreteIndices) { for (String index : concreteIndices) {
AliasFilter aliasFilter = indicesService.buildAliasFilter(clusterState, index, request.indices()); final AliasFilter aliasFilter = indicesService.buildAliasFilter(clusterState, index, request.indices());
indicesAndFilters.put(index, aliasFilter); final String[] aliases = indexNameExpressionResolver.indexAliases(clusterState, index, aliasMetadata -> true, true,
request.indices());
indicesAndFilters.put(index, new AliasFilter(aliasFilter.getQueryBuilder(), aliases));
} }
Set<String> nodeIds = new HashSet<>(); Set<String> nodeIds = new HashSet<>();

View File

@ -48,6 +48,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class IndexNameExpressionResolver extends AbstractComponent { public class IndexNameExpressionResolver extends AbstractComponent {
@ -268,8 +269,19 @@ public class IndexNameExpressionResolver extends AbstractComponent {
* the index itself - null is returned. Returns <tt>null</tt> if no filtering is required. * the index itself - null is returned. Returns <tt>null</tt> if no filtering is required.
*/ */
public String[] filteringAliases(ClusterState state, String index, String... expressions) { public String[] filteringAliases(ClusterState state, String index, String... expressions) {
return indexAliases(state, index, AliasMetaData::filteringRequired, false, expressions);
}
/**
* Iterates through the list of indices and selects the effective list of required aliases for the
* given index.
* <p>Only aliases where the given predicate tests successfully are returned. If the indices list contains a non-required reference to
* the index itself - null is returned. Returns <tt>null</tt> if no filtering is required.
*/
public String[] indexAliases(ClusterState state, String index, Predicate<AliasMetaData> requiredAlias, boolean skipIdentity,
String... expressions) {
// expand the aliases wildcard // expand the aliases wildcard
List<String> resolvedExpressions = expressions != null ? Arrays.asList(expressions) : Collections.<String>emptyList(); List<String> resolvedExpressions = expressions != null ? Arrays.asList(expressions) : Collections.emptyList();
Context context = new Context(state, IndicesOptions.lenientExpandOpen(), true); Context context = new Context(state, IndicesOptions.lenientExpandOpen(), true);
for (ExpressionResolver expressionResolver : expressionResolvers) { for (ExpressionResolver expressionResolver : expressionResolvers) {
resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions); resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions);
@ -278,54 +290,50 @@ public class IndexNameExpressionResolver extends AbstractComponent {
if (isAllIndices(resolvedExpressions)) { if (isAllIndices(resolvedExpressions)) {
return null; return null;
} }
final IndexMetaData indexMetaData = state.metaData().getIndices().get(index);
if (indexMetaData == null) {
// Shouldn't happen
throw new IndexNotFoundException(index);
}
// optimize for the most common single index/alias scenario // optimize for the most common single index/alias scenario
if (resolvedExpressions.size() == 1) { if (resolvedExpressions.size() == 1) {
String alias = resolvedExpressions.get(0); String alias = resolvedExpressions.get(0);
IndexMetaData indexMetaData = state.metaData().getIndices().get(index);
if (indexMetaData == null) {
// Shouldn't happen
throw new IndexNotFoundException(index);
}
AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias); AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias);
boolean filteringRequired = aliasMetaData != null && aliasMetaData.filteringRequired(); if (aliasMetaData == null || requiredAlias.test(aliasMetaData) == false) {
if (!filteringRequired) {
return null; return null;
} }
return new String[]{alias}; return new String[]{alias};
} }
List<String> filteringAliases = null; List<String> aliases = null;
for (String alias : resolvedExpressions) { for (String alias : resolvedExpressions) {
if (alias.equals(index)) { if (alias.equals(index)) {
return null; if (skipIdentity) {
continue;
} else {
return null;
}
} }
IndexMetaData indexMetaData = state.metaData().getIndices().get(index);
if (indexMetaData == null) {
// Shouldn't happen
throw new IndexNotFoundException(index);
}
AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias); AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias);
// Check that this is an alias for the current index // Check that this is an alias for the current index
// Otherwise - skip it // Otherwise - skip it
if (aliasMetaData != null) { if (aliasMetaData != null) {
boolean filteringRequired = aliasMetaData.filteringRequired(); if (requiredAlias.test(aliasMetaData)) {
if (filteringRequired) { // If required - add it to the list of aliases
// If filtering required - add it to the list of filters if (aliases == null) {
if (filteringAliases == null) { aliases = new ArrayList<>();
filteringAliases = new ArrayList<>();
} }
filteringAliases.add(alias); aliases.add(alias);
} else { } else {
// If not, we have a non filtering alias for this index - no filtering needed // If not, we have a non required alias for this index - no futher checking needed
return null; return null;
} }
} }
} }
if (filteringAliases == null) { if (aliases == null) {
return null; return null;
} }
return filteringAliases.toArray(new String[filteringAliases.size()]); return aliases.toArray(new String[aliases.size()]);
} }
/** /**

View File

@ -33,6 +33,7 @@ import org.elasticsearch.test.ESTestCase;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.function.Predicate;
import static org.elasticsearch.common.util.set.Sets.newHashSet; import static org.elasticsearch.common.util.set.Sets.newHashSet;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContaining;
@ -956,4 +957,17 @@ public class IndexNameExpressionResolverTests extends ESTestCase {
strings = indexNameExpressionResolver.filteringAliases(state, "test-0", "test-*,alias-*"); strings = indexNameExpressionResolver.filteringAliases(state, "test-0", "test-*,alias-*");
assertNull(strings); assertNull(strings);
} }
public void testIndexAliases() {
MetaData.Builder mdBuilder = MetaData.builder()
.put(indexBuilder("test-0").state(State.OPEN)
.putAlias(AliasMetaData.builder("test-alias-0").filter("{ \"term\": \"foo\"}"))
.putAlias(AliasMetaData.builder("test-alias-1").filter("{ \"term\": \"foo\"}"))
.putAlias(AliasMetaData.builder("test-alias-non-filtering"))
);
ClusterState state = ClusterState.builder(new ClusterName("_name")).metaData(mdBuilder).build();
String[] strings = indexNameExpressionResolver.indexAliases(state, "test-0", x -> true, true, "test-*");
Arrays.sort(strings);
assertArrayEquals(new String[] {"test-alias-0", "test-alias-1", "test-alias-non-filtering"}, strings);
}
} }

View File

@ -4,7 +4,7 @@
The search shards api returns the indices and shards that a search request would The search shards api returns the indices and shards that a search request would
be executed against. This can give useful feedback for working out issues or be executed against. This can give useful feedback for working out issues or
planning optimizations with routing and shard preferences. When filtered aliases planning optimizations with routing and shard preferences. When filtered aliases
are used, the filter is returned as part of the `indices` section. are used, the filter is returned as part of the `indices` section [5.1.0] Added in 5.1.0.
The `index` may be a single value, or comma-separated. The `index` may be a single value, or comma-separated.
@ -165,4 +165,4 @@ routing values have been specified.
`local`:: `local`::
A boolean value whether to read the cluster state locally in order to A boolean value whether to read the cluster state locally in order to
determine where shards are allocated instead of using the Master node's determine where shards are allocated instead of using the Master node's
cluster state. cluster state.

View File

@ -14,7 +14,7 @@
--- ---
"Search shards aliases with and without filters": "Search shards aliases with and without filters":
- skip: - skip:
version: " - 5.0.99" version: " - 5.99.99" # temporarily disabled
reason: indices section was added in 5.1.0 reason: indices section was added in 5.1.0
- do: - do:
@ -49,7 +49,7 @@
- match: { shards.0.0.index: test_index } - match: { shards.0.0.index: test_index }
- is_true: indices.test_index - is_true: indices.test_index
- is_false: indices.test_index.filter - is_false: indices.test_index.filter
- is_false: indices.test_index.aliases - match: { indices.test_index.aliases: [test_alias_no_filter]}
- do: - do:
search_shards: search_shards:
@ -78,3 +78,30 @@
- match: { indices.test_index.filter.bool.adjust_pure_negative: true} - match: { indices.test_index.filter.bool.adjust_pure_negative: true}
- lte: { indices.test_index.filter.bool.boost: 1.0 } - lte: { indices.test_index.filter.bool.boost: 1.0 }
- gte: { indices.test_index.filter.bool.boost: 1.0 } - gte: { indices.test_index.filter.bool.boost: 1.0 }
- do:
search_shards:
index: "test*"
- length: { shards: 1 }
- match: { shards.0.0.index: test_index }
- match: { indices.test_index.aliases: [test_alias_filter_1, test_alias_filter_2, test_alias_no_filter]}
- is_false: indices.test_index.filter
- do:
search_shards:
index: ["test_alias_filter_1","test_alias_no_filter"]
- length: { shards: 1 }
- match: { shards.0.0.index: test_index }
- match: { indices.test_index.aliases: [test_alias_filter_1, test_alias_no_filter]}
- is_false: indices.test_index.filter
- do:
search_shards:
index: ["test_alias_no_filter"]
- length: { shards: 1 }
- match: { shards.0.0.index: test_index }
- match: { indices.test_index.aliases: [test_alias_no_filter]}
- is_false: indices.test_index.filter