Skip to content

Commit 30b3ae3

Browse files
authored
[7.x] Create data stream aliases from template (#73867) (#75647)
1 parent ec5f392 commit 30b3ae3

File tree

7 files changed

+144
-163
lines changed

7 files changed

+144
-163
lines changed

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamService.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.elasticsearch.threadpool.ThreadPool;
3737

3838
import java.io.IOException;
39+
import java.util.ArrayList;
3940
import java.util.List;
4041
import java.util.Locale;
4142
import java.util.Map;
@@ -160,12 +161,11 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
160161
}
161162

162163
static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIndexService,
163-
ClusterState currentState,
164-
String dataStreamName,
165-
List<IndexMetadata> backingIndices,
166-
IndexMetadata writeIndex,
167-
SystemDataStreamDescriptor systemDataStreamDescriptor) throws Exception
168-
{
164+
ClusterState currentState,
165+
String dataStreamName,
166+
List<IndexMetadata> backingIndices,
167+
IndexMetadata writeIndex,
168+
SystemDataStreamDescriptor systemDataStreamDescriptor) throws Exception {
169169
if (currentState.nodes().getMinNodeVersion().before(Version.V_7_9_0)) {
170170
throw new IllegalStateException("data streams require minimum node version of " + Version.V_7_9_0);
171171
}
@@ -183,8 +183,8 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
183183
throw new IllegalArgumentException("data_stream [" + dataStreamName + "] must be lowercase");
184184
}
185185
if (dataStreamName.startsWith(DataStream.BACKING_INDEX_PREFIX)) {
186-
throw new IllegalArgumentException("data_stream [" + dataStreamName + "] must not start with '"
187-
+ DataStream.BACKING_INDEX_PREFIX + "'");
186+
throw new IllegalArgumentException(
187+
"data_stream [" + dataStreamName + "] must not start with '" + DataStream.BACKING_INDEX_PREFIX + "'");
188188
}
189189

190190
final boolean isSystem = systemDataStreamDescriptor != null;
@@ -226,9 +226,23 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
226226
DataStream newDataStream = new DataStream(dataStreamName, timestampField, dsBackingIndices, 1L,
227227
template.metadata() != null ? org.elasticsearch.core.Map.copyOf(template.metadata()) : null, hidden, false, isSystem);
228228
Metadata.Builder builder = Metadata.builder(currentState.metadata()).put(newDataStream);
229-
logger.info("adding data stream [{}] with write index [{}] and backing indices [{}]", dataStreamName,
229+
230+
List<String> aliases = new ArrayList<>();
231+
if (template.template() != null && template.template().aliases() != null) {
232+
for (AliasMetadata alias : template.template().aliases().values()) {
233+
aliases.add(alias.getAlias());
234+
builder.put(alias.getAlias(), dataStreamName, alias.writeIndex(), alias.filter() == null ? null : alias.filter().string());
235+
}
236+
}
237+
238+
logger.info(
239+
"adding data stream [{}] with write index [{}], backing indices [{}], and aliases [{}]",
240+
dataStreamName,
230241
writeIndex.getIndex().getName(),
231-
Strings.arrayToCommaDelimitedString(backingIndices.stream().map(i -> i.getIndex().getName()).toArray()));
242+
Strings.arrayToCommaDelimitedString(backingIndices.stream().map(i -> i.getIndex().getName()).toArray()),
243+
Strings.collectionToCommaDelimitedString(aliases)
244+
);
245+
232246
return ClusterState.builder(currentState).metadata(builder).build();
233247
}
234248

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,8 @@ private ClusterState applyCreateIndexRequestWithV2Template(final ClusterState cu
513513
logger.debug("applying create index request using composable template [{}]", templateName);
514514

515515
ComposableIndexTemplate template = currentState.getMetadata().templatesV2().get(templateName);
516-
if (request.dataStreamName() == null && template.getDataStreamTemplate() != null) {
516+
final boolean isDataStream = template.getDataStreamTemplate() != null;
517+
if (isDataStream && request.dataStreamName() == null) {
517518
throw new IllegalArgumentException("cannot create index with name [" + request.index() +
518519
"], because it matches with template [" + templateName + "] that creates data streams only, " +
519520
"use create data stream api instead");
@@ -528,14 +529,30 @@ private ClusterState applyCreateIndexRequestWithV2Template(final ClusterState cu
528529
int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null);
529530
IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(aggregatedIndexSettings, request, routingNumShards);
530531

531-
return applyCreateIndexWithTemporaryService(currentState, request, silent, null, tmpImd, mappings,
532-
indexService -> resolveAndValidateAliases(request.index(), request.aliases(),
533-
MetadataIndexTemplateService.resolveAliases(currentState.metadata(), templateName, false), currentState.metadata(),
534-
// the context is only used for validation so it's fine to pass fake values for the
535-
// shard id and the current timestamp
536-
aliasValidator, xContentRegistry, indexService.newSearchExecutionContext(0, 0, null, () -> 0L, null, emptyMap()),
537-
indexService.dateMathExpressionResolverAt(request.getNameResolvedAt())),
538-
Collections.singletonList(templateName), metadataTransformer);
532+
return applyCreateIndexWithTemporaryService(
533+
currentState,
534+
request,
535+
silent,
536+
null,
537+
tmpImd,
538+
mappings,
539+
indexService -> resolveAndValidateAliases(
540+
request.index(),
541+
// data stream aliases are created separately in MetadataCreateDataStreamService::createDataStream
542+
isDataStream ? Collections.emptySet() : request.aliases(),
543+
isDataStream ?
544+
Collections.emptyList() :
545+
MetadataIndexTemplateService.resolveAliases(currentState.metadata(), templateName, false),
546+
currentState.metadata(),
547+
aliasValidator,
548+
xContentRegistry,
549+
// the context is used ony for validation so it's fine to pass fake values for the shard id and the current timestamp
550+
indexService.newSearchExecutionContext(0, 0, null, () -> 0L, null, emptyMap()),
551+
indexService.dateMathExpressionResolverAt(request.getNameResolvedAt())
552+
),
553+
Collections.singletonList(templateName),
554+
metadataTransformer
555+
);
539556
}
540557

541558
private ClusterState applyCreateIndexRequestForSystemDataStream(final ClusterState currentState,

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,9 +1097,9 @@ public static List<Map<String, AliasMetadata>> resolveAliases(final List<IndexTe
10971097
* Resolve the given v2 template into an ordered list of aliases
10981098
*
10991099
* @param failIfTemplateHasDataStream Whether to skip validating if a template has a data stream definition and an alias definition.
1100-
* This validation is needed so that no template gets created that creates datastream and also
1101-
* a an alias pointing to the backing indices of a data stream. Unfortunately this validation
1102-
* was missing in versions prior to 7.11, which mean that there are cluster states out there,
1100+
* This validation is needed so that no template gets created that creates data stream and also
1101+
* an alias pointing to the backing indices of a data stream. Unfortunately this validation
1102+
* was missing in versions prior to 7.11, which mean that there are cluster states out there
11031103
* that have this malformed templates. This method is used when rolling over a data stream
11041104
* or creating new data streams. In order for these clusters to avoid failing these operations
11051105
* immediately after an upgrade the failure should be optional. So that there is time to change
@@ -1141,18 +1141,6 @@ static List<Map<String, AliasMetadata>> resolveAliases(final ComposableIndexTemp
11411141
.map(Template::aliases)
11421142
.ifPresent(aliases::add);
11431143

1144-
// A template that creates data streams can't also create aliases.
1145-
// (otherwise we end up with aliases pointing to backing indices of data streams)
1146-
if (aliases.size() > 0 && template.getDataStreamTemplate() != null) {
1147-
if (failIfTemplateHasDataStream) {
1148-
throw new IllegalArgumentException("template [" + templateName + "] has alias and data stream definitions");
1149-
} else {
1150-
String warning = "template [" + templateName + "] has alias and data stream definitions";
1151-
logger.warn(warning);
1152-
HeaderWarning.addWarning(warning);
1153-
}
1154-
}
1155-
11561144
// Aliases are applied in order, but subsequent alias configuration from the same name is
11571145
// ignored, so in order for the order to be correct, alias configuration should be in order
11581146
// of precedence (with the index template first)

server/src/test/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverServiceTests.java

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -637,96 +637,6 @@ protected String contentType() {
637637
}
638638
}
639639

640-
public void testRolloverDataStreamWorksWithTemplateThatAlsoCreatesAliases() throws Exception {
641-
final DataStream dataStream = DataStreamTestHelper.randomInstance()
642-
// ensure no replicate data stream
643-
.promoteDataStream();
644-
ComposableIndexTemplate template = new ComposableIndexTemplate(
645-
Collections.singletonList(dataStream.getName() + "*"),
646-
new Template(null, null, Collections.singletonMap("my-alias", AliasMetadata.newAliasMetadataBuilder("my-alias").build())),
647-
null,
648-
null,
649-
null,
650-
null,
651-
new ComposableIndexTemplate.DataStreamTemplate(),
652-
null
653-
);
654-
Metadata.Builder builder = Metadata.builder();
655-
builder.put("template", template);
656-
for (Index index : dataStream.getIndices()) {
657-
builder.put(DataStreamTestHelper.getIndexMetadataBuilderForIndex(index));
658-
}
659-
builder.put(dataStream);
660-
final ClusterState clusterState = ClusterState.builder(new ClusterName("test")).metadata(builder).build();
661-
662-
ThreadPool testThreadPool = new TestThreadPool(getTestName());
663-
try {
664-
DateFieldMapper dateFieldMapper = new DateFieldMapper.Builder(
665-
"@timestamp",
666-
DateFieldMapper.Resolution.MILLISECONDS,
667-
null,
668-
ScriptCompiler.NONE,
669-
false,
670-
Version.CURRENT).build(new ContentPath());
671-
MappedFieldType mockedTimestampFieldType = mock(MappedFieldType.class);
672-
when(mockedTimestampFieldType.name()).thenReturn("_data_stream_timestamp");
673-
MetadataFieldMapper mockedTimestampField = new MetadataFieldMapper(mockedTimestampFieldType) {
674-
@Override
675-
protected String contentType() {
676-
return null;
677-
}
678-
};
679-
MetadataFieldMapper[] metadataFieldMappers = {new MetadataIndexTemplateServiceTests.MetadataTimestampFieldMapper(true)};
680-
RootObjectMapper.Builder root = new RootObjectMapper.Builder("_doc");
681-
root.add(new DateFieldMapper.Builder(dataStream.getTimeStampField().getName(), DateFieldMapper.Resolution.MILLISECONDS,
682-
DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER, ScriptCompiler.NONE, true, Version.CURRENT));
683-
Mapping mapping = new Mapping(root.build(new ContentPath("")), metadataFieldMappers, Collections.emptyMap());
684-
MappingLookup mappingLookup = MappingLookup.fromMappers(
685-
mapping,
686-
org.elasticsearch.core.List.of(mockedTimestampField, dateFieldMapper),
687-
org.elasticsearch.core.List.of(),
688-
org.elasticsearch.core.List.of());
689-
ClusterService clusterService = ClusterServiceUtils.createClusterService(testThreadPool);
690-
Environment env = mock(Environment.class);
691-
when(env.sharedDataFile()).thenReturn(null);
692-
AllocationService allocationService = mock(AllocationService.class);
693-
when(allocationService.reroute(any(ClusterState.class), any(String.class))).then(i -> i.getArguments()[0]);
694-
IndicesService indicesService = mockIndicesServices(mappingLookup);
695-
IndexNameExpressionResolver mockIndexNameExpressionResolver = mock(IndexNameExpressionResolver.class);
696-
when(mockIndexNameExpressionResolver.resolveDateMathExpression(any())).then(returnsFirstArg());
697-
698-
ShardLimitValidator shardLimitValidator = new ShardLimitValidator(Settings.EMPTY, clusterService);
699-
MetadataCreateIndexService createIndexService = new MetadataCreateIndexService(Settings.EMPTY,
700-
clusterService, indicesService, allocationService, new AliasValidator(), shardLimitValidator, env,
701-
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, testThreadPool, null, EmptySystemIndices.INSTANCE, false);
702-
MetadataIndexAliasesService indexAliasesService = new MetadataIndexAliasesService(clusterService, indicesService,
703-
new AliasValidator(), null, xContentRegistry());
704-
MetadataRolloverService rolloverService = new MetadataRolloverService(testThreadPool, createIndexService, indexAliasesService,
705-
mockIndexNameExpressionResolver, EmptySystemIndices.INSTANCE);
706-
707-
MaxDocsCondition condition = new MaxDocsCondition(randomNonNegativeLong());
708-
List<Condition<?>> metConditions = Collections.singletonList(condition);
709-
CreateIndexRequest createIndexRequest = new CreateIndexRequest("_na_");
710-
711-
// Ensure that a warning header is emitted
712-
MetadataRolloverService.RolloverResult rolloverResult =
713-
rolloverService.rolloverClusterState(clusterState, dataStream.getName(), null, createIndexRequest, metConditions,
714-
randomBoolean(), false);
715-
assertWarnings(
716-
"aliases [my-alias] cannot refer to backing indices of data streams",
717-
"template [template] has alias and data stream definitions"
718-
);
719-
720-
// Just checking that the rollover was successful:
721-
String sourceIndexName = DataStream.getDefaultBackingIndexName(dataStream.getName(), dataStream.getGeneration());
722-
String newIndexName = DataStream.getDefaultBackingIndexName(dataStream.getName(), dataStream.getGeneration() + 1);
723-
assertEquals(sourceIndexName, rolloverResult.sourceIndexName);
724-
assertEquals(newIndexName, rolloverResult.rolloverIndexName);
725-
} finally {
726-
testThreadPool.shutdown();
727-
}
728-
}
729-
730640
public void testValidation() throws Exception {
731641
final String rolloverTarget;
732642
final String sourceIndexName;

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataCreateDataStreamServiceTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
import org.elasticsearch.indices.SystemIndices.Feature;
2424
import org.elasticsearch.test.ESTestCase;
2525

26+
import java.util.ArrayList;
2627
import java.util.Collections;
28+
import java.util.HashMap;
2729
import java.util.Map;
2830

2931
import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.createFirstBackingIndex;
3032
import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.createTimestampField;
3133
import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.generateMapping;
34+
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
3235
import static org.hamcrest.Matchers.containsString;
3336
import static org.hamcrest.Matchers.equalTo;
3437
import static org.hamcrest.Matchers.is;
@@ -66,6 +69,61 @@ public void testCreateDataStream() throws Exception {
6669
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).isSystem(), is(false));
6770
}
6871

72+
public void testCreateDataStreamWithAliasFromTemplate() throws Exception {
73+
final MetadataCreateIndexService metadataCreateIndexService = getMetadataCreateIndexService();
74+
final String dataStreamName = "my-data-stream";
75+
final int aliasCount = randomIntBetween(0, 3);
76+
Map<String, AliasMetadata> aliases = new HashMap<>(aliasCount);
77+
for (int k = 0; k < aliasCount; k++) {
78+
final String aliasName = randomAlphaOfLength(6);
79+
AliasMetadata.Builder builder = AliasMetadata.newAliasMetadataBuilder(aliasName);
80+
if (randomBoolean()) {
81+
builder.filter(org.elasticsearch.core.Map.of(
82+
"term",
83+
org.elasticsearch.core.Map.of(
84+
"user",
85+
org.elasticsearch.core.Map.of("value", randomAlphaOfLength(5)))
86+
)
87+
);
88+
}
89+
builder.writeIndex(randomBoolean());
90+
aliases.put(aliasName, builder.build());
91+
}
92+
ComposableIndexTemplate template = new ComposableIndexTemplate.Builder()
93+
.indexPatterns(org.elasticsearch.core.List.of(dataStreamName + "*"))
94+
.dataStreamTemplate(new DataStreamTemplate())
95+
.template(new Template(null, null, aliases))
96+
.build();
97+
ClusterState cs = ClusterState.builder(new ClusterName("_name"))
98+
.metadata(Metadata.builder().put("template", template).build())
99+
.build();
100+
CreateDataStreamClusterStateUpdateRequest req =
101+
new CreateDataStreamClusterStateUpdateRequest(dataStreamName, TimeValue.ZERO, TimeValue.ZERO);
102+
ClusterState newState = MetadataCreateDataStreamService.createDataStream(metadataCreateIndexService, cs, req);
103+
assertThat(newState.metadata().dataStreams().size(), equalTo(1));
104+
assertThat(newState.metadata().dataStreams().get(dataStreamName).getName(), equalTo(dataStreamName));
105+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isSystem(), is(false));
106+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isHidden(), is(false));
107+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isReplicated(), is(false));
108+
assertThat(newState.metadata().dataStreamAliases().size(), is(aliasCount));
109+
for (String aliasName : aliases.keySet()) {
110+
AliasMetadata expectedAlias = aliases.get(aliasName);
111+
DataStreamAlias actualAlias = newState.metadata().dataStreamAliases().get(aliasName);
112+
assertThat(actualAlias, is(notNullValue()));
113+
assertThat(actualAlias.getName(), equalTo(expectedAlias.alias()));
114+
assertThat(actualAlias.getFilter(), equalTo(expectedAlias.filter()));
115+
assertThat(actualAlias.getWriteDataStream(), equalTo(expectedAlias.writeIndex() ? dataStreamName : null));
116+
}
117+
118+
assertThat(newState.metadata().dataStreamAliases().values().stream().map(DataStreamAlias::getName).toArray(),
119+
arrayContainingInAnyOrder (new ArrayList<>(aliases.keySet()).toArray()));
120+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)), notNullValue());
121+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).getAliases().size(), is(0));
122+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).getSettings().get("index.hidden"),
123+
equalTo("true"));
124+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).isSystem(), is(false));
125+
}
126+
69127
public void testCreateSystemDataStream() throws Exception {
70128
final MetadataCreateIndexService metadataCreateIndexService = getMetadataCreateIndexService();
71129
final String dataStreamName = ".system-data-stream";

0 commit comments

Comments
 (0)