浏览代码

Add mode filter to _resolve/index (#133616)

* Add mode filter to _resolve/index
Stanislav Malyshev 1 月之前
父节点
当前提交
6e82ade9dd

+ 5 - 0
docs/changelog/133616.yaml

@@ -0,0 +1,5 @@
+pr: 133616
+summary: Add mode filter to _resolve/index
+area: Indices APIs
+type: enhancement
+issues: []

+ 4 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/indices.resolve_index.json

@@ -47,6 +47,10 @@
         "type":"boolean",
         "description":"Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)",
         "default":true
+      },
+      "mode": {
+        "type": "string",
+        "description": "Filter indices by index mode"
       }
     }
   }

+ 108 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/30_resolve_index_mode_filter.yml

@@ -0,0 +1,108 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: my-std-index
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          aliases:
+            my-std-alias: { }
+
+  # Create a time-series index
+  - do:
+      indices.create:
+        index: my-ts-index
+        body:
+          settings:
+            index.mode: time_series
+            number_of_shards: 1
+            number_of_replicas: 0
+            index.routing_path: [ "host" ]
+          mappings:
+            properties:
+              "@timestamp":
+                type: date
+              host:
+                type: keyword
+                time_series_dimension: true
+              metric:
+                type: keyword
+              value:
+                type: double
+          aliases:
+            my-ts-alias: { }
+  # Create a lookup index
+  - do:
+      indices.create:
+        index: my-lookup-index
+        body:
+          settings:
+            index.mode: lookup
+          aliases:
+            my-lookup-alias: { }
+
+---
+"resolve index filters by mode":
+  - requires:
+      test_runner_features: [ capabilities ]
+      capabilities:
+        - method: GET
+          path: /_resolve/index/*
+          capabilities: [ mode_filter ]
+      reason: "Need support for mode filtering in indices.resolve_index"
+
+  - do:
+      indices.resolve_index:
+        name: 'my-*'
+        mode: lookup
+  - length: { indices: 1 }
+  - match: { indices.0.name: "my-lookup-index" }
+  - match: { indices.0.mode: "lookup" }
+  - match: { indices.0.aliases: [ "my-lookup-alias" ] }
+  - length: { aliases: 1 }
+  - match: { aliases.0.name: "my-lookup-alias" }
+  - match: { aliases.0.indices: [ "my-lookup-index" ] }
+
+---
+"resolve index filters by mode, multiple modes":
+  - requires:
+      test_runner_features: [ capabilities ]
+      capabilities:
+        - method: GET
+          path: /_resolve/index/*
+          capabilities: [ mode_filter ]
+      reason: "Need support for mode filtering in indices.resolve_index"
+
+  - do:
+      indices.resolve_index:
+        name: 'my-*'
+        mode: time_series,standard
+  - length: { indices: 2 }
+  - match: { indices.0.name: "my-std-index" }
+  - match: { indices.0.mode: "standard" }
+  - match: { indices.0.aliases: [ "my-std-alias" ] }
+  - match: { indices.1.name: "my-ts-index" }
+  - match: { indices.1.mode: "time_series" }
+  - match: { indices.1.aliases: [ "my-ts-alias" ] }
+  - length: { aliases: 2 }
+  - match: { aliases.0.name: "my-std-alias" }
+  - match: { aliases.0.indices: [ "my-std-index" ] }
+  - match: { aliases.1.name: "my-ts-alias" }
+  - match: { aliases.1.indices: [ "my-ts-index" ] }
+
+---
+teardown:
+  - do:
+      indices.delete:
+        index: my-std-index
+        ignore_unavailable: true
+  - do:
+      indices.delete:
+        index: my-ts-index
+        ignore_unavailable: true
+  - do:
+      indices.delete:
+        index: my-lookup-index
+        ignore_unavailable: true

+ 1 - 0
server/src/main/java/org/elasticsearch/TransportVersions.java

@@ -355,6 +355,7 @@ public class TransportVersions {
     public static final TransportVersion ESQL_SAMPLE_OPERATOR_STATUS = def(9_127_0_00);
     public static final TransportVersion PROJECT_RESERVED_STATE_MOVE_TO_REGISTRY = def(9_147_0_00);
     public static final TransportVersion STREAMS_ENDPOINT_PARAM_RESTRICTIONS = def(9_148_0_00);
+    public static final TransportVersion RESOLVE_INDEX_MODE_FILTER = def(9_149_0_00);
 
     /*
      * STOP! READ THIS FIRST! No, really,

+ 153 - 15
server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java

@@ -10,6 +10,7 @@
 package org.elasticsearch.action.admin.indices.resolve;
 
 import org.elasticsearch.TransportVersion;
+import org.elasticsearch.TransportVersions;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.ActionResponse;
@@ -56,6 +57,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -63,6 +65,8 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.elasticsearch.action.search.TransportSearchHelper.checkCCSVersionCompatibility;
@@ -85,6 +89,7 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
 
         private String[] names;
         private IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS;
+        private EnumSet<IndexMode> indexModes = EnumSet.noneOf(IndexMode.class);
 
         public Request(String[] names) {
             this.names = names;
@@ -95,6 +100,14 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             this.indicesOptions = indicesOptions;
         }
 
+        public Request(String[] names, IndicesOptions indicesOptions, @Nullable EnumSet<IndexMode> indexModes) {
+            this.names = names;
+            this.indicesOptions = indicesOptions;
+            if (indexModes != null) {
+                this.indexModes = indexModes;
+            }
+        }
+
         @Override
         public ActionRequestValidationException validate() {
             return null;
@@ -104,6 +117,11 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             super(in);
             this.names = in.readStringArray();
             this.indicesOptions = IndicesOptions.readIndicesOptions(in);
+            if (in.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_FILTER)) {
+                this.indexModes = in.readEnumSet(IndexMode.class);
+            } else {
+                this.indexModes = EnumSet.noneOf(IndexMode.class);
+            }
         }
 
         @Override
@@ -111,6 +129,9 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             super.writeTo(out);
             out.writeStringArray(names);
             indicesOptions.writeIndicesOptions(out);
+            if (out.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_FILTER)) {
+                out.writeEnumSet(indexModes);
+            }
         }
 
         @Override
@@ -118,12 +139,12 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             Request request = (Request) o;
-            return Arrays.equals(names, request.names);
+            return Arrays.equals(names, request.names) && indexModes.equals(request.indexModes);
         }
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(names);
+            return Objects.hash(Arrays.hashCode(names), indexModes);
         }
 
         @Override
@@ -276,6 +297,19 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             result = 31 * result + Arrays.hashCode(attributes);
             return result;
         }
+
+        @Override
+        public String toString() {
+            return String.format(
+                Locale.ROOT,
+                "ResolvedIndex{name=%s, aliases=%s, attributes=%s, dataStream=%s, mode=%s}",
+                getName(),
+                Arrays.toString(aliases),
+                Arrays.toString(attributes),
+                dataStream,
+                mode
+            );
+        }
     }
 
     public static class ResolvedAlias extends ResolvedIndexAbstraction implements Writeable, ToXContentObject {
@@ -333,6 +367,11 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             result = 31 * result + Arrays.hashCode(indices);
             return result;
         }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "ResolvedAlias{name=%s, indices=%s}", getName(), Arrays.toString(indices));
+        }
     }
 
     public static class ResolvedDataStream extends ResolvedIndexAbstraction implements Writeable, ToXContentObject {
@@ -400,6 +439,17 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             result = 31 * result + Arrays.hashCode(backingIndices);
             return result;
         }
+
+        @Override
+        public String toString() {
+            return String.format(
+                Locale.ROOT,
+                "ResolvedDataStream{name=%s, backingIndices=%s, timestampField=%s}",
+                getName(),
+                Arrays.toString(backingIndices),
+                timestampField
+            );
+        }
     }
 
     public static class Response extends ActionResponse implements ToXContentObject {
@@ -505,7 +555,7 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             List<ResolvedIndex> indices = new ArrayList<>();
             List<ResolvedAlias> aliases = new ArrayList<>();
             List<ResolvedDataStream> dataStreams = new ArrayList<>();
-            resolveIndices(localIndices, projectState, indexNameExpressionResolver, indices, aliases, dataStreams);
+            resolveIndices(localIndices, projectState, indexNameExpressionResolver, indices, aliases, dataStreams, request.indexModes);
 
             if (remoteClusterIndices.size() > 0) {
                 final int remoteRequests = remoteClusterIndices.size();
@@ -513,7 +563,7 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
                 final SortedMap<String, Response> remoteResponses = Collections.synchronizedSortedMap(new TreeMap<>());
                 final Runnable terminalHandler = () -> {
                     if (completionCounter.countDown()) {
-                        mergeResults(remoteResponses, indices, aliases, dataStreams);
+                        mergeResults(remoteResponses, indices, aliases, dataStreams, request.indexModes);
                         listener.onResponse(new Response(indices, aliases, dataStreams));
                     }
                 };
@@ -554,12 +604,35 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             IndexNameExpressionResolver resolver,
             List<ResolvedIndex> indices,
             List<ResolvedAlias> aliases,
-            List<ResolvedDataStream> dataStreams
+            List<ResolvedDataStream> dataStreams,
+            Set<IndexMode> indexModes
         ) {
             if (localIndices == null) {
                 return;
             }
-            resolveIndices(localIndices.indices(), localIndices.indicesOptions(), projectState, resolver, indices, aliases, dataStreams);
+            resolveIndices(
+                localIndices.indices(),
+                localIndices.indicesOptions(),
+                projectState,
+                resolver,
+                indices,
+                aliases,
+                dataStreams,
+                indexModes
+            );
+        }
+
+        // Shortcut for tests that don't need index mode filtering
+        static void resolveIndices(
+            String[] names,
+            IndicesOptions indicesOptions,
+            ProjectState projectState,
+            IndexNameExpressionResolver resolver,
+            List<ResolvedIndex> indices,
+            List<ResolvedAlias> aliases,
+            List<ResolvedDataStream> dataStreams
+        ) {
+            resolveIndices(names, indicesOptions, projectState, resolver, indices, aliases, dataStreams, Collections.emptySet());
         }
 
         /**
@@ -580,7 +653,8 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
             IndexNameExpressionResolver resolver,
             List<ResolvedIndex> indices,
             List<ResolvedAlias> aliases,
-            List<ResolvedDataStream> dataStreams
+            List<ResolvedDataStream> dataStreams,
+            Set<IndexMode> indexModes
         ) {
             // redundant check to ensure that we don't resolve the list of empty names to "all" in this context
             if (names.length == 0) {
@@ -603,47 +677,104 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
                 names
             );
             for (ResolvedExpression s : resolvedIndexAbstractions) {
-                enrichIndexAbstraction(projectState, s, indices, aliases, dataStreams);
+                enrichIndexAbstraction(projectState, s, indices, aliases, dataStreams, indexModes);
             }
             indices.sort(Comparator.comparing(ResolvedIndexAbstraction::getName));
             aliases.sort(Comparator.comparing(ResolvedIndexAbstraction::getName));
             dataStreams.sort(Comparator.comparing(ResolvedIndexAbstraction::getName));
         }
 
+        /**
+         * Merge the results from remote clusters into the local results lists.
+         * This will also do index mode filtering (if requested), as the remote cluster might be too old to do it itself.
+         */
         private static void mergeResults(
             Map<String, Response> remoteResponses,
             List<ResolvedIndex> indices,
             List<ResolvedAlias> aliases,
-            List<ResolvedDataStream> dataStreams
+            List<ResolvedDataStream> dataStreams,
+            Set<IndexMode> indexModes
         ) {
             for (Map.Entry<String, Response> responseEntry : remoteResponses.entrySet()) {
                 String clusterAlias = responseEntry.getKey();
                 Response response = responseEntry.getValue();
                 for (ResolvedIndex index : response.indices) {
+                    // We want to filter by mode here because the linked cluster might be too old to be able to filter
+                    if (indexModes.isEmpty() == false && indexModes.contains(index.getMode()) == false) {
+                        continue;
+                    }
                     indices.add(index.copy(RemoteClusterAware.buildRemoteIndexName(clusterAlias, index.getName())));
                 }
+                Set<String> indexNames = indices.stream().map(ResolvedIndexAbstraction::getName).collect(Collectors.toSet());
                 for (ResolvedAlias alias : response.aliases) {
-                    aliases.add(alias.copy(RemoteClusterAware.buildRemoteIndexName(clusterAlias, alias.getName())));
+                    if (indexModes.isEmpty() == false) {
+                        // We filter out indices that are not included in the main index list after index mode filtering
+                        String[] filteredIndices = Arrays.stream(alias.getIndices())
+                            .filter(idxName -> indexNames.contains(RemoteClusterAware.buildRemoteIndexName(clusterAlias, idxName)))
+                            .toArray(String[]::new);
+                        if (filteredIndices.length == 0) {
+                            // If this alias points to no indices after filtering, we skip it
+                            continue;
+                        }
+                        alias = new ResolvedAlias(RemoteClusterAware.buildRemoteIndexName(clusterAlias, alias.getName()), filteredIndices);
+                    } else {
+                        alias = alias.copy(RemoteClusterAware.buildRemoteIndexName(clusterAlias, alias.getName()));
+                    }
+                    aliases.add(alias);
                 }
                 for (ResolvedDataStream dataStream : response.dataStreams) {
-                    dataStreams.add(dataStream.copy(RemoteClusterAware.buildRemoteIndexName(clusterAlias, dataStream.getName())));
+                    if (indexModes.isEmpty() == false) {
+                        // We filter out indices that are not included in the main index list after index mode filtering
+                        String[] filteredBackingIndices = Arrays.stream(dataStream.getBackingIndices())
+                            .filter(idxName -> indexNames.contains(RemoteClusterAware.buildRemoteIndexName(clusterAlias, idxName)))
+                            .toArray(String[]::new);
+                        if (filteredBackingIndices.length == 0) {
+                            // If this data stream points to no backing indices after filtering, we skip it
+                            continue;
+                        }
+                        dataStream = new ResolvedDataStream(
+                            RemoteClusterAware.buildRemoteIndexName(clusterAlias, dataStream.getName()),
+                            filteredBackingIndices,
+                            dataStream.getTimestampField()
+                        );
+                    } else {
+                        dataStream = dataStream.copy(RemoteClusterAware.buildRemoteIndexName(clusterAlias, dataStream.getName()));
+                    }
+                    dataStreams.add(dataStream);
                 }
             }
         }
 
+        private static Predicate<Index> indexModeFilter(ProjectState projectState, Set<IndexMode> indexModes) {
+            if (indexModes.isEmpty()) {
+                return index -> true;
+            }
+            return index -> {
+                IndexMetadata indexMetadata = projectState.metadata().index(index);
+                IndexMode mode = indexMetadata.getIndexMode() == null ? IndexMode.STANDARD : indexMetadata.getIndexMode();
+                return indexModes.contains(mode);
+            };
+        }
+
         private static void enrichIndexAbstraction(
             ProjectState projectState,
             ResolvedExpression resolvedExpression,
             List<ResolvedIndex> indices,
             List<ResolvedAlias> aliases,
-            List<ResolvedDataStream> dataStreams
+            List<ResolvedDataStream> dataStreams,
+            Set<IndexMode> indexModes
         ) {
             SortedMap<String, IndexAbstraction> indicesLookup = projectState.metadata().getIndicesLookup();
             IndexAbstraction ia = indicesLookup.get(resolvedExpression.resource());
+            var filterPredicate = indexModeFilter(projectState, indexModes);
             if (ia != null) {
                 switch (ia.getType()) {
                     case CONCRETE_INDEX -> {
+                        if (filterPredicate.test(ia.getWriteIndex()) == false) {
+                            return;
+                        }
                         IndexMetadata writeIndex = projectState.metadata().index(ia.getWriteIndex());
+                        IndexMode mode = writeIndex.getIndexMode() == null ? IndexMode.STANDARD : writeIndex.getIndexMode();
                         String[] aliasNames = writeIndex.getAliases().keySet().stream().sorted().toArray(String[]::new);
                         List<Attribute> attributes = new ArrayList<>();
                         attributes.add(writeIndex.getState() == IndexMetadata.State.OPEN ? Attribute.OPEN : Attribute.CLOSED);
@@ -664,13 +795,17 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
                                 aliasNames,
                                 attributes.stream().map(Enum::name).map(e -> e.toLowerCase(Locale.ROOT)).toArray(String[]::new),
                                 ia.getParentDataStream() == null ? null : ia.getParentDataStream().getName(),
-                                writeIndex.getIndexMode() == null ? IndexMode.STANDARD : writeIndex.getIndexMode()
+                                mode
                             )
                         );
                     }
                     case ALIAS -> {
-                        String[] indexNames = getAliasIndexStream(resolvedExpression, ia, projectState.metadata()).map(Index::getName)
+                        String[] indexNames = getAliasIndexStream(resolvedExpression, ia, projectState.metadata()).filter(filterPredicate)
+                            .map(Index::getName)
                             .toArray(String[]::new);
+                        if (indexModes.isEmpty() == false && indexNames.length == 0) {
+                            return;
+                        }
                         Arrays.sort(indexNames);
                         aliases.add(new ResolvedAlias(ia.getName(), indexNames));
                     }
@@ -682,7 +817,10 @@ public class ResolveIndexAction extends ActionType<ResolveIndexAction.Response>
                                 case DATA -> dataStream.getDataComponent().getIndices().stream();
                                 case FAILURES -> dataStream.getFailureIndices().stream();
                             };
-                        String[] backingIndices = dataStreamIndices.map(Index::getName).toArray(String[]::new);
+                        String[] backingIndices = dataStreamIndices.filter(filterPredicate).map(Index::getName).toArray(String[]::new);
+                        if (indexModes.isEmpty() == false && backingIndices.length == 0) {
+                            return;
+                        }
                         dataStreams.add(new ResolvedDataStream(dataStream.getName(), backingIndices, DataStream.TIMESTAMP_FIELD_NAME));
                     }
                     default -> throw new IllegalStateException("unknown index abstraction type: " + ia.getType());

+ 17 - 1
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestResolveIndexAction.java

@@ -13,6 +13,7 @@ import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.internal.node.NodeClient;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.index.IndexMode;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.Scope;
@@ -20,12 +21,16 @@ import org.elasticsearch.rest.ServerlessScope;
 import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Set;
 
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 
 @ServerlessScope(Scope.PUBLIC)
 public class RestResolveIndexAction extends BaseRestHandler {
+    private static final Set<String> CAPABILITIES = Set.of("mode_filter");
 
     @Override
     public String getName() {
@@ -37,12 +42,23 @@ public class RestResolveIndexAction extends BaseRestHandler {
         return List.of(new Route(GET, "/_resolve/index/{name}"));
     }
 
+    @Override
+    public Set<String> supportedCapabilities() {
+        return CAPABILITIES;
+    }
+
     @Override
     protected BaseRestHandler.RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
         String[] indices = Strings.splitStringByCommaToArray(request.param("name"));
+        String modeParam = request.param("mode");
         ResolveIndexAction.Request resolveRequest = new ResolveIndexAction.Request(
             indices,
-            IndicesOptions.fromRequest(request, ResolveIndexAction.Request.DEFAULT_INDICES_OPTIONS)
+            IndicesOptions.fromRequest(request, ResolveIndexAction.Request.DEFAULT_INDICES_OPTIONS),
+            modeParam == null
+                ? null
+                : Arrays.stream(modeParam.split(","))
+                    .map(IndexMode::fromString)
+                    .collect(() -> EnumSet.noneOf(IndexMode.class), EnumSet::add, EnumSet::addAll)
         );
         return channel -> client.admin().indices().resolveIndex(resolveRequest, new RestToXContentListener<>(channel));
     }

+ 10 - 1
server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexRequestTests.java

@@ -12,8 +12,12 @@ package org.elasticsearch.action.admin.indices.resolve;
 import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.Request;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.index.IndexMode;
 import org.elasticsearch.test.AbstractWireSerializingTestCase;
 
+import java.util.Arrays;
+import java.util.EnumSet;
+
 public class ResolveIndexRequestTests extends AbstractWireSerializingTestCase<Request> {
 
     @Override
@@ -35,7 +39,12 @@ public class ResolveIndexRequestTests extends AbstractWireSerializingTestCase<Re
             randomBoolean(),
             randomBoolean()
         );
-        return new Request(names, indicesOptions);
+        if (randomBoolean()) {
+            return new Request(names, indicesOptions);
+        } else {
+            EnumSet<IndexMode> randomModes = EnumSet.copyOf(randomNonEmptySubsetOf(Arrays.asList(IndexMode.values())));
+            return new Request(names, indicesOptions, randomBoolean() ? null : randomModes);
+        }
     }
 
     @Override

+ 79 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java

@@ -46,6 +46,7 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -170,6 +171,84 @@ public class ResolveIndexTests extends ESTestCase {
         validateDataStreams(dataStreams, "logs-mysql-prod", "logs-mysql-test");
     }
 
+    public void testResolveModesFilter() {
+        String[] names = randomFrom(new String[] { "*" }, new String[] { "_all" });
+        List<ResolvedIndex> indices = new ArrayList<>();
+        List<ResolvedAlias> aliases = new ArrayList<>();
+        List<ResolvedDataStream> dataStreams = new ArrayList<>();
+
+        // One type
+        TransportAction.resolveIndices(
+            names,
+            IndicesOptions.STRICT_EXPAND_OPEN_CLOSED_HIDDEN,
+            projectState,
+            resolver,
+            indices,
+            aliases,
+            dataStreams,
+            EnumSet.of(IndexMode.TIME_SERIES)
+        );
+        validateIndices(indices, "logs-pgsql-prod-20200102");
+        assertThat(aliases.size(), equalTo(2));
+        var alias1 = aliases.get(0);
+        assertThat(alias1.getName(), equalTo("logs-pgsql-prod"));
+        assertThat(alias1.getIndices(), arrayContaining("logs-pgsql-prod-20200102"));
+        var alias2 = aliases.get(1);
+        assertThat(alias2.getName(), equalTo("one-off-alias"));
+        assertThat(alias2.getIndices(), arrayContaining("logs-pgsql-prod-20200102"));
+        assertThat(dataStreams.size(), equalTo(0));
+
+        indices = new ArrayList<>();
+        aliases = new ArrayList<>();
+        dataStreams = new ArrayList<>();
+        // Everything
+        TransportAction.resolveIndices(
+            names,
+            IndicesOptions.STRICT_EXPAND_OPEN_CLOSED_HIDDEN,
+            projectState,
+            resolver,
+            indices,
+            aliases,
+            dataStreams,
+            EnumSet.of(IndexMode.TIME_SERIES, IndexMode.STANDARD)
+        );
+        validateIndices(
+            indices,
+            ".ds-logs-mysql-prod-" + dateString + "-000001",
+            ".ds-logs-mysql-prod-" + dateString + "-000002",
+            ".ds-logs-mysql-prod-" + dateString + "-000003",
+            ".ds-logs-mysql-prod-" + dateString + "-000004",
+            ".ds-logs-mysql-test-" + dateString + "-000001",
+            ".ds-logs-mysql-test-" + dateString + "-000002",
+            ".test-system-index",
+            "logs-pgsql-prod-20200101",
+            "logs-pgsql-prod-20200102",
+            "logs-pgsql-prod-20200103",
+            "logs-pgsql-test-20200101",
+            "logs-pgsql-test-20200102",
+            "logs-pgsql-test-20200103"
+        );
+        validateAliases(aliases, "logs-pgsql-prod", "logs-pgsql-test", "one-off-alias");
+        validateDataStreams(dataStreams, "logs-mysql-prod", "logs-mysql-test");
+        // Not matched
+        indices = new ArrayList<>();
+        aliases = new ArrayList<>();
+        dataStreams = new ArrayList<>();
+        TransportAction.resolveIndices(
+            names,
+            IndicesOptions.STRICT_EXPAND_OPEN_CLOSED_HIDDEN,
+            projectState,
+            resolver,
+            indices,
+            aliases,
+            dataStreams,
+            EnumSet.of(IndexMode.LOOKUP)
+        );
+        assertThat(indices.size(), equalTo(0));
+        assertThat(aliases.size(), equalTo(0));
+        assertThat(dataStreams.size(), equalTo(0));
+    }
+
     public void testResolveWithPattern() {
         String[] names = new String[] { "logs-pgsql*" };
         IndicesOptions indicesOptions = Request.DEFAULT_INDICES_OPTIONS;