Skip to content

Commit 34268e1

Browse files
authored
Create IndexNameGenerator utility in server (#97393)
1 parent ab6b09a commit 34268e1

File tree

8 files changed

+259
-198
lines changed

8 files changed

+259
-198
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.common;
10+
11+
import org.elasticsearch.action.ActionRequestValidationException;
12+
import org.elasticsearch.cluster.ClusterState;
13+
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
14+
import org.elasticsearch.core.Nullable;
15+
import org.elasticsearch.indices.InvalidIndexNameException;
16+
17+
import java.util.Locale;
18+
import java.util.function.Supplier;
19+
20+
/**
21+
* Generates valid Elasticsearch index names.
22+
*/
23+
public final class IndexNameGenerator {
24+
25+
static final String ILLEGAL_INDEXNAME_CHARS_REGEX = "[/:\"*?<>|# ,\\\\]+";
26+
static final int MAX_GENERATED_UUID_LENGTH = 4;
27+
28+
private IndexNameGenerator() {}
29+
30+
/**
31+
* This generates a valid unique index name by using the provided prefix, appended with a generated UUID, and the index name.
32+
*/
33+
public static String generateValidIndexName(String prefix, String indexName) {
34+
String randomUUID = generateValidIndexSuffix(UUIDs::randomBase64UUID);
35+
randomUUID = randomUUID.substring(0, Math.min(randomUUID.length(), MAX_GENERATED_UUID_LENGTH));
36+
return prefix + randomUUID + "-" + indexName;
37+
}
38+
39+
/**
40+
*
41+
* @param randomGenerator
42+
* @return
43+
*/
44+
public static String generateValidIndexSuffix(Supplier<String> randomGenerator) {
45+
String randomSuffix = randomGenerator.get().toLowerCase(Locale.ROOT);
46+
randomSuffix = randomSuffix.replaceAll(ILLEGAL_INDEXNAME_CHARS_REGEX, "");
47+
if (randomSuffix.length() == 0) {
48+
throw new IllegalArgumentException("unable to generate random index name suffix");
49+
}
50+
51+
return randomSuffix;
52+
}
53+
54+
/**
55+
* Validates the provided index name against the provided cluster state. This checks the index name for invalid characters
56+
* and that it doesn't clash with existing indices or aliases.
57+
* Returns null for valid indices.
58+
*/
59+
@Nullable
60+
public static ActionRequestValidationException validateGeneratedIndexName(String generatedIndexName, ClusterState state) {
61+
ActionRequestValidationException err = new ActionRequestValidationException();
62+
try {
63+
MetadataCreateIndexService.validateIndexOrAliasName(generatedIndexName, InvalidIndexNameException::new);
64+
} catch (InvalidIndexNameException e) {
65+
err.addValidationError(e.getMessage());
66+
}
67+
if (state.routingTable().hasIndex(generatedIndexName) || state.metadata().hasIndex(generatedIndexName)) {
68+
err.addValidationError("the index name we generated [" + generatedIndexName + "] already exists");
69+
}
70+
if (state.metadata().hasAlias(generatedIndexName)) {
71+
err.addValidationError("the index name we generated [" + generatedIndexName + "] already exists as alias");
72+
}
73+
74+
if (err.validationErrors().size() > 0) {
75+
return err;
76+
} else {
77+
return null;
78+
}
79+
}
80+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.common;
10+
11+
import org.elasticsearch.Version;
12+
import org.elasticsearch.action.ActionRequestValidationException;
13+
import org.elasticsearch.cluster.ClusterName;
14+
import org.elasticsearch.cluster.ClusterState;
15+
import org.elasticsearch.cluster.TestShardRoutingRoleStrategies;
16+
import org.elasticsearch.cluster.metadata.AliasMetadata;
17+
import org.elasticsearch.cluster.metadata.IndexMetadata;
18+
import org.elasticsearch.cluster.metadata.Metadata;
19+
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
20+
import org.elasticsearch.cluster.routing.RoutingTable;
21+
import org.elasticsearch.indices.InvalidIndexNameException;
22+
import org.elasticsearch.test.ESTestCase;
23+
24+
import java.util.Locale;
25+
26+
import static org.elasticsearch.common.IndexNameGenerator.ILLEGAL_INDEXNAME_CHARS_REGEX;
27+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexName;
28+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexSuffix;
29+
import static org.elasticsearch.common.IndexNameGenerator.validateGeneratedIndexName;
30+
import static org.hamcrest.Matchers.containsInAnyOrder;
31+
import static org.hamcrest.Matchers.containsString;
32+
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
33+
import static org.hamcrest.Matchers.is;
34+
import static org.hamcrest.Matchers.notNullValue;
35+
import static org.hamcrest.Matchers.nullValue;
36+
import static org.hamcrest.Matchers.startsWith;
37+
38+
public class IndexNameGeneratorTests extends ESTestCase {
39+
40+
public void testGenerateValidIndexName() {
41+
String prefix = randomAlphaOfLengthBetween(5, 15);
42+
String indexName = randomAlphaOfLengthBetween(5, 100);
43+
44+
String generatedValidIndexName = generateValidIndexName(prefix, indexName);
45+
assertThat(generatedValidIndexName, startsWith(prefix));
46+
assertThat(generatedValidIndexName, containsString(indexName));
47+
try {
48+
MetadataCreateIndexService.validateIndexOrAliasName(generatedValidIndexName, InvalidIndexNameException::new);
49+
} catch (InvalidIndexNameException e) {
50+
fail("generated index name [" + generatedValidIndexName + "] which is invalid due to [" + e.getDetailedMessage() + "]");
51+
}
52+
}
53+
54+
public void testGenerateValidIndexSuffix() {
55+
{
56+
String indexSuffix = generateValidIndexSuffix(() -> UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT));
57+
assertThat(indexSuffix, notNullValue());
58+
assertThat(indexSuffix.length(), greaterThanOrEqualTo(1));
59+
assertThat(indexSuffix.matches(ILLEGAL_INDEXNAME_CHARS_REGEX), is(false));
60+
}
61+
62+
{
63+
IllegalArgumentException illegalArgumentException = expectThrows(
64+
IllegalArgumentException.class,
65+
() -> generateValidIndexSuffix(() -> "****???><><>,# \\/:||")
66+
);
67+
assertThat(illegalArgumentException.getMessage(), is("unable to generate random index name suffix"));
68+
}
69+
70+
{
71+
assertThat(generateValidIndexSuffix(() -> "LegalChars|||# *"), is("legalchars"));
72+
}
73+
}
74+
75+
public void testValidateGeneratedIndexName() {
76+
{
77+
assertThat(
78+
validateGeneratedIndexName(
79+
generateValidIndexName(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 150)),
80+
ClusterState.EMPTY_STATE
81+
),
82+
nullValue()
83+
);
84+
}
85+
86+
{
87+
// index name is validated (invalid chars etc)
88+
String generatedIndexName = generateValidIndexName("_prefix-", randomAlphaOfLengthBetween(5, 150));
89+
assertThat(
90+
validateGeneratedIndexName(generatedIndexName, ClusterState.EMPTY_STATE).validationErrors(),
91+
containsInAnyOrder("Invalid index name [" + generatedIndexName + "], must not start with '_', '-', or '+'")
92+
);
93+
}
94+
95+
{
96+
// index name is validated (invalid chars etc)
97+
String generatedIndexName = generateValidIndexName("shrink-", "shrink-indexName-random###");
98+
assertThat(
99+
validateGeneratedIndexName(generatedIndexName, ClusterState.EMPTY_STATE).validationErrors(),
100+
containsInAnyOrder("Invalid index name [" + generatedIndexName + "], must not contain '#'")
101+
);
102+
}
103+
104+
{
105+
// generated index already exists as a standalone index
106+
String generatedIndexName = generateValidIndexName(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 150));
107+
IndexMetadata indexMetadata = IndexMetadata.builder(generatedIndexName)
108+
.settings(settings(Version.CURRENT))
109+
.numberOfShards(randomIntBetween(1, 5))
110+
.numberOfReplicas(randomIntBetween(1, 5))
111+
.build();
112+
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
113+
.metadata(Metadata.builder().put(indexMetadata, false))
114+
.build();
115+
116+
ActionRequestValidationException validationException = validateGeneratedIndexName(generatedIndexName, clusterState);
117+
assertThat(validationException, notNullValue());
118+
assertThat(
119+
validationException.validationErrors(),
120+
containsInAnyOrder("the index name we generated [" + generatedIndexName + "] already exists")
121+
);
122+
}
123+
124+
{
125+
// generated index name already exists as an index (cluster state routing table is also populated)
126+
String generatedIndexName = generateValidIndexName(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 150));
127+
IndexMetadata indexMetadata = IndexMetadata.builder(generatedIndexName)
128+
.settings(settings(Version.CURRENT))
129+
.numberOfShards(randomIntBetween(1, 5))
130+
.numberOfReplicas(randomIntBetween(1, 5))
131+
.build();
132+
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
133+
.routingTable(RoutingTable.builder(TestShardRoutingRoleStrategies.DEFAULT_ROLE_ONLY).addAsNew(indexMetadata).build())
134+
.metadata(Metadata.builder().put(indexMetadata, false))
135+
.build();
136+
137+
ActionRequestValidationException validationException = validateGeneratedIndexName(generatedIndexName, clusterState);
138+
assertThat(validationException, notNullValue());
139+
assertThat(
140+
validationException.validationErrors(),
141+
containsInAnyOrder("the index name we generated [" + generatedIndexName + "] already exists")
142+
);
143+
;
144+
}
145+
146+
{
147+
// generated index name already exists as an alias to another index
148+
String generatedIndexName = generateValidIndexName(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 150));
149+
IndexMetadata indexMetadata = IndexMetadata.builder(randomAlphaOfLengthBetween(10, 30))
150+
.settings(settings(Version.CURRENT))
151+
.numberOfShards(randomIntBetween(1, 5))
152+
.numberOfReplicas(randomIntBetween(1, 5))
153+
.putAlias(AliasMetadata.builder(generatedIndexName).build())
154+
.build();
155+
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
156+
.metadata(Metadata.builder().put(indexMetadata, false))
157+
.build();
158+
159+
ActionRequestValidationException validationException = validateGeneratedIndexName(generatedIndexName, clusterState);
160+
assertThat(validationException, notNullValue());
161+
assertThat(
162+
validationException.validationErrors(),
163+
containsInAnyOrder("the index name we generated [" + generatedIndexName + "] already exists as alias")
164+
);
165+
}
166+
}
167+
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/GenerateUniqueIndexNameStep.java

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@
1313
import org.elasticsearch.cluster.metadata.IndexMetadata;
1414
import org.elasticsearch.cluster.metadata.LifecycleExecutionState;
1515
import org.elasticsearch.cluster.metadata.LifecycleExecutionState.Builder;
16-
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
17-
import org.elasticsearch.common.UUIDs;
18-
import org.elasticsearch.core.Nullable;
1916
import org.elasticsearch.index.Index;
20-
import org.elasticsearch.indices.InvalidIndexNameException;
2117

22-
import java.util.Locale;
2318
import java.util.Objects;
2419
import java.util.function.BiFunction;
25-
import java.util.function.Supplier;
20+
21+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexName;
22+
import static org.elasticsearch.common.IndexNameGenerator.validateGeneratedIndexName;
2623

2724
/**
2825
* Generates a unique index name prefixing the original index name with the configured
@@ -35,8 +32,6 @@ public class GenerateUniqueIndexNameStep extends ClusterStateActionStep {
3532
private static final Logger logger = LogManager.getLogger(GenerateUniqueIndexNameStep.class);
3633

3734
public static final String NAME = "generate-index-name";
38-
static final String ILLEGAL_INDEXNAME_CHARS_REGEX = "[/:\"*?<>|# ,\\\\]+";
39-
static final int MAX_GENERATED_UUID_LENGTH = 4;
4035

4136
private final String prefix;
4237
private final BiFunction<String, Builder, Builder> lifecycleStateSetter;
@@ -98,28 +93,6 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
9893
);
9994
}
10095

101-
@Nullable
102-
static ActionRequestValidationException validateGeneratedIndexName(String generatedIndexName, ClusterState state) {
103-
ActionRequestValidationException err = new ActionRequestValidationException();
104-
try {
105-
MetadataCreateIndexService.validateIndexOrAliasName(generatedIndexName, InvalidIndexNameException::new);
106-
} catch (InvalidIndexNameException e) {
107-
err.addValidationError(e.getMessage());
108-
}
109-
if (state.routingTable().hasIndex(generatedIndexName) || state.metadata().hasIndex(generatedIndexName)) {
110-
err.addValidationError("the index name we generated [" + generatedIndexName + "] already exists");
111-
}
112-
if (state.metadata().hasAlias(generatedIndexName)) {
113-
err.addValidationError("the index name we generated [" + generatedIndexName + "] already exists as alias");
114-
}
115-
116-
if (err.validationErrors().size() > 0) {
117-
return err;
118-
} else {
119-
return null;
120-
}
121-
}
122-
12396
@Override
12497
public boolean equals(Object o) {
12598
if (this == o) {
@@ -137,22 +110,4 @@ public int hashCode() {
137110
return Objects.hash(super.hashCode(), prefix);
138111
}
139112

140-
/**
141-
* This generates a valid unique index name by using the provided prefix, appended with a generated UUID, and the index name.
142-
*/
143-
static String generateValidIndexName(String prefix, String indexName) {
144-
String randomUUID = generateValidIndexSuffix(UUIDs::randomBase64UUID);
145-
randomUUID = randomUUID.substring(0, Math.min(randomUUID.length(), MAX_GENERATED_UUID_LENGTH));
146-
return prefix + randomUUID + "-" + indexName;
147-
}
148-
149-
static String generateValidIndexSuffix(Supplier<String> randomGenerator) {
150-
String randomSuffix = randomGenerator.get().toLowerCase(Locale.ROOT);
151-
randomSuffix = randomSuffix.replaceAll(ILLEGAL_INDEXNAME_CHARS_REGEX, "");
152-
if (randomSuffix.length() == 0) {
153-
throw new IllegalArgumentException("unable to generate random index name suffix");
154-
}
155-
156-
return randomSuffix;
157-
}
158113
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CleanupShrinkIndexStepTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import java.util.Map;
2424

25-
import static org.elasticsearch.xpack.core.ilm.GenerateUniqueIndexNameStep.generateValidIndexName;
25+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexName;
2626
import static org.hamcrest.Matchers.arrayContaining;
2727
import static org.hamcrest.Matchers.is;
2828

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CleanupTargetIndexStepTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import java.util.Map;
2424
import java.util.function.Function;
2525

26-
import static org.elasticsearch.xpack.core.ilm.GenerateUniqueIndexNameStep.generateValidIndexName;
26+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexName;
2727
import static org.hamcrest.Matchers.arrayContaining;
2828
import static org.hamcrest.Matchers.is;
2929

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/DownsampleStepTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.newInstance;
3030
import static org.elasticsearch.cluster.metadata.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY;
31+
import static org.elasticsearch.common.IndexNameGenerator.generateValidIndexName;
3132
import static org.elasticsearch.xpack.core.ilm.DownsampleAction.DOWNSAMPLED_INDEX_PREFIX;
3233
import static org.hamcrest.Matchers.equalTo;
3334
import static org.hamcrest.Matchers.instanceOf;
@@ -163,7 +164,7 @@ public void testPerformActionCompletedDownsampleIndexExists() {
163164
lifecycleState.setStep(step.getKey().name());
164165
lifecycleState.setIndexCreationDate(randomNonNegativeLong());
165166

166-
String downsampleIndex = GenerateUniqueIndexNameStep.generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
167+
String downsampleIndex = generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
167168
lifecycleState.setDownsampleIndexName(downsampleIndex);
168169

169170
IndexMetadata sourceIndexMetadata = IndexMetadata.builder(sourceIndexName)
@@ -213,7 +214,7 @@ public void testPerformActionDownsampleInProgressIndexExists() {
213214
lifecycleState.setStep(step.getKey().name());
214215
lifecycleState.setIndexCreationDate(randomNonNegativeLong());
215216

216-
String downsampleIndex = GenerateUniqueIndexNameStep.generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
217+
String downsampleIndex = generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
217218
lifecycleState.setDownsampleIndexName(downsampleIndex);
218219

219220
IndexMetadata sourceIndexMetadata = IndexMetadata.builder(sourceIndexName)
@@ -255,7 +256,7 @@ public void testNextStepKey() {
255256
LifecycleExecutionState.Builder lifecycleState = LifecycleExecutionState.builder();
256257
lifecycleState.setIndexCreationDate(randomNonNegativeLong());
257258

258-
String downsampleIndex = GenerateUniqueIndexNameStep.generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
259+
String downsampleIndex = generateValidIndexName(DOWNSAMPLED_INDEX_PREFIX, sourceIndexName);
259260
lifecycleState.setDownsampleIndexName(downsampleIndex);
260261

261262
IndexMetadata sourceIndexMetadata = IndexMetadata.builder(sourceIndexName)

0 commit comments

Comments
 (0)