diff --git a/CHANGELOG.md b/CHANGELOG.md index e69ea261c0c49..b83ad4a5cb10c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Use Lucene `pack` method for `half_float` and `usigned_long` when using `ApproximatePointRangeQuery`. - Add a mapper for context aware segments grouping criteria ([#19233](https://github.com/opensearch-project/OpenSearch/pull/19233)) - Return full error for GRPC error response ([#19568](https://github.com/opensearch-project/OpenSearch/pull/19568)) +- Introduced internal API for retrieving metadata about requested indices from transport actions ([#18523](https://github.com/opensearch-project/OpenSearch/pull/18523)) ### Changed - Faster `terms` query creation for `keyword` field with index and docValues enabled ([#19350](https://github.com/opensearch-project/OpenSearch/pull/19350)) diff --git a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportRenderSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportRenderSearchTemplateAction.java index abd5d768f4a11..28a411d010543 100644 --- a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportRenderSearchTemplateAction.java +++ b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportRenderSearchTemplateAction.java @@ -8,6 +8,7 @@ package org.opensearch.script.mustache; +import org.opensearch.action.search.TransportSearchAction; import org.opensearch.action.support.ActionFilters; import org.opensearch.common.inject.Inject; import org.opensearch.core.xcontent.NamedXContentRegistry; @@ -23,8 +24,17 @@ public TransportRenderSearchTemplateAction( ActionFilters actionFilters, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - NodeClient client + NodeClient client, + TransportSearchAction transportSearchAction ) { - super(RenderSearchTemplateAction.NAME, transportService, actionFilters, scriptService, xContentRegistry, client); + super( + RenderSearchTemplateAction.NAME, + transportService, + actionFilters, + scriptService, + xContentRegistry, + client, + transportSearchAction + ); } } diff --git a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportSearchTemplateAction.java index e24f3c0d7d560..62c2ef35f5c94 100644 --- a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportSearchTemplateAction.java +++ b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/TransportSearchTemplateAction.java @@ -34,8 +34,12 @@ import org.opensearch.action.search.SearchRequest; import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.TransportSearchAction; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.common.inject.Inject; import org.opensearch.common.xcontent.LoggingDeprecationHandler; import org.opensearch.core.action.ActionListener; @@ -57,13 +61,15 @@ import java.io.IOException; import java.util.Collections; -public class TransportSearchTemplateAction extends HandledTransportAction { - +public class TransportSearchTemplateAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private static final String TEMPLATE_LANG = MustacheScriptEngine.NAME; protected final ScriptService scriptService; protected final NamedXContentRegistry xContentRegistry; protected final NodeClient client; + private final TransportSearchAction transportSearchAction; @Inject public TransportSearchTemplateAction( @@ -71,12 +77,14 @@ public TransportSearchTemplateAction( ActionFilters actionFilters, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - NodeClient client + NodeClient client, + TransportSearchAction transportSearchAction ) { super(SearchTemplateAction.NAME, transportService, actionFilters, SearchTemplateRequest::new); this.scriptService = scriptService; this.xContentRegistry = xContentRegistry; this.client = client; + this.transportSearchAction = transportSearchAction; } public TransportSearchTemplateAction( @@ -85,12 +93,14 @@ public TransportSearchTemplateAction( ActionFilters actionFilters, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - NodeClient client + NodeClient client, + TransportSearchAction transportSearchAction ) { super(actionName, transportService, actionFilters, SearchTemplateRequest::new); this.scriptService = scriptService; this.xContentRegistry = xContentRegistry; this.client = client; + this.transportSearchAction = transportSearchAction; } @Override @@ -180,4 +190,14 @@ private static void checkRestTotalHitsAsInt(SearchRequest searchRequest, SearchS } } } + + @Override + public OptionallyResolvedIndices resolveIndices(SearchTemplateRequest request) { + if (request.getRequest() != null) { + return transportSearchAction.resolveIndices(request.getRequest()); + } else { + // For the RenderSearchTemplateAction, request.getRequest() will be null. + return ResolvedIndices.unknown(); + } + } } diff --git a/modules/reindex/src/main/java/org/opensearch/index/reindex/TransportDeleteByQueryAction.java b/modules/reindex/src/main/java/org/opensearch/index/reindex/TransportDeleteByQueryAction.java index 76e3cc42697ff..986d1a32845fb 100644 --- a/modules/reindex/src/main/java/org/opensearch/index/reindex/TransportDeleteByQueryAction.java +++ b/modules/reindex/src/main/java/org/opensearch/index/reindex/TransportDeleteByQueryAction.java @@ -32,8 +32,11 @@ package org.opensearch.index.reindex; +import org.opensearch.action.search.TransportSearchAction; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -45,12 +48,15 @@ import org.opensearch.transport.client.Client; import org.opensearch.transport.client.ParentTaskAssigningClient; -public class TransportDeleteByQueryAction extends HandledTransportAction { +public class TransportDeleteByQueryAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ThreadPool threadPool; private final Client client; private final ScriptService scriptService; private final ClusterService clusterService; + private final TransportSearchAction transportSearchAction; @Inject public TransportDeleteByQueryAction( @@ -59,7 +65,8 @@ public TransportDeleteByQueryAction( Client client, TransportService transportService, ScriptService scriptService, - ClusterService clusterService + ClusterService clusterService, + TransportSearchAction transportSearchAction ) { super( DeleteByQueryAction.NAME, @@ -71,6 +78,7 @@ public TransportDeleteByQueryAction( this.client = client; this.scriptService = scriptService; this.clusterService = clusterService; + this.transportSearchAction = transportSearchAction; } @Override @@ -95,4 +103,9 @@ public void doExecute(Task task, DeleteByQueryRequest request, ActionListener { +public class TransportReindexAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { public static final Setting> REMOTE_CLUSTER_ALLOWLIST = Setting.listSetting( "reindex.remote.allowlist", emptyList(), @@ -88,6 +94,8 @@ public class TransportReindexAction extends HandledTransportAction { +public class TransportUpdateByQueryAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ThreadPool threadPool; private final Client client; private final ScriptService scriptService; private final ClusterService clusterService; + private final TransportSearchAction transportSearchAction; @Inject public TransportUpdateByQueryAction( @@ -69,7 +75,8 @@ public TransportUpdateByQueryAction( Client client, TransportService transportService, ScriptService scriptService, - ClusterService clusterService + ClusterService clusterService, + TransportSearchAction transportSearchAction ) { super( UpdateByQueryAction.NAME, @@ -81,6 +88,7 @@ public TransportUpdateByQueryAction( this.client = client; this.scriptService = scriptService; this.clusterService = clusterService; + this.transportSearchAction = transportSearchAction; } @Override @@ -107,6 +115,11 @@ protected void doExecute(Task task, UpdateByQueryRequest request, ActionListener ); } + @Override + public ResolvedIndices resolveIndices(UpdateByQueryRequest request) { + return transportSearchAction.resolveIndices(request.getSearchRequest()); + } + /** * Simple implementation of update-by-query using scrolling and bulk. */ diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteWithAuthTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteWithAuthTests.java index 51aa02c55070c..8cb63b91c44de 100644 --- a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteWithAuthTests.java +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteWithAuthTests.java @@ -39,6 +39,7 @@ import org.opensearch.action.search.SearchAction; import org.opensearch.action.support.ActionFilter; import org.opensearch.action.support.ActionFilterChain; +import org.opensearch.action.support.ActionRequestMetadata; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; @@ -233,6 +234,7 @@ public void app Task task, String action, Request request, + ActionRequestMetadata actionRequestMetadata, ActionListener listener, ActionFilterChain chain ) { diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/TransportReindexActionTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/TransportReindexActionTests.java new file mode 100644 index 0000000000000..dd6a32147e7a2 --- /dev/null +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/TransportReindexActionTests.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.index.reindex; + +import org.opensearch.action.index.IndexAction; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.TransportSearchAction; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.script.ScriptService; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; +import org.opensearch.transport.client.Client; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TransportReindexActionTests extends OpenSearchTestCase { + public void testResolveIndices() { + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.getThreadContext()).thenReturn(threadContext); + TransportSearchAction transportSearchAction = mock(TransportSearchAction.class); + when(transportSearchAction.resolveIndices(any())).thenAnswer(i -> ResolvedIndices.of(((SearchRequest) i.getArgument(0)).indices())); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(threadContext); + + TransportReindexAction action = new TransportReindexAction( + Settings.EMPTY, + threadPool, + mock(ActionFilters.class), + indexNameExpressionResolver, + mock(ClusterService.class), + mock(ScriptService.class), + null, + mock(Client.class), + mock(TransportService.class), + mock(ReindexSslConfig.class), + transportSearchAction + ); + + { + ResolvedIndices resolvedIndices = action.resolveIndices( + new ReindexRequest(new SearchRequest("searched-index"), new IndexRequest("target-index")) + ); + assertEquals( + ResolvedIndices.of("searched-index").withLocalSubActions(IndexAction.INSTANCE, ResolvedIndices.Local.of("target-index")), + resolvedIndices + ); + } + } +} diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/UpdateByQueryWithScriptTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/UpdateByQueryWithScriptTests.java index ce982dcb6bd34..56e3e9cbe311a 100644 --- a/modules/reindex/src/test/java/org/opensearch/index/reindex/UpdateByQueryWithScriptTests.java +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/UpdateByQueryWithScriptTests.java @@ -79,6 +79,7 @@ protected TransportUpdateByQueryAction.AsyncIndexBySearchAction action(ScriptSer null, transportService, scriptService, + null, null ); return new TransportUpdateByQueryAction.AsyncIndexBySearchAction( diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/AutoTaggingActionFilter.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/AutoTaggingActionFilter.java index 112e2a11956f0..c6294ed7ac242 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/AutoTaggingActionFilter.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/AutoTaggingActionFilter.java @@ -13,6 +13,7 @@ import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.ActionFilter; import org.opensearch.action.support.ActionFilterChain; +import org.opensearch.action.support.ActionRequestMetadata; import org.opensearch.core.action.ActionListener; import org.opensearch.core.action.ActionResponse; import org.opensearch.plugin.wlm.rule.attribute_extractor.IndicesExtractor; @@ -75,6 +76,7 @@ public void app Task task, String action, Request request, + ActionRequestMetadata actionRequestMetadata, ActionListener listener, ActionFilterChain chain ) { diff --git a/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/AutoTaggingActionFilterTests.java b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/AutoTaggingActionFilterTests.java index e5fb23af4a7e9..ed5e8e25843ea 100644 --- a/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/AutoTaggingActionFilterTests.java +++ b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/AutoTaggingActionFilterTests.java @@ -12,6 +12,7 @@ import org.opensearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.ActionFilterChain; +import org.opensearch.action.support.ActionRequestMetadata; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.core.action.ActionResponse; @@ -77,7 +78,7 @@ public void testApplyForValidRequest() { when(request.indices()).thenReturn(new String[] { "foo" }); try (ThreadContext.StoredContext context = threadPool.getThreadContext().stashContext()) { when(ruleProcessingService.evaluateLabel(anyList())).thenReturn(Optional.of("TestQG_ID")); - autoTaggingActionFilter.apply(mock(Task.class), "Test", request, null, mockFilterChain); + autoTaggingActionFilter.apply(mock(Task.class), "Test", request, ActionRequestMetadata.empty(), null, mockFilterChain); assertEquals("TestQG_ID", threadPool.getThreadContext().getHeader(WorkloadGroupTask.WORKLOAD_GROUP_ID_HEADER)); verify(ruleProcessingService, times(1)).evaluateLabel(anyList()); @@ -87,7 +88,7 @@ public void testApplyForValidRequest() { public void testApplyForInValidRequest() { ActionFilterChain mockFilterChain = mock(TestActionFilterChain.class); CancelTasksRequest request = new CancelTasksRequest(); - autoTaggingActionFilter.apply(mock(Task.class), "Test", request, null, mockFilterChain); + autoTaggingActionFilter.apply(mock(Task.class), "Test", request, ActionRequestMetadata.empty(), null, mockFilterChain); verify(ruleProcessingService, times(0)).evaluateLabel(anyList()); } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/shards/TransportClusterSearchShardsAction.java b/server/src/main/java/org/opensearch/action/admin/cluster/shards/TransportClusterSearchShardsAction.java index 11323499efd8b..3924c95a0f58b 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/shards/TransportClusterSearchShardsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/shards/TransportClusterSearchShardsAction.java @@ -33,11 +33,13 @@ package org.opensearch.action.admin.cluster.shards; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.GroupShardsIterator; import org.opensearch.cluster.routing.ShardIterator; @@ -65,7 +67,7 @@ */ public class TransportClusterSearchShardsAction extends TransportClusterManagerNodeReadAction< ClusterSearchShardsRequest, - ClusterSearchShardsResponse> { + ClusterSearchShardsResponse> implements TransportIndicesResolvingAction { private final IndicesService indicesService; @@ -100,7 +102,7 @@ protected String executor() { @Override protected ClusterBlockException checkBlock(ClusterSearchShardsRequest request, ClusterState state) { return state.blocks() - .indicesBlockedException(ClusterBlockLevel.METADATA_READ, indexNameExpressionResolver.concreteIndexNames(state, request)); + .indicesBlockedException(ClusterBlockLevel.METADATA_READ, resolveIndices(state, request).namesOfConcreteIndicesAsArray()); } @Override @@ -115,7 +117,7 @@ protected void clusterManagerOperation( final ActionListener listener ) { ClusterState clusterState = clusterService.state(); - String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request); + String[] concreteIndices = resolveIndices(clusterState, request).namesOfConcreteIndicesAsArray(); Map> routingMap = indexNameExpressionResolver.resolveSearchRouting(state, request.routing(), request.indices()); Map indicesAndFilters = new HashMap<>(); Set indicesAndAliases = indexNameExpressionResolver.resolveExpressions(clusterState, request.indices()); @@ -155,4 +157,13 @@ protected void clusterManagerOperation( } listener.onResponse(new ClusterSearchShardsResponse(groupResponses, nodes, indicesAndFilters)); } + + @Override + public ResolvedIndices resolveIndices(ClusterSearchShardsRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } + + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState clusterState, ClusterSearchShardsRequest request) { + return this.indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/alias/IndicesAliasesRequest.java b/server/src/main/java/org/opensearch/action/admin/indices/alias/IndicesAliasesRequest.java index dd915f8857162..32fc713c93047 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/alias/IndicesAliasesRequest.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/alias/IndicesAliasesRequest.java @@ -35,6 +35,7 @@ import org.opensearch.OpenSearchGenerationException; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.action.AliasesRequest; +import org.opensearch.action.CompositeIndicesRequest; import org.opensearch.action.support.IndicesOptions; import org.opensearch.action.support.clustermanager.AcknowledgedRequest; import org.opensearch.cluster.metadata.AliasAction; @@ -76,7 +77,7 @@ * @opensearch.api */ @PublicApi(since = "1.0.0") -public class IndicesAliasesRequest extends AcknowledgedRequest implements ToXContentObject { +public class IndicesAliasesRequest extends AcknowledgedRequest implements ToXContentObject, CompositeIndicesRequest { private List allAliasActions = new ArrayList<>(); private String origin = ""; diff --git a/server/src/main/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesAction.java b/server/src/main/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesAction.java index a5e87482f7645..e885ac69a28ec 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesAction.java @@ -35,7 +35,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.RequestValidators; +import org.opensearch.action.admin.indices.delete.DeleteIndexAction; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; @@ -48,6 +50,7 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.metadata.MetadataIndexAliasesService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.regex.Regex; @@ -68,6 +71,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static java.util.Collections.unmodifiableList; @@ -76,7 +80,9 @@ * * @opensearch.internal */ -public class TransportIndicesAliasesAction extends TransportClusterManagerNodeAction { +public class TransportIndicesAliasesAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportIndicesAliasesAction.class); @@ -131,47 +137,113 @@ protected void clusterManagerOperation( final IndicesAliasesRequest request, final ClusterState state, final ActionListener listener - ) { + ) throws Exception { // Expand the indices names List actions = request.aliasActions(); - List finalActions = new ArrayList<>(); + List finalActions = resolvedAliasActions(request, state, true); + if (finalActions.isEmpty() && false == actions.isEmpty()) { + throw new AliasesNotFoundException( + actions.stream().flatMap(a -> Arrays.stream(a.getOriginalAliases())).collect(Collectors.toSet()).toArray(new String[0]) + ); + } + request.aliasActions().clear(); + IndicesAliasesClusterStateUpdateRequest updateRequest = new IndicesAliasesClusterStateUpdateRequest(unmodifiableList(finalActions)) + .ackTimeout(request.timeout()) + .clusterManagerNodeTimeout(request.clusterManagerNodeTimeout()); + + indexAliasesService.indicesAliases(updateRequest, new ActionListener() { + @Override + public void onResponse(ClusterStateUpdateResponse response) { + listener.onResponse(new AcknowledgedResponse(response.isAcknowledged())); + } + + @Override + public void onFailure(Exception t) { + logger.debug("failed to perform aliases", t); + listener.onFailure(t); + } + }); + } + + @Override + public ResolvedIndices resolveIndices(IndicesAliasesRequest request) { + try { + Set indices = new HashSet<>(); + Set indicesToBeDeleted = new HashSet<>(); + + for (AliasAction aliasAction : resolvedAliasActions(request, clusterService.state(), false)) { + if (aliasAction instanceof AliasAction.Add addAliasAction) { + indices.add(addAliasAction.getIndex()); + indices.add(addAliasAction.getAlias()); + } else if (aliasAction instanceof AliasAction.Remove removeAliasAction) { + indices.add(removeAliasAction.getIndex()); + indices.add(removeAliasAction.getAlias()); + } else if (aliasAction instanceof AliasAction.RemoveIndex removeIndexAction) { + indicesToBeDeleted.add(removeIndexAction.getIndex()); + } + } + + ResolvedIndices result = ResolvedIndices.of(indices); + if (!indicesToBeDeleted.isEmpty()) { + result = result.withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of(indicesToBeDeleted)); + } + return result; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + // This should not happen if validate=false is passed to resolvedAliasActions() + throw new RuntimeException(e); + } + } + + /** + * Resolves the actions from the IndicesAliasesRequest into concrete AliasAction instances. + * This method has two modes: validate=true makes validation of the parameters and can potentially cause + * exceptions to be thrown upon validation errors. validate=false skips any code that could throw exceptions. This + * is meant for the resolveIndices() method. + */ + private List resolvedAliasActions(IndicesAliasesRequest request, ClusterState state, boolean validate) throws Exception { + List result = new ArrayList<>(); // Resolve all the AliasActions into AliasAction instances and gather all the aliases - Set aliases = new HashSet<>(); - for (IndicesAliasesRequest.AliasActions action : actions) { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices( + for (IndicesAliasesRequest.AliasActions action : request.aliasActions()) { + ResolvedIndices.Local.Concrete concreteIndices = indexNameExpressionResolver.concreteResolvedIndices( state, request.indicesOptions(), false, action.indices() ); - for (Index concreteIndex : concreteIndices) { - IndexAbstraction indexAbstraction = state.metadata().getIndicesLookup().get(concreteIndex.getName()); - assert indexAbstraction != null : "invalid cluster metadata. index [" + concreteIndex.getName() + "] was not found"; - if (indexAbstraction.getParentDataStream() != null) { - throw new IllegalArgumentException( - "The provided expressions [" - + String.join(",", action.indices()) - + "] match a backing index belonging to data stream [" - + indexAbstraction.getParentDataStream().getName() - + "]. Data streams and their backing indices don't support aliases." - ); + if (validate) { + for (Index concreteIndex : concreteIndices.concreteIndices()) { + IndexAbstraction indexAbstraction = state.metadata().getIndicesLookup().get(concreteIndex.getName()); + assert indexAbstraction != null : "invalid cluster metadata. index [" + concreteIndex.getName() + "] was not found"; + if (indexAbstraction.getParentDataStream() != null) { + throw new IllegalArgumentException( + "The provided expressions [" + + String.join(",", action.indices()) + + "] match a backing index belonging to data stream [" + + indexAbstraction.getParentDataStream().getName() + + "]. Data streams and their backing indices don't support aliases." + ); + } + } + final Optional maybeException = requestValidators.validateRequest( + request, + state, + concreteIndices.concreteIndicesAsArray() + ); + if (maybeException.isPresent()) { + throw maybeException.get(); } - } - final Optional maybeException = requestValidators.validateRequest(request, state, concreteIndices); - if (maybeException.isPresent()) { - listener.onFailure(maybeException.get()); - return; } - Collections.addAll(aliases, action.getOriginalAliases()); - for (final Index index : concreteIndices) { + for (String index : concreteIndices.namesOfIndices(state)) { switch (action.actionType()) { case ADD: - for (String alias : concreteAliases(action, state.metadata(), index.getName())) { - finalActions.add( + for (String alias : concreteAliases(action, state.metadata(), index)) { + result.add( new AliasAction.Add( - index.getName(), + index, alias, action.filter(), action.indexRouting(), @@ -183,38 +255,20 @@ protected void clusterManagerOperation( } break; case REMOVE: - for (String alias : concreteAliases(action, state.metadata(), index.getName())) { - finalActions.add(new AliasAction.Remove(index.getName(), alias, action.mustExist())); + for (String alias : concreteAliases(action, state.metadata(), index)) { + result.add(new AliasAction.Remove(index, alias, action.mustExist())); } break; case REMOVE_INDEX: - finalActions.add(new AliasAction.RemoveIndex(index.getName())); + result.add(new AliasAction.RemoveIndex(index)); break; default: throw new IllegalArgumentException("Unsupported action [" + action.actionType() + "]"); } } } - if (finalActions.isEmpty() && false == actions.isEmpty()) { - throw new AliasesNotFoundException(aliases.toArray(new String[0])); - } - request.aliasActions().clear(); - IndicesAliasesClusterStateUpdateRequest updateRequest = new IndicesAliasesClusterStateUpdateRequest(unmodifiableList(finalActions)) - .ackTimeout(request.timeout()) - .clusterManagerNodeTimeout(request.clusterManagerNodeTimeout()); - indexAliasesService.indicesAliases(updateRequest, new ActionListener() { - @Override - public void onResponse(ClusterStateUpdateResponse response) { - listener.onResponse(new AcknowledgedResponse(response.isAcknowledged())); - } - - @Override - public void onFailure(Exception t) { - logger.debug("failed to perform aliases", t); - listener.onFailure(t); - } - }); + return result; } private static String[] concreteAliases(IndicesAliasesRequest.AliasActions action, Metadata metadata, String concreteIndex) { @@ -255,4 +309,5 @@ private static String[] concreteAliases(IndicesAliasesRequest.AliasActions actio return action.aliases(); } } + } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesAction.java b/server/src/main/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesAction.java index 4f4e3bd481ee7..d18041b2208dc 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesAction.java @@ -32,6 +32,7 @@ package org.opensearch.action.admin.indices.alias.get; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; @@ -39,6 +40,7 @@ import org.opensearch.cluster.metadata.AliasMetadata; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.logging.DeprecationLogger; @@ -51,6 +53,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -65,7 +68,9 @@ * * @opensearch.internal */ -public class TransportGetAliasesAction extends TransportClusterManagerNodeReadAction { +public class TransportGetAliasesAction extends TransportClusterManagerNodeReadAction + implements + TransportIndicesResolvingAction { private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(TransportGetAliasesAction.class); private final SystemIndices systemIndices; @@ -195,4 +200,52 @@ private static void checkSystemAliasAccess(GetAliasesRequest request, SystemIndi ); } } + + @Override + public ResolvedIndices resolveIndices(GetAliasesRequest request) { + ClusterState state = this.clusterService.state(); + + // The index resolution object in this method is advanced, even though it might not look like it in + // the clusterManagerOperation() method on the first glance. + // + // GetAliasesRequest can be in several different states: + // - no aliases and no indices specified: both the aliases and the indices attribute in GetAliasesRequest are + // empty arrays. This will then cause all aliases and all indices to be resolved and referenced. + // - an alias and no indices: the indices attribute in GetAliasesRequest will be an empty array, which will be + // resolved by indexNameExpressionResolver.concreteIndexNames() to all indices. The action will then filter + // all indices to those that are member of the specified alias + // - no aliases and one or more indices: the aliases attribute in GetAliasesRequest will be an empty array, + // which will be resolved by state.metadata().findAliases() to all aliases, but limited to the aliases + // containing one of the specified indices + // - both aliases and indices specified: this is then the intersection + // + // For both indices and aliases, patterns can be specified. The indicesOptions of the request only apply to the indices. + // + // The consequence for the resolveIndices() method is that the semantics of the return value might be debatable. + // We resort to have just the index names on the top level, because it is the most precise dimension. + // Alias names are included as a sub action named "indices:admin/aliases/get[aliases]" + + boolean noAliasesSpecified = request.getOriginalAliases() == null || request.getOriginalAliases().length == 0; + String[] concreteIndices; + + try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().newStoredContext(false)) { + concreteIndices = indexNameExpressionResolver.concreteResolvedIndices(state, request) + .withoutResolutionErrors() + .namesOfConcreteIndicesAsArray(); + } + Map> indexToAliasesMap = state.metadata().findAliases(request, concreteIndices); + + // If no aliases were specified in the request, we will report all indices matching the indices expression + // If there were aliases specified, we will report just the indices that are members of the aliases + // That follows the logic in the postProcess() method above. + Collection indices = noAliasesSpecified ? Arrays.asList(concreteIndices) : indexToAliasesMap.keySet(); + + return ResolvedIndices.of(indices) + .withLocalSubActions( + GetAliasesAction.NAME + "[aliases]", + ResolvedIndices.Local.of( + indexToAliasesMap.values().stream().flatMap(Collection::stream).map(AliasMetadata::alias).collect(Collectors.toSet()) + ) + ); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/close/TransportCloseIndexAction.java b/server/src/main/java/org/opensearch/action/admin/indices/close/TransportCloseIndexAction.java index 4a0822a9bb754..61f510041b6f3 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/close/TransportCloseIndexAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/close/TransportCloseIndexAction.java @@ -37,12 +37,14 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataIndexStateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.ClusterSettings; @@ -67,7 +69,9 @@ * * @opensearch.internal */ -public class TransportCloseIndexAction extends TransportClusterManagerNodeAction { +public class TransportCloseIndexAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportCloseIndexAction.class); @@ -159,7 +163,7 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) throws Exception { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final Index[] concreteIndices = resolveIndices(state, request).concreteIndicesAsArray(); if (concreteIndices == null || concreteIndices.length == 0) { listener.onResponse(new CloseIndexResponse(true, false, Collections.emptyList())); return; @@ -177,6 +181,15 @@ protected void clusterManagerOperation( })); } + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, CloseIndexRequest request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + + @Override + public ResolvedIndices resolveIndices(CloseIndexRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } + /** * Reject close index request if cluster mode is [MIXED] and migration direction is [RemoteStore] * @throws IllegalStateException if cluster mode is [MIXED] and migration direction is [RemoteStore] diff --git a/server/src/main/java/org/opensearch/action/admin/indices/create/AutoCreateAction.java b/server/src/main/java/org/opensearch/action/admin/indices/create/AutoCreateAction.java index 4638c420ddb4d..efa84bdca660a 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/create/AutoCreateAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/create/AutoCreateAction.java @@ -35,6 +35,7 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.ActiveShardCount; import org.opensearch.action.support.ActiveShardsObserver; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.AckedClusterStateUpdateTask; import org.opensearch.cluster.ClusterState; @@ -49,6 +50,7 @@ import org.opensearch.cluster.metadata.MetadataCreateDataStreamService.CreateDataStreamClusterStateUpdateRequest; import org.opensearch.cluster.metadata.MetadataCreateIndexService; import org.opensearch.cluster.metadata.MetadataIndexTemplateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterManagerTaskThrottler; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Priority; @@ -82,7 +84,9 @@ private AutoCreateAction() { * * @opensearch.internal */ - public static final class TransportAction extends TransportClusterManagerNodeAction { + public static final class TransportAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final ActiveShardsObserver activeShardsObserver; private final MetadataCreateIndexService createIndexService; @@ -187,6 +191,19 @@ public ClusterState execute(ClusterState currentState) throws Exception { protected ClusterBlockException checkBlock(CreateIndexRequest request, ClusterState state) { return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_WRITE, request.index()); } + + @Override + public ResolvedIndices resolveIndices(CreateIndexRequest request) { + ClusterState clusterState = clusterService.state(); + DataStreamTemplate dataStreamTemplate = resolveAutoCreateDataStream(request, clusterState.metadata()); + + if (dataStreamTemplate != null) { + // No date math is supported when a data stream is created + return ResolvedIndices.of(request.index()); + } else { + return ResolvedIndices.of(indexNameExpressionResolver.resolveDateMathExpression(request.index())); + } + } } static DataStreamTemplate resolveAutoCreateDataStream(CreateIndexRequest request, Metadata metadata) { diff --git a/server/src/main/java/org/opensearch/action/admin/indices/create/TransportCreateIndexAction.java b/server/src/main/java/org/opensearch/action/admin/indices/create/TransportCreateIndexAction.java index c1f7c6ef87e8a..d6bd0f5254413 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/create/TransportCreateIndexAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/create/TransportCreateIndexAction.java @@ -32,13 +32,17 @@ package org.opensearch.action.admin.indices.create; +import org.opensearch.action.admin.indices.alias.Alias; +import org.opensearch.action.admin.indices.alias.IndicesAliasesAction; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataCreateIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -48,13 +52,16 @@ import org.opensearch.transport.TransportService; import java.io.IOException; +import java.util.stream.Collectors; /** * Create index action. * * @opensearch.internal */ -public class TransportCreateIndexAction extends TransportClusterManagerNodeAction { +public class TransportCreateIndexAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataCreateIndexService createIndexService; private final MappingTransformerRegistry mappingTransformerRegistry; @@ -115,7 +122,7 @@ protected void clusterManagerOperation( cause = "api"; } - final String indexName = indexNameExpressionResolver.resolveDateMathExpression(request.index()); + final String indexName = resolveIndexName(request); final String finalCause = cause; final ActionListener mappingTransformListener = ActionListener.wrap(transformedMappings -> { @@ -143,4 +150,21 @@ protected void clusterManagerOperation( mappingTransformerRegistry.applyTransformers(request.mappings(), null, mappingTransformListener); } + @Override + public ResolvedIndices resolveIndices(CreateIndexRequest request) { + ResolvedIndices result = ResolvedIndices.of(resolveIndexName(request)); + + if (request.aliases().isEmpty()) { + return result; + } else { + return result.withLocalSubActions( + IndicesAliasesAction.INSTANCE, + ResolvedIndices.Local.of(request.aliases().stream().map(Alias::name).collect(Collectors.toSet())) + ); + } + } + + private String resolveIndexName(CreateIndexRequest request) { + return indexNameExpressionResolver.resolveDateMathExpression(request.index()); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/datastream/CreateDataStreamAction.java b/server/src/main/java/org/opensearch/action/admin/indices/datastream/CreateDataStreamAction.java index 6b5091e0b2ab5..70bcf2d015b98 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/datastream/CreateDataStreamAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/datastream/CreateDataStreamAction.java @@ -37,6 +37,7 @@ import org.opensearch.action.ValidateActions; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedRequest; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; @@ -46,6 +47,7 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataCreateDataStreamService; import org.opensearch.cluster.metadata.MetadataCreateDataStreamService.CreateDataStreamClusterStateUpdateRequest; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.annotation.PublicApi; import org.opensearch.common.inject.Inject; @@ -137,7 +139,9 @@ public IndicesOptions indicesOptions() { * * @opensearch.internal */ - public static class TransportAction extends TransportClusterManagerNodeAction { + public static class TransportAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataCreateDataStreamService metadataCreateDataStreamService; @@ -179,6 +183,11 @@ protected void clusterManagerOperation(Request request, ClusterState state, Acti protected ClusterBlockException checkBlock(Request request, ClusterState state) { return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); } + + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(request.name); + } } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/datastream/DataStreamsStatsAction.java b/server/src/main/java/org/opensearch/action/admin/indices/datastream/DataStreamsStatsAction.java index fcb13f4091638..e7194583912ee 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/datastream/DataStreamsStatsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/datastream/DataStreamsStatsAction.java @@ -47,6 +47,7 @@ import org.opensearch.cluster.metadata.IndexAbstractionResolver; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.routing.ShardRouting; import org.opensearch.cluster.routing.ShardsIterator; import org.opensearch.cluster.service.ClusterService; @@ -412,16 +413,7 @@ protected ClusterBlockException checkRequestBlock(ClusterState state, Request re @Override protected ShardsIterator shards(ClusterState clusterState, Request request, String[] concreteIndices) { - String[] requestIndices = request.indices(); - if (requestIndices == null || requestIndices.length == 0) { - requestIndices = new String[] { "*" }; - } - List abstractionNames = indexAbstractionResolver.resolveIndexAbstractions( - requestIndices, - request.indicesOptions(), - clusterState.getMetadata(), - true - ); // Always include data streams for data streams stats api + Set abstractionNames = resolvedIndices(request, clusterState, true).local().names(); SortedMap indicesLookup = clusterState.getMetadata().getIndicesLookup(); String[] concreteDatastreamIndices = abstractionNames.stream().flatMap(abstractionName -> { @@ -523,5 +515,26 @@ protected Response newResponse( dataStreamStats ); } + + @Override + public ResolvedIndices resolveIndices(Request request) { + return resolvedIndices(request, clusterService.state(), false); + } + + private ResolvedIndices resolvedIndices(Request request, ClusterState clusterState, boolean throwExceptions) { + String[] requestIndices = request.indices(); + if (requestIndices == null || requestIndices.length == 0) { + requestIndices = new String[] { "*" }; + } + return ResolvedIndices.of( + indexAbstractionResolver.resolveIndexAbstractions( + requestIndices, + request.indicesOptions(), + clusterState.getMetadata(), + true, + throwExceptions + ) + ); + } } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/datastream/DeleteDataStreamAction.java b/server/src/main/java/org/opensearch/action/admin/indices/datastream/DeleteDataStreamAction.java index 9db621b1a5367..91dc4482d12d5 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/datastream/DeleteDataStreamAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/datastream/DeleteDataStreamAction.java @@ -38,6 +38,7 @@ import org.opensearch.action.IndicesRequest; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.ClusterManagerNodeRequest; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; @@ -49,6 +50,7 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.metadata.MetadataDeleteIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterManagerTaskThrottler; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Priority; @@ -169,7 +171,9 @@ public IndicesRequest indices(String... indices) { * * @opensearch.internal */ - public static class TransportAction extends TransportClusterManagerNodeAction { + public static class TransportAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataDeleteIndexService deleteIndexService; private final ClusterManagerTaskThrottler.ThrottlingKey removeDataStreamTaskKey; @@ -235,17 +239,8 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS } static ClusterState removeDataStream(MetadataDeleteIndexService deleteIndexService, ClusterState currentState, Request request) { - Set dataStreams = new HashSet<>(); - Set snapshottingDataStreams = new HashSet<>(); - for (String name : request.names) { - for (String dataStreamName : currentState.metadata().dataStreams().keySet()) { - if (Regex.simpleMatch(name, dataStreamName)) { - dataStreams.add(dataStreamName); - } - } - - snapshottingDataStreams.addAll(SnapshotsService.snapshottingDataStreams(currentState, dataStreams)); - } + Set dataStreams = resolveDataStreams(currentState, request); + Set snapshottingDataStreams = new HashSet<>(SnapshotsService.snapshottingDataStreams(currentState, dataStreams)); if (snapshottingDataStreams.isEmpty() == false) { throw new SnapshotInProgressException( @@ -279,6 +274,23 @@ static ClusterState removeDataStream(MetadataDeleteIndexService deleteIndexServi protected ClusterBlockException checkBlock(Request request, ClusterState state) { return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); } + + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(resolveDataStreams(clusterService.state(), request)); + } + + private static Set resolveDataStreams(ClusterState state, Request request) { + Set dataStreams = new HashSet<>(); + for (String name : request.names) { + for (String dataStreamName : state.metadata().dataStreams().keySet()) { + if (Regex.simpleMatch(name, dataStreamName)) { + dataStreams.add(dataStreamName); + } + } + } + return dataStreams; + } } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/datastream/GetDataStreamAction.java b/server/src/main/java/org/opensearch/action/admin/indices/datastream/GetDataStreamAction.java index 1db4e85887c23..edefdfa2b3759 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/datastream/GetDataStreamAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/datastream/GetDataStreamAction.java @@ -38,6 +38,7 @@ import org.opensearch.action.IndicesRequest; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.ClusterManagerNodeReadRequest; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.AbstractDiffable; @@ -49,6 +50,7 @@ import org.opensearch.cluster.metadata.DataStream; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataIndexTemplateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; import org.opensearch.common.annotation.PublicApi; @@ -292,7 +294,9 @@ public int hashCode() { * * @opensearch.internal */ - public static class TransportAction extends TransportClusterManagerNodeReadAction { + public static class TransportAction extends TransportClusterManagerNodeReadAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportAction.class); @@ -354,6 +358,15 @@ static List getDataStreams(ClusterState clusterState, IndexNameExpre protected ClusterBlockException checkBlock(Request request, ClusterState state) { return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); } + + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of( + getDataStreams(clusterService.state(), indexNameExpressionResolver, request).stream() + .map(DataStream::getName) + .collect(Collectors.toList()) + ); + } } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/delete/TransportDeleteIndexAction.java b/server/src/main/java/org/opensearch/action/admin/indices/delete/TransportDeleteIndexAction.java index d6fc5386aed92..e42e9ed433384 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/delete/TransportDeleteIndexAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/delete/TransportDeleteIndexAction.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; @@ -44,6 +45,7 @@ import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataDeleteIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -55,15 +57,15 @@ import java.io.IOException; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; /** * Delete index action. * * @opensearch.internal */ -public class TransportDeleteIndexAction extends TransportClusterManagerNodeAction { +public class TransportDeleteIndexAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportDeleteIndexAction.class); @@ -120,15 +122,15 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) { - final Set concreteIndices = new HashSet<>(Arrays.asList(indexNameExpressionResolver.concreteIndices(state, request))); - if (concreteIndices.isEmpty()) { + Index[] concreteIndices = resolveIndices(request, state).concreteIndicesAsArray(); + if (concreteIndices.length == 0) { listener.onResponse(new AcknowledgedResponse(true)); return; } DeleteIndexClusterStateUpdateRequest deleteRequest = new DeleteIndexClusterStateUpdateRequest().ackTimeout(request.timeout()) .clusterManagerNodeTimeout(request.clusterManagerNodeTimeout()) - .indices(concreteIndices.toArray(new Index[0])); + .indices(concreteIndices); deleteIndexService.deleteIndices(deleteRequest, new ActionListener() { @@ -139,9 +141,18 @@ public void onResponse(ClusterStateUpdateResponse response) { @Override public void onFailure(Exception t) { - logger.debug(() -> new ParameterizedMessage("failed to delete indices [{}]", concreteIndices), t); + logger.debug(() -> new ParameterizedMessage("failed to delete indices [{}]", Arrays.asList(concreteIndices)), t); listener.onFailure(t); } }); } + + @Override + public ResolvedIndices resolveIndices(DeleteIndexRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(DeleteIndexRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/exists/indices/TransportIndicesExistsAction.java b/server/src/main/java/org/opensearch/action/admin/indices/exists/indices/TransportIndicesExistsAction.java index a298eae1aa865..3a8cfe7c254a4 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/exists/indices/TransportIndicesExistsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/exists/indices/TransportIndicesExistsAction.java @@ -34,11 +34,13 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -54,7 +56,9 @@ * * @opensearch.internal */ -public class TransportIndicesExistsAction extends TransportClusterManagerNodeReadAction { +public class TransportIndicesExistsAction extends TransportClusterManagerNodeReadAction + implements + TransportIndicesResolvingAction { @Inject public TransportIndicesExistsAction( @@ -119,4 +123,9 @@ protected void clusterManagerOperation( } listener.onResponse(new IndicesExistsResponse(exists)); } + + @Override + public ResolvedIndices resolveIndices(IndicesExistsRequest request) { + return ResolvedIndices.of(indexNameExpressionResolver.concreteResolvedIndices(clusterService.state(), request)); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/mapping/get/TransportGetFieldMappingsAction.java b/server/src/main/java/org/opensearch/action/admin/indices/mapping/get/TransportGetFieldMappingsAction.java index 53dbb86233803..c074e368bc131 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/mapping/get/TransportGetFieldMappingsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/mapping/get/TransportGetFieldMappingsAction.java @@ -34,8 +34,10 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -55,7 +57,9 @@ * * @opensearch.internal */ -public class TransportGetFieldMappingsAction extends HandledTransportAction { +public class TransportGetFieldMappingsAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ClusterService clusterService; private final TransportGetFieldMappingsIndexAction shardAction; @@ -78,7 +82,7 @@ public TransportGetFieldMappingsAction( @Override protected void doExecute(Task task, GetFieldMappingsRequest request, final ActionListener listener) { ClusterState clusterState = clusterService.state(); - String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request); + String[] concreteIndices = resolveIndices(request, clusterState).namesOfConcreteIndicesAsArray(); final AtomicInteger indexCounter = new AtomicInteger(); final AtomicInteger completionCounter = new AtomicInteger(concreteIndices.length); final AtomicReferenceArray indexResponses = new AtomicReferenceArray<>(concreteIndices.length); @@ -110,6 +114,15 @@ public void onFailure(Exception e) { } } + private ResolvedIndices.Local.Concrete resolveIndices(GetFieldMappingsRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } + + @Override + public ResolvedIndices resolveIndices(GetFieldMappingsRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + private GetFieldMappingsResponse merge(AtomicReferenceArray indexResponses) { Map> mergedResponses = new HashMap<>(); for (int i = 0; i < indexResponses.length(); i++) { diff --git a/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportAutoPutMappingAction.java b/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportAutoPutMappingAction.java index f50acb6e2d56e..f87f26afc28c0 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportAutoPutMappingAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportAutoPutMappingAction.java @@ -32,6 +32,7 @@ package org.opensearch.action.admin.indices.mapping.put; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; @@ -39,6 +40,7 @@ import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataMappingService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -55,7 +57,9 @@ * * @opensearch.internal */ -public class TransportAutoPutMappingAction extends TransportClusterManagerNodeAction { +public class TransportAutoPutMappingAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataMappingService metadataMappingService; @@ -116,4 +120,8 @@ protected void clusterManagerOperation( TransportPutMappingAction.performMappingUpdate(concreteIndices, request, listener, metadataMappingService); } + @Override + public ResolvedIndices resolveIndices(PutMappingRequest request) { + return ResolvedIndices.of(request.getConcreteIndex()); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingAction.java b/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingAction.java index 4c37592714185..5bb519765fc91 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingAction.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.RequestValidators; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; @@ -45,6 +46,7 @@ import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataMappingService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -68,7 +70,9 @@ * * @opensearch.internal */ -public class TransportPutMappingAction extends TransportClusterManagerNodeAction { +public class TransportPutMappingAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportPutMappingAction.class); @@ -130,7 +134,7 @@ protected void clusterManagerOperation( final ActionListener listener ) { try { - final Index[] concreteIndices = resolveIndices(state, request, indexNameExpressionResolver); + final Index[] concreteIndices = resolveIndices(state, request, indexNameExpressionResolver).concreteIndicesAsArray(); final Optional maybeValidationException = requestValidators.validateRequest(request, state, concreteIndices); if (maybeValidationException.isPresent()) { @@ -150,7 +154,16 @@ protected void clusterManagerOperation( } } - static Index[] resolveIndices(final ClusterState state, PutMappingRequest request, final IndexNameExpressionResolver iner) { + @Override + public ResolvedIndices resolveIndices(PutMappingRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request, indexNameExpressionResolver)); + } + + static ResolvedIndices.Local.Concrete resolveIndices( + final ClusterState state, + PutMappingRequest request, + final IndexNameExpressionResolver iner + ) { if (request.getConcreteIndex() == null) { if (request.writeIndexOnly()) { List indices = new ArrayList<>(); @@ -165,12 +178,12 @@ static Index[] resolveIndices(final ClusterState state, PutMappingRequest reques ) ); } - return indices.toArray(Index.EMPTY_ARRAY); + return ResolvedIndices.Local.Concrete.of(indices.toArray(Index.EMPTY_ARRAY)); } else { - return iner.concreteIndices(state, request); + return iner.concreteResolvedIndices(state, request); } } else { - return new Index[] { request.getConcreteIndex() }; + return ResolvedIndices.Local.Concrete.of(request.getConcreteIndex()); } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/open/TransportOpenIndexAction.java b/server/src/main/java/org/opensearch/action/admin/indices/open/TransportOpenIndexAction.java index 7bd21925eb11d..a1faa865598fb 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/open/TransportOpenIndexAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/open/TransportOpenIndexAction.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ack.OpenIndexClusterStateUpdateResponse; @@ -44,6 +45,7 @@ import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataIndexStateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -60,7 +62,9 @@ * * @opensearch.internal */ -public class TransportOpenIndexAction extends TransportClusterManagerNodeAction { +public class TransportOpenIndexAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportOpenIndexAction.class); @@ -119,7 +123,7 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final Index[] concreteIndices = resolveIndices(request, state).concreteIndicesAsArray(); if (concreteIndices == null || concreteIndices.length == 0) { listener.onResponse(new OpenIndexResponse(true, true)); return; @@ -143,4 +147,13 @@ public void onFailure(Exception t) { } }); } + + @Override + public ResolvedIndices resolveIndices(OpenIndexRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(OpenIndexRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/readonly/TransportAddIndexBlockAction.java b/server/src/main/java/org/opensearch/action/admin/indices/readonly/TransportAddIndexBlockAction.java index 33efa8e691794..75fe3ba63e859 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/readonly/TransportAddIndexBlockAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/readonly/TransportAddIndexBlockAction.java @@ -37,12 +37,14 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataIndexStateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -65,7 +67,9 @@ * * @opensearch.internal */ -public class TransportAddIndexBlockAction extends TransportClusterManagerNodeAction { +public class TransportAddIndexBlockAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportAddIndexBlockAction.class); @@ -135,7 +139,7 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) throws Exception { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final Index[] concreteIndices = resolveIndices(state, request).concreteIndicesAsArray(); if (concreteIndices == null || concreteIndices.length == 0) { listener.onResponse(new AddIndexBlockResponse(true, false, Collections.emptyList())); return; @@ -150,4 +154,13 @@ protected void clusterManagerOperation( delegatedListener.onFailure(t); })); } + + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, AddIndexBlockRequest request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + + @Override + public ResolvedIndices resolveIndices(AddIndexBlockRequest request) { + return ResolvedIndices.of(resolveIndices(this.clusterService.state(), request)); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/resolve/ResolveIndexAction.java b/server/src/main/java/org/opensearch/action/admin/indices/resolve/ResolveIndexAction.java index 1c21b1b9cc52c..11e01f45ff261 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/resolve/ResolveIndexAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/resolve/ResolveIndexAction.java @@ -40,12 +40,14 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexAbstraction; import org.opensearch.cluster.metadata.IndexAbstractionResolver; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; import org.opensearch.common.annotation.PublicApi; @@ -497,7 +499,9 @@ public int hashCode() { * * @opensearch.internal */ - public static class TransportAction extends HandledTransportAction { + public static class TransportAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ThreadPool threadPool; private final ClusterService clusterService; @@ -574,6 +578,34 @@ protected void doExecute(Task task, Request request, final ActionListener remoteClusterIndices = remoteClusterService.groupIndices( + request.indicesOptions(), + request.indices(), + idx -> indexNameExpressionResolver.hasIndexAbstraction(idx, clusterState) + ); + OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); + + if (localIndices != null) { + return ResolvedIndices.of( + indexAbstractionResolver.resolveIndexAbstractions( + localIndices.indices(), + localIndices.indicesOptions(), + clusterState.metadata(), + request.includeDataStreams(), + false + ) + ).withRemoteIndices(remoteClusterIndices); + } else { + return ResolvedIndices.of(Collections.emptySet()).withRemoteIndices(remoteClusterIndices); + } + } + /** * Resolves the specified names and/or wildcard expressions to index abstractions. Returns results in the supplied lists. * diff --git a/server/src/main/java/org/opensearch/action/admin/indices/rollover/TransportRolloverAction.java b/server/src/main/java/org/opensearch/action/admin/indices/rollover/TransportRolloverAction.java index 3124484716706..876cf84c88e5a 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/rollover/TransportRolloverAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/rollover/TransportRolloverAction.java @@ -33,12 +33,14 @@ package org.opensearch.action.admin.indices.rollover; import org.opensearch.OpenSearchException; +import org.opensearch.action.admin.indices.create.CreateIndexAction; import org.opensearch.action.admin.indices.stats.IndicesStatsAction; import org.opensearch.action.admin.indices.stats.IndicesStatsRequest; import org.opensearch.action.admin.indices.stats.IndicesStatsResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.ActiveShardsObserver; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ClusterStateUpdateTask; @@ -47,6 +49,7 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterManagerTaskThrottler; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Nullable; @@ -77,7 +80,9 @@ * * @opensearch.internal */ -public class TransportRolloverAction extends TransportClusterManagerNodeAction { +public class TransportRolloverAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataRolloverService rolloverService; private final ActiveShardsObserver activeShardsObserver; @@ -260,6 +265,35 @@ public void onFailure(Exception e) { }); } + @Override + public ResolvedIndices resolveIndices(RolloverRequest rolloverRequest) { + try { + MetadataRolloverService.RolloverResult preResult = rolloverService.rolloverClusterState( + clusterService.state(), + rolloverRequest.getRolloverTarget(), + rolloverRequest.getNewIndexName(), + rolloverRequest.getCreateIndexRequest(), + Collections.emptyList(), + true, + true + ); + + return ResolvedIndices.of(rolloverRequest.getRolloverTarget()) + .withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of(preResult.rolloverIndexName)); + } catch (Exception e) { + // Exceptions are mostly occurring due to validation errors (e.g. non-existing indices). + // These are not propagated to the caller because it should be still + // the clusterManagerOperation() that should report these failures. + // Instead, we return a basic result which still allows privilege evaluation. + if (rolloverRequest.getNewIndexName() != null) { + return ResolvedIndices.of(rolloverRequest.getRolloverTarget()) + .withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of(rolloverRequest.getNewIndexName())); + } else { + return ResolvedIndices.of(rolloverRequest.getRolloverTarget()); + } + } + } + static Map evaluateConditions( final Collection> conditions, @Nullable final DocsStats docsStats, @@ -291,4 +325,5 @@ static Map evaluateConditions( return evaluateConditions(conditions, docsStats, metadata); } } + } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsAction.java b/server/src/main/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsAction.java index 393c07ba58c5e..ac51d845e9e55 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsAction.java @@ -17,6 +17,8 @@ import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.routing.AllocationId; import org.opensearch.cluster.routing.PlainShardsIterator; import org.opensearch.cluster.routing.RecoverySource; @@ -96,7 +98,7 @@ public TransportPitSegmentsAction( */ @Override protected void doExecute(Task task, PitSegmentsRequest request, ActionListener listener) { - if (request.getPitIds().size() == 1 && "_all".equals(request.getPitIds().get(0))) { + if (isAllPitsRequest(request)) { pitService.getAllPits(ActionListener.wrap(response -> { request.clearAndSetPitIds(response.getPitInfos().stream().map(ListPitInfo::getPitId).collect(Collectors.toList())); super.doExecute(task, request, listener); @@ -106,6 +108,19 @@ protected void doExecute(Task task, PitSegmentsRequest request, ActionListener { +public class TransportGetSettingsAction extends TransportClusterManagerNodeReadAction + implements + TransportIndicesResolvingAction { private final SettingsFilter settingsFilter; private final IndexScopedSettings indexScopedSettings; @@ -112,7 +116,7 @@ private static boolean isFilteredRequest(GetSettingsRequest request) { @Override protected void clusterManagerOperation(GetSettingsRequest request, ClusterState state, ActionListener listener) { - Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + Index[] concreteIndices = resolveIndices(request, state).concreteIndicesAsArray(); final Map indexToSettingsBuilder = new HashMap<>(); final Map indexToDefaultSettingsBuilder = new HashMap<>(); for (Index concreteIndex : concreteIndices) { @@ -141,4 +145,13 @@ protected void clusterManagerOperation(GetSettingsRequest request, ClusterState } listener.onResponse(new GetSettingsResponse(indexToSettingsBuilder, indexToDefaultSettingsBuilder)); } + + @Override + public ResolvedIndices resolveIndices(GetSettingsRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(GetSettingsRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/settings/put/TransportUpdateSettingsAction.java b/server/src/main/java/org/opensearch/action/admin/indices/settings/put/TransportUpdateSettingsAction.java index fe1b139358b30..06a7a8eb8905f 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/settings/put/TransportUpdateSettingsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/settings/put/TransportUpdateSettingsAction.java @@ -36,6 +36,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; @@ -45,6 +46,7 @@ import org.opensearch.cluster.block.ClusterBlocks; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataUpdateSettingsService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -63,7 +65,9 @@ * * @opensearch.internal */ -public class TransportUpdateSettingsAction extends TransportClusterManagerNodeAction { +public class TransportUpdateSettingsAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportUpdateSettingsAction.class); @@ -158,7 +162,7 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final Index[] concreteIndices = resolveIndices(request, state).concreteIndicesAsArray(); UpdateSettingsClusterStateUpdateRequest clusterStateUpdateRequest = new UpdateSettingsClusterStateUpdateRequest().indices( concreteIndices ) @@ -180,4 +184,13 @@ public void onFailure(Exception t) { } }); } + + @Override + public ResolvedIndices resolveIndices(UpdateSettingsRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(UpdateSettingsRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java b/server/src/main/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java index fad504a476511..a455113d6c849 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java @@ -36,6 +36,7 @@ import org.apache.lucene.util.CollectionUtil; import org.opensearch.action.FailedNodeException; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.ClusterManagerMetrics; import org.opensearch.cluster.ClusterState; @@ -45,6 +46,7 @@ import org.opensearch.cluster.health.ClusterShardHealth; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.IndexRoutingTable; @@ -84,7 +86,7 @@ */ public class TransportIndicesShardStoresAction extends TransportClusterManagerNodeReadAction< IndicesShardStoresRequest, - IndicesShardStoresResponse> { + IndicesShardStoresResponse> implements TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportIndicesShardStoresAction.class); @@ -133,7 +135,7 @@ protected void clusterManagerOperation( ) { final RoutingTable routingTables = state.routingTable(); final RoutingNodes routingNodes = state.getRoutingNodes(); - final String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(state, request); + final String[] concreteIndices = resolveIndices(state, request).namesOfConcreteIndicesAsArray(); final Set> shardsToFetch = new HashSet<>(); logger.trace("using cluster state version [{}] to determine shards", state.version()); @@ -161,10 +163,19 @@ protected void clusterManagerOperation( new AsyncShardStoresInfoFetches(state.nodes(), routingNodes, shardsToFetch, listener, clusterManagerMetrics).start(); } + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, IndicesShardStoresRequest request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + + @Override + public ResolvedIndices resolveIndices(IndicesShardStoresRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } + @Override protected ClusterBlockException checkBlock(IndicesShardStoresRequest request, ClusterState state) { return state.blocks() - .indicesBlockedException(ClusterBlockLevel.METADATA_READ, indexNameExpressionResolver.concreteIndexNames(state, request)); + .indicesBlockedException(ClusterBlockLevel.METADATA_READ, resolveIndices(state, request).namesOfConcreteIndicesAsArray()); } /** diff --git a/server/src/main/java/org/opensearch/action/admin/indices/shrink/TransportResizeAction.java b/server/src/main/java/org/opensearch/action/admin/indices/shrink/TransportResizeAction.java index 0927ec234928a..bd77fb967fd43 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/shrink/TransportResizeAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/shrink/TransportResizeAction.java @@ -33,10 +33,12 @@ package org.opensearch.action.admin.indices.shrink; import org.apache.lucene.index.IndexWriter; +import org.opensearch.action.admin.indices.create.CreateIndexAction; import org.opensearch.action.admin.indices.create.CreateIndexClusterStateUpdateRequest; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.stats.IndexShardStats; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; @@ -44,6 +46,7 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataCreateIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; @@ -79,7 +82,9 @@ * * @opensearch.internal */ -public class TransportResizeAction extends TransportClusterManagerNodeAction { +public class TransportResizeAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private final MetadataCreateIndexService createIndexService; private final Client client; @@ -144,8 +149,8 @@ protected void clusterManagerOperation( ) { // there is no need to fetch docs stats for split but we keep it simple and do it anyway for simplicity of the code - final String sourceIndex = indexNameExpressionResolver.resolveDateMathExpression(resizeRequest.getSourceIndex()); - final String targetIndex = indexNameExpressionResolver.resolveDateMathExpression(resizeRequest.getTargetIndexRequest().index()); + final String sourceIndex = resolveSourceIndex(resizeRequest); + final String targetIndex = resolveTargetIndex(resizeRequest); IndexMetadata indexMetadata = state.metadata().index(sourceIndex); if (indexMetadata.getSettings().getAsBoolean(IndexModule.IS_WARM_INDEX_SETTING.getKey(), false) == true) { throw new IllegalStateException("cannot resize warm index"); @@ -227,6 +232,15 @@ protected void clusterManagerOperation( } + /** + * Reports the resize source index as the main resolved index. The target index is reported as sub-action "indices:admin/create". + */ + @Override + public ResolvedIndices resolveIndices(ResizeRequest resizeRequest) { + return ResolvedIndices.of(resolveSourceIndex(resizeRequest)) + .withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of(resolveTargetIndex(resizeRequest))); + } + // static for unittesting this method static CreateIndexClusterStateUpdateRequest prepareCreateIndexRequest( final ResizeRequest resizeRequest, @@ -415,4 +429,12 @@ private static void validateRemoteMigrationModeSettings( } } } + + private String resolveSourceIndex(ResizeRequest resizeRequest) { + return indexNameExpressionResolver.resolveDateMathExpression(resizeRequest.getSourceIndex()); + } + + private String resolveTargetIndex(ResizeRequest resizeRequest) { + return indexNameExpressionResolver.resolveDateMathExpression(resizeRequest.getTargetIndexRequest().index()); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/pause/TransportPauseIngestionAction.java b/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/pause/TransportPauseIngestionAction.java index aacf67974e5fa..92d8fc10efc56 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/pause/TransportPauseIngestionAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/pause/TransportPauseIngestionAction.java @@ -15,30 +15,32 @@ import org.opensearch.action.admin.indices.streamingingestion.state.UpdateIngestionStateResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataStreamingIngestionStateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.index.Index; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; import java.io.IOException; -import java.util.Arrays; /** * Pause ingestion transport action. * * @opensearch.experimental */ -public class TransportPauseIngestionAction extends TransportClusterManagerNodeAction { +public class TransportPauseIngestionAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportPauseIngestionAction.class); @@ -106,20 +108,22 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) throws Exception { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); - if (concreteIndices == null || concreteIndices.length == 0) { + final ResolvedIndices.Local.Concrete concreteIndices = resolveIndices(request, state); + if (concreteIndices.concreteIndices().isEmpty()) { listener.onResponse(new PauseIngestionResponse(true, false, new IngestionStateShardFailure[0], "")); return; } - String[] indices = Arrays.stream(concreteIndices).map(Index::getName).toArray(String[]::new); - UpdateIngestionStateRequest updateIngestionStateRequest = new UpdateIngestionStateRequest(indices, new int[0]); + UpdateIngestionStateRequest updateIngestionStateRequest = new UpdateIngestionStateRequest( + concreteIndices.namesOfConcreteIndicesAsArray(), + new int[0] + ); updateIngestionStateRequest.timeout(request.clusterManagerNodeTimeout()); updateIngestionStateRequest.setIngestionPaused(true); ingestionStateService.updateIngestionPollerState( "pause-ingestion", - concreteIndices, + concreteIndices.concreteIndicesAsArray(), updateIngestionStateRequest, new ActionListener<>() { @@ -144,4 +148,13 @@ public void onFailure(Exception e) { } ); } + + @Override + public ResolvedIndices resolveIndices(PauseIngestionRequest request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(PauseIngestionRequest request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/resume/TransportResumeIngestionAction.java b/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/resume/TransportResumeIngestionAction.java index dea46c7cf6b23..02b58df74dd86 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/resume/TransportResumeIngestionAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/streamingingestion/resume/TransportResumeIngestionAction.java @@ -15,12 +15,14 @@ import org.opensearch.action.admin.indices.streamingingestion.state.UpdateIngestionStateResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.DestructiveOperations; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataStreamingIngestionStateService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; @@ -38,7 +40,9 @@ * * @opensearch.experimental */ -public class TransportResumeIngestionAction extends TransportClusterManagerNodeAction { +public class TransportResumeIngestionAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportResumeIngestionAction.class); @@ -106,7 +110,7 @@ protected void clusterManagerOperation( final ClusterState state, final ActionListener listener ) throws Exception { - final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + final Index[] concreteIndices = resolveIndices(state, request).concreteIndicesAsArray(); if (concreteIndices == null || concreteIndices.length == 0) { listener.onResponse(new ResumeIngestionResponse(true, false, new IngestionStateShardFailure[0], "")); return; @@ -173,4 +177,13 @@ private UpdateIngestionStateRequest getIngestionResumeRequest(String[] indices, return updateIngestionStateRequest; } + + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, ResumeIngestionRequest request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + + @Override + public ResolvedIndices resolveIndices(ResumeIngestionRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } } diff --git a/server/src/main/java/org/opensearch/action/admin/indices/tiering/TransportHotToWarmTieringAction.java b/server/src/main/java/org/opensearch/action/admin/indices/tiering/TransportHotToWarmTieringAction.java index 8d1ab0bb37cdd..fce2146054767 100644 --- a/server/src/main/java/org/opensearch/action/admin/indices/tiering/TransportHotToWarmTieringAction.java +++ b/server/src/main/java/org/opensearch/action/admin/indices/tiering/TransportHotToWarmTieringAction.java @@ -11,12 +11,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterInfoService; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.routing.allocation.DiskThresholdSettings; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.annotation.ExperimentalApi; @@ -39,7 +41,9 @@ * @opensearch.experimental */ @ExperimentalApi -public class TransportHotToWarmTieringAction extends TransportClusterManagerNodeAction { +public class TransportHotToWarmTieringAction extends TransportClusterManagerNodeAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportHotToWarmTieringAction.class); private final ClusterInfoService clusterInfoService; @@ -90,7 +94,7 @@ protected void clusterManagerOperation( ClusterState state, ActionListener listener ) throws Exception { - Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request); + Index[] concreteIndices = resolveIndices(state, request).concreteIndicesAsArray(); if (concreteIndices == null || concreteIndices.length == 0) { listener.onResponse(new HotToWarmTieringResponse(true)); return; @@ -107,4 +111,13 @@ protected void clusterManagerOperation( return; } } + + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, TieringIndexRequest request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + + @Override + public ResolvedIndices resolveIndices(TieringIndexRequest request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } } diff --git a/server/src/main/java/org/opensearch/action/bulk/BulkShardRequest.java b/server/src/main/java/org/opensearch/action/bulk/BulkShardRequest.java index c86f8c87eee15..453d98ea6255f 100644 --- a/server/src/main/java/org/opensearch/action/bulk/BulkShardRequest.java +++ b/server/src/main/java/org/opensearch/action/bulk/BulkShardRequest.java @@ -74,6 +74,10 @@ public BulkItemRequest[] items() { @Override public String[] indices() { + // TODO: The following comment was possibly true on Elasticsearch, but it is not + // true on OpenSearch. Authorization also works with index names if an alias + // grants privileges for that particular index name. + // Thus, question: Shall we change this? // A bulk shard request encapsulates items targeted at a specific shard of an index. // However, items could be targeting aliases of the index, so the bulk request although // targeting a single concrete index shard might do so using several alias names. diff --git a/server/src/main/java/org/opensearch/action/bulk/TransportBulkAction.java b/server/src/main/java/org/opensearch/action/bulk/TransportBulkAction.java index d0972e1276b39..02f72d55df1c8 100644 --- a/server/src/main/java/org/opensearch/action/bulk/TransportBulkAction.java +++ b/server/src/main/java/org/opensearch/action/bulk/TransportBulkAction.java @@ -51,6 +51,7 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.AutoCreateIndex; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.update.TransportUpdateAction; import org.opensearch.action.update.UpdateRequest; import org.opensearch.action.update.UpdateResponse; @@ -64,6 +65,7 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MappingMetadata; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.ValidationException; import org.opensearch.common.inject.Inject; @@ -123,7 +125,9 @@ * * @opensearch.internal */ -public class TransportBulkAction extends HandledTransportAction { +public class TransportBulkAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportBulkAction.class); @@ -552,6 +556,11 @@ private long buildTookInMillis(long startTimeNanos) { return TimeUnit.NANOSECONDS.toMillis(relativeTime() - startTimeNanos); } + @Override + public ResolvedIndices resolveIndices(BulkRequest request) { + return ResolvedIndices.of(request.getIndices()); + } + /** * retries on retryable cluster blocks, resolves item requests, * constructs shard bulk requests and delegates execution to shard bulk action @@ -924,7 +933,7 @@ void executeBulk( * * @opensearch.internal */ - private static class ConcreteIndices { + static class ConcreteIndices { private final ClusterState state; private final IndexNameExpressionResolver indexNameExpressionResolver; private final Map indices = new HashMap<>(); diff --git a/server/src/main/java/org/opensearch/action/bulk/TransportShardBulkAction.java b/server/src/main/java/org/opensearch/action/bulk/TransportShardBulkAction.java index dce69990e5724..e9e9cb7f37532 100644 --- a/server/src/main/java/org/opensearch/action/bulk/TransportShardBulkAction.java +++ b/server/src/main/java/org/opensearch/action/bulk/TransportShardBulkAction.java @@ -47,6 +47,7 @@ import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.ChannelActionListener; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.WriteRequest; import org.opensearch.action.support.replication.ReplicationMode; import org.opensearch.action.support.replication.ReplicationOperation; @@ -62,6 +63,7 @@ import org.opensearch.cluster.action.shard.ShardStateAction; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.MappingMetadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.AllocationId; import org.opensearch.cluster.routing.ShardRouting; @@ -123,7 +125,9 @@ * * @opensearch.internal */ -public class TransportShardBulkAction extends TransportWriteAction { +public class TransportShardBulkAction extends TransportWriteAction + implements + TransportIndicesResolvingAction { public static final String ACTION_NAME = BulkAction.NAME + "[s]"; @@ -218,6 +222,11 @@ protected void handlePrimaryTermValidationRequest( } } + @Override + public ResolvedIndices resolveIndices(BulkShardRequest request) { + return ResolvedIndices.of(request.index()); + } + /** * This action is the primary term validation action which is used for doing primary term validation with replicas. * This is only applicable for TransportShardBulkAction because all writes (delete/update/single write/bulk) diff --git a/server/src/main/java/org/opensearch/action/bulk/TransportSingleItemBulkWriteAction.java b/server/src/main/java/org/opensearch/action/bulk/TransportSingleItemBulkWriteAction.java index 9b901dda24c2b..5b80b507b90e7 100644 --- a/server/src/main/java/org/opensearch/action/bulk/TransportSingleItemBulkWriteAction.java +++ b/server/src/main/java/org/opensearch/action/bulk/TransportSingleItemBulkWriteAction.java @@ -36,10 +36,13 @@ import org.opensearch.action.DocWriteResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.WriteRequest; import org.opensearch.action.support.WriteResponse; import org.opensearch.action.support.replication.ReplicatedWriteRequest; import org.opensearch.action.support.replication.ReplicationResponse; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.tasks.Task; @@ -53,19 +56,24 @@ @Deprecated public abstract class TransportSingleItemBulkWriteAction< Request extends ReplicatedWriteRequest, - Response extends ReplicationResponse & WriteResponse> extends HandledTransportAction { + Response extends ReplicationResponse & WriteResponse> extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final TransportBulkAction bulkAction; + private final IndexNameExpressionResolver indexNameExpressionResolver; protected TransportSingleItemBulkWriteAction( String actionName, TransportService transportService, ActionFilters actionFilters, Writeable.Reader requestReader, - TransportBulkAction bulkAction + TransportBulkAction bulkAction, + IndexNameExpressionResolver indexNameExpressionResolver ) { super(actionName, transportService, actionFilters, requestReader); this.bulkAction = bulkAction; + this.indexNameExpressionResolver = indexNameExpressionResolver; } @Override @@ -73,6 +81,11 @@ protected void doExecute(Task task, final Request request, final ActionListener< bulkAction.execute(task, toSingleItemBulkRequest(request), wrapBulkResponse(listener)); } + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(indexNameExpressionResolver.resolveDateMathExpression(request.index())); + } + public static ActionListener wrapBulkResponse( ActionListener listener ) { @@ -97,4 +110,5 @@ public static BulkRequest toSingleItemBulkRequest(ReplicatedWriteRequest requ request.setRefreshPolicy(WriteRequest.RefreshPolicy.NONE); return bulkRequest; } + } diff --git a/server/src/main/java/org/opensearch/action/delete/TransportDeleteAction.java b/server/src/main/java/org/opensearch/action/delete/TransportDeleteAction.java index 6cbabfec6d763..96f6fe5df94f1 100644 --- a/server/src/main/java/org/opensearch/action/delete/TransportDeleteAction.java +++ b/server/src/main/java/org/opensearch/action/delete/TransportDeleteAction.java @@ -35,6 +35,7 @@ import org.opensearch.action.bulk.TransportBulkAction; import org.opensearch.action.bulk.TransportSingleItemBulkWriteAction; import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.common.inject.Inject; import org.opensearch.transport.TransportService; @@ -49,7 +50,12 @@ public class TransportDeleteAction extends TransportSingleItemBulkWriteAction { @Inject - public TransportDeleteAction(TransportService transportService, ActionFilters actionFilters, TransportBulkAction bulkAction) { - super(DeleteAction.NAME, transportService, actionFilters, DeleteRequest::new, bulkAction); + public TransportDeleteAction( + TransportService transportService, + ActionFilters actionFilters, + TransportBulkAction bulkAction, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super(DeleteAction.NAME, transportService, actionFilters, DeleteRequest::new, bulkAction, indexNameExpressionResolver); } } diff --git a/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesAction.java b/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesAction.java index 28328e4cfc415..8ee24f6acf281 100644 --- a/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesAction.java +++ b/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesAction.java @@ -35,13 +35,14 @@ import org.opensearch.action.OriginalIndices; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.util.concurrent.CountDown; import org.opensearch.core.action.ActionListener; -import org.opensearch.core.common.Strings; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.RemoteClusterAware; @@ -63,7 +64,9 @@ * * @opensearch.internal */ -public class TransportFieldCapabilitiesAction extends HandledTransportAction { +public class TransportFieldCapabilitiesAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ThreadPool threadPool; private final ClusterService clusterService; private final TransportFieldCapabilitiesIndexAction shardAction; @@ -91,21 +94,11 @@ public TransportFieldCapabilitiesAction( protected void doExecute(Task task, FieldCapabilitiesRequest request, final ActionListener listener) { // retrieve the initial timestamp in case the action is a cross cluster search long nowInMillis = request.nowInMillis() == null ? System.currentTimeMillis() : request.nowInMillis(); - final ClusterState clusterState = clusterService.state(); - final Map remoteClusterIndices = remoteClusterService.groupIndices( - request.indicesOptions(), - request.indices(), - idx -> indexNameExpressionResolver.hasIndexAbstraction(idx, clusterState) - ); - final OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); - final String[] concreteIndices; - if (localIndices == null) { - // in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices - concreteIndices = Strings.EMPTY_ARRAY; - } else { - concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, localIndices); - } - final int totalNumRequest = concreteIndices.length + remoteClusterIndices.size(); + ResolvedIndices allResolvedIndices = resolveIndices(request, clusterService.state()); + Set concreteIndices = ((ResolvedIndices.Local.Concrete) allResolvedIndices.local()).namesOfConcreteIndices(); + Map remoteClusterIndices = allResolvedIndices.remote().asClusterToOriginalIndicesMap(); + OriginalIndices localIndices = allResolvedIndices.local().originalIndices(); + final int totalNumRequest = concreteIndices.size() + remoteClusterIndices.size(); final CountDown completionCounter = new CountDown(totalNumRequest); final List indexResponses = Collections.synchronizedList(new ArrayList<>()); final Runnable onResponse = () -> { @@ -171,6 +164,29 @@ public void onFailure(Exception e) { } } + @Override + public ResolvedIndices resolveIndices(FieldCapabilitiesRequest request) { + return resolveIndices(request, clusterService.state()); + } + + private ResolvedIndices resolveIndices(FieldCapabilitiesRequest request, ClusterState clusterState) { + final Map remoteClusterIndices = remoteClusterService.groupIndices( + request.indicesOptions(), + request.indices(), + idx -> indexNameExpressionResolver.hasIndexAbstraction(idx, clusterState) + ); + final OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); + final ResolvedIndices.Local.Concrete concreteIndices; + if (localIndices == null) { + // in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices + concreteIndices = ResolvedIndices.Local.Concrete.empty(); + } else { + concreteIndices = indexNameExpressionResolver.concreteResolvedIndices(clusterState, localIndices); + } + + return ResolvedIndices.of(concreteIndices).withLocalOriginalIndices(localIndices).withRemoteIndices(remoteClusterIndices); + } + private FieldCapabilitiesResponse merge(List indexResponses, boolean includeUnmapped) { String[] indices = indexResponses.stream().map(FieldCapabilitiesIndexResponse::getIndexName).sorted().toArray(String[]::new); final Map> responseMapBuilder = new HashMap<>(); diff --git a/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java b/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java index 52937182e6a63..73cb878d192f8 100644 --- a/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java +++ b/server/src/main/java/org/opensearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java @@ -41,10 +41,12 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.ChannelActionListener; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.FailAwareWeightedRouting; @@ -93,7 +95,7 @@ */ public class TransportFieldCapabilitiesIndexAction extends HandledTransportAction< FieldCapabilitiesIndexRequest, - FieldCapabilitiesIndexResponse> { + FieldCapabilitiesIndexResponse> implements TransportIndicesResolvingAction { private static final Logger logger = LogManager.getLogger(TransportFieldCapabilitiesIndexAction.class); @@ -213,6 +215,11 @@ private ClusterBlockException checkRequestBlock(ClusterState state, String concr return state.blocks().indexBlockedException(ClusterBlockLevel.READ, concreteIndex); } + @Override + public ResolvedIndices resolveIndices(FieldCapabilitiesIndexRequest request) { + return ResolvedIndices.of(request.index()); + } + /** * An action that executes on each shard sequentially until it finds one that can match the provided * {@link FieldCapabilitiesIndexRequest#indexFilter()}. In which case the shard is used diff --git a/server/src/main/java/org/opensearch/action/get/TransportMultiGetAction.java b/server/src/main/java/org/opensearch/action/get/TransportMultiGetAction.java index 8bbfef381aea8..6d528f41a449e 100644 --- a/server/src/main/java/org/opensearch/action/get/TransportMultiGetAction.java +++ b/server/src/main/java/org/opensearch/action/get/TransportMultiGetAction.java @@ -35,10 +35,12 @@ import org.opensearch.action.RoutingMissingException; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.routing.Preference; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; @@ -51,13 +53,16 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * Perform the multi get action. * * @opensearch.internal */ -public class TransportMultiGetAction extends HandledTransportAction { +public class TransportMultiGetAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ClusterService clusterService; private final TransportShardMultiGetAction shardAction; @@ -174,4 +179,9 @@ private void finishHim() { private static MultiGetItemResponse newItemFailure(String index, String id, Exception exception) { return new MultiGetItemResponse(null, new MultiGetResponse.Failure(index, id, exception)); } + + @Override + public ResolvedIndices resolveIndices(MultiGetRequest request) { + return ResolvedIndices.of(request.items.stream().map(MultiGetRequest.Item::index).collect(Collectors.toSet())); + } } diff --git a/server/src/main/java/org/opensearch/action/index/TransportIndexAction.java b/server/src/main/java/org/opensearch/action/index/TransportIndexAction.java index ce32840f6751b..dccf2f720934a 100644 --- a/server/src/main/java/org/opensearch/action/index/TransportIndexAction.java +++ b/server/src/main/java/org/opensearch/action/index/TransportIndexAction.java @@ -35,6 +35,7 @@ import org.opensearch.action.bulk.TransportBulkAction; import org.opensearch.action.bulk.TransportSingleItemBulkWriteAction; import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.common.inject.Inject; import org.opensearch.transport.TransportService; @@ -56,7 +57,12 @@ public class TransportIndexAction extends TransportSingleItemBulkWriteAction { @Inject - public TransportIndexAction(ActionFilters actionFilters, TransportService transportService, TransportBulkAction bulkAction) { - super(IndexAction.NAME, transportService, actionFilters, IndexRequest::new, bulkAction); + public TransportIndexAction( + ActionFilters actionFilters, + TransportService transportService, + TransportBulkAction bulkAction, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super(IndexAction.NAME, transportService, actionFilters, IndexRequest::new, bulkAction, indexNameExpressionResolver); } } diff --git a/server/src/main/java/org/opensearch/action/search/CreatePitController.java b/server/src/main/java/org/opensearch/action/search/CreatePitController.java index 87eb27bdb8255..1b50fb48d5d7e 100644 --- a/server/src/main/java/org/opensearch/action/search/CreatePitController.java +++ b/server/src/main/java/org/opensearch/action/search/CreatePitController.java @@ -15,6 +15,7 @@ import org.opensearch.action.StepListener; import org.opensearch.action.support.GroupedActionListener; import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; @@ -86,11 +87,7 @@ public void executeCreatePit( StepListener createPitListener, ActionListener updatePitIdListener ) { - SearchRequest searchRequest = new SearchRequest(request.getIndices()); - searchRequest.preference(request.getPreference()); - searchRequest.routing(request.getRouting()); - searchRequest.indicesOptions(request.getIndicesOptions()); - searchRequest.allowPartialSearchResults(request.shouldAllowPartialPitCreation()); + SearchRequest searchRequest = request.toSearchRequest(); SearchTask searchTask = searchRequest.createTask( task.getId(), task.getType(), @@ -326,4 +323,8 @@ public void onFailure(Exception e) { } pitService.deletePitContexts(nodeToContextsMap, deleteListener); } + + ResolvedIndices resolveIndices(CreatePitRequest request) { + return transportSearchAction.resolveIndices(request.toSearchRequest()); + } } diff --git a/server/src/main/java/org/opensearch/action/search/CreatePitRequest.java b/server/src/main/java/org/opensearch/action/search/CreatePitRequest.java index 840d4becda714..f6791da13a328 100644 --- a/server/src/main/java/org/opensearch/action/search/CreatePitRequest.java +++ b/server/src/main/java/org/opensearch/action/search/CreatePitRequest.java @@ -196,4 +196,13 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } return builder; } + + SearchRequest toSearchRequest() { + SearchRequest searchRequest = new SearchRequest(this.getIndices()); + searchRequest.preference(this.getPreference()); + searchRequest.routing(this.getRouting()); + searchRequest.indicesOptions(this.getIndicesOptions()); + searchRequest.allowPartialSearchResults(this.shouldAllowPartialPitCreation()); + return searchRequest; + } } diff --git a/server/src/main/java/org/opensearch/action/search/PitService.java b/server/src/main/java/org/opensearch/action/search/PitService.java index 88bd572ab8eaa..9ec896e559fd1 100644 --- a/server/src/main/java/org/opensearch/action/search/PitService.java +++ b/server/src/main/java/org/opensearch/action/search/PitService.java @@ -28,9 +28,11 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -157,7 +159,7 @@ public void onFailure(final Exception e) { } /** - * This method returns indices associated for each pit + * This method returns indices associated for each pit. The result will be a Map that maps pitIds to index names. */ public Map getIndicesForPits(List pitIds) { Map pitToIndicesMap = new HashMap<>(); @@ -167,6 +169,17 @@ public Map getIndicesForPits(List pitIds) { return pitToIndicesMap; } + /** + * This method returns indices associated for each pit. The result will be a Set of all index names. + */ + public Set getIndicesForPitsFlat(Collection pitIds) { + Set result = new HashSet<>(); + for (String pitId : pitIds) { + result.addAll(Arrays.asList(SearchContextId.decode(nodeClient.getNamedWriteableRegistry(), pitId).getActualIndices())); + } + return result; + } + /** * Get all active point in time contexts */ diff --git a/server/src/main/java/org/opensearch/action/search/TransportCreatePitAction.java b/server/src/main/java/org/opensearch/action/search/TransportCreatePitAction.java index baa113997f243..e15d216bdabe0 100644 --- a/server/src/main/java/org/opensearch/action/search/TransportCreatePitAction.java +++ b/server/src/main/java/org/opensearch/action/search/TransportCreatePitAction.java @@ -12,6 +12,8 @@ import org.opensearch.action.StepListener; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.unit.TimeValue; @@ -32,7 +34,9 @@ /** * Transport action for creating PIT reader context */ -public class TransportCreatePitAction extends HandledTransportAction { +public class TransportCreatePitAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { public static final String CREATE_PIT_ACTION = "create_pit"; private final TransportService transportService; @@ -76,6 +80,11 @@ protected void doExecute(Task task, CreatePitRequest request, ActionListener { +public class TransportDeletePitAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final NamedWriteableRegistry namedWriteableRegistry; private final PitService pitService; @@ -48,13 +53,26 @@ public TransportDeletePitAction( */ @Override protected void doExecute(Task task, DeletePitRequest request, ActionListener listener) { - if (request.getPitIds().size() == 1 && "_all".equals(request.getPitIds().get(0))) { + if (isAllPitsRequest(request)) { deleteAllPits(listener); } else { deletePits(listener, request); } } + @Override + public OptionallyResolvedIndices resolveIndices(DeletePitRequest request) { + if (isAllPitsRequest(request)) { + return ResolvedIndices.unknown(); + } else { + return ResolvedIndices.of(this.pitService.getIndicesForPitsFlat(request.getPitIds())); + } + } + + private boolean isAllPitsRequest(DeletePitRequest request) { + return request.getPitIds().size() == 1 && "_all".equals(request.getPitIds().get(0)); + } + /** * Deletes one or more point in time search contexts. */ diff --git a/server/src/main/java/org/opensearch/action/search/TransportSearchAction.java b/server/src/main/java/org/opensearch/action/search/TransportSearchAction.java index 541ed989b35a8..34dbeb7cbac16 100644 --- a/server/src/main/java/org/opensearch/action/search/TransportSearchAction.java +++ b/server/src/main/java/org/opensearch/action/search/TransportSearchAction.java @@ -41,11 +41,13 @@ import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.IndicesOptions; import org.opensearch.action.support.TimeoutTaskCancellationUtility; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.GroupShardsIterator; @@ -135,7 +137,9 @@ * * @opensearch.internal */ -public class TransportSearchAction extends HandledTransportAction { +public class TransportSearchAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { /** The maximum number of shards for a single search request. */ public static final Setting SHARD_COUNT_LIMIT_SETTING = Setting.longSetting( @@ -227,12 +231,12 @@ public TransportSearchAction( private Map buildPerIndexAliasFilter( SearchRequest request, ClusterState clusterState, - Index[] concreteIndices, + ResolvedIndices.Local.Concrete concreteIndices, Map remoteAliasMap ) { final Map aliasFilterMap = new HashMap<>(); final Set indicesAndAliases = indexNameExpressionResolver.resolveExpressions(clusterState, request.indices()); - for (Index index : concreteIndices) { + for (Index index : concreteIndices.concreteIndices()) { clusterState.blocks().indexBlockedRaiseException(ClusterBlockLevel.READ, index.getName()); AliasFilter aliasFilter = searchService.buildAliasFilter(clusterState, index.getName(), indicesAndAliases); assert aliasFilter != null; @@ -505,20 +509,10 @@ private ActionListener buildRewriteListener( searchRequest.source(source); } final ClusterState clusterState = clusterService.state(); - final SearchContextId searchContext; - final Map remoteClusterIndices; - if (searchRequest.pointInTimeBuilder() != null) { - searchContext = SearchContextId.decode(namedWriteableRegistry, searchRequest.pointInTimeBuilder().getId()); - remoteClusterIndices = getIndicesFromSearchContexts(searchContext, searchRequest.indicesOptions()); - } else { - searchContext = null; - remoteClusterIndices = remoteClusterService.groupIndices( - searchRequest.indicesOptions(), - searchRequest.indices(), - idx -> indexNameExpressionResolver.hasIndexAbstraction(idx, clusterState) - ); - } - OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); + final OriginalIndicesAndSearchContextId requestedIndices = extractRequestedIndices(searchRequest, clusterState); + final SearchContextId searchContext = requestedIndices.searchContextId; + final Map remoteClusterIndices = requestedIndices.remoteClusterIndices; + OriginalIndices localIndices = requestedIndices.localOriginalIndices; if (remoteClusterIndices.isEmpty()) { executeLocalSearch( task, @@ -996,11 +990,47 @@ static List getRemoteShardsIteratorFromPointInTime( return remoteShardIterators; } - private Index[] resolveLocalIndices(OriginalIndices localIndices, ClusterState clusterState, SearchTimeProvider timeProvider) { + @Override + public ResolvedIndices resolveIndices(SearchRequest searchRequest) { + ClusterState clusterState = clusterService.state(); + OriginalIndicesAndSearchContextId requestedIndices = extractRequestedIndices(searchRequest, clusterState); + ResolvedIndices.Local.Concrete localConcreteIndices = resolveLocalIndices( + requestedIndices.localOriginalIndices, + clusterState, + new SearchTimeProvider(searchRequest.getOrCreateAbsoluteStartMillis(), System.nanoTime(), System::nanoTime) + ); + + return ResolvedIndices.of(localConcreteIndices).withRemoteIndices(requestedIndices.remoteClusterIndices); + } + + private OriginalIndicesAndSearchContextId extractRequestedIndices(SearchRequest searchRequest, ClusterState clusterState) { + final SearchContextId searchContext; + final Map remoteClusterIndices; + if (searchRequest.pointInTimeBuilder() != null) { + searchContext = SearchContextId.decode(namedWriteableRegistry, searchRequest.pointInTimeBuilder().getId()); + remoteClusterIndices = getIndicesFromSearchContexts(searchContext, searchRequest.indicesOptions()); + } else { + searchContext = null; + remoteClusterIndices = remoteClusterService.groupIndices( + searchRequest.indicesOptions(), + searchRequest.indices(), + idx -> indexNameExpressionResolver.hasIndexAbstraction(idx, clusterState) + ); + } + OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); + return new OriginalIndicesAndSearchContextId(localIndices, remoteClusterIndices, searchContext); + } + + private ResolvedIndices.Local.Concrete resolveLocalIndices( + OriginalIndices localIndices, + ClusterState clusterState, + SearchTimeProvider timeProvider + ) { if (localIndices == null) { - return Index.EMPTY_ARRAY; // don't search on any local index (happens when only remote indices were specified) + // don't search on any local index (happens when only remote indices were specified) + return ResolvedIndices.Local.Concrete.empty(); } - return indexNameExpressionResolver.concreteIndices(clusterState, localIndices, timeProvider.getAbsoluteStartMillis()); + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, localIndices, timeProvider.getAbsoluteStartMillis()); } private void executeSearch( @@ -1040,17 +1070,14 @@ private void executeSearch( searchRequest.pointInTimeBuilder().getKeepAlive() ); } else { - final Index[] indices = resolveLocalIndices(localIndices, clusterState, timeProvider); + ResolvedIndices.Local.Concrete indices = resolveLocalIndices(localIndices, clusterState, timeProvider); Map> routingMap = indexNameExpressionResolver.resolveSearchRouting( clusterState, searchRequest.routing(), searchRequest.indices() ); routingMap = routingMap == null ? Collections.emptyMap() : Collections.unmodifiableMap(routingMap); - concreteLocalIndices = new String[indices.length]; - for (int i = 0; i < indices.length; i++) { - concreteLocalIndices[i] = indices[i].getName(); - } + concreteLocalIndices = indices.namesOfConcreteIndicesAsArray(); Map nodeSearchCounts = searchTransportService.getPendingSearchRequests(); SliceBuilder slice = searchRequest.source() == null ? null : searchRequest.source().slice(); GroupShardsIterator localShardRoutings = clusterService.operationRouting() @@ -1473,4 +1500,9 @@ static List getLocalLocalShardsIteratorFromPointInTime( } return iterators; } + + record OriginalIndicesAndSearchContextId(OriginalIndices localOriginalIndices, Map remoteClusterIndices, + SearchContextId searchContextId) { + + } } diff --git a/server/src/main/java/org/opensearch/action/support/ActionFilter.java b/server/src/main/java/org/opensearch/action/support/ActionFilter.java index e936512004fd2..0c7f5a6e220a0 100644 --- a/server/src/main/java/org/opensearch/action/support/ActionFilter.java +++ b/server/src/main/java/org/opensearch/action/support/ActionFilter.java @@ -57,6 +57,7 @@ void apply( Task task, String action, Request request, + ActionRequestMetadata actionRequestMetadata, ActionListener listener, ActionFilterChain chain ); @@ -72,6 +73,7 @@ public final vo Task task, String action, Request request, + ActionRequestMetadata actionRequestMetadata, ActionListener listener, ActionFilterChain chain ) { diff --git a/server/src/main/java/org/opensearch/action/support/ActionRequestMetadata.java b/server/src/main/java/org/opensearch/action/support/ActionRequestMetadata.java new file mode 100644 index 0000000000000..2f9be658a5aea --- /dev/null +++ b/server/src/main/java/org/opensearch/action/support/ActionRequestMetadata.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.support; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.core.action.ActionResponse; + +/** + * This class can be used to provide metadata about action requests to ActionFilter implementations. + * At the moment, this class provides information about the requested indices of a request, but it can be + * extended to transport further metadata. + */ +public class ActionRequestMetadata { + + /** + * Returns an empty meta data object which will just report unknown results. + */ + public static ActionRequestMetadata empty() { + @SuppressWarnings("unchecked") + ActionRequestMetadata result = (ActionRequestMetadata) EMPTY; + return result; + } + + private static final ActionRequestMetadata EMPTY = new ActionRequestMetadata<>(null, null); + + private final TransportAction transportAction; + private final Request request; + + ActionRequestMetadata(TransportAction transportAction, Request request) { + this.transportAction = transportAction; + this.request = request; + } + + /** + * If the current action request references indices, this method actually referenced indices. That means that any + * expressions or patterns will be resolved. + *

+ * If the request cannot reference indices OR if the respective action does not support resolving of requests, + * this returns an {@link OptionallyResolvedIndices} with unknown = true. If indices can be resolved, actually + * a {@link ResolvedIndices} object will be returned. + */ + public OptionallyResolvedIndices resolvedIndices() { + if (!(transportAction instanceof TransportIndicesResolvingAction)) { + return OptionallyResolvedIndices.unknown(); + } + + // We should not cache and re-use results in this object, as each ActionFilter might modify the request + // and thus change the result + @SuppressWarnings("unchecked") + TransportIndicesResolvingAction indicesResolvingAction = (TransportIndicesResolvingAction) this.transportAction; + return indicesResolvingAction.resolveIndices(request); + } +} diff --git a/server/src/main/java/org/opensearch/action/support/TransportAction.java b/server/src/main/java/org/opensearch/action/support/TransportAction.java index f71347f6f1d07..3cc79a407bd29 100644 --- a/server/src/main/java/org/opensearch/action/support/TransportAction.java +++ b/server/src/main/java/org/opensearch/action/support/TransportAction.java @@ -215,7 +215,7 @@ public void proceed(Task task, String actionName, Request request, ActionListene int i = index.getAndIncrement(); try { if (i < this.action.filters.length) { - this.action.filters[i].apply(task, actionName, request, listener, this); + this.action.filters[i].apply(task, actionName, request, new ActionRequestMetadata<>(action, request), listener, this); } else if (i == this.action.filters.length) { this.action.doExecute(task, request, listener); } else { diff --git a/server/src/main/java/org/opensearch/action/support/TransportIndicesResolvingAction.java b/server/src/main/java/org/opensearch/action/support/TransportIndicesResolvingAction.java new file mode 100644 index 0000000000000..73024a5bbdc0f --- /dev/null +++ b/server/src/main/java/org/opensearch/action/support/TransportIndicesResolvingAction.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.support; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; + +/** + * An additional interface that should be implemented by TransportAction implementations which need to resolve + * IndicesRequests or other action requests which specify indices. This interface allows other components to retrieve + * precise information about the indices an action is going to operate on. This is particularly useful for access + * control implementations, but can be also used for other purposes, such as monitoring, audit logging, etc. + *

+ * Classes implementing this interface should make sure that the reported indices are also actually the indices + * the action will operate on. The best way to achieve this, is to move the index extraction code from the execute + * methods into reusable methods and to depend on these both for execution and reporting. + */ +public interface TransportIndicesResolvingAction { + + /** + * Returns the actual indices the action will operate on, given the specified request and cluster state. + */ + OptionallyResolvedIndices resolveIndices(Request request); +} diff --git a/server/src/main/java/org/opensearch/action/support/broadcast/TransportBroadcastAction.java b/server/src/main/java/org/opensearch/action/support/broadcast/TransportBroadcastAction.java index 8bf8555194976..47ac4edeff86f 100644 --- a/server/src/main/java/org/opensearch/action/support/broadcast/TransportBroadcastAction.java +++ b/server/src/main/java/org/opensearch/action/support/broadcast/TransportBroadcastAction.java @@ -38,9 +38,11 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.TransportActions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.FailAwareWeightedRouting; @@ -73,7 +75,9 @@ public abstract class TransportBroadcastAction< Request extends BroadcastRequest, Response extends BroadcastResponse, ShardRequest extends BroadcastShardRequest, - ShardResponse extends BroadcastShardResponse> extends HandledTransportAction { + ShardResponse extends BroadcastShardResponse> extends HandledTransportAction + implements + TransportIndicesResolvingAction { protected final ClusterService clusterService; protected final TransportService transportService; @@ -107,6 +111,15 @@ protected void doExecute(Task task, Request request, ActionListener li new AsyncBroadcastAction(task, request, listener).start(); } + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + protected ResolvedIndices.Local.Concrete resolveIndices(Request request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } + protected abstract Response newResponse(Request request, AtomicReferenceArray shardsResponses, ClusterState clusterState); protected abstract ShardRequest newShardRequest(int numShards, ShardRouting shard, Request request); @@ -154,7 +167,7 @@ protected AsyncBroadcastAction(Task task, Request request, ActionListener, Response extends BroadcastResponse, - ShardOperationResult extends Writeable> extends HandledTransportAction { + ShardOperationResult extends Writeable> extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ClusterService clusterService; private final TransportService transportService; @@ -269,10 +274,10 @@ protected void nodeOperation(List results, List li new AsyncAction(task, request, listener).start(); } + @Override + public OptionallyResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(resolveConcreteIndices(clusterService.state(), request)); + } + /** * Asynchronous action * @@ -309,7 +319,7 @@ protected AsyncAction(Task task, Request request, ActionListener liste throw globalBlockException; } - String[] concreteIndices = resolveConcreteIndexNames(clusterState, request); + String[] concreteIndices = resolveConcreteIndices(clusterState, request).namesOfConcreteIndicesAsArray(); ClusterBlockException requestBlockException = checkRequestBlock(clusterState, request, concreteIndices); if (requestBlockException != null) { throw requestBlockException; diff --git a/server/src/main/java/org/opensearch/action/support/clustermanager/info/TransportClusterInfoAction.java b/server/src/main/java/org/opensearch/action/support/clustermanager/info/TransportClusterInfoAction.java index 883d2e7429e2d..fec73fc596ae3 100644 --- a/server/src/main/java/org/opensearch/action/support/clustermanager/info/TransportClusterInfoAction.java +++ b/server/src/main/java/org/opensearch/action/support/clustermanager/info/TransportClusterInfoAction.java @@ -32,11 +32,13 @@ package org.opensearch.action.support.clustermanager.info; import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeReadAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.core.action.ActionListener; import org.opensearch.core.action.ActionResponse; @@ -50,7 +52,9 @@ * @opensearch.internal */ public abstract class TransportClusterInfoAction, Response extends ActionResponse> extends - TransportClusterManagerNodeReadAction { + TransportClusterManagerNodeReadAction + implements + TransportIndicesResolvingAction { public TransportClusterInfoAction( String actionName, @@ -74,15 +78,24 @@ protected String executor() { @Override protected ClusterBlockException checkBlock(Request request, ClusterState state) { return state.blocks() - .indicesBlockedException(ClusterBlockLevel.METADATA_READ, indexNameExpressionResolver.concreteIndexNames(state, request)); + .indicesBlockedException(ClusterBlockLevel.METADATA_READ, resolveIndices(state, request).namesOfConcreteIndicesAsArray()); } @Override protected final void clusterManagerOperation(final Request request, final ClusterState state, final ActionListener listener) { - String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(state, request); + String[] concreteIndices = resolveIndices(state, request).namesOfConcreteIndicesAsArray(); doClusterManagerOperation(request, concreteIndices, state, listener); } + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(resolveIndices(clusterService.state(), request)); + } + + private ResolvedIndices.Local.Concrete resolveIndices(ClusterState state, Request request) { + return indexNameExpressionResolver.concreteResolvedIndices(state, request); + } + protected abstract void doClusterManagerOperation( Request request, String[] concreteIndices, diff --git a/server/src/main/java/org/opensearch/action/support/replication/TransportBroadcastReplicationAction.java b/server/src/main/java/org/opensearch/action/support/replication/TransportBroadcastReplicationAction.java index e235adbc162fc..a1c0dd99e3c44 100644 --- a/server/src/main/java/org/opensearch/action/support/replication/TransportBroadcastReplicationAction.java +++ b/server/src/main/java/org/opensearch/action/support/replication/TransportBroadcastReplicationAction.java @@ -36,12 +36,14 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.TransportActions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.broadcast.BroadcastRequest; import org.opensearch.action.support.broadcast.BroadcastResponse; import org.opensearch.action.support.broadcast.BroadcastShardOperationFailedException; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.routing.IndexShardRoutingTable; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.util.concurrent.CountDown; @@ -55,6 +57,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -67,7 +70,9 @@ public abstract class TransportBroadcastReplicationAction< Request extends BroadcastRequest, Response extends BroadcastResponse, ShardRequest extends ReplicationRequest, - ShardResponse extends ReplicationResponse> extends HandledTransportAction { + ShardResponse extends ReplicationResponse> extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final TransportReplicationAction replicatedBroadcastShardAction; private final ClusterService clusterService; @@ -138,6 +143,15 @@ public void onFailure(Exception e) { } } + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(resolveIndices(request, clusterService.state())); + } + + private ResolvedIndices.Local.Concrete resolveIndices(Request request, ClusterState clusterState) { + return indexNameExpressionResolver.concreteResolvedIndices(clusterState, request); + } + protected void shardExecute(Task task, Request request, ShardId shardId, ActionListener shardActionListener) { ShardRequest shardRequest = newShardRequest(request, shardId); shardRequest.setParentTask(clusterService.localNode().getId(), task.getId()); @@ -149,7 +163,7 @@ protected void shardExecute(Task task, Request request, ShardId shardId, ActionL */ protected List shards(Request request, ClusterState clusterState) { List shardIds = new ArrayList<>(); - String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request); + Set concreteIndices = resolveIndices(request, clusterState).namesOfConcreteIndices(); for (String index : concreteIndices) { IndexMetadata indexMetadata = clusterState.metadata().getIndices().get(index); if (indexMetadata != null) { diff --git a/server/src/main/java/org/opensearch/action/support/replication/TransportReplicationAction.java b/server/src/main/java/org/opensearch/action/support/replication/TransportReplicationAction.java index f474ce787992f..f6fe930e5ce20 100644 --- a/server/src/main/java/org/opensearch/action/support/replication/TransportReplicationAction.java +++ b/server/src/main/java/org/opensearch/action/support/replication/TransportReplicationAction.java @@ -43,6 +43,7 @@ import org.opensearch.action.support.ChannelActionListener; import org.opensearch.action.support.TransportAction; import org.opensearch.action.support.TransportActions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.action.support.replication.ReplicationOperation.Replicas; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ClusterStateObserver; @@ -50,6 +51,7 @@ import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.AllocationId; import org.opensearch.cluster.routing.ShardRouting; @@ -111,7 +113,7 @@ public abstract class TransportReplicationAction< Request extends ReplicationRequest, ReplicaRequest extends ReplicationRequest, - Response extends ReplicationResponse> extends TransportAction { + Response extends ReplicationResponse> extends TransportAction implements TransportIndicesResolvingAction { /** * The timeout for retrying replication requests. @@ -318,6 +320,11 @@ protected void doExecute(Task task, Request request, ActionListener li runReroutePhase(task, request, listener, true); } + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.of(request.index); + } + private void runReroutePhase(Task task, Request request, ActionListener listener, boolean initiatedByNodeClient) { try { new ReroutePhase((ReplicationTask) task, request, listener, initiatedByNodeClient).run(); diff --git a/server/src/main/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationAction.java b/server/src/main/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationAction.java index 21d4ba726e86f..0317275bd90d4 100644 --- a/server/src/main/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationAction.java +++ b/server/src/main/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationAction.java @@ -35,11 +35,13 @@ import org.opensearch.action.UnavailableShardsException; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ClusterStateObserver; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.ShardIterator; import org.opensearch.cluster.routing.ShardRouting; @@ -76,7 +78,7 @@ */ public abstract class TransportInstanceSingleOperationAction< Request extends InstanceShardOperationRequest, - Response extends ActionResponse> extends HandledTransportAction { + Response extends ActionResponse> extends HandledTransportAction implements TransportIndicesResolvingAction { protected final ThreadPool threadPool; protected final ClusterService clusterService; @@ -108,6 +110,21 @@ protected void doExecute(Task task, Request request, ActionListener li new AsyncSingleAction(request, listener).start(); } + @Override + public ResolvedIndices resolveIndices(Request request) { + if (request.concreteIndex() != null) { + return ResolvedIndices.of(request.concreteIndex()); + } else { + try { + return ResolvedIndices.of(indexNameExpressionResolver.concreteWriteIndex(clusterService.state(), request).getName()); + } catch (IndexNotFoundException e) { + // We just return the original unresolved expression. The error we encountered here will + // be encountered again in the doStart() method + return ResolvedIndices.of(request.index()); + } + } + } + protected abstract String executor(ShardId shardId); protected abstract void shardOperation(Request request, ActionListener listener); diff --git a/server/src/main/java/org/opensearch/action/support/single/shard/TransportSingleShardAction.java b/server/src/main/java/org/opensearch/action/support/single/shard/TransportSingleShardAction.java index df91559a2f8cb..329c542f39216 100644 --- a/server/src/main/java/org/opensearch/action/support/single/shard/TransportSingleShardAction.java +++ b/server/src/main/java/org/opensearch/action/support/single/shard/TransportSingleShardAction.java @@ -39,10 +39,12 @@ import org.opensearch.action.support.ChannelActionListener; import org.opensearch.action.support.TransportAction; import org.opensearch.action.support.TransportActions; +import org.opensearch.action.support.TransportIndicesResolvingAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.FailAwareWeightedRouting; @@ -76,7 +78,9 @@ * @opensearch.internal */ public abstract class TransportSingleShardAction, Response extends ActionResponse> extends - TransportAction { + TransportAction + implements + TransportIndicesResolvingAction { protected final ThreadPool threadPool; protected final ClusterService clusterService; @@ -154,6 +158,19 @@ protected void resolveRequest(ClusterState state, InternalRequest request) { @Nullable protected abstract ShardsIterator shards(ClusterState state, InternalRequest request); + @Override + public ResolvedIndices resolveIndices(Request request) { + return ResolvedIndices.ofNonNull(resolveToConcreteSingleIndex(request, clusterService.state())); + } + + private String resolveToConcreteSingleIndex(Request request, ClusterState clusterState) { + if (resolveIndex(request)) { + return indexNameExpressionResolver.concreteSingleIndex(clusterState, request).getName(); + } else { + return request.index(); + } + } + /** * Asynchronous single action * @@ -180,12 +197,7 @@ private AsyncSingleAction(Request request, ActionListener listener) { throw blockException; } - String concreteSingleIndex; - if (resolveIndex(request)) { - concreteSingleIndex = indexNameExpressionResolver.concreteSingleIndex(clusterState, request).getName(); - } else { - concreteSingleIndex = request.index(); - } + String concreteSingleIndex = resolveToConcreteSingleIndex(request, clusterState); this.internalRequest = new InternalRequest(request, concreteSingleIndex); resolveRequest(clusterState, internalRequest); diff --git a/server/src/main/java/org/opensearch/action/termvectors/TransportMultiTermVectorsAction.java b/server/src/main/java/org/opensearch/action/termvectors/TransportMultiTermVectorsAction.java index 0364f36106cb0..2348c669cddfc 100644 --- a/server/src/main/java/org/opensearch/action/termvectors/TransportMultiTermVectorsAction.java +++ b/server/src/main/java/org/opensearch/action/termvectors/TransportMultiTermVectorsAction.java @@ -35,9 +35,12 @@ import org.opensearch.action.RoutingMissingException; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.TransportIndicesResolvingAction; +import org.opensearch.action.support.single.shard.SingleShardRequest; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.util.concurrent.AtomicArray; @@ -50,13 +53,16 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * Performs the multi term get operation. * * @opensearch.internal */ -public class TransportMultiTermVectorsAction extends HandledTransportAction { +public class TransportMultiTermVectorsAction extends HandledTransportAction + implements + TransportIndicesResolvingAction { private final ClusterService clusterService; private final TransportShardMultiTermsVectorAction shardAction; @@ -186,4 +192,9 @@ private void finishHim() { }); } } + + @Override + public ResolvedIndices resolveIndices(MultiTermVectorsRequest request) { + return ResolvedIndices.of(request.getRequests().stream().map(SingleShardRequest::index).collect(Collectors.toSet())); + } } diff --git a/server/src/main/java/org/opensearch/cluster/metadata/IndexAbstractionResolver.java b/server/src/main/java/org/opensearch/cluster/metadata/IndexAbstractionResolver.java index a83c778a4b83a..889c70b0a7972 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/IndexAbstractionResolver.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/IndexAbstractionResolver.java @@ -62,14 +62,25 @@ public List resolveIndexAbstractions( Metadata metadata, boolean includeDataStreams ) { - return resolveIndexAbstractions(Arrays.asList(indices), indicesOptions, metadata, includeDataStreams); + return resolveIndexAbstractions(indices, indicesOptions, metadata, includeDataStreams, true); + } + + public List resolveIndexAbstractions( + String[] indices, + IndicesOptions indicesOptions, + Metadata metadata, + boolean includeDataStreams, + boolean throwExceptions + ) { + return resolveIndexAbstractions(Arrays.asList(indices), indicesOptions, metadata, includeDataStreams, throwExceptions); } public List resolveIndexAbstractions( Iterable indices, IndicesOptions indicesOptions, Metadata metadata, - boolean includeDataStreams + boolean includeDataStreams, + boolean throwExceptions ) { final boolean replaceWildcards = indicesOptions.expandWildcardsOpen() || indicesOptions.expandWildcardsClosed(); Set availableIndexAbstractions = metadata.getIndicesLookup().keySet(); @@ -79,7 +90,8 @@ public List resolveIndexAbstractions( metadata, availableIndexAbstractions, replaceWildcards, - includeDataStreams + includeDataStreams, + throwExceptions ); } @@ -89,7 +101,8 @@ public List resolveIndexAbstractions( Metadata metadata, Collection availableIndexAbstractions, boolean replaceWildcards, - boolean includeDataStreams + boolean includeDataStreams, + boolean throwExceptions ) { List finalIndices = new ArrayList<>(); boolean wildcardSeen = false; @@ -110,18 +123,29 @@ public List resolveIndexAbstractions( if (replaceWildcards && Regex.isSimpleMatchPattern(dateMathName)) { // continue indexAbstraction = dateMathName; - } else if (availableIndexAbstractions.contains(dateMathName) - && isIndexVisible(indexAbstraction, dateMathName, indicesOptions, metadata, includeDataStreams, true)) { + } else if (availableIndexAbstractions.contains(dateMathName)) { + if (isIndexVisible(indexAbstraction, dateMathName, indicesOptions, metadata, includeDataStreams, true)) { if (minus) { finalIndices.remove(dateMathName); } else { finalIndices.add(dateMathName); } - } else { + } else if (throwExceptions) { if (indicesOptions.ignoreUnavailable() == false) { throw new IndexNotFoundException(dateMathName); } } + } else { + if (!throwExceptions) { + if (minus) { + finalIndices.remove(dateMathName); + } else { + finalIndices.add(dateMathName); + } + } else if (indicesOptions.ignoreUnavailable() == false) { + throw new IndexNotFoundException(dateMathName); + } + } } if (replaceWildcards && Regex.isSimpleMatchPattern(indexAbstraction)) { @@ -135,7 +159,7 @@ && isIndexVisible(indexAbstraction, authorizedIndex, indicesOptions, metadata, i } if (resolvedIndices.isEmpty()) { // es core honours allow_no_indices for each wildcard expression, we do the same here by throwing index not found. - if (indicesOptions.allowNoIndices() == false) { + if (indicesOptions.allowNoIndices() == false && throwExceptions) { throw new IndexNotFoundException(indexAbstraction); } } else { diff --git a/server/src/main/java/org/opensearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/opensearch/cluster/metadata/IndexNameExpressionResolver.java index abfabdd10c340..3ea2cf97ef358 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/IndexNameExpressionResolver.java @@ -144,7 +144,7 @@ public String[] concreteIndexNamesWithSystemIndexAccess(ClusterState state, Indi * Same as {@link #concreteIndices(ClusterState, IndicesOptions, String...)}, but the index expressions and options * are encapsulated in the specified request and resolves data streams. */ - public Index[] concreteIndices(ClusterState state, IndicesRequest request) { + public ResolvedIndices.Local.Concrete concreteResolvedIndices(ClusterState state, IndicesRequest request) { Context context = new Context( state, request.indicesOptions(), @@ -153,7 +153,15 @@ public Index[] concreteIndices(ClusterState state, IndicesRequest request) { request.includeDataStreams(), isSystemIndexAccessAllowed() ); - return concreteIndices(context, request.indices()); + return concreteResolvedIndices(context, request.indices()); + } + + /** + * Same as {@link #concreteIndices(ClusterState, IndicesOptions, String...)}, but the index expressions and options + * are encapsulated in the specified request and resolves data streams. + */ + public Index[] concreteIndices(ClusterState state, IndicesRequest request) { + return concreteResolvedIndices(state, request).concreteIndicesAsArray(); } /** @@ -199,6 +207,10 @@ public List dataStreamNames(ClusterState state, IndicesOptions options, List dataStreams = wildcardExpressionResolver.resolve(context, finalExpressions); + if (!context.resolutionErrors.isEmpty()) { + throw context.resolutionErrors.getFirst(); + } + return ((dataStreams == null) ? List.of() : dataStreams).stream() .map(x -> state.metadata().getIndicesLookup().get(x)) .filter(Objects::nonNull) @@ -207,6 +219,32 @@ public List dataStreamNames(ClusterState state, IndicesOptions options, .collect(Collectors.toList()); } + /** + * Translates the provided index expression into actual concrete indices, properly deduplicated. + * + * @param state the cluster state containing all the data to resolve to expressions to concrete indices + * @param options defines how the aliases or indices need to be resolved to concrete indices + * @param indexExpressions expressions that can be resolved to alias or index names. + * @return the resolved concrete indices based on the cluster state, indices options and index expressions. Any errors + * encountered during the resolution will be not thrown immediately as an exception, but only when you try + * to access the concrete indices from the ResolvedIndices.Local.Concrete object. You can use the method + * names() to access the names of the requested indices in a way that is safe from thrown exceptions due to + * resolution errors. + */ + public ResolvedIndices.Local.Concrete concreteResolvedIndices(ClusterState state, IndicesOptions options, String... indexExpressions) { + return concreteResolvedIndices(state, options, false, indexExpressions); + } + + public ResolvedIndices.Local.Concrete concreteResolvedIndices( + ClusterState state, + IndicesOptions options, + boolean includeDataStreams, + String... indexExpressions + ) { + Context context = new Context(state, options, false, false, includeDataStreams, isSystemIndexAccessAllowed()); + return concreteResolvedIndices(context, indexExpressions); + } + /** * Translates the provided index expression into actual concrete indices, properly deduplicated. * @@ -225,8 +263,7 @@ public Index[] concreteIndices(ClusterState state, IndicesOptions options, Strin } public Index[] concreteIndices(ClusterState state, IndicesOptions options, boolean includeDataStreams, String... indexExpressions) { - Context context = new Context(state, options, false, false, includeDataStreams, isSystemIndexAccessAllowed()); - return concreteIndices(context, indexExpressions); + return concreteResolvedIndices(state, options, includeDataStreams, indexExpressions).concreteIndicesAsArray(); } /** @@ -235,13 +272,13 @@ public Index[] concreteIndices(ClusterState state, IndicesOptions options, boole * @param state the cluster state containing all the data to resolve to expressions to concrete indices * @param startTime The start of the request where concrete indices is being invoked for * @param request request containing expressions that can be resolved to alias, index, or data stream names. - * @return the resolved concrete indices based on the cluster state, indices options and index expressions - * provided indices options in the context don't allow such a case, or if the final result of the indices resolution - * contains no indices and the indices options in the context don't allow such a case. - * @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided - * indices options in the context don't allow such a case. + * @return the resolved concrete indices based on the cluster state, indices options and index expressions. Any errors + * encountered during the resolution will be not thrown immediately as an exception, but only when you try + * to access the concrete indices from the ResolvedIndices.Local.Concrete object. You can use the method + * names() to access the names of the requested indices in a way that is safe from thrown exceptions due to + * resolution errors. */ - public Index[] concreteIndices(ClusterState state, IndicesRequest request, long startTime) { + public ResolvedIndices.Local.Concrete concreteResolvedIndices(ClusterState state, IndicesRequest request, long startTime) { Context context = new Context( state, request.indicesOptions(), @@ -252,24 +289,40 @@ public Index[] concreteIndices(ClusterState state, IndicesRequest request, long false, isSystemIndexAccessAllowed() ); - return concreteIndices(context, request.indices()); + return concreteResolvedIndices(context, request.indices()); + } + + /** + * Translates the provided index expression into actual concrete indices, properly deduplicated. + * + * @param state the cluster state containing all the data to resolve to expressions to concrete indices + * @param startTime The start of the request where concrete indices is being invoked for + * @param request request containing expressions that can be resolved to alias, index, or data stream names. + * @return the resolved concrete indices based on the cluster state, indices options and index expressions + * provided indices options in the context don't allow such a case, or if the final result of the indices resolution + * contains no indices and the indices options in the context don't allow such a case. + * @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided + * indices options in the context don't allow such a case. + */ + public Index[] concreteIndices(ClusterState state, IndicesRequest request, long startTime) { + return concreteResolvedIndices(state, request, startTime).concreteIndicesAsArray(); } String[] concreteIndexNames(Context context, String... indexExpressions) { - Index[] indexes = concreteIndices(context, indexExpressions); - String[] names = new String[indexes.length]; - for (int i = 0; i < indexes.length; i++) { - names[i] = indexes[i].getName(); - } - return names; + return concreteResolvedIndices(context, indexExpressions).namesOfConcreteIndicesAsArray(); } Index[] concreteIndices(Context context, String... indexExpressions) { + return concreteResolvedIndices(context, indexExpressions).concreteIndicesAsArray(); + } + + ResolvedIndices.Local.Concrete concreteResolvedIndices(Context context, String... indexExpressions) { if (indexExpressions == null || indexExpressions.length == 0) { indexExpressions = new String[] { Metadata.ALL }; } Metadata metadata = context.getState().metadata(); IndicesOptions options = context.getOptions(); + context.resolutionErrors.clear(); // If only one index is specified then whether we fail a request if an index is missing depends on the allow_no_indices // option. At some point we should change this, because there shouldn't be a reason why whether a single index // or multiple indices are specified yield different behaviour. @@ -292,14 +345,18 @@ Index[] concreteIndices(Context context, String... indexExpressions) { infe = new IndexNotFoundException((String) null); } infe.setResources("index_expression", indexExpressions); - throw infe; + return ResolvedIndices.Local.Concrete.empty() + .withResolutionErrors(context.getResolutionErrors()) + .withResolutionErrors(infe); } else { - return Index.EMPTY_ARRAY; + return ResolvedIndices.Local.Concrete.empty(); } } boolean excludedDataStreams = false; final Set concreteIndices = new HashSet<>(expressions.size()); + Set acceptedExpressions = new HashSet<>(expressions.size()); + for (String expression : expressions) { IndexAbstraction indexAbstraction = metadata.getIndicesLookup().get(expression); if (indexAbstraction == null) { @@ -311,34 +368,38 @@ Index[] concreteIndices(Context context, String... indexExpressions) { infe = new IndexNotFoundException(expression); } infe.setResources("index_expression", expression); - throw infe; - } else { - continue; + context.addResolutionError(infe); } + acceptedExpressions.add(expression); + continue; } else if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && context.getOptions().ignoreAliases()) { if (failNoIndices) { - throw aliasesNotSupportedException(expression); - } else { - continue; + context.addResolutionError(aliasesNotSupportedException(expression)); } + continue; } else if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM && context.includeDataStreams() == false) { excludedDataStreams = true; continue; } + acceptedExpressions.add(expression); + if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && context.isResolveToWriteIndex()) { IndexMetadata writeIndex = indexAbstraction.getWriteIndex(); if (writeIndex == null) { - throw new IllegalArgumentException( - "no write index is defined for alias [" - + indexAbstraction.getName() - + "]." - + " The write index may be explicitly disabled using is_write_index=false or the alias points to multiple" - + " indices without one being designated as a write index" + context.addResolutionError( + new IllegalArgumentException( + "no write index is defined for alias [" + + indexAbstraction.getName() + + "]." + + " The write index may be explicitly disabled using is_write_index=false or the alias points to multiple" + + " indices without one being designated as a write index" + ) ); - } - if (addIndex(writeIndex, context)) { - concreteIndices.add(writeIndex.getIndex()); + } else { + if (addIndex(writeIndex, context)) { + concreteIndices.add(writeIndex.getIndex()); + } } } else if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM && context.isResolveToWriteIndex()) { IndexMetadata writeIndex = indexAbstraction.getWriteIndex(); @@ -352,19 +413,26 @@ Index[] concreteIndices(Context context, String... indexExpressions) { for (IndexMetadata indexMetadata : indexAbstraction.getIndices()) { indexNames[i++] = indexMetadata.getIndex().getName(); } - throw new IllegalArgumentException( - indexAbstraction.getType().getDisplayName() - + " [" - + expression - + "] has more than one index associated with it " - + Arrays.toString(indexNames) - + ", can't execute a single index op" + context.addResolutionError( + new IllegalArgumentException( + indexAbstraction.getType().getDisplayName() + + " [" + + expression + + "] has more than one index associated with it " + + Arrays.toString(indexNames) + + ", can't execute a single index op" + ) ); + continue; } for (IndexMetadata index : indexAbstraction.getIndices()) { - if (shouldTrackConcreteIndex(context, options, index)) { - concreteIndices.add(index.getIndex()); + try { + if (shouldTrackConcreteIndex(context, options, index)) { + concreteIndices.add(index.getIndex()); + } + } catch (IndexClosedException e) { + context.addResolutionError(e); } } } @@ -377,10 +445,10 @@ Index[] concreteIndices(Context context, String... indexExpressions) { // Allows callers to handle IndexNotFoundException differently based on whether data streams were excluded. infe.addMetadata(EXCLUDED_DATA_STREAMS_KEY, "true"); } - throw infe; + context.addResolutionError(infe); } checkSystemIndexAccess(context, metadata, concreteIndices, indexExpressions); - return concreteIndices.toArray(new Index[0]); + return ResolvedIndices.Local.Concrete.of(concreteIndices, acceptedExpressions).withResolutionErrors(context.getResolutionErrors()); } private void checkSystemIndexAccess(Context context, Metadata metadata, Set concreteIndices, String[] originalPatterns) { @@ -549,6 +617,9 @@ public Set resolveExpressions(ClusterState state, String... expressions) for (ExpressionResolver expressionResolver : expressionResolvers) { resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions); } + if (!context.resolutionErrors.isEmpty()) { + throw context.resolutionErrors.getFirst(); + } return Collections.unmodifiableSet(new HashSet<>(resolvedExpressions)); } @@ -643,6 +714,9 @@ public Map> resolveSearchRouting(ClusterState state, @Nullab for (ExpressionResolver expressionResolver : expressionResolvers) { resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions); } + if (!context.resolutionErrors.isEmpty()) { + throw context.resolutionErrors.getFirst(); + } // TODO: it appears that this can never be true? if (isAllIndices(resolvedExpressions)) { @@ -816,6 +890,7 @@ public static class Context { private final boolean includeDataStreams; private final boolean preserveDataStreams; private final boolean isSystemIndexAccessAllowed; + private final List resolutionErrors = new ArrayList<>(); public Context(ClusterState state, IndicesOptions options, boolean isSystemIndexAccessAllowed) { this(state, options, System.currentTimeMillis(), isSystemIndexAccessAllowed); @@ -929,6 +1004,36 @@ public boolean isPreserveDataStreams() { public boolean isSystemIndexAccessAllowed() { return isSystemIndexAccessAllowed; } + + /** + * Adds a new error encountered while resolving index resolution. If this method is used, index resolution + * will continue until it is completed. However, after completion The methods concreteIndices(), + * concreteIndexNames(), concreteWriteIndex(), etc. will howe will throw the first resolution error that was + * tracked with this method. The method concreteResolvedIndices() can be used to retrieve the resolution + * result without a thrown exception. + * + * @param resolutionError the encountered resolution error. Ideally this extends OpenSearchException. + */ + public void addResolutionError(RuntimeException resolutionError) { + this.resolutionErrors.add(resolutionError); + } + + /** + * Returns the currently encountered resolution errors. + * Note: This is on purpose package private. External resolvers should have no need to inspect the + * present resolution errors. + */ + List getResolutionErrors() { + return this.resolutionErrors; + } + + RuntimeException getFirstResolutionError() { + if (this.resolutionErrors.isEmpty()) { + return null; + } else { + return this.resolutionErrors.getFirst(); + } + } } /** @@ -996,11 +1101,6 @@ public List resolve(Context context, List expressions) { if (result == null) { return expressions; } - if (result.isEmpty() && !options.allowNoIndices()) { - IndexNotFoundException infe = new IndexNotFoundException((String) null); - infe.setResources("index_or_alias", expressions.toArray(new String[0])); - throw infe; - } return new ArrayList<>(result); } @@ -1010,9 +1110,9 @@ private Set innerResolve(Context context, List expressions, Indi for (int i = 0; i < expressions.size(); i++) { String expression = expressions.get(i); if (Strings.isEmpty(expression)) { - throw indexNotFoundException(expression); + context.addResolutionError(indexNotFoundException(expression)); } - validateAliasOrIndex(expression); + validateAliasOrIndex(context, expression); if (aliasOrIndexExists(context, options, metadata, expression)) { if (result != null) { result.add(expression); @@ -1031,16 +1131,26 @@ private Set innerResolve(Context context, List expressions, Indi result = new HashSet<>(expressions.subList(0, i)); } if (Regex.isSimpleMatchPattern(expression) == false) { - // TODO why does wildcard resolver throw exceptions regarding non wildcarded expressions? This should not be done here. + // The following code enforces the ignoreUnavailable rule for non-wildcard expressions. + // This might seem to be non-intuitive, as we are in the class WildcardExpressionResolver which + // should be responsible only for wildcard expressions. However - given the current architecture - + // there is no good other place to perform this logic: + // - We cannot perform it before the resolver chain, as we still might have expressions which we + // just cannot understand. So, we have to wait for all other resolvers to complete. + // - We cannot perform it efficiently after the resolver chain, as then the WildcardExpressionResolver + // will have expanded wildcards to potentially many indices. If we did these checks then, + // we would do many redundant checks, which would harm performance. + // The only more or less sensible way out might be a new resolver between DateMathExpressioResolver + // and WildcardExpressionResolver, which only checks the non-wildcard index names. if (options.ignoreUnavailable() == false) { IndexAbstraction indexAbstraction = metadata.getIndicesLookup().get(expression); if (indexAbstraction == null) { - throw indexNotFoundException(expression); + context.addResolutionError(indexNotFoundException(expression)); } else if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && options.ignoreAliases()) { - throw aliasesNotSupportedException(expression); + context.addResolutionError(aliasesNotSupportedException(expression)); } else if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM && context.includeDataStreams() == false) { - throw indexNotFoundException(expression); + context.addResolutionError(indexNotFoundException(expression)); } } if (add) { @@ -1060,7 +1170,7 @@ private Set innerResolve(Context context, List expressions, Indi result.removeAll(expand); } if (options.allowNoIndices() == false && matches.isEmpty()) { - throw indexNotFoundException(expression); + context.addResolutionError(indexNotFoundException(expression)); } if (Regex.isSimpleMatchPattern(expression)) { wildcardSeen = true; @@ -1069,13 +1179,13 @@ private Set innerResolve(Context context, List expressions, Indi return result; } - private static void validateAliasOrIndex(String expression) { + private static void validateAliasOrIndex(Context context, String expression) { // Expressions can not start with an underscore. This is reserved for APIs. If the check gets here, the API // does not exist and the path is interpreted as an expression. If the expression begins with an underscore, // throw a specific error that is different from the [[IndexNotFoundException]], which is typically thrown // if the expression can't be found. if (expression.charAt(0) == '_') { - throw new InvalidIndexNameException(expression, "must not start with '_'."); + context.addResolutionError(new InvalidIndexNameException(expression, "must not start with '_'.")); } } diff --git a/server/src/main/java/org/opensearch/cluster/metadata/OptionallyResolvedIndices.java b/server/src/main/java/org/opensearch/cluster/metadata/OptionallyResolvedIndices.java new file mode 100644 index 0000000000000..286d871e06e74 --- /dev/null +++ b/server/src/main/java/org/opensearch/cluster/metadata/OptionallyResolvedIndices.java @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.cluster.metadata; + +import org.opensearch.cluster.ClusterState; +import org.opensearch.common.annotation.ExperimentalApi; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Predicate; + +/** + * A class that possibly encapsulates resolved indices. See the documentation of {@link ResolvedIndices} for a full + * description. + *

+ * In contrast to ResolvedIndices, objects of this class may convey the message "the resolved indices are unknown". + * This may be for several reasons: + *

    + *
  • The information can be only obtained on a master node
  • + *
  • The particular action does not implement the TransportIndicesResolvingAction interface
  • + *
  • It is infeasible to collect the information
  • + *
+ * For authorization purposes, the case of unknown resolved indices should be usually treated as a "requires + * privileges for all indices" case. + *

+ * The class {@link ResolvedIndices} extends OptionallyResolvedIndices. A safe usage pattern would be thus: + *

+ *     if (optionallyResolvedIndices instanceof ResolvedIndices resolvedIndices) {
+ *         Set<String;gt names = resolvedIndices.local().names();
+ *     }
+ * 
+ */ +@ExperimentalApi +public class OptionallyResolvedIndices { + private static final OptionallyResolvedIndices NOT_PRESENT = new OptionallyResolvedIndices(); + + public static OptionallyResolvedIndices unknown() { + return NOT_PRESENT; + } + + public OptionallyResolvedIndices.Local local() { + return Local.NOT_PRESENT; + } + + @Override + public String toString() { + return "ResolvedIndices{unknown=true}"; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof OptionallyResolvedIndices otherResolvedIndices)) { + return false; + } + + return this.local() == otherResolvedIndices.local(); + } + + @Override + public int hashCode() { + return 92; + } + + /** + * Represents the local (i.e., non-remote) indices referenced by the respective request. + */ + @ExperimentalApi + public static class Local { + private static final OptionallyResolvedIndices.Local NOT_PRESENT = new OptionallyResolvedIndices.Local(); + + /** + * Returns all the local names. These names might be indices, aliases or data streams, depending on the usage. + *

+ * In case this is an isUnknown() object, this will return a set of all concrete indices on the cluster (incl. + * hidden and closed indices). This might be a large object. Be prepared to handle such a large object. + *

+ * This method will be only rarely needed. In most cases ResolvedIndices.names() will be sufficient. + */ + public Set names(ClusterState clusterState) { + return clusterState.metadata().getIndicesLookup().keySet(); + } + + /** + * Returns always true. For the unknown resolved indices, we always assume that these are not empty. + */ + public boolean isEmpty() { + return false; + } + + /** + * Returns always true, as we need to assume that an index is potentially contained if the set of indices is unknown. + */ + public boolean contains(String name) { + return true; + } + + /** + * Returns always true, as we need to assume that an index is potentially contained if the set of indices is unknown. + */ + public boolean containsAny(Collection names) { + return true; + } + + /** + * Returns always true, as we need to assume that an index is potentially contained if the set of indices is unknown. + */ + public boolean containsAny(Predicate namePredicate) { + return true; + } + } +} diff --git a/server/src/main/java/org/opensearch/cluster/metadata/ResolvedIndices.java b/server/src/main/java/org/opensearch/cluster/metadata/ResolvedIndices.java new file mode 100644 index 0000000000000..b55214d84602f --- /dev/null +++ b/server/src/main/java/org/opensearch/cluster/metadata/ResolvedIndices.java @@ -0,0 +1,704 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.cluster.metadata; + +import org.opensearch.action.ActionType; +import org.opensearch.action.OriginalIndices; +import org.opensearch.cluster.ClusterState; +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.core.index.Index; +import org.opensearch.transport.RemoteClusterService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A class that encapsulates resolved indices. Resolved indices do not any wildcards or date math expressions. + * However, in contrast to the concept of "concrete indices", resolved indices might not exist yet, or might + * refer to aliases or data streams. + *

+ * ResolvedIndices classes are primarily created by the resolveIndices() method in TransportIndicesResolvingAction. + *

+ * Instances of ResolvedIndices are immutable. It will be not possible to modify the returned collection instances. + * All methods which add/modify elements will return a new ResolvedIndices instance. + *

+ * How resolved indices are obtained depends on the respective action and the associated requests: + *

    + *
  • If a request carries an index expression (i.e, might contain patterns or date math expressions), the index + * expression must be resolved using the appropriate index options; these might be request-specific or action-specific.
  • + *
  • Some requests already carry concrete indices; in these cases, the names of the concrete indices can be + * just taken without further evaluation
  • + *
+ */ +@ExperimentalApi +public class ResolvedIndices extends OptionallyResolvedIndices { + + /** + * Creates a ResolvedIndices object with a ResolvedIndices.Local object that returns the given indicesAliasesAndDataStreams + * as names(). + * + * @param indicesAliasesAndDataStreams An array of strings. The strings must be not null. No further validation will be performed. + */ + public static ResolvedIndices of(String... indicesAliasesAndDataStreams) { + return new ResolvedIndices(new Local(Set.of(indicesAliasesAndDataStreams), null, Map.of()), Remote.EMPTY); + } + + /** + * Creates a ResolvedIndices object with a ResolvedIndices.Local.Concrete object that returns the given indices + * by its concreteIndices() and namesOfConcreteIndices() method. The names() method will return the same names as + * namesOfConcreteIndices(). + */ + public static ResolvedIndices of(Index... indices) { + return new ResolvedIndices( + new Local.Concrete( + Set.of(indices), + Stream.of(indices).map(Index::getName).collect(Collectors.toUnmodifiableSet()), + null, + Map.of(), + List.of() + ), + Remote.EMPTY + ); + } + + /** + * Creates a ResolvedIndices object with a ResolvedIndices.Local object that returns the given indicesAliasesAndDataStreams + * as names(). + * + * @param indicesAliasesAndDataStreams A collection of strings. The strings must be not null. No further validation will be performed. + */ + public static ResolvedIndices of(Collection indicesAliasesAndDataStreams) { + return new ResolvedIndices(new Local(Set.copyOf(indicesAliasesAndDataStreams), null, Map.of()), Remote.EMPTY); + } + + /** + * Creates a ResolvedIndices object where local() returns the given Local object. + */ + public static ResolvedIndices of(Local local) { + return new ResolvedIndices(local, Remote.EMPTY); + } + + /** + * Returns an OptionallyResolvedIndices object which is not a ResolvedIndices object. This represents the case + * that indices cannot be determined. + */ + public static OptionallyResolvedIndices unknown() { + return OptionallyResolvedIndices.unknown(); + } + + /** + * Creates a ResolvedIndices object with a ResolvedIndices.Local object that returns the given indicesAliasesAndDataStreams + * as names(). + * + * @param indicesAliasesAndDataStreams An array of strings. Any string that is null will be not included in this object. No further validation will be performed. + */ + public static ResolvedIndices ofNonNull(String... indicesAliasesAndDataStreams) { + Set indexSet = new HashSet<>(indicesAliasesAndDataStreams.length); + + for (String index : indicesAliasesAndDataStreams) { + if (index != null) { + indexSet.add(index); + } + } + + return new ResolvedIndices(new Local(Collections.unmodifiableSet(indexSet), null, Map.of()), Remote.EMPTY); + } + + private final Local local; + private final Remote remote; + + private ResolvedIndices(Local local, Remote remote) { + this.local = local; + this.remote = remote; + } + + @Override + public Local local() { + return this.local; + } + + public Remote remote() { + return this.remote; + } + + /** + * Creates a new copy of this ResolvedIndices object which has the given remote indices associated. + */ + public ResolvedIndices withRemoteIndices(Map remoteIndices) { + if (remoteIndices.isEmpty()) { + return this; + } + + Map newRemoteIndices = new HashMap<>(remoteIndices); + newRemoteIndices.putAll(this.remote.clusterToOriginalIndicesMap); + + return new ResolvedIndices(this.local, new Remote(Collections.unmodifiableMap(newRemoteIndices))); + } + + /** + * Returns a ResolvedIndices object associated with the given OriginalIndices object for the local part. This is only for + * convenience, no semantics are implied. + */ + public ResolvedIndices withLocalOriginalIndices(OriginalIndices originalIndices) { + return new ResolvedIndices(this.local.withOriginalIndices(originalIndices), this.remote); + } + + /** + * Creates a new copy of this ResolvedIndices object which has the given ResolvedIndices.Local instance associated + * by the given actionType. + */ + public ResolvedIndices withLocalSubActions(ActionType actionType, ResolvedIndices.Local local) { + return new ResolvedIndices(this.local.withSubActions(actionType, local), this.remote); + } + + /** + * Creates a new copy of this ResolvedIndices object which has the given ResolvedIndices.Local instance associated + * by the given actionType. + */ + public ResolvedIndices withLocalSubActions(String actionType, ResolvedIndices.Local local) { + return new ResolvedIndices(this.local.withSubActions(actionType, local), this.remote); + } + + /** + * Returns true if both the local and the remote indices do not refer to any names (existing or non-existing). + */ + public boolean isEmpty() { + return this.local.isEmpty() && this.remote.isEmpty(); + } + + @Override + public String toString() { + return "ResolvedIndices{" + "local=" + local + ", remote=" + remote + '}'; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ResolvedIndices otherResolvedIndices)) { + return false; + } + + return this.local.equals(otherResolvedIndices.local) && this.remote.equals(otherResolvedIndices.remote); + } + + @Override + public int hashCode() { + return this.local.hashCode() + this.remote.hashCode() * 31; + } + + /** + * Represents the local (i.e., non-remote) indices referenced by the respective request. + */ + @ExperimentalApi + public static class Local extends OptionallyResolvedIndices.Local { + protected final Set names; + protected final OriginalIndices originalIndices; + protected final Map subActions; + private Set namesOfIndices; + + /** + * Creates a ResolvedIndices.Local object that returns the given indicesAliasesAndDataStreams as names(). + * + * @param indicesAliasesAndDataStreams A collection of strings. The strings must be not null. No further validation will be performed. + */ + public static Local of(Collection indicesAliasesAndDataStreams) { + return new Local(Set.copyOf(indicesAliasesAndDataStreams), null, Map.of()); + } + + /** + * Creates a ResolvedIndices.Local object that returns the given indicesAliasesAndDataStreams as names(). + * + * @param indicesAliasesAndDataStreams An array of strings. The strings must be not null. No further validation will be performed. + */ + public static Local of(String... indicesAliasesAndDataStreams) { + return of(Arrays.asList(indicesAliasesAndDataStreams)); + } + + /** + * Creates a new instance. + *

+ * Note: The caller of this method must make sure that the passed objects are immutable. + * For this reason, this constructor is private. This contract is guaranteed by the static + * constructor methods in this file. + */ + private Local(Set names, OriginalIndices originalIndices, Map subActions) { + this.names = names; + this.originalIndices = originalIndices; + this.subActions = subActions; + } + + /** + * Returns all the local names. These names might be indices, aliases or data streams, depending on the usage. + * + * @return an unmodifiable set of names of indices, aliases and/or data streams. + */ + public Set names() { + return this.names; + } + + /** + * Returns all the local names. These names might be indices, aliases or data streams, depending on the usage. + * + * @return an array of names of indices, aliases and/or data streams. + */ + public String[] namesAsArray() { + return this.names().toArray(new String[0]); + } + + /** + * Returns all the local names. These names might be indices, aliases or data streams, depending on the usage. + *

+ * In case this is an isUnknown() object, this will return a set of all concrete indices on the cluster (incl. + * hidden and closed indices). This might be a large object. Be prepared to handle such a large object. + *

+ * This method will be only rarely needed. In most cases ResolvedIndices.names() will be sufficient. + */ + @Override + public Set names(ClusterState clusterState) { + return this.names; + } + + /** + * Returns all the local names. Any data streams or aliases will be replaced by the member index names. + * In contrast to namesOfConcreteIndices(), this will keep the names of non-existing indices and will never + * throw an exception. + * + * @return an unmodifiable set of index names + */ + public Set namesOfIndices(ClusterState clusterState) { + Set result = this.namesOfIndices; + + if (result == null) { + Map indicesLookup = clusterState.metadata().getIndicesLookup(); + result = new HashSet<>(this.names.size()); + for (String name : this.names) { + IndexAbstraction indexAbstraction = indicesLookup.get(name); + + if (indexAbstraction == null) { + // We keep the names of non existing indices + result.add(name); + } else if (indexAbstraction instanceof IndexAbstraction.Index) { + // For normal indices, we just keep its name + result.add(name); + } else { + // This is an alias or data stream + for (IndexMetadata index : indexAbstraction.getIndices()) { + result.add(index.getIndex().getName()); + } + } + } + + result = Collections.unmodifiableSet(result); + + this.namesOfIndices = result; + } + + return result; + } + + /** + * Returns any OriginalIndices object associated with this object. + * Note: This is just a convenience method for code that passes around ResolvedIndices objects for managing + * information. This object will be only present if you add it to the object. + */ + public OriginalIndices originalIndices() { + return this.originalIndices; + } + + /** + * Sub-actions can be used to specify indices which play a different role in the action processing. + * For example, the swiss-army-knife IndicesAliases action can delete indices. The subActions() property + * can be used to specify indices with such special roles. + */ + public Map subActions() { + return this.subActions; + } + + /** + * Returns true if there are no local indices. + */ + @Override + public boolean isEmpty() { + return this.names.isEmpty(); + } + + /** + * Returns true if the local names contain an entry with the given name. + */ + @Override + public boolean contains(String name) { + return this.names.contains(name); + } + + /** + * Returns true if the local names contain any of the specified names. + */ + @Override + public boolean containsAny(Collection names) { + return names.stream().anyMatch(this.names::contains); + } + + /** + * Returns true if any of the local names match the given predicate. + */ + @Override + public boolean containsAny(Predicate namePredicate) { + return this.names.stream().anyMatch(namePredicate); + } + + /** + * Returns a ResolvedIndices.Local object associated with the given OriginalIndices object. This is only for + * convenience, no semantics are implied. + */ + public ResolvedIndices.Local withOriginalIndices(OriginalIndices originalIndices) { + return new Local(this.names, originalIndices, this.subActions); + } + + public ResolvedIndices.Local withSubActions(String key, ResolvedIndices.Local local) { + Map subActions = new HashMap<>(this.subActions); + subActions.put(key, local); + return new Local(this.names, this.originalIndices, Collections.unmodifiableMap(subActions)); + } + + public ResolvedIndices.Local withSubActions(ActionType actionType, ResolvedIndices.Local local) { + return this.withSubActions(actionType.name(), local); + } + + @Override + public String toString() { + if (this.subActions.isEmpty()) { + return "{names=" + names() + "}"; + } else { + return "{names=" + names() + ", subActions=" + subActions + '}'; + } + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ResolvedIndices.Local otherLocal)) { + return false; + } + + return this.names.equals(otherLocal.names) && this.subActions.equals(otherLocal.subActions); + } + + @Override + public int hashCode() { + return this.names.hashCode() + this.subActions.hashCode() * 31; + } + + /** + * This is a specialization of the {@link ResolvedIndices.Local} class which additionally + * carries {@link Index} objects. The {@link IndexNameExpressionResolver} produces such objects. + *

+ * Important: The methods that give access to the concrete indices can throw + * exceptions such as {@link org.opensearch.index.IndexNotFoundException} if the concrete indices + * could not be determined. The methods from the Local super class, such as names(), still give + * access to index information. + */ + @ExperimentalApi + public static class Concrete extends Local { + private static final Concrete EMPTY = new Concrete(Set.of(), Set.of(), null, Map.of(), List.of()); + + public static Concrete empty() { + return EMPTY; + } + + public static Concrete of(Index... concreteIndices) { + return new Concrete( + Set.of(concreteIndices), + Stream.of(concreteIndices).map(Index::getName).collect(Collectors.toSet()), + null, + Map.of(), + List.of() + ); + } + + /** + * Creates a new RemoteIndices.Local.Concrete object with the given concrete indices and names. + * This is primarily used by IndexNameExpressionResolver to construct return values, that's why it is + * package private. There may be more names than concrete indices, for example when a referenced index does + * not exist. Thus, names should be usually a super set of concreteIndices. This method does not verify + * that, it is the duty of the caller to make this sure. + */ + static Concrete of(Set concreteIndices, Set names) { + return new Concrete(Set.copyOf(concreteIndices), Set.copyOf(names), null, Map.of(), List.of()); + } + + private final Set concreteIndices; + private final List resolutionErrors; + + private Concrete( + Set concreteIndices, + Set names, + OriginalIndices originalIndices, + Map subActions, + List resolutionErrors + ) { + super(names, originalIndices, subActions); + this.concreteIndices = concreteIndices; + this.resolutionErrors = resolutionErrors; + } + + /** + * Returns the concrete indices. This might throw an exception if there were issues during index resolution. + *

+ * If you need access to index information while avoiding exceptions, you can use the following approaches: + *

    + *
  • Use the names() method instead. This might also contain of non-existing indices or invalid names
  • + *
  • Use withoutResolutionErrors().concreteIndices(). This will only contain existing indices.
  • + *
+ * + * @throws org.opensearch.index.IndexNotFoundException This exception is thrown for several conditions: + * 1. If one of the index expression pointed to a missing index, alias or data stream and the IndicesOptions + * used during resolution do not allow such a case. 2. If the set of resolved concrete indices is empty and + * the IndicesOptions used during resolution do not allow such a case. + * @throws IllegalArgumentException if one of the aliases resolved to multiple indices and + * IndicesOptions.FORBID_ALIASES_TO_MULTIPLE_INDICES was set. + */ + public Set concreteIndices() { + checkResolutionErrors(); + return this.concreteIndices; + } + + /** + * Returns the concrete indices. This might throw an exception if there were issues during index resolution. + *

+ * If you need access to index information while avoiding exceptions, you can use the following approaches: + *

    + *
  • Use the names() method instead. This might also contain of non-existing indices or invalid names
  • + *
  • Use withoutResolutionErrors().concreteIndices(). This will only contain existing indices.
  • + *
+ * + * @throws org.opensearch.index.IndexNotFoundException This exception is thrown for several conditions: + * 1. If one of the index expression pointed to a missing index, alias or data stream and the IndicesOptions + * used during resolution do not allow such a case. 2. If the set of resolved concrete indices is empty and + * the IndicesOptions used during resolution do not allow such a case. + * @throws IllegalArgumentException if one of the aliases resolved to multiple indices and + * IndicesOptions.FORBID_ALIASES_TO_MULTIPLE_INDICES was set. + */ + public Index[] concreteIndicesAsArray() { + return this.concreteIndices().toArray(Index.EMPTY_ARRAY); + } + + /** + * Returns the concrete indices. This might throw an exception if there were issues during index resolution. + *

+ * If you need access to index information while avoiding exceptions, you can use the following approaches: + *

    + *
  • Use the names() method instead. This might also contain of non-existing indices or invalid names
  • + *
  • Use withoutResolutionErrors().concreteIndices(). This will only contain existing indices.
  • + *
+ * + * @throws org.opensearch.index.IndexNotFoundException This exception is thrown for several conditions: + * 1. If one of the index expression pointed to a missing index, alias or data stream and the IndicesOptions + * used during resolution do not allow such a case. 2. If the set of resolved concrete indices is empty and + * the IndicesOptions used during resolution do not allow such a case. + * @throws IllegalArgumentException if one of the aliases resolved to multiple indices and + * IndicesOptions.FORBID_ALIASES_TO_MULTIPLE_INDICES was set. + */ + public Set namesOfConcreteIndices() { + return this.concreteIndices().stream().map(Index::getName).collect(Collectors.toSet()); + } + + /** + * Returns the concrete indices. This might throw an exception if there were issues during index resolution. + *

+ * If you need access to index information while avoiding exceptions, you can use the following approaches: + *

    + *
  • Use the names() method instead. This might also contain of non-existing indices or invalid names
  • + *
  • Use withoutResolutionErrors().concreteIndices(). This will only contain existing indices.
  • + *
+ * + * @throws org.opensearch.index.IndexNotFoundException This exception is thrown for several conditions: + * 1. If one of the index expression pointed to a missing index, alias or data stream and the IndicesOptions + * used during resolution do not allow such a case. 2. If the set of resolved concrete indices is empty and + * the IndicesOptions used during resolution do not allow such a case. + * @throws IllegalArgumentException if one of the aliases resolved to multiple indices and + * IndicesOptions.FORBID_ALIASES_TO_MULTIPLE_INDICES was set. + */ + public String[] namesOfConcreteIndicesAsArray() { + return this.concreteIndices().stream().map(Index::getName).toArray(String[]::new); + } + + @Override + public ResolvedIndices.Local.Concrete withOriginalIndices(OriginalIndices originalIndices) { + return new Concrete(this.concreteIndices, this.names, originalIndices, this.subActions, resolutionErrors); + } + + @Override + public ResolvedIndices.Local withSubActions(String actionType, ResolvedIndices.Local local) { + Map subActions = new HashMap<>(this.subActions); + subActions.put(actionType, local); + return new Concrete(this.concreteIndices, this.names, this.originalIndices, subActions, resolutionErrors); + } + + /** + * Returns a new copy of this ResolvedIndices.Local.Concrete instances which has the given resolutionErrors + * associated. + */ + public ResolvedIndices.Local.Concrete withResolutionErrors(List resolutionErrors) { + if (resolutionErrors.isEmpty()) { + return this; + } else { + return new Concrete( + this.concreteIndices, + this.names(), + originalIndices, + this.subActions, + Stream.concat(this.resolutionErrors.stream(), resolutionErrors.stream()).toList() + ); + } + } + + /** + * Returns a new copy of this ResolvedIndices.Local.Concrete instances which has the given resolutionErrors + * associated. + */ + public ResolvedIndices.Local.Concrete withResolutionErrors(RuntimeException... resolutionErrors) { + return withResolutionErrors(Arrays.asList(resolutionErrors)); + } + + /** + * Returns a new copy of this ResolvedIndices.Local.Concrete instances which does not have any resolutionErrors + * associated. + */ + public ResolvedIndices.Local.Concrete withoutResolutionErrors() { + return new Concrete(this.concreteIndices, this.names(), this.originalIndices, this.subActions, List.of()); + } + + @Override + public boolean equals(Object other) { + if (!super.equals(other)) { + return false; + } + + if (!(other instanceof ResolvedIndices.Local.Concrete otherLocal)) { + return false; + } + + return this.concreteIndices.equals(otherLocal.concreteIndices); + } + + @Override + public int hashCode() { + return super.hashCode() * 31 + this.concreteIndices.hashCode(); + } + + List resolutionErrors() { + return this.resolutionErrors; + } + + private void checkResolutionErrors() { + if (!this.resolutionErrors.isEmpty()) { + throw this.resolutionErrors.getFirst(); + } + } + + @Override + public String toString() { + return "{" + "concreteIndices=" + concreteIndices + ", names=" + names() + ", resolutionErrors=" + resolutionErrors + '}'; + } + } + } + + /** + * Represents the remote indices part of the respective request. + */ + @ExperimentalApi + public static class Remote { + static final Remote EMPTY = new Remote(Collections.emptyMap(), Collections.emptyList()); + + private final Map clusterToOriginalIndicesMap; + private List rawExpressions; + + private Remote(Map clusterToOriginalIndicesMap) { + this.clusterToOriginalIndicesMap = clusterToOriginalIndicesMap; + } + + private Remote(Map clusterToOriginalIndicesMap, List rawExpressions) { + this.clusterToOriginalIndicesMap = clusterToOriginalIndicesMap; + this.rawExpressions = rawExpressions; + } + + /** + * Returns a map with remote cluster names as keys and an OriginalIndices object specifying index expressions as values. + */ + public Map asClusterToOriginalIndicesMap() { + return this.clusterToOriginalIndicesMap; + } + + /** + * Returns the remote clusters in the format ["remote_1:index_1", "remote_1:index_2", "remote_2:index_3"] + */ + public List asRawExpressions() { + List result = this.rawExpressions; + + if (result == null) { + result = this.rawExpressions = buildRawExpressions(); + } + + return result; + } + + /** + * Returns the remote clusters in the format ["remote_1:index_1", "remote_1:index_2", "remote_2:index_3"] + */ + public String[] asRawExpressionsArray() { + return this.asRawExpressions().toArray(new String[0]); + } + + /** + * Returns true if no remote indices are present + */ + public boolean isEmpty() { + return this.clusterToOriginalIndicesMap.isEmpty(); + } + + @Override + public String toString() { + return this.asRawExpressions().toString(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ResolvedIndices.Remote otherRemote)) { + return false; + } + + return this.asRawExpressions().equals(otherRemote.asRawExpressions()); + } + + @Override + public int hashCode() { + return this.asRawExpressions().hashCode(); + } + + private List buildRawExpressions() { + List result = new ArrayList<>(this.clusterToOriginalIndicesMap.size()); + + for (Map.Entry entry : this.clusterToOriginalIndicesMap.entrySet()) { + for (String remoteIndex : entry.getValue().indices()) { + result.add(RemoteClusterService.buildRemoteIndexName(entry.getKey(), remoteIndex)); + } + } + + return Collections.unmodifiableList(result); + } + } +} diff --git a/server/src/main/java/org/opensearch/indices/SystemIndexRegistry.java b/server/src/main/java/org/opensearch/indices/SystemIndexRegistry.java index ab2cbd4ef1a73..7f8068e54c52d 100644 --- a/server/src/main/java/org/opensearch/indices/SystemIndexRegistry.java +++ b/server/src/main/java/org/opensearch/indices/SystemIndexRegistry.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Collections.singletonList; @@ -59,6 +60,10 @@ public static Set matchesSystemIndexPattern(Set indexExpressions return indexExpressions.stream().filter(pattern -> Regex.simpleMatch(SYSTEM_INDEX_PATTERNS, pattern)).collect(Collectors.toSet()); } + public static boolean matchesSystemIndexPattern(String index) { + return Regex.simpleMatch(SYSTEM_INDEX_PATTERNS, index); + } + public static Set matchesPluginSystemIndexPattern(String pluginClassName, Set indexExpressions) { if (!SYSTEM_INDEX_DESCRIPTORS_MAP.containsKey(pluginClassName)) { return Collections.emptySet(); @@ -72,6 +77,19 @@ public static Set matchesPluginSystemIndexPattern(String pluginClassName .collect(Collectors.toSet()); } + /** + * Returns a predicate that can be used to test if an index is registered as system index for the given plugin. + */ + public static Predicate getPluginSystemIndexPredicate(String pluginClassName) { + Collection systemIndexDescriptors = SYSTEM_INDEX_DESCRIPTORS_MAP.get(pluginClassName); + if (systemIndexDescriptors == null || systemIndexDescriptors.isEmpty()) { + return index -> false; + } else { + return index -> systemIndexDescriptors.stream() + .anyMatch(systemIndexDescriptor -> Regex.simpleMatch(systemIndexDescriptor.getIndexPattern(), index)); + } + } + static List getAllDescriptors() { return SYSTEM_INDEX_DESCRIPTORS_MAP.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); } diff --git a/server/src/test/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesActionTests.java new file mode 100644 index 0000000000000..fa703b4ea720e --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/alias/TransportIndicesAliasesActionTests.java @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.alias; + +import org.opensearch.Version; +import org.opensearch.action.RequestValidators; +import org.opensearch.action.admin.indices.delete.DeleteIndexAction; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.AliasMetadata; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.MetadataIndexAliasesService; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TransportIndicesAliasesActionTests extends OpenSearchTestCase { + private static ThreadPool threadPool; + private ClusterService clusterService; + private TransportIndicesAliasesAction subject; + + @BeforeClass + public static void beforeClass() { + threadPool = new TestThreadPool(getTestClass().getName()); + } + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + Metadata.Builder mdBuilder = Metadata.builder() + .put(indexBuilder("index_a1").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_a2").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_b1")) + .put(indexBuilder("index_b2")); + + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + this.clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + + this.subject = new TransportIndicesAliasesAction( + mock(TransportService.class), + clusterService, + threadPool, + mock(MetadataIndexAliasesService.class), + mock(ActionFilters.class), + new IndexNameExpressionResolver(threadPool.getThreadContext()), + new RequestValidators<>(List.of()) + ); + } + + @AfterClass + public static void afterClass() { + ThreadPool.terminate(threadPool, 30, TimeUnit.SECONDS); + threadPool = null; + } + + public void testResolvedIndices_addAliasAction() { + ResolvedIndices resolvedIndices = subject.resolveIndices( + new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index_b*").alias("alias_b")) + ); + assertEquals(ResolvedIndices.of("index_b1", "index_b2", "alias_b"), resolvedIndices); + } + + public void testResolvedIndices_removeAliasAction() { + ResolvedIndices resolvedIndices = subject.resolveIndices( + new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.remove().index("index_a*").alias("alias_*")) + ); + assertEquals(ResolvedIndices.of("index_a1", "index_a2", "alias_a"), resolvedIndices); + } + + public void testResolvedIndices_removeIndexAction() { + ResolvedIndices resolvedIndices = subject.resolveIndices( + new IndicesAliasesRequest().addAliasAction(IndicesAliasesRequest.AliasActions.removeIndex().index("index_a*")) + ); + assertEquals( + ResolvedIndices.of(Collections.emptyList()) + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("index_a1", "index_a2")), + resolvedIndices + ); + } + + private static IndexMetadata.Builder indexBuilder(String index) { + return IndexMetadata.builder(index) + .settings( + settings(Version.CURRENT).put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + ); + } + +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesActionTests.java index 0a223d7b6dc77..ce02562f2df62 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/alias/get/TransportGetAliasesActionTests.java @@ -32,19 +32,29 @@ package org.opensearch.action.admin.indices.alias.get; import org.opensearch.Version; +import org.opensearch.action.support.ActionFilters; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.AliasMetadata; import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.indices.SystemIndices; import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; import java.util.Collections; import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TransportGetAliasesActionTests extends OpenSearchTestCase { private final SystemIndices EMPTY_SYSTEM_INDICES = new SystemIndices(Collections.emptyMap()); @@ -242,6 +252,41 @@ public void testDeprecationWarningEmittedWhenRequestingNonExistingAliasInSystemP ); } + public void testResolveIndices() { + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(systemIndexTestClusterState()); + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.getThreadContext()).thenReturn(threadContext); + + TransportGetAliasesAction action = new TransportGetAliasesAction( + mock(TransportService.class), + clusterService, + threadPool, + mock(ActionFilters.class), + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)), + mock(SystemIndices.class) + ); + + { + // Request an alias: Return the indices in the alias + ResolvedIndices resolvedIndices = action.resolveIndices(new GetAliasesRequest("d")); + assertEquals( + ResolvedIndices.of("c").withLocalSubActions("indices:admin/aliases/get[aliases]", ResolvedIndices.Local.of("d")), + resolvedIndices + ); + } + + { + // Request an index: Return the index itself + ResolvedIndices resolvedIndices = action.resolveIndices(new GetAliasesRequest().indices("c")); + assertEquals( + ResolvedIndices.of("c").withLocalSubActions("indices:admin/aliases/get[aliases]", ResolvedIndices.Local.of("d")), + resolvedIndices + ); + } + } + public ClusterState systemIndexTestClusterState() { return ClusterState.builder(ClusterState.EMPTY_STATE) .metadata( diff --git a/server/src/test/java/org/opensearch/action/admin/indices/create/AutoCreateActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/create/AutoCreateActionTests.java index ff069075c5e34..30fdbe53f4f57 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/create/AutoCreateActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/create/AutoCreateActionTests.java @@ -32,16 +32,30 @@ package org.opensearch.action.admin.indices.create; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.ComposableIndexTemplate; import org.opensearch.cluster.metadata.ComposableIndexTemplate.DataStreamTemplate; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.MetadataCreateDataStreamService; +import org.opensearch.cluster.metadata.MetadataCreateIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; import java.util.Collections; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class AutoCreateActionTests extends OpenSearchTestCase { @@ -83,4 +97,37 @@ public void testResolveAutoCreateDataStreams() { assertThat(result, nullValue()); } + public void testResolveIndices() { + Metadata.Builder mdBuilder = new Metadata.Builder(); + DataStreamTemplate dataStreamTemplate = new DataStreamTemplate(); + mdBuilder.put( + "1", + new ComposableIndexTemplate(Collections.singletonList("datastream-*"), null, null, 20L, null, null, dataStreamTemplate) + ); + + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + + AutoCreateAction.TransportAction action = new AutoCreateAction.TransportAction( + mock(TransportService.class), + clusterService, + mock(ThreadPool.class), + mock(ActionFilters.class), + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)), + mock(MetadataCreateIndexService.class), + mock(MetadataCreateDataStreamService.class) + ); + + ResolvedIndices resolvedIndices = action.resolveIndices(new CreateIndexRequest("")); + assertTrue( + resolvedIndices.toString(), + resolvedIndices.local().containsAny(resolved -> resolved.matches("index-\\d+\\.\\d+\\.\\d+")) + ); + + resolvedIndices = action.resolveIndices(new CreateIndexRequest("datastream-test")); + assertEquals(ResolvedIndices.of("datastream-test"), resolvedIndices); + } + } diff --git a/server/src/test/java/org/opensearch/action/admin/indices/create/TransportCreateIndexActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/create/TransportCreateIndexActionTests.java index 5b5c8e5954157..6e09c452ff53e 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/create/TransportCreateIndexActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/create/TransportCreateIndexActionTests.java @@ -8,11 +8,16 @@ package org.opensearch.action.admin.indices.create; +import org.opensearch.action.admin.indices.alias.Alias; +import org.opensearch.action.admin.indices.alias.IndicesAliasesAction; import org.opensearch.action.support.ActionFilter; import org.opensearch.action.support.ActionFilters; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.MetadataCreateIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.index.mapper.MappingTransformerRegistry; import org.opensearch.test.OpenSearchTestCase; @@ -40,8 +45,6 @@ public class TransportCreateIndexActionTests extends OpenSearchTestCase { private ThreadPool threadPool; @Mock private MetadataCreateIndexService createIndexService; - @Mock - private IndexNameExpressionResolver indexNameExpressionResolver; @Mock private MappingTransformerRegistry mappingTransformerRegistry; @@ -66,14 +69,12 @@ public void setup() { threadPool, createIndexService, actionFilters, - indexNameExpressionResolver, + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)), mappingTransformerRegistry ); } public void testClusterManagerOperation_usesTransformedMapping() { - when(indexNameExpressionResolver.resolveDateMathExpression(any())).thenReturn("test-index"); - // Arrange: Create a test request final CreateIndexRequest request = new CreateIndexRequest("test-index"); request.mapping("{\"properties\": {\"field\": {\"type\": \"text\"}}}"); @@ -83,11 +84,7 @@ public void testClusterManagerOperation_usesTransformedMapping() { // Capture ActionListener passed to applyTransformers final ArgumentCaptor> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class); - doNothing().when(mappingTransformerRegistry).applyTransformers( - anyString(), - any(), - listenerCaptor.capture() - ); + doNothing().when(mappingTransformerRegistry).applyTransformers(anyString(), any(), listenerCaptor.capture()); // Act: Call the method action.clusterManagerOperation(request, clusterState, responseListener); @@ -96,12 +93,22 @@ public void testClusterManagerOperation_usesTransformedMapping() { listenerCaptor.getValue().onResponse(transformedMapping); // Assert: Capture request sent to createIndexService - ArgumentCaptor updateRequestCaptor = - ArgumentCaptor.forClass(CreateIndexClusterStateUpdateRequest.class); + ArgumentCaptor updateRequestCaptor = ArgumentCaptor.forClass( + CreateIndexClusterStateUpdateRequest.class + ); verify(createIndexService, times(1)).createIndex(updateRequestCaptor.capture(), any()); // Ensure transformed mapping is passed correctly CreateIndexClusterStateUpdateRequest capturedRequest = updateRequestCaptor.getValue(); assertEquals(transformedMapping, capturedRequest.mappings()); } + + public void testResolveIndices() { + assertEquals(ResolvedIndices.of("test_index"), action.resolveIndices(new CreateIndexRequest("test_index"))); + assertEquals( + ResolvedIndices.of("test_index").withLocalSubActions(IndicesAliasesAction.INSTANCE, ResolvedIndices.Local.of("test_alias")), + action.resolveIndices(new CreateIndexRequest("test_index").alias(new Alias("test_alias"))) + ); + } + } diff --git a/server/src/test/java/org/opensearch/action/admin/indices/datastream/DataStreamStatsActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/datastream/DataStreamStatsActionTests.java new file mode 100644 index 0000000000000..4874ada2aa2b2 --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/datastream/DataStreamStatsActionTests.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.datastream; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.DataStream; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.indices.IndicesService; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.transport.TransportService; + +import java.util.List; + +import static org.opensearch.cluster.DataStreamTestHelper.createBackingIndex; +import static org.opensearch.cluster.DataStreamTestHelper.createTimestampField; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DataStreamStatsActionTests extends OpenSearchTestCase { + public void testResolveIndices() throws Exception { + String dataStreamName = "datastream-test"; + + IndexMetadata index1 = createBackingIndex(dataStreamName, 1).build(); + IndexMetadata index2 = createBackingIndex(dataStreamName, 2).build(); + + Metadata.Builder mdBuilder = Metadata.builder() + .put(index1, false) + .put(index2, false) + .put(new DataStream(dataStreamName, createTimestampField("@timestamp"), List.of(index1.getIndex(), index2.getIndex()), 2)); + + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + + DataStreamsStatsAction.TransportAction action = new DataStreamsStatsAction.TransportAction( + clusterService, + mock(TransportService.class), + mock(IndicesService.class), + mock(ActionFilters.class), + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)) + ); + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new DataStreamsStatsAction.Request().indices("datastream-*")); + assertEquals(ResolvedIndices.of("datastream-test"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new DataStreamsStatsAction.Request()); + assertEquals(ResolvedIndices.of("datastream-test"), resolvedIndices); + } + } +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/datastream/GetDataStreamActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/datastream/GetDataStreamActionTests.java new file mode 100644 index 0000000000000..76f2ec55625b8 --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/datastream/GetDataStreamActionTests.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.datastream; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.DataStream; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +import java.util.List; + +import static org.opensearch.cluster.DataStreamTestHelper.createBackingIndex; +import static org.opensearch.cluster.DataStreamTestHelper.createTimestampField; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GetDataStreamActionTests extends OpenSearchTestCase { + public void testResolveIndices() { + String dataStreamName = "datastream-test"; + + IndexMetadata index1 = createBackingIndex(dataStreamName, 1).build(); + IndexMetadata index2 = createBackingIndex(dataStreamName, 2).build(); + + Metadata.Builder mdBuilder = Metadata.builder() + .put(index1, false) + .put(index2, false) + .put(new DataStream(dataStreamName, createTimestampField("@timestamp"), List.of(index1.getIndex(), index2.getIndex()), 2)); + + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + + GetDataStreamAction.TransportAction action = new GetDataStreamAction.TransportAction( + mock(TransportService.class), + clusterService, + mock(ThreadPool.class), + mock(ActionFilters.class), + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)) + ); + + ResolvedIndices resolvedIndices = action.resolveIndices(new GetDataStreamAction.Request(new String[] { "datastream-*" })); + assertEquals(ResolvedIndices.of("datastream-test"), resolvedIndices); + } +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/get/GetIndexActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/get/GetIndexActionTests.java index 67d2163affd28..29f3698fdb2fa 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/get/GetIndexActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/get/GetIndexActionTests.java @@ -38,6 +38,7 @@ import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.Context; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Settings; @@ -199,5 +200,10 @@ public Index[] concreteIndices(ClusterState state, IndicesRequest request) { } return out; } + + @Override + public ResolvedIndices.Local.Concrete concreteResolvedIndices(ClusterState state, IndicesRequest request) { + return ResolvedIndices.Local.Concrete.of(concreteIndices(state, request)); + } } } diff --git a/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/PutMappingRequestTests.java b/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/PutMappingRequestTests.java index 377e2bd0c9397..201fa6c087364 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/PutMappingRequestTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/PutMappingRequestTests.java @@ -184,7 +184,7 @@ public void testResolveIndicesWithWriteIndexOnlyAndDataStreamsAndWriteAliases() cs, request, new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)) - ); + ).concreteIndicesAsArray(); List indexNames = Arrays.stream(indices).map(Index::getName).collect(Collectors.toList()); IndexAbstraction expectedDs = cs.metadata().getIndicesLookup().get("foo"); // should resolve the data stream and each alias to their respective write indices @@ -212,7 +212,7 @@ public void testResolveIndicesWithoutWriteIndexOnlyAndDataStreamsAndWriteAliases cs, request, new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)) - ); + ).concreteIndicesAsArray(); List indexNames = Arrays.stream(indices).map(Index::getName).collect(Collectors.toList()); IndexAbstraction expectedDs = cs.metadata().getIndicesLookup().get("foo"); List expectedIndices = expectedDs.getIndices().stream().map(im -> im.getIndex().getName()).collect(Collectors.toList()); @@ -242,7 +242,7 @@ public void testResolveIndicesWithWriteIndexOnlyAndDataStreamAndIndex() { cs, request, new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)) - ); + ).concreteIndicesAsArray(); List indexNames = Arrays.stream(indices).map(Index::getName).collect(Collectors.toList()); IndexAbstraction expectedDs = cs.metadata().getIndicesLookup().get("foo"); List expectedIndices = expectedDs.getIndices().stream().map(im -> im.getIndex().getName()).collect(Collectors.toList()); diff --git a/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingActionTests.java index f1ed0a09230bf..77d7559e5534f 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/mapping/put/TransportPutMappingActionTests.java @@ -8,14 +8,24 @@ package org.opensearch.action.admin.indices.mapping.put; +import org.opensearch.Version; import org.opensearch.action.RequestValidators; import org.opensearch.action.support.ActionFilter; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.clustermanager.AcknowledgedResponse; +import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.AliasMetadata; +import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.metadata.MetadataMappingService; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; +import org.opensearch.core.index.Index; import org.opensearch.core.xcontent.MediaTypeRegistry; import org.opensearch.index.mapper.MappingTransformerRegistry; import org.opensearch.test.OpenSearchTestCase; @@ -30,6 +40,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,8 +52,6 @@ public class TransportPutMappingActionTests extends OpenSearchTestCase { private ActionFilters actionFilters; @Mock private ThreadPool threadPool; - @Mock - private IndexNameExpressionResolver indexNameExpressionResolver; @Mock private MetadataMappingService metadataMappingService; @@ -53,7 +62,6 @@ public class TransportPutMappingActionTests extends OpenSearchTestCase { @Mock private RequestValidators requestValidators; - @Mock private ClusterState clusterState; @Mock @@ -65,11 +73,31 @@ public class TransportPutMappingActionTests extends OpenSearchTestCase { public void setup() { MockitoAnnotations.openMocks(this); + IndexMetadata.Builder index = IndexMetadata.builder("index") + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexA1 = IndexMetadata.builder("index_a1") + .putAlias(AliasMetadata.builder("alias_a").build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexA2 = IndexMetadata.builder("index_a2") + .putAlias(AliasMetadata.builder("alias_a").writeIndex(true).build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(Metadata.builder().put(index).put(indexA1).put(indexA2)).build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)); + ActionFilter[] emptyActionFilters = new ActionFilter[] {}; when(actionFilters.filters()).thenReturn(emptyActionFilters); action = new TransportPutMappingAction( transportService, - null, // ClusterService not needed for this test + clusterService, threadPool, metadataMappingService, actionFilters, @@ -107,4 +135,25 @@ public void testClusterManagerOperation_transformedMappingUsed() { PutMappingClusterStateUpdateRequest capturedRequest = updateRequestCaptor.getValue(); assertEquals(transformedMapping, capturedRequest.source()); } + + public void testResolveIndices() { + { + ResolvedIndices resolvedIndices = action.resolveIndices(new PutMappingRequest("index_a*")); + assertEquals(ResolvedIndices.of("index_a1", "index_a2"), resolvedIndices); + } + { + ResolvedIndices resolvedIndices = action.resolveIndices(new PutMappingRequest("alias_a")); + assertEquals(ResolvedIndices.of("alias_a"), resolvedIndices); + } + { + ResolvedIndices resolvedIndices = action.resolveIndices(new PutMappingRequest("alias_a").writeIndexOnly(true)); + assertEquals(ResolvedIndices.of("index_a2"), resolvedIndices); + } + { + ResolvedIndices resolvedIndices = action.resolveIndices( + new PutMappingRequest().setConcreteIndex(new Index("index_c", "index_c_uuid")) + ); + assertEquals(ResolvedIndices.of("index_c"), resolvedIndices); + } + } } diff --git a/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexActionTests.java new file mode 100644 index 0000000000000..9a4f333fe23cc --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexActionTests.java @@ -0,0 +1,133 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.resolve; + +import org.opensearch.Version; +import org.opensearch.action.OriginalIndices; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.IndicesOptions; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.AliasMetadata; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.telemetry.tracing.noop.NoopTracer; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.test.transport.MockTransportService; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.RemoteClusterConnectionTests; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ResolveIndexActionTests extends OpenSearchTestCase { + private final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + @Override + public void tearDown() throws Exception { + super.tearDown(); + ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS); + } + + public void testResolveIndices() { + IndexMetadata.Builder indexA1 = IndexMetadata.builder("index_a1") + .putAlias(AliasMetadata.builder("alias_a").build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexA2 = IndexMetadata.builder("index_a2") + .putAlias(AliasMetadata.builder("alias_a").build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metadata(Metadata.builder().put(indexA1).put(indexA2)) + .build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)); + + Settings.Builder settingsBuilder = Settings.builder(); + try ( + MockTransportService remoteSeedTransport = RemoteClusterConnectionTests.startTransport( + "node_remote", + Collections.emptyList(), + Version.CURRENT, + threadPool + ) + ) { + DiscoveryNode remoteSeedNode = remoteSeedTransport.getLocalDiscoNode(); + settingsBuilder.put("cluster.remote.remote1.seeds", remoteSeedNode.getAddress().toString()); + + try ( + MockTransportService transportService = MockTransportService.createNewService( + settingsBuilder.build(), + Version.CURRENT, + threadPool, + NoopTracer.INSTANCE + ) + ) { + transportService.start(); + transportService.acceptIncomingRequests(); + + ResolveIndexAction.TransportAction action = new ResolveIndexAction.TransportAction( + transportService, + clusterService, + threadPool, + mock(ActionFilters.class), + indexNameExpressionResolver + ); + + // Actual test cases start here: + { + ResolvedIndices resolvedIndices = action.resolveIndices(new ResolveIndexAction.Request(new String[] { "index_*" })); + assertEquals(ResolvedIndices.of("index_a1", "index_a2"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new ResolveIndexAction.Request(new String[] { "alias_a" })); + assertEquals(ResolvedIndices.of("alias_a"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices( + new ResolveIndexAction.Request(new String[] { "index_non_existing" }) + ); + assertEquals(ResolvedIndices.of("index_non_existing"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices( + new ResolveIndexAction.Request(new String[] { "index_*", "remote1:remote_index" }) + ); + assertEquals( + ResolvedIndices.of("index_a1", "index_a2") + .withRemoteIndices( + Map.of("remote1", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN)) + ), + resolvedIndices + ); + } + + } + } + } +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexTests.java b/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexTests.java index 8fbe46be856ba..6f7d82333f974 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/resolve/ResolveIndexTests.java @@ -199,7 +199,8 @@ public void testResolveHiddenProperlyWithDateMath() { metadata, asList("logs-pgsql-prod-" + todaySuffix, "logs-pgsql-prod-" + tomorrowSuffix), randomBoolean(), - randomBoolean() + randomBoolean(), + true ); assertThat(resolvedIndices.size(), is(1)); assertThat(resolvedIndices.get(0), oneOf("logs-pgsql-prod-" + todaySuffix, "logs-pgsql-prod-" + tomorrowSuffix)); diff --git a/server/src/test/java/org/opensearch/action/admin/indices/rollover/TransportRolloverActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/rollover/TransportRolloverActionTests.java index 6cef1049b3b50..6ff7c3b722056 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/rollover/TransportRolloverActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/rollover/TransportRolloverActionTests.java @@ -34,6 +34,7 @@ import org.opensearch.Version; import org.opensearch.action.ActionRequest; +import org.opensearch.action.admin.indices.create.CreateIndexAction; import org.opensearch.action.admin.indices.stats.CommonStats; import org.opensearch.action.admin.indices.stats.IndexStats; import org.opensearch.action.admin.indices.stats.IndicesStatsAction; @@ -50,6 +51,7 @@ import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.metadata.MetadataCreateIndexService; import org.opensearch.cluster.metadata.MetadataIndexAliasesService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.RecoverySource; import org.opensearch.cluster.routing.ShardRouting; @@ -58,6 +60,7 @@ import org.opensearch.common.UUIDs; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.util.set.Sets; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.unit.ByteSizeUnit; @@ -326,6 +329,65 @@ public void testConditionEvaluationWhenAliasToWriteAndReadIndicesConsidersOnlyPr assertThat(response.getConditionStatus().get("[max_docs: 300]"), is(true)); } + public void testResolveIndices() { + IndexMetadata.Builder indexMetadata = IndexMetadata.builder("logs-index-000001") + .putAlias(AliasMetadata.builder("logs-alias").writeIndex(false).build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexMetadata2 = IndexMetadata.builder("logs-index-000002") + .putAlias(AliasMetadata.builder("logs-alias").writeIndex(true).build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + ClusterState stateBefore = ClusterState.builder(ClusterName.DEFAULT) + .metadata(Metadata.builder().put(indexMetadata).put(indexMetadata2)) + .build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(stateBefore); + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.getThreadContext()).thenReturn(threadContext); + MetadataCreateIndexService createIndexService = mock(MetadataCreateIndexService.class); + MetadataIndexAliasesService metadataIndexAliasesService = mock(MetadataIndexAliasesService.class); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)); + MetadataRolloverService metadataRolloverService = new MetadataRolloverService( + threadPool, + createIndexService, + metadataIndexAliasesService, + indexNameExpressionResolver + ); + + TransportRolloverAction action = new TransportRolloverAction( + mock(TransportService.class), + clusterService, + threadPool, + mock(ActionFilters.class), + indexNameExpressionResolver, + metadataRolloverService, + mock(Client.class) + ); + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new RolloverRequest("logs-alias", null)); + assertEquals( + ResolvedIndices.of("logs-alias") + .withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of("logs-index-000003")), + resolvedIndices + ); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new RolloverRequest("logs-alias", "explicit-index")); + assertEquals( + ResolvedIndices.of("logs-alias") + .withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of("explicit-index")), + resolvedIndices + ); + } + } + private IndicesStatsResponse createIndicesStatResponse(String indexName, long totalDocs, long primariesDocs) { final CommonStats primaryStats = mock(CommonStats.class); when(primaryStats.getDocs()).thenReturn(new DocsStats(primariesDocs, 0, between(1, 10000))); diff --git a/server/src/test/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsActionTests.java new file mode 100644 index 0000000000000..0b3686505d4d8 --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/segments/TransportPitSegmentsActionTests.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.segments; + +import org.opensearch.action.search.PitService; +import org.opensearch.action.search.PitTestsUtil; +import org.opensearch.action.search.SearchTransportService; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.index.query.IdsQueryBuilder; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.indices.IndicesService; +import org.opensearch.search.SearchService; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.transport.TransportService; +import org.opensearch.transport.client.node.NodeClient; + +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TransportPitSegmentsActionTests extends OpenSearchTestCase { + public void testResolveIndices() { + ClusterService clusterService = mock(ClusterService.class); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)); + NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry( + Arrays.asList( + new NamedWriteableRegistry.Entry(QueryBuilder.class, TermQueryBuilder.NAME, TermQueryBuilder::new), + new NamedWriteableRegistry.Entry(QueryBuilder.class, MatchAllQueryBuilder.NAME, MatchAllQueryBuilder::new), + new NamedWriteableRegistry.Entry(QueryBuilder.class, IdsQueryBuilder.NAME, IdsQueryBuilder::new) + ) + ); + NodeClient client = mock(NodeClient.class); + when(client.getNamedWriteableRegistry()).thenReturn(namedWriteableRegistry); + PitService pitService = new PitService(clusterService, mock(SearchTransportService.class), mock(TransportService.class), client); + + TransportPitSegmentsAction action = new TransportPitSegmentsAction( + clusterService, + mock(TransportService.class), + mock(IndicesService.class), + mock(ActionFilters.class), + indexNameExpressionResolver, + mock(SearchService.class), + namedWriteableRegistry, + pitService + ); + + { + // The generated pitId will contain the indices "idx" and "idy" + String pitId = PitTestsUtil.getPitId(); + OptionallyResolvedIndices resolvedIndices = action.resolveIndices(new PitSegmentsRequest(pitId)); + assertEquals(ResolvedIndices.of("idx", "idy"), resolvedIndices); + } + + { + OptionallyResolvedIndices resolvedIndices = action.resolveIndices(new PitSegmentsRequest("_all")); + assertFalse("result is unknown", resolvedIndices instanceof ResolvedIndices); + } + } +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresActionTests.java new file mode 100644 index 0000000000000..9426bf4cf87aa --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/indices/shards/TransportIndicesShardStoresActionTests.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.indices.shards; + +import org.opensearch.Version; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.gateway.TransportNodesListGatewayStartedShards; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TransportIndicesShardStoresActionTests extends OpenSearchTestCase { + + public void testResolveIndices() { + IndexMetadata.Builder indexA1 = IndexMetadata.builder("index_a1") + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexA2 = IndexMetadata.builder("index_b1") + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metadata(Metadata.builder().put(indexA1).put(indexA2)) + .build(); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.getThreadContext()).thenReturn(threadContext); + + TransportIndicesShardStoresAction action = new TransportIndicesShardStoresAction( + mock(TransportService.class), + clusterService, + threadPool, + mock(ActionFilters.class), + new IndexNameExpressionResolver(threadContext), + mock(TransportNodesListGatewayStartedShards.class), + null + ); + + assertEquals(ResolvedIndices.of("index_a1"), action.resolveIndices(new IndicesShardStoresRequest("index_a*"))); + } +} diff --git a/server/src/test/java/org/opensearch/action/admin/indices/shrink/TransportResizeActionTests.java b/server/src/test/java/org/opensearch/action/admin/indices/shrink/TransportResizeActionTests.java index 18069ede796af..6cf9aa4bc15f7 100644 --- a/server/src/test/java/org/opensearch/action/admin/indices/shrink/TransportResizeActionTests.java +++ b/server/src/test/java/org/opensearch/action/admin/indices/shrink/TransportResizeActionTests.java @@ -34,7 +34,9 @@ import org.apache.lucene.index.IndexWriter; import org.opensearch.Version; +import org.opensearch.action.admin.indices.create.CreateIndexAction; import org.opensearch.action.admin.indices.create.CreateIndexClusterStateUpdateRequest; +import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.ActiveShardCount; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.ClusterState; @@ -42,7 +44,10 @@ import org.opensearch.cluster.OpenSearchAllocationTestCase; import org.opensearch.cluster.block.ClusterBlocks; import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.MetadataCreateIndexService; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodeRole; import org.opensearch.cluster.node.DiscoveryNodes; @@ -51,8 +56,10 @@ import org.opensearch.cluster.routing.allocation.allocator.BalancedShardsAllocator; import org.opensearch.cluster.routing.allocation.decider.AllocationDeciders; import org.opensearch.cluster.routing.allocation.decider.MaxRetryAllocationDecider; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.unit.ByteSizeValue; import org.opensearch.index.shard.DocsStats; import org.opensearch.index.store.StoreStats; @@ -60,6 +67,9 @@ import org.opensearch.snapshots.EmptySnapshotsInfoService; import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.gateway.TestGatewayAllocator; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; +import org.opensearch.transport.client.Client; import java.util.Arrays; import java.util.Collections; @@ -73,6 +83,8 @@ import static org.opensearch.node.remotestore.RemoteStoreNodeService.MIGRATION_DIRECTION_SETTING; import static org.opensearch.node.remotestore.RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING; import static org.hamcrest.CoreMatchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TransportResizeActionTests extends OpenSearchTestCase { @@ -721,6 +733,29 @@ public void testResizeFailuresDuringMigration() { } } + public void testResolveIndices() { + ClusterService clusterService = mock(ClusterService.class); + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.getThreadContext()).thenReturn(threadContext); + + TransportResizeAction action = new TransportResizeAction( + mock(TransportService.class), + clusterService, + threadPool, + mock(MetadataCreateIndexService.class), + mock(ActionFilters.class), + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)), + mock(Client.class) + ); + + ResolvedIndices resolvedIndices = action.resolveIndices(new ResizeRequest("target-index", "source-index")); + assertEquals( + ResolvedIndices.of("source-index").withLocalSubActions(CreateIndexAction.INSTANCE, ResolvedIndices.Local.of("target-index")), + resolvedIndices + ); + } + private DiscoveryNode newNode(String nodeId) { final Set roles = Collections.unmodifiableSet( new HashSet<>(Arrays.asList(DiscoveryNodeRole.CLUSTER_MANAGER_ROLE, DiscoveryNodeRole.DATA_ROLE)) diff --git a/server/src/test/java/org/opensearch/action/bulk/TransportBulkActionIngestTests.java b/server/src/test/java/org/opensearch/action/bulk/TransportBulkActionIngestTests.java index 40ecef9f3a51a..5511b67848c1e 100644 --- a/server/src/test/java/org/opensearch/action/bulk/TransportBulkActionIngestTests.java +++ b/server/src/test/java/org/opensearch/action/bulk/TransportBulkActionIngestTests.java @@ -224,7 +224,8 @@ class TestSingleItemBulkWriteAction extends TransportSingleItemBulkWriteAction getAllPitsListener } } } + + public void testResolveIndices() throws InterruptedException, ExecutionException { + NodeClient client = new NodeClient(settings, threadPool); + client.initialize(null, null, null, namedWriteableRegistry); + ActionFilters actionFilters = mock(ActionFilters.class); + when(actionFilters.filters()).thenReturn(new ActionFilter[0]); + + try ( + MockTransportService transportService = MockTransportService.createNewService( + Settings.EMPTY, + Version.CURRENT, + threadPool, + NoopTracer.INSTANCE + ) + ) { + transportService.start(); + transportService.acceptIncomingRequests(); + SearchTransportService searchTransportService = new SearchTransportService(transportService, null) { + @Override + public Transport.Connection getConnection(String clusterAlias, DiscoveryNode node) { + return new SearchAsyncActionTests.MockConnection(node); + } + }; + PitService pitService = new PitService(clusterServiceMock, searchTransportService, transportService, client); + TransportDeletePitAction action = new TransportDeletePitAction( + transportService, + actionFilters, + namedWriteableRegistry, + pitService + ); + DeletePitRequest deletePITRequest = new DeletePitRequest(pitId); + OptionallyResolvedIndices resolvedIndices = action.resolveIndices(deletePITRequest); + assertEquals(ResolvedIndices.of("idx", "idy"), resolvedIndices); + } + + } + + public void testResolveIndices_allPits() throws InterruptedException, ExecutionException { + NodeClient client = new NodeClient(settings, threadPool); + client.initialize(null, null, null, namedWriteableRegistry); + ActionFilters actionFilters = mock(ActionFilters.class); + when(actionFilters.filters()).thenReturn(new ActionFilter[0]); + + try ( + MockTransportService transportService = MockTransportService.createNewService( + Settings.EMPTY, + Version.CURRENT, + threadPool, + NoopTracer.INSTANCE + ) + ) { + transportService.start(); + transportService.acceptIncomingRequests(); + SearchTransportService searchTransportService = new SearchTransportService(transportService, null) { + @Override + public Transport.Connection getConnection(String clusterAlias, DiscoveryNode node) { + return new SearchAsyncActionTests.MockConnection(node); + } + }; + PitService pitService = new PitService(clusterServiceMock, searchTransportService, transportService, client); + TransportDeletePitAction action = new TransportDeletePitAction( + transportService, + actionFilters, + namedWriteableRegistry, + pitService + ); + DeletePitRequest deletePITRequest = new DeletePitRequest("_all"); + OptionallyResolvedIndices resolvedIndices = action.resolveIndices(deletePITRequest); + assertEquals(ResolvedIndices.unknown(), resolvedIndices); + } + + } } diff --git a/server/src/test/java/org/opensearch/action/search/TransportSearchActionTests.java b/server/src/test/java/org/opensearch/action/search/TransportSearchActionTests.java index 0a0015ae8cbf6..4cde4a2fb49e8 100644 --- a/server/src/test/java/org/opensearch/action/search/TransportSearchActionTests.java +++ b/server/src/test/java/org/opensearch/action/search/TransportSearchActionTests.java @@ -40,42 +40,60 @@ import org.opensearch.action.OriginalIndicesTests; import org.opensearch.action.admin.cluster.shards.ClusterSearchShardsGroup; import org.opensearch.action.admin.cluster.shards.ClusterSearchShardsResponse; +import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.IndicesOptions; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlocks; +import org.opensearch.cluster.metadata.AliasMetadata; import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.GroupShardsIterator; import org.opensearch.cluster.routing.GroupShardsIteratorTests; import org.opensearch.cluster.routing.ShardRouting; import org.opensearch.cluster.routing.ShardRoutingState; import org.opensearch.cluster.routing.TestShardRouting; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.SetOnce; import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.index.Index; import org.opensearch.core.index.shard.ShardId; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.core.rest.RestStatus; +import org.opensearch.index.query.IdsQueryBuilder; import org.opensearch.index.query.InnerHitBuilder; import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.TermQueryBuilder; import org.opensearch.index.query.TermsQueryBuilder; import org.opensearch.search.Scroll; import org.opensearch.search.SearchHit; import org.opensearch.search.SearchHits; +import org.opensearch.search.SearchService; import org.opensearch.search.SearchShardTarget; import org.opensearch.search.aggregations.InternalAggregation; import org.opensearch.search.aggregations.InternalAggregations; +import org.opensearch.search.builder.PointInTimeBuilder; import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.search.collapse.CollapseBuilder; import org.opensearch.search.internal.AliasFilter; import org.opensearch.search.internal.InternalSearchResponse; import org.opensearch.search.internal.SearchContext; +import org.opensearch.search.pipeline.SearchPipelineService; import org.opensearch.search.sort.SortBuilders; +import org.opensearch.tasks.TaskResourceTrackingService; +import org.opensearch.telemetry.metrics.MetricsRegistry; +import org.opensearch.telemetry.tracing.Tracer; import org.opensearch.telemetry.tracing.noop.NoopTracer; import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.transport.MockTransportService; @@ -92,6 +110,7 @@ import org.opensearch.transport.TransportRequest; import org.opensearch.transport.TransportRequestOptions; import org.opensearch.transport.TransportService; +import org.opensearch.transport.client.node.NodeClient; import java.util.ArrayList; import java.util.Arrays; @@ -114,6 +133,8 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TransportSearchActionTests extends OpenSearchTestCase { @@ -1166,4 +1187,103 @@ public void testShouldPreFilterSearchShardsWithReadOnly() { ); } } + + public void testResolveIndices() { + IndexMetadata.Builder indexA1 = IndexMetadata.builder("index_a1") + .putAlias(AliasMetadata.builder("alias_a").build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + IndexMetadata.Builder indexA2 = IndexMetadata.builder("index_a2") + .putAlias(AliasMetadata.builder("alias_a").build()) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metadata(Metadata.builder().put(indexA1).put(indexA2)) + .build(); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)); + NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry( + Arrays.asList( + new NamedWriteableRegistry.Entry(QueryBuilder.class, TermQueryBuilder.NAME, TermQueryBuilder::new), + new NamedWriteableRegistry.Entry(QueryBuilder.class, MatchAllQueryBuilder.NAME, MatchAllQueryBuilder::new), + new NamedWriteableRegistry.Entry(QueryBuilder.class, IdsQueryBuilder.NAME, IdsQueryBuilder::new) + ) + ); + int numClusters = 2; + DiscoveryNode[] nodes = new DiscoveryNode[numClusters]; + Map remoteIndicesByCluster = new HashMap<>(); + Settings.Builder builder = Settings.builder(); + MockTransportService[] mockTransportServices = startTransport(numClusters, nodes, remoteIndicesByCluster, builder); + Settings settings = builder.build(); + try ( + MockTransportService service = MockTransportService.createNewService(settings, Version.CURRENT, threadPool, NoopTracer.INSTANCE) + ) { + service.start(); + service.acceptIncomingRequests(); + RemoteClusterService remoteClusterService = service.getRemoteClusterService(); + + SearchTransportService searchTransportService = mock(SearchTransportService.class); + when(searchTransportService.getRemoteClusterService()).thenReturn(remoteClusterService); + + TransportSearchAction action = new TransportSearchAction( + mock(NodeClient.class), + threadPool, + mock(CircuitBreakerService.class), + mock(TransportService.class), + mock(SearchService.class), + searchTransportService, + new SearchPhaseController(namedWriteableRegistry, (searchSourceBuilder) -> null), + clusterService, + mock(ActionFilters.class), + indexNameExpressionResolver, + namedWriteableRegistry, + mock(SearchPipelineService.class), + mock(MetricsRegistry.class), + new SearchRequestOperationsCompositeListenerFactory(), + mock(Tracer.class), + mock(TaskResourceTrackingService.class) + ); + + // Actual test cases start here: + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new SearchRequest("index_*")); + assertEquals(ResolvedIndices.of("index_a1", "index_a2"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new SearchRequest("alias_a")); + assertEquals(ResolvedIndices.of("alias_a"), resolvedIndices); + } + + { + // The generated pitId will contain the indices "idx" and "idy" + String pitId = PitTestsUtil.getPitId(); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.pointInTimeBuilder(new PointInTimeBuilder(pitId)); + ResolvedIndices resolvedIndices = action.resolveIndices(new SearchRequest().source(searchSourceBuilder)); + assertEquals(ResolvedIndices.of("idx", "idy"), resolvedIndices); + } + + { + ResolvedIndices resolvedIndices = action.resolveIndices(new SearchRequest("index_*", "remote1:remote_index")); + assertEquals( + ResolvedIndices.of("index_a1", "index_a2") + .withRemoteIndices( + Map.of("remote1", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN)) + ), + resolvedIndices + ); + } + + } finally { + for (MockTransportService mockTransportService : mockTransportServices) { + mockTransportService.close(); + } + } + } } diff --git a/server/src/test/java/org/opensearch/action/support/ActionRequestMetadataTests.java b/server/src/test/java/org/opensearch/action/support/ActionRequestMetadataTests.java new file mode 100644 index 0000000000000..f19c339e68159 --- /dev/null +++ b/server/src/test/java/org/opensearch/action/support/ActionRequestMetadataTests.java @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.support; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.tasks.Task; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.Set; + +public class ActionRequestMetadataTests extends OpenSearchTestCase { + public void testResolvedIndices() { + ActionRequestMetadata subject = new ActionRequestMetadata<>( + new TestTransportAction(), + new TestRequest("my_resolution_result") + ); + OptionallyResolvedIndices resolvedIndices = subject.resolvedIndices(); + assertEquals(ResolvedIndices.of("my_resolution_result"), resolvedIndices); + } + + public void testResolvedIndices_unknown() { + ActionRequestMetadata subject = new ActionRequestMetadata<>( + new TestTransportActionUnsupported(), + new TestRequest("my_resolution_result") + ); + OptionallyResolvedIndices resolvedIndices = subject.resolvedIndices(); + assertFalse(resolvedIndices instanceof ResolvedIndices); + } + + static class TestTransportAction extends TransportAction + implements + TransportIndicesResolvingAction { + + protected TestTransportAction() { + super("action", new ActionFilters(Set.of()), null); + } + + @Override + protected void doExecute(Task task, TestRequest request, ActionListener listener) { + + } + + @Override + public OptionallyResolvedIndices resolveIndices(TestRequest request) { + return ResolvedIndices.of(request.resolutionResult); + } + } + + static class TestRequest extends ActionRequest { + final String resolutionResult; + + TestRequest(String resolutionResult) { + this.resolutionResult = resolutionResult; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + } + + static class TestTransportActionUnsupported extends TransportAction { + + protected TestTransportActionUnsupported() { + super("action", new ActionFilters(Set.of()), null); + } + + @Override + protected void doExecute(Task task, TestRequest request, ActionListener listener) { + + } + } +} diff --git a/server/src/test/java/org/opensearch/action/support/TransportActionFilterChainTests.java b/server/src/test/java/org/opensearch/action/support/TransportActionFilterChainTests.java index a4f40db365f9a..f91e6aa7f0f49 100644 --- a/server/src/test/java/org/opensearch/action/support/TransportActionFilterChainTests.java +++ b/server/src/test/java/org/opensearch/action/support/TransportActionFilterChainTests.java @@ -243,6 +243,7 @@ public void app Task task, String action, Request request, + ActionRequestMetadata actionRequestMetadata, ActionListener listener, ActionFilterChain chain ) { diff --git a/server/src/test/java/org/opensearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java b/server/src/test/java/org/opensearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java index 4c0023b69488c..cdb490456dcfd 100644 --- a/server/src/test/java/org/opensearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java +++ b/server/src/test/java/org/opensearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java @@ -49,6 +49,8 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.metadata.OptionallyResolvedIndices; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.routing.IndexRoutingTable; @@ -629,4 +631,10 @@ public void testResultWithTimeouts() throws ExecutionException, InterruptedExcep assertEquals("failed shards", totalFailedShards, response.getFailedShards()); assertEquals("accumulated exceptions", totalFailedShards, response.getShardFailures().length); } + + public void testResolveIndices() { + Request request = new Request(TEST_INDEX); + OptionallyResolvedIndices resolvedIndices = action.resolveIndices(request); + assertEquals(ResolvedIndices.of(TEST_INDEX), resolvedIndices); + } } diff --git a/server/src/test/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java b/server/src/test/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java index 118b4e596fc66..68f639bb674d2 100644 --- a/server/src/test/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java +++ b/server/src/test/java/org/opensearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java @@ -43,6 +43,7 @@ import org.opensearch.cluster.block.ClusterBlockLevel; import org.opensearch.cluster.block.ClusterBlocks; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.ResolvedIndices; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.routing.ShardIterator; import org.opensearch.cluster.routing.ShardRoutingState; @@ -370,4 +371,29 @@ protected void resolveRequest(ClusterState state, Request request) { } } } + + public void testResolveIndices() { + Request request = new Request().index("test"); + request.shardId = new ShardId("test", "_na_", 0); + setState(clusterService, ClusterStateCreationUtils.state("test", true, ShardRoutingState.STARTED)); + ResolvedIndices resolvedIndices = action.resolveIndices(request); + assertEquals(ResolvedIndices.of("test"), resolvedIndices); + } + + public void testResolveIndices_concreteIndex() { + Request request = new Request().index("test"); + request.concreteIndex("concrete_value"); + request.shardId = new ShardId("test", "_na_", 0); + setState(clusterService, ClusterStateCreationUtils.state("test", true, ShardRoutingState.STARTED)); + ResolvedIndices resolvedIndices = action.resolveIndices(request); + assertEquals(ResolvedIndices.of("concrete_value"), resolvedIndices); + } + + public void testResolveIndices_nonExisting() { + Request request = new Request().index("test_non_existing"); + request.shardId = new ShardId("test_non_existing", "_na_", 0); + setState(clusterService, ClusterStateCreationUtils.state("test", true, ShardRoutingState.STARTED)); + ResolvedIndices resolvedIndices = action.resolveIndices(request); + assertEquals(ResolvedIndices.of("test_non_existing"), resolvedIndices); + } } diff --git a/server/src/test/java/org/opensearch/cluster/metadata/IndexNameExpressionResolverTests.java b/server/src/test/java/org/opensearch/cluster/metadata/IndexNameExpressionResolverTests.java index c2caae1dc2586..153e3f389cf05 100644 --- a/server/src/test/java/org/opensearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/server/src/test/java/org/opensearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -35,6 +35,7 @@ import org.opensearch.Version; import org.opensearch.action.DocWriteRequest; import org.opensearch.action.IndicesRequest; +import org.opensearch.action.OriginalIndices; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; import org.opensearch.action.delete.DeleteRequest; @@ -817,6 +818,27 @@ public void testConcreteIndicesIgnoreIndicesOneMissingIndex() { assertThat(infe.getMessage(), is("no such index [testZZZ]")); } + /** + * Same as testConcreteIndicesIgnoreIndicesOneMissingIndex() but using the concreteResolvedIndices() method. + * This should keep the name of the missing index and defer the throwing of the exception to the point + * when ResolvedIndices.Local.Concrete.concreteIndices() is called. + */ + public void testConcreteResolvedIndicesIgnoreIndicesOneMissingIndex() { + Metadata.Builder mdBuilder = Metadata.builder().put(indexBuilder("testXXX")).put(indexBuilder("kuku")); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context( + state, + IndicesOptions.strictExpandOpen(), + false + ); + + ResolvedIndices.Local.Concrete concreteResolvedIndices = indexNameExpressionResolver.concreteResolvedIndices(context, "testZZZ"); + assertEquals(concreteResolvedIndices.names(), Set.of("testZZZ")); + + IndexNotFoundException infe = expectThrows(IndexNotFoundException.class, () -> concreteResolvedIndices.concreteIndices()); + assertThat(infe.getMessage(), is("no such index [testZZZ]")); + } + public void testConcreteIndicesIgnoreIndicesOneMissingIndexOtherFound() { Metadata.Builder mdBuilder = Metadata.builder().put(indexBuilder("testXXX")).put(indexBuilder("kuku")); ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); @@ -1578,6 +1600,7 @@ public void testResolveExpressions() { indexNameExpressionResolver.resolveExpressions(state, "test-*", "alias-*") ); assertEquals(new HashSet<>(Arrays.asList("test-1", "alias-1")), indexNameExpressionResolver.resolveExpressions(state, "*-1")); + expectThrows(InvalidIndexNameException.class, () -> indexNameExpressionResolver.resolveExpressions(state, "_invalid_index_name")); } public void testFilteringAliases() { @@ -2268,8 +2291,13 @@ public void testDataStreams() { IndicesOptions indicesOptions = IndicesOptions.STRICT_EXPAND_OPEN; Index[] result = indexNameExpressionResolver.concreteIndices(state, indicesOptions, true, "my-data-stream"); assertThat(result.length, equalTo(2)); - assertThat(result[0].getName(), equalTo(DataStream.getDefaultBackingIndexName(dataStreamName, 1))); - assertThat(result[1].getName(), equalTo(DataStream.getDefaultBackingIndexName(dataStreamName, 2))); + assertThat( + Arrays.stream(result).map(Index::getName).collect(Collectors.toList()), + containsInAnyOrder( + DataStream.getDefaultBackingIndexName(dataStreamName, 1), + DataStream.getDefaultBackingIndexName(dataStreamName, 2) + ) + ); } { // Ignore data streams @@ -2280,6 +2308,16 @@ public void testDataStreams() { ); assertThat(e.getMessage(), equalTo("no such index [my-data-stream]")); } + { + // Ignore data streams; use different overload + IndicesOptions indicesOptions = IndicesOptions.STRICT_EXPAND_OPEN; + ResolvedIndices.Local.Concrete concreteResolvedIndices = indexNameExpressionResolver.concreteResolvedIndices( + state, + indicesOptions, + "my-data-stream" + ); + assertThat(concreteResolvedIndices.resolutionErrors().getFirst().getMessage(), equalTo("no such index [my-data-stream]")); + } { // Ignore data streams and allow no indices IndicesOptions indicesOptions = new IndicesOptions( @@ -2521,6 +2559,87 @@ public void testDataStreamsNames() { assertThat(names, empty()); } + public void testConcreteResolvedIndicesWithWildcards() { + Metadata.Builder mdBuilder = Metadata.builder() + .put(indexBuilder("index_a1").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_a2").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_b1")) + .put(indexBuilder("index_b2")); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context( + state, + IndicesOptions.lenientExpandOpen(), + false + ); + + ResolvedIndices.Local.Concrete concreteResolvedIndices = indexNameExpressionResolver.concreteResolvedIndices( + context, + "alias_a", + "index_b*", + "index_c*", + "index_d" + ); + + // We test the following semantics: + // - Of course, the concrete indices will contain all existing indices that are matched. + // - For the names() property we include: + // - resolved index patterns + // - names of existing indices + // - names of non-existing indices + assertThat(concreteResolvedIndices.names(), containsInAnyOrder("alias_a", "index_b1", "index_b2", "index_d")); + assertThat(concreteResolvedIndices.namesOfConcreteIndices(), containsInAnyOrder("index_a1", "index_a2", "index_b1", "index_b2")); + } + + public void testConcreteResolvedIndicesWithErrors() { + Metadata.Builder mdBuilder = Metadata.builder() + .put(indexBuilder("index_a1").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_a2").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_closed").state(IndexMetadata.State.CLOSE)) + .put(indexBuilder("index_b1").putAlias(AliasMetadata.builder("alias_b"))) + .put(indexBuilder("index_c1")); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + IndexNameExpressionResolver.Context context = new IndexNameExpressionResolver.Context( + state, + IndicesOptions.strictSingleIndexNoExpandForbidClosed(), + false + ); + + ResolvedIndices.Local.Concrete concreteResolvedIndices = indexNameExpressionResolver.concreteResolvedIndices( + context, + "baz*", + "alias_a", + "index_closed", + "alias_b", + "index_c1" + ); + assertThat(concreteResolvedIndices.names(), containsInAnyOrder("index_closed", "index_c1", "baz*", "alias_b", "alias_a")); + assertThat(concreteResolvedIndices.withoutResolutionErrors().namesOfConcreteIndices(), containsInAnyOrder("index_b1", "index_c1")); + assertThat( + concreteResolvedIndices.resolutionErrors().stream().map(Throwable::getMessage).toList(), + containsInAnyOrder( + "no such index [baz*]", + "alias [alias_a] has more than one index associated with it [index_a1, index_a2], can't execute a single index op", + "closed" + ) + ); + } + + public void testConcreteResolvedIndicesWithIndicesRequestParam() { + Metadata.Builder mdBuilder = Metadata.builder().put(indexBuilder("index_b1")).put(indexBuilder("index_b2")); + ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build(); + + ResolvedIndices.Local.Concrete concreteResolvedIndices = indexNameExpressionResolver.concreteResolvedIndices( + state, + new OriginalIndices(new String[] { "index_b*", "index_d" }, IndicesOptions.lenientExpandOpen()), + 0 + ); + + assertThat(concreteResolvedIndices.names(), containsInAnyOrder("index_b1", "index_b2", "index_d")); + assertThat(concreteResolvedIndices.namesOfConcreteIndices(), containsInAnyOrder("index_b1", "index_b2")); + } + static class FakeExpressionResolver implements IndexNameExpressionResolver.ExpressionResolver { @Override public List resolve(IndexNameExpressionResolver.Context context, List expressions) { diff --git a/server/src/test/java/org/opensearch/cluster/metadata/ResolvedIndicesTests.java b/server/src/test/java/org/opensearch/cluster/metadata/ResolvedIndicesTests.java new file mode 100644 index 0000000000000..32399e89578f0 --- /dev/null +++ b/server/src/test/java/org/opensearch/cluster/metadata/ResolvedIndicesTests.java @@ -0,0 +1,223 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.cluster.metadata; + +import org.opensearch.Version; +import org.opensearch.action.OriginalIndices; +import org.opensearch.action.admin.indices.delete.DeleteIndexAction; +import org.opensearch.action.support.IndicesOptions; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.core.index.Index; +import org.opensearch.index.IndexNotFoundException; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isA; + +public class ResolvedIndicesTests extends OpenSearchTestCase { + + public void testOfNames() { + ResolvedIndices resolvedIndices = ResolvedIndices.of("a", "b", "c"); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("a", "b", "c")); + assertThat(resolvedIndices.local().names(ClusterState.EMPTY_STATE), containsInAnyOrder("a", "b", "c")); + assertThat(Arrays.asList(resolvedIndices.local().namesAsArray()), containsInAnyOrder("a", "b", "c")); + assertFalse(resolvedIndices.local().isEmpty()); + assertTrue(resolvedIndices.remote().isEmpty()); + assertFalse(resolvedIndices.isEmpty()); + } + + public void testOfNamesCollection() { + ResolvedIndices resolvedIndices = ResolvedIndices.of(List.of("a", "b")); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("a", "b")); + } + + public void testOfConcreteIndices() { + ResolvedIndices resolvedIndices = ResolvedIndices.of(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b")); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("index_a", "index_b")); + assertThat(resolvedIndices.local(), isA(ResolvedIndices.Local.Concrete.class)); + ResolvedIndices.Local.Concrete concrete = (ResolvedIndices.Local.Concrete) resolvedIndices.local(); + assertThat(concrete.concreteIndices(), containsInAnyOrder(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b"))); + } + + public void testOfNonNull() { + ResolvedIndices resolvedIndices = ResolvedIndices.ofNonNull("a", "b", "c", null); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("a", "b", "c")); + } + + public void testWithLocalOriginalIndices() { + OriginalIndices originalIndices = new OriginalIndices(new String[] { "x", "y" }, IndicesOptions.LENIENT_EXPAND_OPEN); + ResolvedIndices resolvedIndices = ResolvedIndices.of("a", "b", "c").withLocalOriginalIndices(originalIndices); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("a", "b", "c")); + assertArrayEquals(resolvedIndices.local().originalIndices().indices(), new String[] { "x", "y" }); + } + + public void testOfEmpty() { + ResolvedIndices resolvedIndices = ResolvedIndices.of(new String[0]); + assertTrue(resolvedIndices.local().isEmpty()); + assertTrue(resolvedIndices.remote().isEmpty()); + assertTrue(resolvedIndices.isEmpty()); + } + + public void testUnknown() { + OptionallyResolvedIndices resolvedIndices = ResolvedIndices.unknown(); + assertFalse(resolvedIndices instanceof ResolvedIndices); + resolvedIndices = OptionallyResolvedIndices.unknown(); + assertFalse(resolvedIndices instanceof ResolvedIndices); + assertFalse(resolvedIndices.local().isEmpty()); + assertTrue(resolvedIndices.local().contains("whatever")); + assertTrue(resolvedIndices.local().containsAny(List.of("whatever"))); + assertTrue(resolvedIndices.local().containsAny(i -> false)); + assertEquals("ResolvedIndices{unknown=true}", resolvedIndices.toString()); + } + + public void testWithRemoteIndices() { + Map remoteIndices = Map.of( + "remote_cluster", + new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN) + ); + ResolvedIndices resolvedIndices = ResolvedIndices.of(new String[0]).withRemoteIndices(remoteIndices); + assertTrue(resolvedIndices.remote().asClusterToOriginalIndicesMap().containsKey("remote_cluster")); + assertArrayEquals( + new String[] { "remote_index" }, + resolvedIndices.remote().asClusterToOriginalIndicesMap().get("remote_cluster").indices() + ); + assertThat(resolvedIndices.remote().asRawExpressions(), containsInAnyOrder("remote_cluster:remote_index")); + assertEquals(resolvedIndices.remote().asRawExpressions(), Arrays.asList(resolvedIndices.remote().asRawExpressionsArray())); + } + + public void testWithoutRemoteIndices() { + ResolvedIndices resolvedIndices = ResolvedIndices.of(new String[0]); + assertTrue(resolvedIndices.remote().asClusterToOriginalIndicesMap().isEmpty()); + assertTrue(resolvedIndices.remote().asRawExpressions().isEmpty()); + } + + public void testLocalConcreteOf() { + Set indices = Set.of(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b")); + Set names = Set.of("index_a", "index_b", "index_c"); + ResolvedIndices.Local.Concrete concrete = ResolvedIndices.Local.Concrete.of(indices, names).withResolutionErrors(); + assertThat(concrete.concreteIndices(), containsInAnyOrder(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b"))); + assertThat(concrete.namesOfConcreteIndices(), containsInAnyOrder("index_a", "index_b")); + assertThat(Arrays.asList(concrete.namesOfConcreteIndicesAsArray()), containsInAnyOrder("index_a", "index_b")); + assertThat(concrete.names(), containsInAnyOrder("index_a", "index_b", "index_c")); + } + + public void testLocalConcreteWithResolutionErrors() { + Set indices = Set.of(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b")); + Set names = Set.of("index_a", "index_b", "index_c"); + ResolvedIndices.Local.Concrete concrete = ResolvedIndices.Local.Concrete.of(indices, names) + .withResolutionErrors(new IndexNotFoundException("index_x")); + assertThat(concrete.names(), containsInAnyOrder("index_a", "index_b", "index_c")); + IndexNotFoundException exception = expectThrows(IndexNotFoundException.class, concrete::concreteIndices); + assertThat(exception.getIndex().getName(), equalTo("index_x")); + concrete = concrete.withoutResolutionErrors(); + assertThat(concrete.concreteIndices(), containsInAnyOrder(new Index("index_a", "uuid_a"), new Index("index_b", "uuid_b"))); + } + + public void testWithLocalSubActions() { + ResolvedIndices resolvedIndices = ResolvedIndices.of("a", "b", "c") + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("x", "y", "z")); + assertThat(resolvedIndices.local().names(), containsInAnyOrder("a", "b", "c")); + assertThat(resolvedIndices.local().subActions().get(DeleteIndexAction.NAME).names(), containsInAnyOrder("x", "y", "z")); + } + + public void testLocalOf() { + ResolvedIndices.Local local = ResolvedIndices.Local.of(List.of("o", "p", "q")); + assertThat(local.names(), containsInAnyOrder("o", "p", "q")); + } + + public void testNamesWithClusterState() { + Metadata.Builder metadata = Metadata.builder() + .put(indexBuilder("index_a1").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_a2").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_b1")); + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(metadata).build(); + ResolvedIndices resolvedIndices = ResolvedIndices.of("index_not_existing", "alias_a", "index_b1"); + assertThat(resolvedIndices.local().names(clusterState), containsInAnyOrder("index_not_existing", "alias_a", "index_b1")); + assertThat( + ResolvedIndices.unknown().local().names(clusterState), + containsInAnyOrder("index_a1", "index_a2", "index_b1", "alias_a") + ); + } + + public void testNamesOfIndices() { + Metadata.Builder metadata = Metadata.builder() + .put(indexBuilder("index_a1").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_a2").putAlias(AliasMetadata.builder("alias_a"))) + .put(indexBuilder("index_b1")); + ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(metadata).build(); + ResolvedIndices resolvedIndices = ResolvedIndices.of("index_not_existing", "alias_a", "index_b1"); + assertThat( + resolvedIndices.local().namesOfIndices(clusterState), + containsInAnyOrder("index_not_existing", "index_a1", "index_a2", "index_b1") + ); + } + + public void testContains() { + ResolvedIndices resolvedIndices = ResolvedIndices.of("a", "b", "c"); + assertTrue(resolvedIndices.local().contains("a")); + assertFalse(resolvedIndices.local().contains("x")); + assertTrue(resolvedIndices.local().containsAny(List.of("a", "x"))); + assertFalse(resolvedIndices.local().containsAny(List.of("x", "y"))); + assertTrue(resolvedIndices.local().containsAny(i -> i.equals("a"))); + assertFalse(resolvedIndices.local().containsAny(i -> i.equals("z"))); + } + + public void testEqualsHashCode() { + ResolvedIndices resolvedIndices1 = ResolvedIndices.of("index") + .withRemoteIndices(Map.of("remote", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN))) + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("other")); + ResolvedIndices resolvedIndices2 = ResolvedIndices.of("index") + .withRemoteIndices(Map.of("remote", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN))) + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("other")); + assertEquals(resolvedIndices1.hashCode(), resolvedIndices2.hashCode()); + assertTrue(resolvedIndices1 + " equals " + resolvedIndices2, resolvedIndices1.equals(resolvedIndices2)); + assertTrue(resolvedIndices2 + " equals " + resolvedIndices1, resolvedIndices2.equals(resolvedIndices1)); + ResolvedIndices resolvedIndices3 = resolvedIndices2.withRemoteIndices( + Map.of("remote2", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN)) + ); + assertNotEquals(resolvedIndices1.hashCode(), resolvedIndices3.hashCode()); + assertFalse(resolvedIndices1.equals(resolvedIndices3)); + assertFalse(resolvedIndices3.equals(resolvedIndices1)); + assertFalse(resolvedIndices1.equals(ResolvedIndices.unknown())); + assertNotEquals(resolvedIndices1.hashCode(), ResolvedIndices.unknown().hashCode()); + } + + public void testHashCodeForConcreteIndices() { + ResolvedIndices resolvedIndices1 = ResolvedIndices.of(ResolvedIndices.Local.Concrete.of(new Index("index", "index_uuid"))) + .withRemoteIndices(Map.of("remote", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN))) + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("other")); + ResolvedIndices resolvedIndices2 = ResolvedIndices.of(ResolvedIndices.Local.Concrete.of(new Index("index", "index_uuid"))) + .withRemoteIndices(Map.of("remote", new OriginalIndices(new String[] { "remote_index" }, IndicesOptions.LENIENT_EXPAND_OPEN))) + .withLocalSubActions(DeleteIndexAction.INSTANCE, ResolvedIndices.Local.of("other")); + assertEquals(resolvedIndices1.hashCode(), resolvedIndices2.hashCode()); + assertTrue(resolvedIndices1 + " equals " + resolvedIndices2, resolvedIndices1.equals(resolvedIndices2)); + assertTrue(resolvedIndices2 + " equals " + resolvedIndices1, resolvedIndices2.equals(resolvedIndices1)); + ResolvedIndices resolvedIndices3 = resolvedIndices2.withLocalSubActions( + DeleteIndexAction.INSTANCE, + ResolvedIndices.Local.of("other2") + ); + assertNotEquals(resolvedIndices1.hashCode(), resolvedIndices3.hashCode()); + assertFalse(resolvedIndices1.equals(resolvedIndices3)); + assertFalse(resolvedIndices3.equals(resolvedIndices1)); + } + + private static IndexMetadata.Builder indexBuilder(String index) { + return IndexMetadata.builder(index) + .settings( + settings(Version.CURRENT).put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + ); + } +} diff --git a/server/src/test/java/org/opensearch/cluster/metadata/WildcardExpressionResolverTests.java b/server/src/test/java/org/opensearch/cluster/metadata/WildcardExpressionResolverTests.java index 03807fb5f8c4d..bd698990dc5cc 100644 --- a/server/src/test/java/org/opensearch/cluster/metadata/WildcardExpressionResolverTests.java +++ b/server/src/test/java/org/opensearch/cluster/metadata/WildcardExpressionResolverTests.java @@ -233,11 +233,6 @@ public void testResolveAliases() { ); // ignoreAliases option is set, WildcardExpressionResolver resolves the provided expressions only against the defined indices IndicesOptions errorOnAliasIndicesOptions = IndicesOptions.fromOptions(false, false, true, false, true, false, true, false); - IndexNameExpressionResolver.Context skipAliasesStrictContext = new IndexNameExpressionResolver.Context( - state, - errorOnAliasIndicesOptions, - false - ); { List indices = resolver.resolve(indicesAndAliasesContext, Collections.singletonList("foo_a*")); @@ -248,11 +243,13 @@ public void testResolveAliases() { assertEquals(0, indices.size()); } { - IndexNotFoundException infe = expectThrows( - IndexNotFoundException.class, - () -> resolver.resolve(skipAliasesStrictContext, Collections.singletonList("foo_a*")) + IndexNameExpressionResolver.Context skipAliasesStrictContext = new IndexNameExpressionResolver.Context( + state, + errorOnAliasIndicesOptions, + false ); - assertEquals("foo_a*", infe.getIndex().getName()); + resolver.resolve(skipAliasesStrictContext, Collections.singletonList("foo_a*")); + assertEquals("foo_a*", ((IndexNotFoundException) skipAliasesStrictContext.getFirstResolutionError()).getIndex().getName()); } { List indices = resolver.resolve(indicesAndAliasesContext, Collections.singletonList("foo*")); @@ -263,6 +260,11 @@ public void testResolveAliases() { assertThat(indices, containsInAnyOrder("foo_foo", "foo_index")); } { + IndexNameExpressionResolver.Context skipAliasesStrictContext = new IndexNameExpressionResolver.Context( + state, + errorOnAliasIndicesOptions, + false + ); List indices = resolver.resolve(skipAliasesStrictContext, Collections.singletonList("foo*")); assertThat(indices, containsInAnyOrder("foo_foo", "foo_index")); } @@ -275,10 +277,13 @@ public void testResolveAliases() { assertThat(indices, containsInAnyOrder("foo_alias")); } { - IllegalArgumentException iae = expectThrows( - IllegalArgumentException.class, - () -> resolver.resolve(skipAliasesStrictContext, Collections.singletonList("foo_alias")) + IndexNameExpressionResolver.Context skipAliasesStrictContext = new IndexNameExpressionResolver.Context( + state, + errorOnAliasIndicesOptions, + false ); + resolver.resolve(skipAliasesStrictContext, Collections.singletonList("foo_alias")); + IllegalArgumentException iae = (IllegalArgumentException) skipAliasesStrictContext.getFirstResolutionError(); assertEquals( "The provided expression [foo_alias] matches an alias, " + "specify the corresponding concrete indices instead.", iae.getMessage() diff --git a/server/src/test/java/org/opensearch/indices/SystemIndicesTests.java b/server/src/test/java/org/opensearch/indices/SystemIndicesTests.java index ca9370645dec3..5cfd2c06d97e4 100644 --- a/server/src/test/java/org/opensearch/indices/SystemIndicesTests.java +++ b/server/src/test/java/org/opensearch/indices/SystemIndicesTests.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Collections.emptyMap; @@ -189,6 +190,9 @@ public void testSystemIndexMatching() { equalTo(Set.of(".system-index1", ".system-index-pattern1")) ); assertThat(SystemIndexRegistry.matchesSystemIndexPattern(Set.of(".not-system")), equalTo(Collections.emptySet())); + + assertTrue(SystemIndexRegistry.matchesSystemIndexPattern(".system-index1")); + assertFalse(SystemIndexRegistry.matchesSystemIndexPattern(".not-system-index")); } public void testRegisteredSystemIndexGetAllDescriptors() { @@ -259,6 +263,18 @@ public void testRegisteredSystemIndexMatchingForPlugin() { Set.of("other-index") ); assertEquals(0, noMatchingSystemIndices.size()); + + Predicate plugin1systemIndexPredicate = SystemIndexRegistry.getPluginSystemIndexPredicate( + SystemIndexPlugin1.class.getCanonicalName() + ); + assertTrue(plugin1systemIndexPredicate.test(SystemIndexPlugin1.SYSTEM_INDEX_1)); + assertFalse(plugin1systemIndexPredicate.test(SystemIndexPlugin2.SYSTEM_INDEX_2)); + assertFalse(plugin1systemIndexPredicate.test("unknown")); + + Predicate unknownPluginSystemIndexPredicate = SystemIndexRegistry.getPluginSystemIndexPredicate("unknown_plugin"); + assertFalse(unknownPluginSystemIndexPredicate.test(SystemIndexPlugin1.SYSTEM_INDEX_1)); + assertFalse(unknownPluginSystemIndexPredicate.test(SystemIndexPlugin2.SYSTEM_INDEX_2)); + assertFalse(unknownPluginSystemIndexPredicate.test("unknown")); } static final class SystemIndexPlugin1 extends Plugin implements SystemIndexPlugin {