Skip to content

Commit e18fae6

Browse files
[Refactor] Use Lists instead of Maps for SystemIndices features (#87004) (#87049)
The SystemIndices constructor should take a list instead of a map as an argument so that we can guarantee that the map we use for feature lookups is keyed on the feature name. We also provide some new getter methods so that calling code does not have to handle the map directly.
1 parent 9b79ae5 commit e18fae6

25 files changed

+135
-156
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/migration/TransportGetFeatureUpgradeStatusAction.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ protected void masterOperation(
8989
) throws Exception {
9090

9191
List<GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus> features = systemIndices.getFeatures()
92-
.values()
9392
.stream()
9493
.sorted(Comparator.comparing(SystemIndices.Feature::getName))
9594
.map(feature -> getFeatureUpgradeStatus(state, feature))

server/src/main/java/org/elasticsearch/action/admin/cluster/migration/TransportPostFeatureUpgradeAction.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ protected void masterOperation(
8181
GetFeatureUpgradeStatusResponse.UpgradeStatus.ERROR
8282
);
8383
List<PostFeatureUpgradeResponse.Feature> featuresToMigrate = systemIndices.getFeatures()
84-
.values()
8584
.stream()
8685
.map(feature -> getFeatureUpgradeStatus(state, feature))
8786
.filter(status -> upgradableStatuses.contains(status.getUpgradeStatus()))

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/features/TransportResetFeatureStateAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ protected void masterOperation(
8080
systemIndices.getFeatures().size()
8181
);
8282

83-
for (SystemIndices.Feature feature : systemIndices.getFeatures().values()) {
83+
for (SystemIndices.Feature feature : systemIndices.getFeatures()) {
8484
feature.getCleanUpFunction().apply(clusterService, client, groupedActionListener);
8585
}
8686
}

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/features/TransportSnapshottableFeaturesAction.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,8 @@ protected void masterOperation(
6161
listener.onResponse(
6262
new GetSnapshottableFeaturesResponse(
6363
systemIndices.getFeatures()
64-
.entrySet()
6564
.stream()
66-
.map(
67-
featureEntry -> new GetSnapshottableFeaturesResponse.SnapshottableFeature(
68-
featureEntry.getKey(),
69-
featureEntry.getValue().getDescription()
70-
)
71-
)
65+
.map(feature -> new GetSnapshottableFeaturesResponse.SnapshottableFeature(feature.getName(), feature.getDescription()))
7266
.toList()
7367
)
7468
);

server/src/main/java/org/elasticsearch/indices/SystemIndices.java

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.Map;
4646
import java.util.Map.Entry;
4747
import java.util.Optional;
48+
import java.util.Set;
4849
import java.util.function.Function;
4950
import java.util.function.Predicate;
5051
import java.util.stream.Collectors;
@@ -65,30 +66,39 @@ public class SystemIndices {
6566

6667
private static final Automaton EMPTY = Automata.makeEmpty();
6768

68-
private static final Map<String, Feature> SERVER_SYSTEM_INDEX_DESCRIPTORS = Map.of(
69-
TASKS_FEATURE_NAME,
69+
/**
70+
* This is the source for non-plugin system features.
71+
*/
72+
private static final Map<String, Feature> SERVER_SYSTEM_FEATURE_DESCRIPTORS = Stream.of(
7073
new Feature(TASKS_FEATURE_NAME, "Manages task results", List.of(TASKS_DESCRIPTOR))
71-
);
74+
).collect(Collectors.toUnmodifiableMap(Feature::getName, Function.identity()));
75+
76+
/**
77+
* The node's full list of system features is stored here. The map is keyed
78+
* on the value of {@link Feature#getName()}, and is used for fast lookup of
79+
* feature objects via {@link #getFeature(String)}.
80+
*/
81+
private final Map<String, Feature> featureDescriptors;
7282

7383
private final Automaton systemNameAutomaton;
7484
private final CharacterRunAutomaton netNewSystemIndexAutomaton;
7585
private final CharacterRunAutomaton systemNameRunAutomaton;
7686
private final CharacterRunAutomaton systemIndexRunAutomaton;
7787
private final CharacterRunAutomaton systemDataStreamIndicesRunAutomaton;
7888
private final Predicate<String> systemDataStreamPredicate;
79-
private final Map<String, Feature> featureDescriptors;
8089
private final SystemIndexDescriptor[] indexDescriptors;
8190
private final Map<String, SystemDataStreamDescriptor> dataStreamDescriptors;
8291
private final Map<String, CharacterRunAutomaton> productToSystemIndicesMatcher;
8392
private final ExecutorSelector executorSelector;
8493

8594
/**
8695
* Initialize the SystemIndices object
87-
* @param pluginAndModulesDescriptors A map of this node's feature names to
88-
* feature objects.
96+
* @param pluginAndModuleFeatures A list of features from which we will load system indices.
97+
* These features come from plugins and modules. Non-plugin system
98+
* features such as Tasks will be added automatically.
8999
*/
90-
public SystemIndices(Map<String, Feature> pluginAndModulesDescriptors) {
91-
featureDescriptors = buildSystemIndexDescriptorMap(pluginAndModulesDescriptors);
100+
public SystemIndices(List<Feature> pluginAndModuleFeatures) {
101+
featureDescriptors = buildFeatureMap(pluginAndModuleFeatures);
92102
indexDescriptors = featureDescriptors.values()
93103
.stream()
94104
.flatMap(f -> f.getIndexDescriptors().stream())
@@ -115,21 +125,20 @@ public SystemIndices(Map<String, Feature> pluginAndModulesDescriptors) {
115125
this.systemNameRunAutomaton = new CharacterRunAutomaton(systemNameAutomaton);
116126
}
117127

118-
static void ensurePatternsAllowSuffix(Map<String, Feature> features) {
128+
static void ensurePatternsAllowSuffix(Map<String, Feature> featureDescriptors) {
119129
String suffixPattern = "*" + UPGRADED_INDEX_SUFFIX;
120-
final List<String> descriptorsWithNoRoomForSuffix = features.entrySet()
130+
final List<String> descriptorsWithNoRoomForSuffix = featureDescriptors.values()
121131
.stream()
122132
.flatMap(
123-
feature -> feature.getValue()
124-
.getIndexDescriptors()
133+
feature -> feature.getIndexDescriptors()
125134
.stream()
126135
// The below filter & map are inside the enclosing flapMap so we have access to both the feature and the descriptor
127136
.filter(descriptor -> overlaps(descriptor.getIndexPattern(), suffixPattern) == false)
128137
.map(
129138
descriptor -> new ParameterizedMessage(
130139
"pattern [{}] from feature [{}]",
131140
descriptor.getIndexPattern(),
132-
feature.getKey()
141+
feature.getName()
133142
).getFormattedMessage()
134143
)
135144
)
@@ -167,9 +176,9 @@ private static void checkForDuplicateAliases(Collection<SystemIndexDescriptor> d
167176
}
168177
}
169178

170-
private static Map<String, CharacterRunAutomaton> getProductToSystemIndicesMap(Map<String, Feature> descriptors) {
179+
private static Map<String, CharacterRunAutomaton> getProductToSystemIndicesMap(Map<String, Feature> featureDescriptors) {
171180
Map<String, Automaton> productToSystemIndicesMap = new HashMap<>();
172-
for (Feature feature : descriptors.values()) {
181+
for (Feature feature : featureDescriptors.values()) {
173182
feature.getIndexDescriptors().forEach(systemIndexDescriptor -> {
174183
if (systemIndexDescriptor.isExternal()) {
175184
systemIndexDescriptor.getAllowedElasticProductOrigins()
@@ -334,12 +343,37 @@ public Predicate<String> getProductSystemIndexNamePredicate(ThreadContext thread
334343
return automaton::run;
335344
}
336345

337-
public Map<String, Feature> getFeatures() {
338-
return featureDescriptors;
346+
/**
347+
* Get a set of feature names. This is useful for checking whether particular
348+
* features are present on the node.
349+
* @return A set of all feature names
350+
*/
351+
public Set<String> getFeatureNames() {
352+
return Set.copyOf(featureDescriptors.keySet());
353+
}
354+
355+
/**
356+
* Get a feature by name.
357+
* @param name Name of a feature.
358+
* @return The corresponding feature if it exists on this node, null otherwise.
359+
*/
360+
public Feature getFeature(String name) {
361+
return featureDescriptors.get(name);
362+
}
363+
364+
/**
365+
* Get a collection of the Features this SystemIndices object is managing.
366+
* @return A collection of Features.
367+
*/
368+
public Collection<Feature> getFeatures() {
369+
return List.copyOf(featureDescriptors.values());
339370
}
340371

341-
private static Automaton buildIndexAutomaton(Map<String, Feature> descriptors) {
342-
Optional<Automaton> automaton = descriptors.values().stream().map(SystemIndices::featureToIndexAutomaton).reduce(Operations::union);
372+
private static Automaton buildIndexAutomaton(Map<String, Feature> featureDescriptors) {
373+
Optional<Automaton> automaton = featureDescriptors.values()
374+
.stream()
375+
.map(SystemIndices::featureToIndexAutomaton)
376+
.reduce(Operations::union);
343377
return MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE);
344378
}
345379

@@ -362,8 +396,8 @@ private static Automaton featureToIndexAutomaton(Feature feature) {
362396
return systemIndexAutomaton.orElse(EMPTY);
363397
}
364398

365-
private static Automaton buildDataStreamAutomaton(Map<String, Feature> descriptors) {
366-
Optional<Automaton> automaton = descriptors.values()
399+
private static Automaton buildDataStreamAutomaton(Map<String, Feature> featureDescriptors) {
400+
Optional<Automaton> automaton = featureDescriptors.values()
367401
.stream()
368402
.flatMap(feature -> feature.getDataStreamDescriptors().stream())
369403
.map(SystemDataStreamDescriptor::getDataStreamName)
@@ -373,13 +407,13 @@ private static Automaton buildDataStreamAutomaton(Map<String, Feature> descripto
373407
return automaton.isPresent() ? MinimizationOperations.minimize(automaton.get(), Integer.MAX_VALUE) : EMPTY;
374408
}
375409

376-
private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> descriptors) {
377-
CharacterRunAutomaton characterRunAutomaton = new CharacterRunAutomaton(buildDataStreamAutomaton(descriptors));
410+
private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> featureDescriptors) {
411+
CharacterRunAutomaton characterRunAutomaton = new CharacterRunAutomaton(buildDataStreamAutomaton(featureDescriptors));
378412
return characterRunAutomaton::run;
379413
}
380414

381-
private static Automaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> descriptors) {
382-
Optional<Automaton> automaton = descriptors.values()
415+
private static Automaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> featureDescriptors) {
416+
Optional<Automaton> automaton = featureDescriptors.values()
383417
.stream()
384418
.map(SystemIndices::featureToDataStreamBackingIndicesAutomaton)
385419
.reduce(Operations::union);
@@ -504,21 +538,19 @@ public enum SystemIndexAccessLevel {
504538
* Given a collection of {@link SystemIndexDescriptor}s and their sources, checks to see if the index patterns of the listed
505539
* descriptors overlap with any of the other patterns. If any do, throws an exception.
506540
*
507-
* @param sourceToFeature A map of source (plugin) names to the SystemIndexDescriptors they provide.
541+
* @param featureDescriptors A map of feature names to the Features that will provide SystemIndexDescriptors
508542
* @throws IllegalStateException Thrown if any of the index patterns overlaps with another.
509543
*/
510-
static void checkForOverlappingPatterns(Map<String, Feature> sourceToFeature) {
511-
List<Tuple<String, SystemIndexDescriptor>> sourceDescriptorPair = sourceToFeature.entrySet()
544+
static void checkForOverlappingPatterns(Map<String, Feature> featureDescriptors) {
545+
List<Tuple<String, SystemIndexDescriptor>> sourceDescriptorPair = featureDescriptors.values()
512546
.stream()
513-
.flatMap(entry -> entry.getValue().getIndexDescriptors().stream().map(descriptor -> new Tuple<>(entry.getKey(), descriptor)))
547+
.flatMap(feature -> feature.getIndexDescriptors().stream().map(descriptor -> new Tuple<>(feature.getName(), descriptor)))
514548
.sorted(Comparator.comparing(d -> d.v1() + ":" + d.v2().getIndexPattern())) // Consistent ordering -> consistent error message
515549
.toList();
516-
List<Tuple<String, SystemDataStreamDescriptor>> sourceDataStreamDescriptorPair = sourceToFeature.entrySet()
550+
List<Tuple<String, SystemDataStreamDescriptor>> sourceDataStreamDescriptorPair = featureDescriptors.values()
517551
.stream()
518-
.filter(entry -> entry.getValue().getDataStreamDescriptors().isEmpty() == false)
519-
.flatMap(
520-
entry -> entry.getValue().getDataStreamDescriptors().stream().map(descriptor -> new Tuple<>(entry.getKey(), descriptor))
521-
)
552+
.filter(feature -> feature.getDataStreamDescriptors().isEmpty() == false)
553+
.flatMap(feature -> feature.getDataStreamDescriptors().stream().map(descriptor -> new Tuple<>(feature.getName(), descriptor)))
522554
.sorted(Comparator.comparing(d -> d.v1() + ":" + d.v2().getDataStreamName())) // Consistent ordering -> consistent error message
523555
.toList();
524556

@@ -577,11 +609,11 @@ private static boolean overlaps(String pattern1, String pattern2) {
577609
return Operations.isEmpty(Operations.intersection(a1Automaton, a2Automaton)) == false;
578610
}
579611

580-
private static Map<String, Feature> buildSystemIndexDescriptorMap(Map<String, Feature> featuresMap) {
581-
final Map<String, Feature> map = Maps.newMapWithExpectedSize(featuresMap.size() + SERVER_SYSTEM_INDEX_DESCRIPTORS.size());
582-
map.putAll(featuresMap);
612+
private static Map<String, Feature> buildFeatureMap(List<Feature> features) {
613+
final Map<String, Feature> map = Maps.newMapWithExpectedSize(features.size() + SERVER_SYSTEM_FEATURE_DESCRIPTORS.size());
614+
features.forEach(feature -> map.put(feature.getName(), feature));
583615
// put the server items last since we expect less of them
584-
SERVER_SYSTEM_INDEX_DESCRIPTORS.forEach((source, feature) -> {
616+
SERVER_SYSTEM_FEATURE_DESCRIPTORS.forEach((source, feature) -> {
585617
if (map.putIfAbsent(source, feature) != null) {
586618
throw new IllegalArgumentException(
587619
"plugin or module attempted to define the same source [" + source + "] as a built-in system index"

server/src/main/java/org/elasticsearch/node/Node.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -507,16 +507,11 @@ protected Node(
507507
SystemIndexMigrationExecutor.getNamedXContentParsers().stream()
508508
).flatMap(Function.identity()).collect(toList())
509509
);
510-
final Map<String, SystemIndices.Feature> featuresMap = pluginsService.filterPlugins(SystemIndexPlugin.class)
511-
.stream()
512-
.peek(plugin -> SystemIndices.validateFeatureName(plugin.getFeatureName(), plugin.getClass().getCanonicalName()))
513-
.collect(
514-
Collectors.toUnmodifiableMap(
515-
SystemIndexPlugin::getFeatureName,
516-
plugin -> SystemIndices.Feature.fromSystemIndexPlugin(plugin, settings)
517-
)
518-
);
519-
final SystemIndices systemIndices = new SystemIndices(featuresMap);
510+
final List<SystemIndices.Feature> features = pluginsService.filterPlugins(SystemIndexPlugin.class).stream().map(plugin -> {
511+
SystemIndices.validateFeatureName(plugin.getFeatureName(), plugin.getClass().getCanonicalName());
512+
return SystemIndices.Feature.fromSystemIndexPlugin(plugin, settings);
513+
}).toList();
514+
final SystemIndices systemIndices = new SystemIndices(features);
520515
final ExecutorSelector executorSelector = systemIndices.getExecutorSelector();
521516

522517
ModulesBuilder modules = new ModulesBuilder();

server/src/main/java/org/elasticsearch/snapshots/RestoreService.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,8 @@ private void startRestore(
330330
.flatMap(Collection::stream)
331331
.collect(Collectors.toSet());
332332

333-
final Map<String, SystemIndices.Feature> featureSet = systemIndices.getFeatures();
334333
final Set<String> featureStateDataStreams = featureStatesToRestore.keySet().stream().filter(featureName -> {
335-
if (featureSet.containsKey(featureName)) {
334+
if (systemIndices.getFeatureNames().contains(featureName)) {
336335
return true;
337336
}
338337
logger.warn(
@@ -344,7 +343,7 @@ private void startRestore(
344343
);
345344
return false;
346345
})
347-
.map(name -> systemIndices.getFeatures().get(name))
346+
.map(systemIndices::getFeature)
348347
.flatMap(feature -> feature.getDataStreamDescriptors().stream())
349348
.map(SystemDataStreamDescriptor::getDataStreamName)
350349
.collect(Collectors.toSet());
@@ -649,7 +648,7 @@ private Map<String, List<String>> getFeatureStatesToRestore(
649648

650649
final List<String> featuresNotOnThisNode = featureStatesToRestore.keySet()
651650
.stream()
652-
.filter(featureName -> systemIndices.getFeatures().containsKey(featureName) == false)
651+
.filter(s -> systemIndices.getFeatureNames().contains(s) == false)
653652
.toList();
654653
if (featuresNotOnThisNode.isEmpty() == false) {
655654
throw new SnapshotRestoreException(
@@ -677,7 +676,7 @@ private Set<Index> resolveSystemIndicesToDelete(ClusterState currentState, Set<S
677676
}
678677

679678
return featureStatesToRestore.stream()
680-
.map(featureName -> systemIndices.getFeatures().get(featureName))
679+
.map(systemIndices::getFeature)
681680
.filter(Objects::nonNull) // Features that aren't present on this node will be warned about in `getFeatureStatesToRestore`
682681
.flatMap(feature -> feature.getIndexDescriptors().stream())
683682
.flatMap(descriptor -> descriptor.getMatchingIndices(currentState.metadata()).stream())

server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ public void createSnapshot(final CreateSnapshotRequest request, final ActionList
263263
if (request.includeGlobalState() || requestedStates.isEmpty() == false) {
264264
if (request.includeGlobalState() && requestedStates.isEmpty()) {
265265
// If we're including global state and feature states aren't specified, include all of them
266-
featureStatesSet = systemIndices.getFeatures().keySet();
266+
featureStatesSet = systemIndices.getFeatureNames();
267267
} else if (requestedStates.size() == 1 && NO_FEATURE_STATES_VALUE.equalsIgnoreCase(requestedStates.get(0))) {
268268
// If there's exactly one value and it's "none", include no states
269269
featureStatesSet = Collections.emptySet();
@@ -282,7 +282,7 @@ public void createSnapshot(final CreateSnapshotRequest request, final ActionList
282282
return;
283283
}
284284
featureStatesSet = new HashSet<>(requestedStates);
285-
featureStatesSet.retainAll(systemIndices.getFeatures().keySet());
285+
featureStatesSet.retainAll(systemIndices.getFeatureNames());
286286
}
287287
} else {
288288
featureStatesSet = Collections.emptySet();
@@ -334,7 +334,7 @@ public ClusterState execute(ClusterState currentState) {
334334
// been requested by the request directly
335335
final Set<String> indexNames = new HashSet<>(indices);
336336
for (String featureName : featureStatesSet) {
337-
SystemIndices.Feature feature = systemIndices.getFeatures().get(featureName);
337+
SystemIndices.Feature feature = systemIndices.getFeature(featureName);
338338

339339
Set<String> featureSystemIndices = feature.getIndexDescriptors()
340340
.stream()

server/src/main/java/org/elasticsearch/upgrades/SystemIndexMigrationInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ static SystemIndexMigrationInfo fromTaskState(
239239
IndexScopedSettings indexScopedSettings
240240
) {
241241
SystemIndexDescriptor descriptor = systemIndices.findMatchingDescriptor(taskState.getCurrentIndex());
242-
SystemIndices.Feature feature = systemIndices.getFeatures().get(taskState.getCurrentFeature());
242+
SystemIndices.Feature feature = systemIndices.getFeature(taskState.getCurrentFeature());
243243
IndexMetadata imd = metadata.index(taskState.getCurrentIndex());
244244

245245
// It's possible for one or both of these to happen if the executing node fails during execution and:

0 commit comments

Comments
 (0)