From 31e68e6b8707b2ebbdc75ec83fc020e659151008 Mon Sep 17 00:00:00 2001 From: Ines Potier Date: Thu, 12 Mar 2026 16:53:25 -0400 Subject: [PATCH 1/4] Extract reroute behavior from create-index request classes The reroute behavior was embedded in CreateIndexClusterStateUpdateRequest and CreateDataStreamClusterStateUpdateRequest, while the reroute listener was passed separately. This change clarifies the relationship between the two by passing RerouteBehavior alongside the listener as a method parameter. It also enables the batched reroute logic in #144074. Relates to ES-13198. --- .../TransportCreateDataStreamAction.java | 3 +- .../DataStreamGetWriteIndexTests.java | 6 +- .../indices/create/AutoCreateAction.java | 14 ++--- .../CreateIndexClusterStateUpdateRequest.java | 11 ---- .../rollover/MetadataRolloverService.java | 11 ++-- .../MetadataCreateDataStreamService.java | 36 ++++++++---- .../metadata/MetadataCreateIndexService.java | 57 ++++++++++++++++--- .../MetadataMigrateToDataStreamService.java | 1 + .../cluster/metadata/RerouteBehavior.java | 30 ++++++++++ .../allocator/AllocationActionListener.java | 6 +- .../TransportRolloverActionTests.java | 7 ++- .../MetadataCreateDataStreamServiceTests.java | 37 ++++++++++-- .../MetadataCreateIndexServiceTests.java | 24 +++++++- .../downsample/TransportDownsampleAction.java | 3 +- 14 files changed, 183 insertions(+), 63 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/cluster/metadata/RerouteBehavior.java diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/TransportCreateDataStreamAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/TransportCreateDataStreamAction.java index 114715bdab76e..74d248a4cb650 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/TransportCreateDataStreamAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/TransportCreateDataStreamAction.java @@ -75,8 +75,7 @@ protected void masterOperation( request.getStartTime(), systemDataStreamDescriptor, request.masterNodeTimeout(), - request.ackTimeout(), - true + request.ackTimeout() ); metadataCreateDataStreamService.createDataStream(updateRequest, listener); } diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java index e6ac0bfe7561a..0d85787dcecbe 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.cluster.metadata.MetadataIndexAliasesService; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.metadata.RerouteBehavior; import org.elasticsearch.cluster.metadata.Template; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.WriteLoadForecaster; @@ -332,10 +333,9 @@ private ClusterState createDataStream(ClusterState state, String name, Instant t time.toEpochMilli(), null, TimeValue.ZERO, - TimeValue.ZERO, - false + TimeValue.ZERO ); - return createDataStreamService.createDataStream(request, state, ActionListener.noop(), false); + return createDataStreamService.createDataStream(request, state, RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false); } private MetadataRolloverService.RolloverResult rolloverOver(ClusterState state, String name, Instant time) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/create/AutoCreateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/create/AutoCreateAction.java index 095e2303d26c2..21e0ec90cd2dd 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/create/AutoCreateAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/create/AutoCreateAction.java @@ -33,6 +33,7 @@ import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.metadata.RerouteBehavior; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.routing.allocation.AllocationService; @@ -289,14 +290,12 @@ ClusterState execute( request.index(), dataStreamDescriptor, request.masterNodeTimeout(), - request.ackTimeout(), - false + request.ackTimeout() ); - assert createRequest.performReroute() == false - : "rerouteCompletionIsNotRequired() assumes reroute is not called by underlying service"; ClusterState clusterState = metadataCreateDataStreamService.createDataStream( createRequest, currentState, + RerouteBehavior.SKIP_REROUTE, rerouteCompletionIsNotRequired(), request.isInitializeFailureStore() ); @@ -372,12 +371,11 @@ ClusterState execute( updateRequest = buildUpdateRequest(projectId, indexName); } - assert updateRequest.performReroute() == false - : "rerouteCompletionIsNotRequired() assumes reroute is not called by underlying service"; final var clusterState = createIndexService.applyCreateIndexRequest( currentState, updateRequest, false, + RerouteBehavior.SKIP_REROUTE, rerouteCompletionIsNotRequired() ); taskContext.success(getAckListener(indexName, allocationActionMultiListener)); @@ -392,7 +390,7 @@ private CreateIndexClusterStateUpdateRequest buildUpdateRequest(ProjectId projec projectId, indexName, request.index() - ).performReroute(false); + ); logger.debug("Auto-creating index {}", indexName); return updateRequest; } @@ -414,7 +412,7 @@ private CreateIndexClusterStateUpdateRequest buildSystemIndexUpdateRequest( projectId, concreteIndexName, request.index() - ).performReroute(false); + ); updateRequest.waitForActiveShards(ActiveShardCount.ALL); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexClusterStateUpdateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexClusterStateUpdateRequest.java index a9983af26d735..1f175031c8d40 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexClusterStateUpdateRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexClusterStateUpdateRequest.java @@ -49,8 +49,6 @@ public class CreateIndexClusterStateUpdateRequest { private ActiveShardCount waitForActiveShards = ActiveShardCount.DEFAULT; - private boolean performReroute = true; - private ComposableIndexTemplate matchingTemplate; private boolean settingsSystemProvided = false; @@ -202,15 +200,6 @@ public CreateIndexClusterStateUpdateRequest dataStreamName(String dataStreamName return this; } - public boolean performReroute() { - return performReroute; - } - - public CreateIndexClusterStateUpdateRequest performReroute(boolean performReroute) { - this.performReroute = performReroute; - return this; - } - /** * @return The composable index template that matches with the index that will be created by this request. */ diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverService.java b/server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverService.java index b4eb1fe8ed1bb..c9309f321533a 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverService.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverService.java @@ -36,6 +36,7 @@ import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.metadata.RerouteBehavior; import org.elasticsearch.cluster.routing.allocation.WriteLoadForecaster; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; @@ -265,12 +266,11 @@ private RolloverResult rolloverAlias( rolloverIndexName, createIndexRequest ); - assert createIndexClusterStateRequest.performReroute() == false - : "rerouteCompletionIsNotRequired() assumes reroute is not called by underlying service"; ClusterState newState = createIndexService.applyCreateIndexRequest( projectState.cluster(), createIndexClusterStateRequest, silent, + RerouteBehavior.SKIP_REROUTE, rerouteCompletionIsNotRequired() ); @@ -419,13 +419,11 @@ yield new DataStreamAutoShardingEvent( now ); createIndexClusterStateRequest.setMatchingTemplate(templateV2); - assert createIndexClusterStateRequest.performReroute() == false - : "rerouteCompletionIsNotRequired() assumes reroute is not called by underlying service"; - newState = createIndexService.applyCreateIndexRequest( projectState.cluster(), createIndexClusterStateRequest, silent, + RerouteBehavior.SKIP_REROUTE, (builder, indexMetadata) -> { downgradeBrokenTsdbBackingIndices(dataStream, builder); builder.put( @@ -581,8 +579,7 @@ static CreateIndexClusterStateUpdateRequest prepareCreateIndexRequest( return new CreateIndexClusterStateUpdateRequest(cause, projectId, targetIndexName, providedIndexName).settings(b.build()) .aliases(createIndexRequest.aliases()) .waitForActiveShards(ActiveShardCount.NONE) // not waiting for shards here, will wait on the alias switch operation - .mappings(createIndexRequest.mappings()) - .performReroute(false); + .mappings(createIndexRequest.mappings()); } /** diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamService.java index d0b32df29077a..1fe482e1cbeb7 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamService.java @@ -109,7 +109,13 @@ public void createDataStream(CreateDataStreamClusterStateUpdateRequest request, public ClusterState execute(ClusterState currentState) throws Exception { // When we're manually creating a data stream (i.e. not an auto creation), we don't need to initialize the failure store // because we don't need to redirect any failures in the same request. - ClusterState clusterState = createDataStream(request, currentState, delegate.reroute(), false); + ClusterState clusterState = createDataStream( + request, + currentState, + RerouteBehavior.PERFORM_REROUTE, + delegate.reroute(), + false + ); DataStream createdDataStream = clusterState.metadata().getProject(request.projectId()).dataStreams().get(request.name); firstBackingIndexRef.set(createdDataStream.getIndices().get(0).getName()); if (createdDataStream.getFailureIndices().isEmpty() == false) { @@ -129,6 +135,7 @@ private void submitUnbatchedTask(@SuppressWarnings("SameParameterValue") String public ClusterState createDataStream( CreateDataStreamClusterStateUpdateRequest request, ClusterState current, + RerouteBehavior rerouteBehavior, ActionListener rerouteListener, boolean initializeFailureStore ) throws Exception { @@ -138,6 +145,7 @@ public ClusterState createDataStream( current, isDslOnlyMode, request, + rerouteBehavior, rerouteListener, initializeFailureStore ); @@ -149,8 +157,7 @@ public record CreateDataStreamClusterStateUpdateRequest( long startTime, @Nullable SystemDataStreamDescriptor systemDataStreamDescriptor, TimeValue masterNodeTimeout, - TimeValue ackTimeout, - boolean performReroute + TimeValue ackTimeout ) { public CreateDataStreamClusterStateUpdateRequest { Objects.requireNonNull(name); @@ -159,7 +166,7 @@ public record CreateDataStreamClusterStateUpdateRequest( } public CreateDataStreamClusterStateUpdateRequest(final ProjectId projectId, String name) { - this(projectId, name, null, TimeValue.ZERO, TimeValue.ZERO, true); + this(projectId, name, null, TimeValue.ZERO, TimeValue.ZERO); } public CreateDataStreamClusterStateUpdateRequest( @@ -167,10 +174,9 @@ public CreateDataStreamClusterStateUpdateRequest( String name, SystemDataStreamDescriptor systemDataStreamDescriptor, TimeValue masterNodeTimeout, - TimeValue ackTimeout, - boolean performReroute + TimeValue ackTimeout ) { - this(projectId, name, System.currentTimeMillis(), systemDataStreamDescriptor, masterNodeTimeout, ackTimeout, performReroute); + this(projectId, name, System.currentTimeMillis(), systemDataStreamDescriptor, masterNodeTimeout, ackTimeout); } public boolean isSystem() { @@ -184,6 +190,7 @@ static ClusterState createDataStream( ClusterState currentState, boolean isDslOnlyMode, CreateDataStreamClusterStateUpdateRequest request, + RerouteBehavior rerouteBehavior, ActionListener rerouteListener, boolean initializeFailureStore ) throws Exception { @@ -195,6 +202,7 @@ static ClusterState createDataStream( request, List.of(), null, + rerouteBehavior, rerouteListener, initializeFailureStore ); @@ -220,6 +228,7 @@ static ClusterState createDataStream( CreateDataStreamClusterStateUpdateRequest request, List backingIndices, IndexMetadata writeIndex, + RerouteBehavior rerouteBehavior, ActionListener rerouteListener, boolean initializeFailureStore ) throws Exception { @@ -304,6 +313,7 @@ static ClusterState createDataStream( metadataCreateIndexService, currentState, request, + rerouteBehavior, rerouteListener, dataStreamName, systemDataStreamDescriptor, @@ -374,6 +384,7 @@ private static ClusterState createBackingIndex( MetadataCreateIndexService metadataCreateIndexService, ClusterState currentState, CreateDataStreamClusterStateUpdateRequest request, + RerouteBehavior rerouteBehavior, ActionListener rerouteListener, String dataStreamName, SystemDataStreamDescriptor systemDataStreamDescriptor, @@ -389,7 +400,6 @@ private static ClusterState createBackingIndex( ).dataStreamName(dataStreamName) .systemDataStreamDescriptor(systemDataStreamDescriptor) .nameResolvedInstant(request.startTime()) - .performReroute(request.performReroute()) .setMatchingTemplate(template); if (isSystem) { @@ -399,7 +409,13 @@ private static ClusterState createBackingIndex( } try { - currentState = metadataCreateIndexService.applyCreateIndexRequest(currentState, createIndexRequest, false, rerouteListener); + currentState = metadataCreateIndexService.applyCreateIndexRequest( + currentState, + createIndexRequest, + false, + rerouteBehavior, + rerouteListener + ); } catch (ResourceAlreadyExistsException e) { // Rethrow as ElasticsearchStatusException, so that bulk transport action doesn't ignore it during // auto index/data stream creation. @@ -436,7 +452,6 @@ public static ClusterState createFailureStoreIndex( failureStoreIndexName ).dataStreamName(dataStreamName) .nameResolvedInstant(nameResolvedInstant) - .performReroute(false) .setMatchingTemplate(template) .settings(indexSettings) .isFailureIndex(true) @@ -447,6 +462,7 @@ public static ClusterState createFailureStoreIndex( currentState, createIndexRequest, false, + RerouteBehavior.SKIP_REROUTE, metadataTransformer, AllocationActionListener.rerouteCompletionIsNotRequired() ); diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java index 98daa0c31d99e..4d0d81d0ef240 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java @@ -248,15 +248,12 @@ public void validateIndexLimit(ProjectMetadata projectMetadata, CreateIndexClust if (maxIndicesPerProjectEnabled == false) { return; } - if (systemIndices.isSystemIndex(request.index()) || systemIndices.isSystemIndexBackingDataStream(request.index())) { return; } - if (systemIndices.isFeatureAssociatedIndex(request.index())) { return; } - var totalUserIndices = projectMetadata.stream() .filter( indexMetadata -> indexMetadata.isSystem() == false @@ -444,7 +441,7 @@ private void onlyCreateIndex( @Override public ClusterState execute(ClusterState currentState) throws Exception { - return applyCreateIndexRequest(currentState, request, false, null, delegate.reroute()); + return applyCreateIndexRequest(currentState, request, false, RerouteBehavior.PERFORM_REROUTE, delegate.reroute()); } @Override @@ -482,6 +479,7 @@ public ClusterState applyCreateIndexRequest( ClusterState currentState, CreateIndexClusterStateUpdateRequest request, boolean silent, + RerouteBehavior rerouteBehavior, BiConsumer metadataTransformer, ActionListener rerouteListener ) throws Exception { @@ -512,6 +510,7 @@ public ClusterState applyCreateIndexRequest( silent, sourceMetadata, metadataTransformer, + rerouteBehavior, rerouteListener ); } else { @@ -520,14 +519,28 @@ public ClusterState applyCreateIndexRequest( // The index being created is for a system data stream, so the backing index will also be a system index if (request.systemDataStreamDescriptor() != null) { - return applyCreateIndexRequestForSystemDataStream(currentState, request, silent, metadataTransformer, rerouteListener); + return applyCreateIndexRequestForSystemDataStream( + currentState, + request, + silent, + metadataTransformer, + rerouteBehavior, + rerouteListener + ); } SystemIndexDescriptor descriptor = systemIndices.findMatchingDescriptor(request.index()); // ignore all templates for all system indices that do not allow templates. // Essentially, all but .kibana indices, see KibanaPlugin.java. if (Objects.nonNull(descriptor) && descriptor.allowsTemplates() == false) { - return applyCreateIndexRequestForSystemIndex(currentState, request, silent, descriptor.getIndexPattern(), rerouteListener); + return applyCreateIndexRequestForSystemIndex( + currentState, + request, + silent, + descriptor.getIndexPattern(), + rerouteBehavior, + rerouteListener + ); } // Hidden indices apply templates slightly differently (ignoring wildcard '*' @@ -545,6 +558,7 @@ public ClusterState applyCreateIndexRequest( silent, templateFromRequest, metadataTransformer, + rerouteBehavior, rerouteListener ); } @@ -565,6 +579,7 @@ public ClusterState applyCreateIndexRequest( silent, v2Template, metadataTransformer, + rerouteBehavior, rerouteListener ); } else { @@ -592,6 +607,7 @@ public ClusterState applyCreateIndexRequest( silent, v1Templates, metadataTransformer, + rerouteBehavior, rerouteListener ); } @@ -602,9 +618,10 @@ public ClusterState applyCreateIndexRequest( ClusterState currentState, CreateIndexClusterStateUpdateRequest request, boolean silent, + RerouteBehavior rerouteBehavior, ActionListener rerouteListener ) throws Exception { - return applyCreateIndexRequest(currentState, request, silent, null, rerouteListener); + return applyCreateIndexRequest(currentState, request, silent, rerouteBehavior, null, rerouteListener); } /** @@ -633,6 +650,7 @@ private ClusterState applyCreateIndexWithTemporaryService( final Function> aliasSupplier, final List templatesApplied, final BiConsumer metadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { // create the index here (on the master) to validate it can be created, as well as adding the mapping @@ -692,7 +710,9 @@ private ClusterState applyCreateIndexWithTemporaryService( allocationService.getShardRoutingRoleStrategy() ); assert assertHasRefreshBlock(indexMetadata, updated.projectState(request.projectId())); - if (request.performReroute()) { + if (rerouteBehavior == RerouteBehavior.SKIP_REROUTE) { + rerouteListener.onResponse(null); + } else { updated = allocationService.reroute( updated, "index [" + indexMetadata.getIndex().getName() + "] created in project [" + request.projectId() + "]", @@ -740,6 +760,7 @@ private ClusterState applyCreateIndexRequestWithV1Templates( final boolean silent, final List templates, final BiConsumer projectMetadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { logger.debug( @@ -803,6 +824,7 @@ private ClusterState applyCreateIndexRequestWithV1Templates( ), templates.stream().map(IndexTemplateMetadata::getName).collect(toList()), projectMetadataTransformer, + rerouteBehavior, rerouteListener ); } @@ -813,6 +835,7 @@ private ClusterState applyCreateIndexRequestWithV2Template( final boolean silent, final String templateName, final BiConsumer projectMetadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { logger.debug("applying create index request using composable template [{}]", templateName); @@ -832,7 +855,15 @@ private ClusterState applyCreateIndexRequestWithV2Template( + "use create data stream api instead" ); } - return applyCreateIndexRequestWithV2Template(currentState, request, silent, template, projectMetadataTransformer, rerouteListener); + return applyCreateIndexRequestWithV2Template( + currentState, + request, + silent, + template, + projectMetadataTransformer, + rerouteBehavior, + rerouteListener + ); } private ClusterState applyCreateIndexRequestWithV2Template( @@ -841,6 +872,7 @@ private ClusterState applyCreateIndexRequestWithV2Template( final boolean silent, final ComposableIndexTemplate template, final BiConsumer projectMetadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { @@ -896,6 +928,7 @@ private ClusterState applyCreateIndexRequestWithV2Template( ), Collections.singletonList("provided in request"), projectMetadataTransformer, + rerouteBehavior, rerouteListener ); } @@ -905,6 +938,7 @@ private ClusterState applyCreateIndexRequestForSystemIndex( final CreateIndexClusterStateUpdateRequest request, final boolean silent, final String indexPattern, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { logger.debug("applying create index request for system index [{}] matching pattern [{}]", request.index(), indexPattern); @@ -951,6 +985,7 @@ private ClusterState applyCreateIndexRequestForSystemIndex( ), List.of(), null, + rerouteBehavior, rerouteListener ); } @@ -960,6 +995,7 @@ private ClusterState applyCreateIndexRequestForSystemDataStream( final CreateIndexClusterStateUpdateRequest request, final boolean silent, final BiConsumer projectMetadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { Objects.requireNonNull(request.systemDataStreamDescriptor()); @@ -1018,6 +1054,7 @@ private ClusterState applyCreateIndexRequestForSystemDataStream( ), List.of(), projectMetadataTransformer, + rerouteBehavior, rerouteListener ); } @@ -1073,6 +1110,7 @@ private ClusterState applyCreateIndexRequestWithExistingMetadata( final boolean silent, final IndexMetadata sourceMetadata, final BiConsumer projectMetadataTransformer, + final RerouteBehavior rerouteBehavior, final ActionListener rerouteListener ) throws Exception { logger.info("applying create index request using existing index [{}] metadata", sourceMetadata.getIndex().getName()); @@ -1127,6 +1165,7 @@ private ClusterState applyCreateIndexRequestWithExistingMetadata( ), List.of(), projectMetadataTransformer, + rerouteBehavior, rerouteListener ); } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataMigrateToDataStreamService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataMigrateToDataStreamService.java index 607ab79ffe59f..1e800c0f81f66 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataMigrateToDataStreamService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataMigrateToDataStreamService.java @@ -177,6 +177,7 @@ static ClusterState migrateToDataStream( req, backingIndices, updatedState.metadata().getProject(project.id()).index(writeIndex), + RerouteBehavior.PERFORM_REROUTE, listener, // No need to initialize the failure store when migrating to a data stream. false diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/RerouteBehavior.java b/server/src/main/java/org/elasticsearch/cluster/metadata/RerouteBehavior.java new file mode 100644 index 0000000000000..d3cee53951629 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/RerouteBehavior.java @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.cluster.metadata; + +/** + * Specifies whether reroute() should be called after applying request. + */ +public enum RerouteBehavior { + /** Invoke reroute after applying the request. */ + PERFORM_REROUTE, + /** Do not reroute; the caller is responsible for ensuring that a follow-up reroute occurs. */ + SKIP_REROUTE; + + /** + * Returns {@link #PERFORM_REROUTE} if either {@code this} or {@code other} is {@link #PERFORM_REROUTE}. + */ + public RerouteBehavior or(RerouteBehavior other) { + if (this == PERFORM_REROUTE || other == PERFORM_REROUTE) { + return PERFORM_REROUTE; + } + return SKIP_REROUTE; + } +} diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionListener.java b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionListener.java index 2d7e35613b698..9a59b9374bfea 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionListener.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionListener.java @@ -28,10 +28,10 @@ public class AllocationActionListener { private final SetOnce>> additionalResponseHeaders = new SetOnce<>(); /** - * This listener could be used when reroute completion (such as even balancing shards across the cluster) is not required for the - * completion of the caller operation. + * Returns a no-op listener for callers that do not need to wait for reroute to finish (e.g. shard rebalancing across the cluster) + * before completing their own operation. * - * For example, it is required to compute the desired balance to properly allocate newly created index, but it is not when deleting one. + * For example, waiting for reroute is needed to allocate shards for a newly created index, but not when deleting one. */ public static ActionListener rerouteCompletionIsNotRequired() { return ActionListener.noop(); diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java index 2177733ad3f86..05a77af871a5d 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.cluster.metadata.MetadataIndexAliasesService; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.metadata.RerouteBehavior; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.project.TestProjectResolvers; import org.elasticsearch.cluster.routing.RecoverySource; @@ -382,7 +383,11 @@ public void testConditionEvaluationWhenAliasToWriteAndReadIndicesConsidersOnlyPr .putProjectMetadata(ProjectMetadata.builder(projectId).put(indexMetadata).put(indexMetadata2)) .build(); - when(mockCreateIndexService.applyCreateIndexRequest(any(), any(), anyBoolean(), any())).thenReturn(stateBefore); + when(mockCreateIndexService.applyCreateIndexRequest(any(), any(), anyBoolean(), any(RerouteBehavior.class), any())).thenReturn( + stateBefore + ); + when(mockCreateIndexService.applyCreateIndexRequest(any(), any(), anyBoolean(), any(RerouteBehavior.class), any(), any())) + .thenReturn(stateBefore); when(mdIndexAliasesService.applyAliasActions(any(), any())).thenReturn(stateBefore); final TransportRolloverAction transportRolloverAction = new TransportRolloverAction( diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamServiceTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamServiceTests.java index ec18627fca5b9..1b67dd4051ed5 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamServiceTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamServiceTests.java @@ -70,6 +70,7 @@ public void testCreateDataStream() throws Exception { cs, true, req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -106,6 +107,7 @@ public void testCreateDataStreamLogsdb() throws Exception { cs, true, req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -148,6 +150,7 @@ public void testCreateDataStreamWithAliasFromTemplate() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -226,6 +229,7 @@ public void testCreateDataStreamWithAliasFromComponentTemplate() throws Exceptio cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -282,6 +286,7 @@ public void testCreateDataStreamWithFailureStoreInitialized() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), true ); @@ -326,6 +331,7 @@ public void testCreateDataStreamWithFailureStoreUninitialized() throws Exception cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -367,6 +373,7 @@ public void testCreateDataStreamWithFailureStoreWithRefreshRate() throws Excepti cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), true ); @@ -395,8 +402,7 @@ public void testCreateSystemDataStream() throws Exception { dataStreamName, systemDataStreamDescriptor(), TimeValue.MAX_VALUE, - TimeValue.ZERO, - true + TimeValue.ZERO ); ClusterState newState = MetadataCreateDataStreamService.createDataStream( metadataCreateIndexService, @@ -404,6 +410,7 @@ public void testCreateSystemDataStream() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ); @@ -440,6 +447,7 @@ public void testCreateDuplicateDataStream() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -463,6 +471,7 @@ public void testCreateDataStreamWithInvalidName() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -486,6 +495,7 @@ public void testCreateDataStreamWithUppercaseCharacters() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -509,6 +519,7 @@ public void testCreateDataStreamStartingWithPeriod() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -532,6 +543,7 @@ public void testCreateDataStreamNoTemplate() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -556,6 +568,7 @@ public void testCreateDataStreamNoValidTemplate() throws Exception { cs, randomBoolean(), req, + RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false ) @@ -591,10 +604,24 @@ private static MetadataCreateIndexService getMetadataCreateIndexService() throws ); return ClusterState.builder(currentState).putProjectMetadata(b.build()).build(); }; - when(s.applyCreateIndexRequest(any(ClusterState.class), any(CreateIndexClusterStateUpdateRequest.class), anyBoolean(), any())) - .thenAnswer(objectAnswer); when( - s.applyCreateIndexRequest(any(ClusterState.class), any(CreateIndexClusterStateUpdateRequest.class), anyBoolean(), any(), any()) + s.applyCreateIndexRequest( + any(ClusterState.class), + any(CreateIndexClusterStateUpdateRequest.class), + anyBoolean(), + any(RerouteBehavior.class), + any() + ) + ).thenAnswer(objectAnswer); + when( + s.applyCreateIndexRequest( + any(ClusterState.class), + any(CreateIndexClusterStateUpdateRequest.class), + anyBoolean(), + any(RerouteBehavior.class), + any(), + any() + ) ).thenAnswer(objectAnswer); return s; diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexServiceTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexServiceTests.java index 42384f977a93c..63c6de472a973 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexServiceTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexServiceTests.java @@ -1862,7 +1862,13 @@ public void testSetPrivateSettingsFails() throws Exception { IndexCreationException exception = assertThrows( IndexCreationException.class, - () -> service.applyCreateIndexRequest(clusterService.state(), request, false, ActionListener.wrap(r -> {}, e -> {})) + () -> service.applyCreateIndexRequest( + clusterService.state(), + request, + false, + RerouteBehavior.PERFORM_REROUTE, + ActionListener.noop() + ) ); assertThat( exception.getCause().getMessage(), @@ -1895,7 +1901,13 @@ public void testSetPrivateSettingsSucceedsWhenSystemProvided() throws Exception ); try { - service.applyCreateIndexRequest(clusterService.state(), request, false, ActionListener.wrap(r -> {}, e -> {})); + service.applyCreateIndexRequest( + clusterService.state(), + request, + false, + RerouteBehavior.PERFORM_REROUTE, + ActionListener.noop() + ); } catch (Exception e) { fail(e, "did not expect private setting to be rejected when system provided"); } @@ -1928,7 +1940,13 @@ public void testIndexSettingProviderPrivateSetting() throws Exception { ); try { - service.applyCreateIndexRequest(clusterService.state(), request, false, ActionListener.wrap(r -> {}, e -> {})); + service.applyCreateIndexRequest( + clusterService.state(), + request, + false, + RerouteBehavior.PERFORM_REROUTE, + ActionListener.noop() + ); } catch (Exception e) { fail(e, "did not expect private setting to be rejected when added via IndexSettingProvider"); } diff --git a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java index 7539f1d53a8ff..7ca5a7c7423c5 100644 --- a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java +++ b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java @@ -37,6 +37,7 @@ import org.elasticsearch.cluster.metadata.MetadataCreateIndexService; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.metadata.RerouteBehavior; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.routing.allocation.DataTier; @@ -1015,7 +1016,7 @@ public ClusterState execute(ClusterState currentState) throws Exception { currentState, createIndexClusterStateUpdateRequest, true, - // Copy index metadata from source index to downsample index + RerouteBehavior.PERFORM_REROUTE, (builder, indexMetadata) -> builder.put(copyIndexMetadata(sourceIndexMetadata, indexMetadata, indexScopedSettings)), delegate.reroute() ); From 86c625bc8fe23be38bd919034dec877c5f169518 Mon Sep 17 00:00:00 2001 From: Ines Potier Date: Thu, 12 Mar 2026 16:55:56 -0400 Subject: [PATCH 2/4] Re-add deleted comment --- .../xpack/downsample/TransportDownsampleAction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java index 7ca5a7c7423c5..31be19a27ca80 100644 --- a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java +++ b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/TransportDownsampleAction.java @@ -1016,6 +1016,7 @@ public ClusterState execute(ClusterState currentState) throws Exception { currentState, createIndexClusterStateUpdateRequest, true, + // Copy index metadata from source index to downsample index RerouteBehavior.PERFORM_REROUTE, (builder, indexMetadata) -> builder.put(copyIndexMetadata(sourceIndexMetadata, indexMetadata, indexScopedSettings)), delegate.reroute() From bc85c79dbe5f76593f7b863c2a27edf6b2d89175 Mon Sep 17 00:00:00 2001 From: Ines Potier Date: Thu, 12 Mar 2026 18:07:10 -0400 Subject: [PATCH 3/4] Small fixes --- .../datastreams/DataStreamGetWriteIndexTests.java | 2 +- .../cluster/metadata/MetadataCreateIndexService.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java index 0d85787dcecbe..a0b53af1f0eeb 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java @@ -335,7 +335,7 @@ private ClusterState createDataStream(ClusterState state, String name, Instant t TimeValue.ZERO, TimeValue.ZERO ); - return createDataStreamService.createDataStream(request, state, RerouteBehavior.PERFORM_REROUTE, ActionListener.noop(), false); + return createDataStreamService.createDataStream(request, state, RerouteBehavior.SKIP_REROUTE, ActionListener.noop(), false); } private MetadataRolloverService.RolloverResult rolloverOver(ClusterState state, String name, Instant time) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java index 4d0d81d0ef240..cd00e911e902b 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java @@ -710,8 +710,11 @@ private ClusterState applyCreateIndexWithTemporaryService( allocationService.getShardRoutingRoleStrategy() ); assert assertHasRefreshBlock(indexMetadata, updated.projectState(request.projectId())); + if (rerouteBehavior == RerouteBehavior.SKIP_REROUTE) { - rerouteListener.onResponse(null); + if (rerouteListener != null) { + rerouteListener.onResponse(null); + } } else { updated = allocationService.reroute( updated, From b641ddfad8e3a06a125eab21cb73ac8441b16b4b Mon Sep 17 00:00:00 2001 From: Ines Potier Date: Thu, 12 Mar 2026 22:36:54 -0400 Subject: [PATCH 4/4] Missing param in javadoc --- .../cluster/metadata/MetadataCreateIndexService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java index cd00e911e902b..b0211348d0f3e 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java @@ -631,6 +631,7 @@ public ClusterState applyCreateIndexRequest( * @param currentState the current state to base the new state off of * @param request the create index request * @param silent a boolean for whether logging should be at a lower or higher level + * @param rerouteBehavior controls whether allocation reroute() is trigger after index creation * @param sourceMetadata when recovering from an existing index, metadata that should be copied to the new index * @param temporaryIndexMeta metadata for the new index built from templates, source metadata, and request settings * @param mappings a list of all mapping definitions to apply, in order