Skip to content

Commit c731646

Browse files
committed
Deprecate creation of non-system dot-prefixed index names
This commit deprecates the creation of dot-prefixed index names (e.g. `.watches`) unless they are registered by a plugin that extends `SystemIndexPlugin`. This is the first step towards more thorough proections for system indices. This commit also modifies several plugins which use dot-prefixed indices to register indices they own as system indices.
1 parent 16a7a04 commit c731646

File tree

12 files changed

+191
-21
lines changed

12 files changed

+191
-21
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverAction.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
import java.io.IOException;
5959
import java.util.Collection;
60+
import java.util.Collections;
6061
import java.util.List;
6162
import java.util.Locale;
6263
import java.util.Map;
@@ -122,7 +123,7 @@ protected void masterOperation(Task task, final RolloverRequest rolloverRequest,
122123
? rolloverRequest.getNewIndexName()
123124
: generateRolloverIndexName(sourceProvidedName, indexNameExpressionResolver);
124125
final String rolloverIndexName = indexNameExpressionResolver.resolveDateMathExpression(unresolvedName);
125-
MetaDataCreateIndexService.validateIndexName(rolloverIndexName, state); // will fail if the index already exists
126+
MetaDataCreateIndexService.validateIndexName(rolloverIndexName, state, Collections.emptySet()); // fails if the index already exists
126127
checkNoDuplicatedAliasInIndexTemplate(metaData, rolloverIndexName, rolloverRequest.getAlias());
127128
IndicesStatsRequest statsRequest = new IndicesStatsRequest().indices(rolloverRequest.getAlias())
128129
.clear()

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

+25-12
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
import org.elasticsearch.cluster.routing.ShardRoutingState;
4747
import org.elasticsearch.cluster.routing.allocation.AllocationService;
4848
import org.elasticsearch.cluster.service.ClusterService;
49+
import org.elasticsearch.common.Nullable;
4950
import org.elasticsearch.common.Priority;
5051
import org.elasticsearch.common.Strings;
5152
import org.elasticsearch.common.UUIDs;
5253
import org.elasticsearch.common.ValidationException;
5354
import org.elasticsearch.common.compress.CompressedXContent;
5455
import org.elasticsearch.common.io.PathUtils;
56+
import org.elasticsearch.common.logging.DeprecationLogger;
5557
import org.elasticsearch.common.settings.IndexScopedSettings;
5658
import org.elasticsearch.common.settings.Setting;
5759
import org.elasticsearch.common.settings.Settings;
@@ -68,6 +70,7 @@
6870
import org.elasticsearch.indices.IndexCreationException;
6971
import org.elasticsearch.indices.IndicesService;
7072
import org.elasticsearch.indices.InvalidIndexNameException;
73+
import org.elasticsearch.indices.SystemIndexDescriptor;
7174
import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason;
7275
import org.elasticsearch.threadpool.ThreadPool;
7376

@@ -80,6 +83,7 @@
8083
import java.util.List;
8184
import java.util.Locale;
8285
import java.util.Map;
86+
import java.util.Objects;
8387
import java.util.Optional;
8488
import java.util.Set;
8589
import java.util.concurrent.atomic.AtomicInteger;
@@ -98,6 +102,7 @@
98102
*/
99103
public class MetaDataCreateIndexService {
100104
private static final Logger logger = LogManager.getLogger(MetaDataCreateIndexService.class);
105+
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
101106

102107
public static final int MAX_INDEX_NAME_BYTES = 255;
103108

@@ -110,19 +115,21 @@ public class MetaDataCreateIndexService {
110115
private final IndexScopedSettings indexScopedSettings;
111116
private final ActiveShardsObserver activeShardsObserver;
112117
private final NamedXContentRegistry xContentRegistry;
118+
private final Map<String, SystemIndexDescriptor> systemIndexDescriptors;
113119
private final boolean forbidPrivateIndexSettings;
114120

115121
public MetaDataCreateIndexService(
116-
final Settings settings,
117-
final ClusterService clusterService,
118-
final IndicesService indicesService,
119-
final AllocationService allocationService,
120-
final AliasValidator aliasValidator,
121-
final Environment env,
122-
final IndexScopedSettings indexScopedSettings,
123-
final ThreadPool threadPool,
124-
final NamedXContentRegistry xContentRegistry,
125-
final boolean forbidPrivateIndexSettings) {
122+
final Settings settings,
123+
final ClusterService clusterService,
124+
final IndicesService indicesService,
125+
final AllocationService allocationService,
126+
final AliasValidator aliasValidator,
127+
final Environment env,
128+
final IndexScopedSettings indexScopedSettings,
129+
final ThreadPool threadPool,
130+
final NamedXContentRegistry xContentRegistry,
131+
final Map<String, SystemIndexDescriptor> systemIndexDescriptors,
132+
final boolean forbidPrivateIndexSettings) {
126133
this.settings = settings;
127134
this.clusterService = clusterService;
128135
this.indicesService = indicesService;
@@ -132,17 +139,23 @@ public MetaDataCreateIndexService(
132139
this.indexScopedSettings = indexScopedSettings;
133140
this.activeShardsObserver = new ActiveShardsObserver(clusterService, threadPool);
134141
this.xContentRegistry = xContentRegistry;
142+
this.systemIndexDescriptors = systemIndexDescriptors;
135143
this.forbidPrivateIndexSettings = forbidPrivateIndexSettings;
136144
}
137145

138146
/**
139147
* Validate the name for an index against some static rules and a cluster state.
148+
* Set of allowed system index names is *optional* - provide it only if you want to allow certain dot-prefixed names.
140149
*/
141-
public static void validateIndexName(String index, ClusterState state) {
150+
public static void validateIndexName(String index, ClusterState state, @Nullable final Set<String> allowedSystemIndices) {
142151
validateIndexOrAliasName(index, InvalidIndexNameException::new);
143152
if (!index.toLowerCase(Locale.ROOT).equals(index)) {
144153
throw new InvalidIndexNameException(index, "must be lowercase");
145154
}
155+
if (index.charAt(0) == '.' && (Objects.isNull(allowedSystemIndices) || allowedSystemIndices.contains(index) == false)) {
156+
deprecationLogger.deprecated("index name [{}] starts with a dot '.', in the next major version, creating indices with " +
157+
"names starting with a dot will fail as these names are reserved for system indices", index);
158+
}
146159
if (state.routingTable().hasIndex(index)) {
147160
throw new ResourceAlreadyExistsException(state.routingTable().index(index).getIndex());
148161
}
@@ -591,7 +604,7 @@ public void onFailure(String source, Exception e) {
591604
}
592605

593606
private void validate(CreateIndexClusterStateUpdateRequest request, ClusterState state) {
594-
validateIndexName(request.index(), state);
607+
validateIndexName(request.index(), state, systemIndexDescriptors.keySet());
595608
validateIndexSettings(request.index(), request.settings(), forbidPrivateIndexSettings);
596609
}
597610

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.indices;
21+
22+
/**
23+
* Describes a system index. Provides the information required to create and maintain the system index.
24+
*/
25+
public class SystemIndexDescriptor {
26+
private final String name;
27+
private final String sourcePluginName;
28+
29+
/**
30+
*
31+
* @param indexName The name of the system index.
32+
* @param sourcePluginName The name of the plugin responsible for this system index.
33+
*/
34+
public SystemIndexDescriptor(String indexName, String sourcePluginName) {
35+
this.name = indexName;
36+
this.sourcePluginName = sourcePluginName;
37+
}
38+
39+
/**
40+
* Get the name of the system index.
41+
* @return The name of the system index.
42+
*/
43+
public String getIndexName() {
44+
return name;
45+
}
46+
47+
/**
48+
* Get the name of the plugin responsible for this system index. It is recommended, but not required, that this is
49+
* the name of the class implementing {@link org.elasticsearch.plugins.SystemIndexPlugin}.
50+
* @return The name of the plugin responsible for this system index.
51+
*/
52+
public String getSourcePluginName() {
53+
return sourcePluginName;
54+
}
55+
56+
// TODO: Index settings and mapping
57+
// TODO: getThreadpool()
58+
// TODO: Upgrade handling (reindex script?)
59+
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import org.elasticsearch.index.engine.EngineFactory;
101101
import org.elasticsearch.indices.IndicesModule;
102102
import org.elasticsearch.indices.IndicesService;
103+
import org.elasticsearch.indices.SystemIndexDescriptor;
103104
import org.elasticsearch.indices.analysis.AnalysisModule;
104105
import org.elasticsearch.indices.breaker.CircuitBreakerService;
105106
import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService;
@@ -132,6 +133,7 @@
132133
import org.elasticsearch.plugins.RepositoryPlugin;
133134
import org.elasticsearch.plugins.ScriptPlugin;
134135
import org.elasticsearch.plugins.SearchPlugin;
136+
import org.elasticsearch.plugins.SystemIndexPlugin;
135137
import org.elasticsearch.repositories.RepositoriesModule;
136138
import org.elasticsearch.repositories.RepositoriesService;
137139
import org.elasticsearch.rest.RestController;
@@ -422,6 +424,15 @@ protected Node(
422424
.flatMap(m -> m.entrySet().stream())
423425
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
424426

427+
final Map<String, SystemIndexDescriptor> systemIndexDescriptors = pluginsService.filterPlugins(SystemIndexPlugin.class)
428+
.stream()
429+
.flatMap(plugin -> plugin.getSystemIndexDescriptors().stream())
430+
.collect(Collectors.toUnmodifiableMap(SystemIndexDescriptor::getIndexName, Function.identity(),
431+
(descA, descB) -> {
432+
throw new IllegalStateException("\"multiple plugins, [" + descA.getSourcePluginName() + "] and [" +
433+
descB.getSourcePluginName() + "], attempted to register index name [" + descA.getIndexName() + "]," +
434+
"but each system index can only be managed by a single plugin");
435+
}));
425436
final IndicesService indicesService =
426437
new IndicesService(settings, pluginsService, nodeEnvironment, xContentRegistry, analysisModule.getAnalysisRegistry(),
427438
clusterModule.getIndexNameExpressionResolver(), indicesModule.getMapperRegistry(), namedWriteableRegistry,
@@ -440,6 +451,7 @@ protected Node(
440451
settingsModule.getIndexScopedSettings(),
441452
threadPool,
442453
xContentRegistry,
454+
systemIndexDescriptors,
443455
forbidPrivateIndexSettings);
444456

445457
Collection<Object> pluginComponents = pluginsService.filterPlugins(Plugin.class).stream()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.plugins;
21+
22+
import org.elasticsearch.indices.SystemIndexDescriptor;
23+
24+
import java.util.Collection;
25+
import java.util.Collections;
26+
27+
/**
28+
* Plugin for defining system indices. Extends {@link ActionPlugin} because system indices must be accessed via APIs
29+
* added by the plugin that owns the system index, rather than standard APIs.
30+
*/
31+
public interface SystemIndexPlugin extends ActionPlugin {
32+
33+
/**
34+
* Returns a {@link Collection} of {@link SystemIndexDescriptor}s that describe this plugin's system indices, including
35+
* name, mapping, and settings.
36+
* @return Descriptions of the system indices managed by this plugin.
37+
*/
38+
default Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
39+
return Collections.emptyList();
40+
}
41+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ public ClusterState execute(ClusterState currentState) {
269269
if (currentIndexMetaData == null) {
270270
// Index doesn't exist - create it and start recovery
271271
// Make sure that the index we are about to create has a validate name
272-
MetaDataCreateIndexService.validateIndexName(renamedIndexName, currentState);
272+
MetaDataCreateIndexService.validateIndexName(renamedIndexName, currentState, Collections.emptySet());
273273
createIndexService.validateIndexSettings(renamedIndexName, snapshotIndexMetaData.getSettings(), false);
274274
IndexMetaData.Builder indexMdBuilder = IndexMetaData.builder(snapshotIndexMetaData)
275275
.state(IndexMetaData.State.OPEN)

server/src/test/java/org/elasticsearch/action/admin/indices/template/put/MetaDataIndexTemplateServiceTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ private static List<Throwable> putTemplate(NamedXContentRegistry xContentRegistr
160160
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS,
161161
null,
162162
xContentRegistry,
163+
Collections.emptyMap(),
163164
true);
164165
MetaDataIndexTemplateService service = new MetaDataIndexTemplateService(null, createIndexService,
165166
new AliasValidator(), null,
@@ -193,6 +194,7 @@ private List<Throwable> putTemplateDetail(PutRequest request) throws Exception {
193194
null,
194195
null,
195196
xContentRegistry(),
197+
Collections.emptyMap(),
196198
true);
197199
MetaDataIndexTemplateService service = new MetaDataIndexTemplateService(
198200
clusterService, createIndexService, new AliasValidator(), indicesService,

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.Collection;
5252
import java.util.Collections;
5353
import java.util.Comparator;
54+
import java.util.HashSet;
5455
import java.util.List;
5556
import java.util.Locale;
5657
import java.util.Set;
@@ -423,7 +424,7 @@ public void testValidateIndexName() throws Exception {
423424
private void validateIndexName(String indexName, String errorMessage) {
424425
InvalidIndexNameException e = expectThrows(InvalidIndexNameException.class,
425426
() -> MetaDataCreateIndexService.validateIndexName(indexName, ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING
426-
.getDefault(Settings.EMPTY)).build()));
427+
.getDefault(Settings.EMPTY)).build(), Collections.emptySet()));
427428
assertThat(e.getMessage(), endsWith(errorMessage));
428429
}
429430

@@ -486,4 +487,22 @@ public void testShardLimit() {
486487
assertThat(e, hasToString(containsString(expectedMessage)));
487488
}
488489

490+
public void testValidateIndexNameChecksSystemIndexNames() {
491+
// Check deprecations
492+
MetaDataCreateIndexService.validateIndexName(".test", ClusterState.EMPTY_STATE, null);
493+
assertWarnings("index name [.test] starts with a dot '.', in the next major version, creating indices with " +
494+
"names starting with a dot will fail as these names are reserved for system indices");
495+
MetaDataCreateIndexService.validateIndexName(".test2", ClusterState.EMPTY_STATE, Collections.emptySet());
496+
assertWarnings("index name [.test2] starts with a dot '.', in the next major version, creating indices with " +
497+
"names starting with a dot will fail as these names are reserved for system indices");
498+
MetaDataCreateIndexService.validateIndexName(".test3", ClusterState.EMPTY_STATE,
499+
new HashSet<>(Arrays.asList(".test1", ".test2", ".foobar")));
500+
assertWarnings("index name [.test3] starts with a dot '.', in the next major version, creating indices with " +
501+
"names starting with a dot will fail as these names are reserved for system indices");
502+
503+
// Check NO deprecation warnings if we give the index name
504+
MetaDataCreateIndexService.validateIndexName(".test4", ClusterState.EMPTY_STATE, new HashSet<>(Arrays.asList(".test4")));
505+
MetaDataCreateIndexService.validateIndexName(".test5", ClusterState.EMPTY_STATE,
506+
new HashSet<>(Arrays.asList(".foo", ".bar", ".baz", ".test5", ".quux")));
507+
}
489508
}

server/src/test/java/org/elasticsearch/indices/cluster/ClusterStateChanges.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public IndexMetaData upgradeIndexMetaData(IndexMetaData indexMetaData, Version m
204204
allocationService, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, indicesService, threadPool);
205205
MetaDataCreateIndexService createIndexService = new MetaDataCreateIndexService(SETTINGS, clusterService, indicesService,
206206
allocationService, new AliasValidator(), environment,
207-
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, threadPool, xContentRegistry, true);
207+
IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, threadPool, xContentRegistry, Collections.emptyMap(), true);
208208

209209
transportCloseIndexAction = new TransportCloseIndexAction(SETTINGS, transportService, clusterService, threadPool,
210210
indexStateService, clusterSettings, actionFilters, indexNameExpressionResolver, destructiveOperations);

server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ public void onFailure(final Exception e) {
11381138
final MetaDataCreateIndexService metaDataCreateIndexService = new MetaDataCreateIndexService(settings, clusterService,
11391139
indicesService,
11401140
allocationService, new AliasValidator(), environment, indexScopedSettings,
1141-
threadPool, namedXContentRegistry, false);
1141+
threadPool, namedXContentRegistry, Collections.emptyMap(), false);
11421142
actions.put(CreateIndexAction.INSTANCE,
11431143
new TransportCreateIndexAction(
11441144
transportService, clusterService, threadPool,

0 commit comments

Comments
 (0)