Skip to content

Commit 201114c

Browse files
committed
MetaData Builder doesn't properly prevent an alias with the same name as an index (#26804)
Elasticsearch doesn't allow having an index alias named with the same name as an existing index. We currently have logic that tries to prevents that in the `MetaData.Builder#build()` method. Sadly that logic is flawed. Depending on iteration order, we may allow the above to happen (if we encounter the alias before the index). This commit fixes the above and improves the error message while at it. Note that we have a lot of protections in place before we end up relying on the metadata builder (validating this when we process APIs). I takes quite an abuse of the cluster to get that far.
1 parent 02ee440 commit 201114c

File tree

2 files changed

+91
-35
lines changed

2 files changed

+91
-35
lines changed

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

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.carrotsearch.hppc.ObjectHashSet;
2323
import com.carrotsearch.hppc.cursors.ObjectCursor;
2424
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
25-
2625
import org.apache.logging.log4j.Logger;
2726
import org.apache.lucene.util.CollectionUtil;
2827
import org.elasticsearch.cluster.Diff;
@@ -33,6 +32,7 @@
3332
import org.elasticsearch.cluster.block.ClusterBlock;
3433
import org.elasticsearch.cluster.block.ClusterBlockLevel;
3534
import org.elasticsearch.common.Nullable;
35+
import org.elasticsearch.common.Strings;
3636
import org.elasticsearch.common.UUIDs;
3737
import org.elasticsearch.common.collect.HppcMaps;
3838
import org.elasticsearch.common.collect.ImmutableOpenMap;
@@ -63,9 +63,11 @@
6363
import java.util.Comparator;
6464
import java.util.EnumSet;
6565
import java.util.HashMap;
66+
import java.util.HashSet;
6667
import java.util.Iterator;
6768
import java.util.List;
6869
import java.util.Map;
70+
import java.util.Set;
6971
import java.util.SortedMap;
7072
import java.util.TreeMap;
7173

@@ -915,55 +917,70 @@ public MetaData build() {
915917
// while these datastructures aren't even used.
916918
// 2) The aliasAndIndexLookup can be updated instead of rebuilding it all the time.
917919

918-
// build all concrete indices arrays:
919-
// TODO: I think we can remove these arrays. it isn't worth the effort, for operations on all indices.
920-
// When doing an operation across all indices, most of the time is spent on actually going to all shards and
921-
// do the required operations, the bottleneck isn't resolving expressions into concrete indices.
922-
List<String> allIndicesLst = new ArrayList<>();
920+
final Set<String> allIndices = new HashSet<>(indices.size());
921+
final List<String> allOpenIndices = new ArrayList<>();
922+
final List<String> allClosedIndices = new ArrayList<>();
923+
final Set<String> duplicateAliasesIndices = new HashSet<>();
923924
for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
924-
allIndicesLst.add(cursor.value.getIndex().getName());
925-
}
926-
String[] allIndices = allIndicesLst.toArray(new String[allIndicesLst.size()]);
927-
928-
List<String> allOpenIndicesLst = new ArrayList<>();
929-
List<String> allClosedIndicesLst = new ArrayList<>();
930-
for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
931-
IndexMetaData indexMetaData = cursor.value;
925+
final IndexMetaData indexMetaData = cursor.value;
926+
final String name = indexMetaData.getIndex().getName();
927+
boolean added = allIndices.add(name);
928+
assert added : "double index named [" + name + "]";
932929
if (indexMetaData.getState() == IndexMetaData.State.OPEN) {
933-
allOpenIndicesLst.add(indexMetaData.getIndex().getName());
930+
allOpenIndices.add(indexMetaData.getIndex().getName());
934931
} else if (indexMetaData.getState() == IndexMetaData.State.CLOSE) {
935-
allClosedIndicesLst.add(indexMetaData.getIndex().getName());
932+
allClosedIndices.add(indexMetaData.getIndex().getName());
933+
}
934+
indexMetaData.getAliases().keysIt().forEachRemaining(duplicateAliasesIndices::add);
935+
}
936+
duplicateAliasesIndices.retainAll(allIndices);
937+
if (duplicateAliasesIndices.isEmpty() == false) {
938+
// iterate again and constructs a helpful message
939+
ArrayList<String> duplicates = new ArrayList<>();
940+
for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
941+
for (String alias: duplicateAliasesIndices) {
942+
if (cursor.value.getAliases().containsKey(alias)) {
943+
duplicates.add(alias + " (alias of " + cursor.value.getIndex() + ")");
944+
}
945+
}
936946
}
947+
assert duplicates.size() > 0;
948+
throw new IllegalStateException("index and alias names need to be unique, but the following duplicates were found ["
949+
+ Strings.collectionToCommaDelimitedString(duplicates)+ "]");
950+
937951
}
938-
String[] allOpenIndices = allOpenIndicesLst.toArray(new String[allOpenIndicesLst.size()]);
939-
String[] allClosedIndices = allClosedIndicesLst.toArray(new String[allClosedIndicesLst.size()]);
940952

941953
// build all indices map
942954
SortedMap<String, AliasOrIndex> aliasAndIndexLookup = new TreeMap<>();
943955
for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
944956
IndexMetaData indexMetaData = cursor.value;
945-
aliasAndIndexLookup.put(indexMetaData.getIndex().getName(), new AliasOrIndex.Index(indexMetaData));
957+
AliasOrIndex existing = aliasAndIndexLookup.put(indexMetaData.getIndex().getName(), new AliasOrIndex.Index(indexMetaData));
958+
assert existing == null : "duplicate for " + indexMetaData.getIndex();
946959

947960
for (ObjectObjectCursor<String, AliasMetaData> aliasCursor : indexMetaData.getAliases()) {
948961
AliasMetaData aliasMetaData = aliasCursor.value;
949-
AliasOrIndex aliasOrIndex = aliasAndIndexLookup.get(aliasMetaData.getAlias());
950-
if (aliasOrIndex == null) {
951-
aliasOrIndex = new AliasOrIndex.Alias(aliasMetaData, indexMetaData);
952-
aliasAndIndexLookup.put(aliasMetaData.getAlias(), aliasOrIndex);
953-
} else if (aliasOrIndex instanceof AliasOrIndex.Alias) {
954-
AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
955-
alias.addIndex(indexMetaData);
956-
} else if (aliasOrIndex instanceof AliasOrIndex.Index) {
957-
AliasOrIndex.Index index = (AliasOrIndex.Index) aliasOrIndex;
958-
throw new IllegalStateException("index and alias names need to be unique, but alias [" + aliasMetaData.getAlias() + "] and index " + index.getIndex().getIndex() + " have the same name");
959-
} else {
960-
throw new IllegalStateException("unexpected alias [" + aliasMetaData.getAlias() + "][" + aliasOrIndex + "]");
961-
}
962+
aliasAndIndexLookup.compute(aliasMetaData.getAlias(), (aliasName, alias) -> {
963+
if (alias == null) {
964+
return new AliasOrIndex.Alias(aliasMetaData, indexMetaData);
965+
} else {
966+
assert alias instanceof AliasOrIndex.Alias : alias.getClass().getName();
967+
((AliasOrIndex.Alias) alias).addIndex(indexMetaData);
968+
return alias;
969+
}
970+
});
962971
}
963972
}
964973
aliasAndIndexLookup = Collections.unmodifiableSortedMap(aliasAndIndexLookup);
974+
// build all concrete indices arrays:
975+
// TODO: I think we can remove these arrays. it isn't worth the effort, for operations on all indices.
976+
// When doing an operation across all indices, most of the time is spent on actually going to all shards and
977+
// do the required operations, the bottleneck isn't resolving expressions into concrete indices.
978+
String[] allIndicesArray = allIndices.toArray(new String[allIndices.size()]);
979+
String[] allOpenIndicesArray = allOpenIndices.toArray(new String[allOpenIndices.size()]);
980+
String[] allClosedIndicesArray = allClosedIndices.toArray(new String[allClosedIndices.size()]);
981+
965982
return new MetaData(clusterUUID, version, transientSettings, persistentSettings, indices.build(), templates.build(),
966-
customs.build(), allIndices, allOpenIndices, allClosedIndices, aliasAndIndexLookup);
983+
customs.build(), allIndicesArray, allOpenIndicesArray, allClosedIndicesArray, aliasAndIndexLookup);
967984
}
968985

969986
public static String toXContent(MetaData metaData) throws IOException {

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.elasticsearch.common.io.stream.BytesStreamOutput;
2727
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
2828
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
29-
import org.elasticsearch.common.io.stream.StreamInput;
3029
import org.elasticsearch.common.settings.Settings;
3130
import org.elasticsearch.common.xcontent.ToXContent;
3231
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -36,9 +35,14 @@
3635
import org.elasticsearch.test.ESTestCase;
3736

3837
import java.io.IOException;
38+
import java.util.HashMap;
39+
import java.util.HashSet;
40+
import java.util.Map;
41+
import java.util.Set;
3942

4043
import static org.hamcrest.Matchers.equalTo;
4144
import static org.hamcrest.Matchers.is;
45+
import static org.hamcrest.Matchers.startsWith;
4246

4347
public class MetaDataTests extends ESTestCase {
4448

@@ -52,7 +56,42 @@ public void testIndexAndAliasWithSameName() {
5256
MetaData.builder().put(builder).build();
5357
fail("exception should have been thrown");
5458
} catch (IllegalStateException e) {
55-
assertThat(e.getMessage(), equalTo("index and alias names need to be unique, but alias [index] and index [index] have the same name"));
59+
assertThat(e.getMessage(), equalTo("index and alias names need to be unique, but the following duplicates were found [index (alias of [index])]"));
60+
}
61+
}
62+
63+
public void testAliasCollidingWithAnExistingIndex() {
64+
int indexCount = randomIntBetween(10, 100);
65+
Set<String> indices = new HashSet<>(indexCount);
66+
for (int i = 0; i < indexCount; i++) {
67+
indices.add(randomAlphaOfLength(10));
68+
}
69+
Map<String, Set<String>> aliasToIndices = new HashMap<>();
70+
for (String alias: randomSubsetOf(randomIntBetween(1, 10), indices)) {
71+
aliasToIndices.put(alias, new HashSet<>(randomSubsetOf(randomIntBetween(1, 3), indices)));
72+
}
73+
int properAliases = randomIntBetween(0, 3);
74+
for (int i = 0; i < properAliases; i++) {
75+
aliasToIndices.put(randomAlphaOfLength(5), new HashSet<>(randomSubsetOf(randomIntBetween(1, 3), indices)));
76+
}
77+
MetaData.Builder metaDataBuilder = MetaData.builder();
78+
for (String index : indices) {
79+
IndexMetaData.Builder indexBuilder = IndexMetaData.builder(index)
80+
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT))
81+
.numberOfShards(1)
82+
.numberOfReplicas(0);
83+
aliasToIndices.forEach((key, value) -> {
84+
if (value.contains(index)) {
85+
indexBuilder.putAlias(AliasMetaData.builder(key).build());
86+
}
87+
});
88+
metaDataBuilder.put(indexBuilder);
89+
}
90+
try {
91+
metaDataBuilder.build();
92+
fail("exception should have been thrown");
93+
} catch (IllegalStateException e) {
94+
assertThat(e.getMessage(), startsWith("index and alias names need to be unique"));
5695
}
5796
}
5897

0 commit comments

Comments
 (0)