Skip to content

Commit db6aba4

Browse files
authored
Validation for data stream creation (#54083)
1 parent 32f46f2 commit db6aba4

File tree

4 files changed

+81
-4
lines changed

4 files changed

+81
-4
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/datastream/CreateDataStreamAction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.cluster.metadata.DataStream;
3636
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
3737
import org.elasticsearch.cluster.metadata.MetaData;
38+
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
3839
import org.elasticsearch.cluster.service.ClusterService;
3940
import org.elasticsearch.common.Priority;
4041
import org.elasticsearch.common.Strings;
@@ -165,9 +166,11 @@ static ClusterState createDataStream(ClusterState currentState, Request request)
165166
throw new IllegalArgumentException("data_stream [" + request.name + "] already exists");
166167
}
167168

169+
MetaDataCreateIndexService.validateIndexOrAliasName(request.name,
170+
(s1, s2) -> new IllegalArgumentException("data_stream [" + s1 + "] " + s2));
171+
168172
MetaData.Builder builder = MetaData.builder(currentState.metaData()).put(
169173
new DataStream(request.name, request.timestampFieldName, Collections.emptyList()));
170-
171174
logger.info("adding data stream [{}]", request.name);
172175
return ClusterState.builder(currentState).metaData(builder).build();
173176
}

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,7 +1277,7 @@ public Builder generateClusterUuidIfNeeded() {
12771277

12781278
public MetaData build() {
12791279
// TODO: We should move these datastructures to IndexNameExpressionResolver, this will give the following benefits:
1280-
// 1) The datastructures will only be rebuilded when needed. Now during serializing we rebuild these datastructures
1280+
// 1) The datastructures will be rebuilt only when needed. Now during serializing we rebuild these datastructures
12811281
// while these datastructures aren't even used.
12821282
// 2) The aliasAndIndexLookup can be updated instead of rebuilding it all the time.
12831283

@@ -1315,20 +1315,21 @@ public MetaData build() {
13151315
// iterate again and constructs a helpful message
13161316
ArrayList<String> duplicates = new ArrayList<>();
13171317
for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
1318-
for (String alias: duplicateAliasesIndices) {
1318+
for (String alias : duplicateAliasesIndices) {
13191319
if (cursor.value.getAliases().containsKey(alias)) {
13201320
duplicates.add(alias + " (alias of " + cursor.value.getIndex() + ")");
13211321
}
13221322
}
13231323
}
13241324
assert duplicates.size() > 0;
13251325
throw new IllegalStateException("index and alias names need to be unique, but the following duplicates were found ["
1326-
+ Strings.collectionToCommaDelimitedString(duplicates)+ "]");
1326+
+ Strings.collectionToCommaDelimitedString(duplicates) + "]");
13271327

13281328
}
13291329

13301330
SortedMap<String, AliasOrIndex> aliasAndIndexLookup = Collections.unmodifiableSortedMap(buildAliasAndIndexLookup());
13311331

1332+
validateDataStreams(aliasAndIndexLookup);
13321333

13331334
// build all concrete indices arrays:
13341335
// TODO: I think we can remove these arrays. it isn't worth the effort, for operations on all indices.
@@ -1371,6 +1372,24 @@ private SortedMap<String, AliasOrIndex> buildAliasAndIndexLookup() {
13711372
return aliasAndIndexLookup;
13721373
}
13731374

1375+
private void validateDataStreams(SortedMap<String, AliasOrIndex> aliasAndIndexLookup) {
1376+
DataStreamMetadata dsMetadata = (DataStreamMetadata) customs.get(DataStreamMetadata.TYPE);
1377+
if (dsMetadata != null) {
1378+
for (DataStream ds : dsMetadata.dataStreams().values()) {
1379+
if (aliasAndIndexLookup.containsKey(ds.getName())) {
1380+
throw new IllegalStateException("data stream [" + ds.getName() + "] conflicts with existing index or alias");
1381+
}
1382+
1383+
SortedMap<?, ?> map = aliasAndIndexLookup.subMap(ds.getName() + "-", ds.getName() + "."); // '.' is the char after '-'
1384+
if (map.size() != 0) {
1385+
throw new IllegalStateException("data stream [" + ds.getName() +
1386+
"] could create backing indices that conflict with " + map.size() + " existing index(s) or alias(s)" +
1387+
" including '" + map.firstKey() + "'");
1388+
}
1389+
}
1390+
}
1391+
}
1392+
13741393
public static void toXContent(MetaData metaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
13751394
XContentContext context = XContentContext.valueOf(params.param(CONTEXT_MODE_PARAM, CONTEXT_MODE_API));
13761395

server/src/test/java/org/elasticsearch/action/admin/indices/datastream/CreateDataStreamRequestTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,13 @@ public void testCreateDuplicateDataStream() {
8282
() -> CreateDataStreamAction.TransportAction.createDataStream(cs, req));
8383
assertThat(e.getMessage(), containsString("data_stream [" + dataStreamName + "] already exists"));
8484
}
85+
86+
public void testCreateDataStreamWithInvalidName() {
87+
final String dataStreamName = "_My-da#ta- ,stream-";
88+
ClusterState cs = ClusterState.builder(new ClusterName("_name")).build();
89+
CreateDataStreamAction.Request req = new CreateDataStreamAction.Request(dataStreamName);
90+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
91+
() -> CreateDataStreamAction.TransportAction.createDataStream(cs, req));
92+
assertThat(e.getMessage(), containsString("must not contain the following characters"));
93+
}
8594
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import java.io.IOException;
4646
import java.util.Arrays;
47+
import java.util.Collections;
4748
import java.util.HashMap;
4849
import java.util.HashSet;
4950
import java.util.List;
@@ -907,6 +908,51 @@ public void testBuilderRejectsNullInCustoms() {
907908
assertThat(expectThrows(NullPointerException.class, () -> builder.customs(map)).getMessage(), containsString(key));
908909
}
909910

911+
public void testBuilderRejectsDataStreamThatConflictsWithIndex() {
912+
final String dataStreamName = "my-data-stream";
913+
MetaData.Builder b = MetaData.builder()
914+
.put(IndexMetaData.builder(dataStreamName)
915+
.settings(settings(Version.CURRENT))
916+
.numberOfShards(1)
917+
.numberOfReplicas(1)
918+
.build(), false)
919+
.put(new DataStream(dataStreamName, "ts", Collections.emptyList()));
920+
921+
IllegalStateException e = expectThrows(IllegalStateException.class, b::build);
922+
assertThat(e.getMessage(), containsString("data stream [" + dataStreamName + "] conflicts with existing index or alias"));
923+
}
924+
925+
public void testBuilderRejectsDataStreamThatConflictsWithAlias() {
926+
final String dataStreamName = "my-data-stream";
927+
MetaData.Builder b = MetaData.builder()
928+
.put(IndexMetaData.builder(dataStreamName + "z")
929+
.settings(settings(Version.CURRENT))
930+
.numberOfShards(1)
931+
.numberOfReplicas(1)
932+
.putAlias(AliasMetaData.builder(dataStreamName).build())
933+
.build(), false)
934+
.put(new DataStream(dataStreamName, "ts", Collections.emptyList()));
935+
936+
IllegalStateException e = expectThrows(IllegalStateException.class, b::build);
937+
assertThat(e.getMessage(), containsString("data stream [" + dataStreamName + "] conflicts with existing index or alias"));
938+
}
939+
940+
public void testBuilderRejectsDataStreamWithConflictingBackingIndices() {
941+
final String dataStreamName = "my-data-stream";
942+
final String conflictingIndex = dataStreamName + "-00001";
943+
MetaData.Builder b = MetaData.builder()
944+
.put(IndexMetaData.builder(conflictingIndex)
945+
.settings(settings(Version.CURRENT))
946+
.numberOfShards(1)
947+
.numberOfReplicas(1)
948+
.build(), false)
949+
.put(new DataStream(dataStreamName, "ts", Collections.emptyList()));
950+
951+
IllegalStateException e = expectThrows(IllegalStateException.class, b::build);
952+
assertThat(e.getMessage(), containsString("data stream [" + dataStreamName +
953+
"] could create backing indices that conflict with 1 existing index(s) or alias(s) including '" + conflictingIndex + "'"));
954+
}
955+
910956
public void testSerialization() throws IOException {
911957
final MetaData orig = randomMetaData();
912958
final BytesStreamOutput out = new BytesStreamOutput();

0 commit comments

Comments
 (0)