Skip to content

Commit 0232675

Browse files
authored
Create data stream aliases from template (#73867)
1 parent ff6ec59 commit 0232675

File tree

7 files changed

+127
-134
lines changed

7 files changed

+127
-134
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
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.threadpool.ThreadPool;
3636

3737
import java.io.IOException;
38+
import java.util.ArrayList;
3839
import java.util.List;
3940
import java.util.Locale;
4041
import java.util.Map;
@@ -157,12 +158,11 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
157158
}
158159

159160
static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIndexService,
160-
ClusterState currentState,
161-
String dataStreamName,
162-
List<IndexMetadata> backingIndices,
163-
IndexMetadata writeIndex,
164-
SystemDataStreamDescriptor systemDataStreamDescriptor) throws Exception
165-
{
161+
ClusterState currentState,
162+
String dataStreamName,
163+
List<IndexMetadata> backingIndices,
164+
IndexMetadata writeIndex,
165+
SystemDataStreamDescriptor systemDataStreamDescriptor) throws Exception {
166166
Objects.requireNonNull(metadataCreateIndexService);
167167
Objects.requireNonNull(currentState);
168168
Objects.requireNonNull(backingIndices);
@@ -177,8 +177,8 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
177177
throw new IllegalArgumentException("data_stream [" + dataStreamName + "] must be lowercase");
178178
}
179179
if (dataStreamName.startsWith(DataStream.BACKING_INDEX_PREFIX)) {
180-
throw new IllegalArgumentException("data_stream [" + dataStreamName + "] must not start with '"
181-
+ DataStream.BACKING_INDEX_PREFIX + "'");
180+
throw new IllegalArgumentException(
181+
"data_stream [" + dataStreamName + "] must not start with '" + DataStream.BACKING_INDEX_PREFIX + "'");
182182
}
183183

184184
final boolean isSystem = systemDataStreamDescriptor != null;
@@ -219,9 +219,23 @@ static ClusterState createDataStream(MetadataCreateIndexService metadataCreateIn
219219
DataStream newDataStream = new DataStream(dataStreamName, timestampField, dsBackingIndices, 1L,
220220
template.metadata() != null ? Map.copyOf(template.metadata()) : null, hidden, false, isSystem);
221221
Metadata.Builder builder = Metadata.builder(currentState.metadata()).put(newDataStream);
222-
logger.info("adding data stream [{}] with write index [{}] and backing indices [{}]", dataStreamName,
222+
223+
List<String> aliases = new ArrayList<>();
224+
if (template.template() != null && template.template().aliases() != null) {
225+
for (var alias : template.template().aliases().values()) {
226+
aliases.add(alias.getAlias());
227+
builder.put(alias.getAlias(), dataStreamName, alias.writeIndex(), alias.filter() == null ? null : alias.filter().string());
228+
}
229+
}
230+
231+
logger.info(
232+
"adding data stream [{}] with write index [{}], backing indices [{}], and aliases [{}]",
233+
dataStreamName,
223234
writeIndex.getIndex().getName(),
224-
Strings.arrayToCommaDelimitedString(backingIndices.stream().map(i -> i.getIndex().getName()).toArray()));
235+
Strings.arrayToCommaDelimitedString(backingIndices.stream().map(i -> i.getIndex().getName()).toArray()),
236+
Strings.collectionToCommaDelimitedString(aliases)
237+
);
238+
225239
return ClusterState.builder(currentState).metadata(builder).build();
226240
}
227241

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

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

506506
ComposableIndexTemplate template = currentState.getMetadata().templatesV2().get(templateName);
507-
if (request.dataStreamName() == null && template.getDataStreamTemplate() != null) {
507+
final boolean isDataStream = template.getDataStreamTemplate() != null;
508+
if (isDataStream && request.dataStreamName() == null) {
508509
throw new IllegalArgumentException("cannot create index with name [" + request.index() +
509510
"], because it matches with template [" + templateName + "] that creates data streams only, " +
510511
"use create data stream api instead");
@@ -519,14 +520,28 @@ private ClusterState applyCreateIndexRequestWithV2Template(final ClusterState cu
519520
int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null);
520521
IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(aggregatedIndexSettings, request, routingNumShards);
521522

522-
return applyCreateIndexWithTemporaryService(currentState, request, silent, null, tmpImd, mappings,
523-
indexService -> resolveAndValidateAliases(request.index(), request.aliases(),
524-
MetadataIndexTemplateService.resolveAliases(currentState.metadata(), templateName), currentState.metadata(),
525-
// the context is only used for validation so it's fine to pass fake values for the
526-
// shard id and the current timestamp
527-
aliasValidator, xContentRegistry, indexService.newSearchExecutionContext(0, 0, null, () -> 0L, null, emptyMap()),
528-
indexService.dateMathExpressionResolverAt(request.getNameResolvedAt())),
529-
Collections.singletonList(templateName), metadataTransformer);
523+
return applyCreateIndexWithTemporaryService(
524+
currentState,
525+
request,
526+
silent,
527+
null,
528+
tmpImd,
529+
mappings,
530+
indexService -> resolveAndValidateAliases(
531+
request.index(),
532+
// data stream aliases are created separately in MetadataCreateDataStreamService::createDataStream
533+
isDataStream ? Set.of() : request.aliases(),
534+
isDataStream ? List.of() : MetadataIndexTemplateService.resolveAliases(currentState.metadata(), templateName),
535+
currentState.metadata(),
536+
aliasValidator,
537+
xContentRegistry,
538+
// the context is used ony for validation so it's fine to pass fake values for the shard id and the current timestamp
539+
indexService.newSearchExecutionContext(0, 0, null, () -> 0L, null, emptyMap()),
540+
indexService.dateMathExpressionResolverAt(request.getNameResolvedAt())
541+
),
542+
Collections.singletonList(templateName),
543+
metadataTransformer
544+
);
530545
}
531546

532547
private ClusterState applyCreateIndexRequestForSystemDataStream(final ClusterState currentState,

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,12 +1146,6 @@ static List<Map<String, AliasMetadata>> resolveAliases(final ComposableIndexTemp
11461146
.map(Template::aliases)
11471147
.ifPresent(aliases::add);
11481148

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

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

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -635,82 +635,6 @@ protected String contentType() {
635635
}
636636
}
637637

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

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

Lines changed: 52 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;
27+
import java.util.HashMap;
2628
import java.util.List;
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,55 @@ 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+
var builder = AliasMetadata.newAliasMetadataBuilder(aliasName);
80+
if (randomBoolean()) {
81+
builder.filter(Map.of("term", Map.of("user", Map.of("value", randomAlphaOfLength(5)))));
82+
}
83+
builder.writeIndex(randomBoolean());
84+
aliases.put(aliasName, builder.build());
85+
}
86+
ComposableIndexTemplate template = new ComposableIndexTemplate.Builder()
87+
.indexPatterns(List.of(dataStreamName + "*"))
88+
.dataStreamTemplate(new DataStreamTemplate())
89+
.template(new Template(null, null, aliases))
90+
.build();
91+
ClusterState cs = ClusterState.builder(new ClusterName("_name"))
92+
.metadata(Metadata.builder().put("template", template).build())
93+
.build();
94+
CreateDataStreamClusterStateUpdateRequest req =
95+
new CreateDataStreamClusterStateUpdateRequest(dataStreamName, TimeValue.ZERO, TimeValue.ZERO);
96+
ClusterState newState = MetadataCreateDataStreamService.createDataStream(metadataCreateIndexService, cs, req);
97+
assertThat(newState.metadata().dataStreams().size(), equalTo(1));
98+
assertThat(newState.metadata().dataStreams().get(dataStreamName).getName(), equalTo(dataStreamName));
99+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isSystem(), is(false));
100+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isHidden(), is(false));
101+
assertThat(newState.metadata().dataStreams().get(dataStreamName).isReplicated(), is(false));
102+
assertThat(newState.metadata().dataStreamAliases().size(), is(aliasCount));
103+
for (String aliasName : aliases.keySet()) {
104+
var expectedAlias = aliases.get(aliasName);
105+
var actualAlias = newState.metadata().dataStreamAliases().get(aliasName);
106+
assertThat(actualAlias, is(notNullValue()));
107+
assertThat(actualAlias.getName(), equalTo(expectedAlias.alias()));
108+
assertThat(actualAlias.getFilter(), equalTo(expectedAlias.filter()));
109+
assertThat(actualAlias.getWriteDataStream(), equalTo(expectedAlias.writeIndex() ? dataStreamName : null));
110+
}
111+
112+
assertThat(newState.metadata().dataStreamAliases().values().stream().map(DataStreamAlias::getName).toArray(),
113+
arrayContainingInAnyOrder (new ArrayList<>(aliases.keySet()).toArray()));
114+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)), notNullValue());
115+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).getAliases().size(), is(0));
116+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).getSettings().get("index.hidden"),
117+
equalTo("true"));
118+
assertThat(newState.metadata().index(DataStream.getDefaultBackingIndexName(dataStreamName, 1)).isSystem(), is(false));
119+
}
120+
69121
public void testCreateSystemDataStream() throws Exception {
70122
final MetadataCreateIndexService metadataCreateIndexService = getMetadataCreateIndexService();
71123
final String dataStreamName = ".system-data-stream";

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,31 +1207,6 @@ public void testResolveAliases() throws Exception {
12071207
assertThat(resolvedAliases, equalTo(List.of(a3, a1, a2)));
12081208
}
12091209

1210-
public void testResolveAliasesDataStreams() throws Exception {
1211-
Map<String, AliasMetadata> a1 = new HashMap<>();
1212-
a1.put("logs", AliasMetadata.newAliasMetadataBuilder("logs").build());
1213-
1214-
// index template can't have data streams and aliases
1215-
ComposableIndexTemplate it = new ComposableIndexTemplate(List.of("logs-*"),
1216-
new Template(null, null, a1), null, 0L, 1L, null, new ComposableIndexTemplate.DataStreamTemplate(), null);
1217-
ClusterState state1 = ClusterState.builder(ClusterState.EMPTY_STATE)
1218-
.metadata(Metadata.builder().put("1", it).build())
1219-
.build();
1220-
Exception e =
1221-
expectThrows(IllegalArgumentException.class, () -> MetadataIndexTemplateService.resolveAliases(state1.metadata(), "1"));
1222-
assertThat(e.getMessage(), equalTo("template [1] has alias and data stream definitions"));
1223-
1224-
// index template can't have data streams and a component template with an aliases
1225-
ComponentTemplate componentTemplate = new ComponentTemplate(new Template(null, null, a1), null, null);
1226-
it = new ComposableIndexTemplate(List.of("logs-*"), null, List.of("c1"), 0L, 1L, null,
1227-
new ComposableIndexTemplate.DataStreamTemplate(), null);
1228-
ClusterState state2 = ClusterState.builder(ClusterState.EMPTY_STATE)
1229-
.metadata(Metadata.builder().put("1", it).put("c1", componentTemplate).build())
1230-
.build();
1231-
e = expectThrows(IllegalArgumentException.class, () -> MetadataIndexTemplateService.resolveAliases(state2.metadata(), "1"));
1232-
assertThat(e.getMessage(), equalTo("template [1] has alias and data stream definitions"));
1233-
}
1234-
12351210
public void testAddInvalidTemplate() throws Exception {
12361211
ComposableIndexTemplate template = new ComposableIndexTemplate(Collections.singletonList("a"), null,
12371212
Arrays.asList("good", "bad"), null, null, null);

0 commit comments

Comments
 (0)