Skip to content

Commit 2e24e88

Browse files
Rollover: refactor out cluster state update (#53965)
Make it possible to reuse the cluster state update of rollover for simulation purposes by extracting it. Also now run the full rollover in the pre-rollover phase and the actual rollover phase, allowing a dedicated exception in case of concurrent rollovers as well as a more thorough pre-check.
1 parent 10f1970 commit 2e24e88

File tree

7 files changed

+671
-416
lines changed

7 files changed

+671
-416
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.indices.rollover;
21+
22+
import org.elasticsearch.action.admin.indices.create.CreateIndexClusterStateUpdateRequest;
23+
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
24+
import org.elasticsearch.action.support.ActiveShardCount;
25+
import org.elasticsearch.cluster.ClusterState;
26+
import org.elasticsearch.cluster.metadata.AliasAction;
27+
import org.elasticsearch.cluster.metadata.AliasMetaData;
28+
import org.elasticsearch.cluster.metadata.AliasOrIndex;
29+
import org.elasticsearch.cluster.metadata.IndexMetaData;
30+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
31+
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
32+
import org.elasticsearch.cluster.metadata.MetaData;
33+
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
34+
import org.elasticsearch.cluster.metadata.MetaDataIndexAliasesService;
35+
import org.elasticsearch.common.Nullable;
36+
import org.elasticsearch.common.inject.Inject;
37+
import org.elasticsearch.threadpool.ThreadPool;
38+
39+
import java.util.List;
40+
import java.util.Locale;
41+
import java.util.regex.Pattern;
42+
43+
import static org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService.findTemplates;
44+
45+
public class MetaDataRolloverService {
46+
private static final Pattern INDEX_NAME_PATTERN = Pattern.compile("^.*-\\d+$");
47+
48+
private final ThreadPool threadPool;
49+
private final MetaDataCreateIndexService createIndexService;
50+
private final MetaDataIndexAliasesService indexAliasesService;
51+
private final IndexNameExpressionResolver indexNameExpressionResolver;
52+
53+
@Inject
54+
public MetaDataRolloverService(ThreadPool threadPool,
55+
MetaDataCreateIndexService createIndexService, MetaDataIndexAliasesService indexAliasesService,
56+
IndexNameExpressionResolver indexNameExpressionResolver) {
57+
this.threadPool = threadPool;
58+
this.createIndexService = createIndexService;
59+
this.indexAliasesService = indexAliasesService;
60+
this.indexNameExpressionResolver = indexNameExpressionResolver;
61+
}
62+
63+
public static class RolloverResult {
64+
public final String rolloverIndexName;
65+
public final String sourceIndexName;
66+
public final ClusterState clusterState;
67+
68+
private RolloverResult(String rolloverIndexName, String sourceIndexName, ClusterState clusterState) {
69+
this.rolloverIndexName = rolloverIndexName;
70+
this.sourceIndexName = sourceIndexName;
71+
this.clusterState = clusterState;
72+
}
73+
}
74+
75+
public RolloverResult rolloverClusterState(ClusterState currentState, String aliasName, String newIndexName,
76+
CreateIndexRequest createIndexRequest, List<Condition<?>> metConditions,
77+
boolean silent) throws Exception {
78+
final MetaData metaData = currentState.metaData();
79+
validate(metaData, aliasName);
80+
final AliasOrIndex.Alias alias = (AliasOrIndex.Alias) metaData.getAliasAndIndexLookup().get(aliasName);
81+
final IndexMetaData indexMetaData = alias.getWriteIndex();
82+
final AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias.getAliasName());
83+
final String sourceProvidedName = indexMetaData.getSettings().get(IndexMetaData.SETTING_INDEX_PROVIDED_NAME,
84+
indexMetaData.getIndex().getName());
85+
final String sourceIndexName = indexMetaData.getIndex().getName();
86+
final String unresolvedName = (newIndexName != null)
87+
? newIndexName
88+
: generateRolloverIndexName(sourceProvidedName, indexNameExpressionResolver);
89+
final String rolloverIndexName = indexNameExpressionResolver.resolveDateMathExpression(unresolvedName);
90+
final boolean explicitWriteIndex = Boolean.TRUE.equals(aliasMetaData.writeIndex());
91+
final Boolean isHidden = IndexMetaData.INDEX_HIDDEN_SETTING.exists(createIndexRequest.settings()) ?
92+
IndexMetaData.INDEX_HIDDEN_SETTING.get(createIndexRequest.settings()) : null;
93+
createIndexService.validateIndexName(rolloverIndexName, currentState); // fails if the index already exists
94+
checkNoDuplicatedAliasInIndexTemplate(metaData, rolloverIndexName, aliasName, isHidden);
95+
96+
CreateIndexClusterStateUpdateRequest createIndexClusterStateRequest =
97+
prepareCreateIndexRequest(unresolvedName, rolloverIndexName, createIndexRequest);
98+
ClusterState newState = createIndexService.applyCreateIndexRequest(currentState, createIndexClusterStateRequest, silent);
99+
newState = indexAliasesService.applyAliasActions(newState,
100+
rolloverAliasToNewIndex(sourceIndexName, rolloverIndexName, explicitWriteIndex, aliasMetaData.isHidden(), aliasName));
101+
102+
RolloverInfo rolloverInfo = new RolloverInfo(aliasName, metConditions, threadPool.absoluteTimeInMillis());
103+
newState = ClusterState.builder(newState)
104+
.metaData(MetaData.builder(newState.metaData())
105+
.put(IndexMetaData.builder(newState.metaData().index(sourceIndexName))
106+
.putRolloverInfo(rolloverInfo))).build();
107+
108+
return new RolloverResult(rolloverIndexName, sourceIndexName, newState);
109+
}
110+
111+
static String generateRolloverIndexName(String sourceIndexName, IndexNameExpressionResolver indexNameExpressionResolver) {
112+
String resolvedName = indexNameExpressionResolver.resolveDateMathExpression(sourceIndexName);
113+
final boolean isDateMath = sourceIndexName.equals(resolvedName) == false;
114+
if (INDEX_NAME_PATTERN.matcher(resolvedName).matches()) {
115+
int numberIndex = sourceIndexName.lastIndexOf("-");
116+
assert numberIndex != -1 : "no separator '-' found";
117+
int counter = Integer.parseInt(sourceIndexName.substring(numberIndex + 1,
118+
isDateMath ? sourceIndexName.length()-1 : sourceIndexName.length()));
119+
String newName = sourceIndexName.substring(0, numberIndex) + "-" + String.format(Locale.ROOT, "%06d", ++counter)
120+
+ (isDateMath ? ">" : "");
121+
return newName;
122+
} else {
123+
throw new IllegalArgumentException("index name [" + sourceIndexName + "] does not match pattern '^.*-\\d+$'");
124+
}
125+
}
126+
127+
static CreateIndexClusterStateUpdateRequest prepareCreateIndexRequest(final String providedIndexName, final String targetIndexName,
128+
CreateIndexRequest createIndexRequest) {
129+
createIndexRequest.cause("rollover_index");
130+
createIndexRequest.index(targetIndexName);
131+
return new CreateIndexClusterStateUpdateRequest(
132+
"rollover_index", targetIndexName, providedIndexName)
133+
.ackTimeout(createIndexRequest.timeout())
134+
.masterNodeTimeout(createIndexRequest.masterNodeTimeout())
135+
.settings(createIndexRequest.settings())
136+
.aliases(createIndexRequest.aliases())
137+
.waitForActiveShards(ActiveShardCount.NONE) // not waiting for shards here, will wait on the alias switch operation
138+
.mappings(createIndexRequest.mappings());
139+
}
140+
141+
/**
142+
* Creates the alias actions to reflect the alias rollover from the old (source) index to the new (target/rolled over) index. An
143+
* alias pointing to multiple indices will have to be an explicit write index (ie. the old index alias has is_write_index set to true)
144+
* in which case, after the rollover, the new index will need to be the explicit write index.
145+
*/
146+
static List<AliasAction> rolloverAliasToNewIndex(String oldIndex, String newIndex, boolean explicitWriteIndex,
147+
@Nullable Boolean isHidden, String alias) {
148+
if (explicitWriteIndex) {
149+
return List.of(
150+
new AliasAction.Add(newIndex, alias, null, null, null, true, isHidden),
151+
new AliasAction.Add(oldIndex, alias, null, null, null, false, isHidden));
152+
} else {
153+
return List.of(
154+
new AliasAction.Add(newIndex, alias, null, null, null, null, isHidden),
155+
new AliasAction.Remove(oldIndex, alias));
156+
}
157+
}
158+
159+
/**
160+
* If the newly created index matches with an index template whose aliases contains the rollover alias,
161+
* the rollover alias will point to multiple indices. This causes indexing requests to be rejected.
162+
* To avoid this, we make sure that there is no duplicated alias in index templates before creating a new index.
163+
*/
164+
static void checkNoDuplicatedAliasInIndexTemplate(MetaData metaData, String rolloverIndexName, String rolloverRequestAlias,
165+
@Nullable Boolean isHidden) {
166+
final List<IndexTemplateMetaData> matchedTemplates = findTemplates(metaData, rolloverIndexName, isHidden);
167+
for (IndexTemplateMetaData template : matchedTemplates) {
168+
if (template.aliases().containsKey(rolloverRequestAlias)) {
169+
throw new IllegalArgumentException(String.format(Locale.ROOT,
170+
"Rollover alias [%s] can point to multiple indices, found duplicated alias [%s] in index template [%s]",
171+
rolloverRequestAlias, template.aliases().keys(), template.name()));
172+
}
173+
}
174+
}
175+
176+
static void validate(MetaData metaData, String aliasName) {
177+
final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(aliasName);
178+
if (aliasOrIndex == null) {
179+
throw new IllegalArgumentException("source alias does not exist");
180+
}
181+
if (aliasOrIndex.isAlias() == false) {
182+
throw new IllegalArgumentException("source alias is a concrete index");
183+
}
184+
final AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
185+
if (alias.getWriteIndex() == null) {
186+
throw new IllegalArgumentException("source alias [" + alias.getAliasName() + "] does not point to a write index");
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)