Skip to content

Commit 7f16a1d

Browse files
committed
Improve upgrade experience of node level index settings
In 5.0 we don't allow index settings to be specified on the node level ie. in yaml files or via commandline argument. This can cause problems during upgrade if this was used extensively. For instance if analyzers where specified on a node level this might cause the index to be closed when imported (see #17187). In such a case all indices relying on this must be updated via `PUT /${index}/_settings`. Yet, this API has slightly different semantics since it overrides existing settings. To make this less painful this change adds a `preserve_existing` parameter on that API to ensure we have the same semantics as if the setting was applied on the node level. This change also adds a better error message and a change to the migration guide to ensure upgrades are smooth if index settings are specified on the node level. If a index setting is detected this change fails the node startup and prints a message like this: ``` ************************************************************************************* Found index level settings on node level configuration. Since elasticsearch 5.x index level settings can NOT be set on the nodes configuration like the elasticsearch.yaml, in system properties or command line arguments.In order to upgrade all indices the settings must be updated via the /${index}/_settings API. Unless all settings are dynamic all indices must be closed in order to apply the upgradeIndices created in the future should use index templates to set default values. Please ensure all required values are updated on all indices by executing: curl -XPUT 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '{ "index.number_of_shards" : "1", "index.query.default_field" : "main_field", "index.translog.durability" : "async", "index.ttl.disable_purge" : "true" }' ************************************************************************************* ```
1 parent bd44f37 commit 7f16a1d

File tree

12 files changed

+237
-11
lines changed

12 files changed

+237
-11
lines changed

core/src/main/java/org/elasticsearch/action/admin/indices/settings/put/TransportUpdateSettingsAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ protected void masterOperation(final UpdateSettingsRequest request, final Cluste
8080
UpdateSettingsClusterStateUpdateRequest clusterStateUpdateRequest = new UpdateSettingsClusterStateUpdateRequest()
8181
.indices(concreteIndices)
8282
.settings(request.settings())
83+
.setPreserveExisting(request.isPreserveExisting())
8384
.ackTimeout(request.timeout())
8485
.masterNodeTimeout(request.masterNodeTimeout());
8586

core/src/main/java/org/elasticsearch/action/admin/indices/settings/put/UpdateSettingsClusterStateUpdateRequest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,23 @@ public class UpdateSettingsClusterStateUpdateRequest extends IndicesClusterState
2929

3030
private Settings settings;
3131

32-
public UpdateSettingsClusterStateUpdateRequest() {
32+
private boolean preserveExisting = false;
3333

34+
/**
35+
* Returns <code>true</code> iff the settings update should only add but not update settings. If the setting already exists
36+
* it should not be overwritten by this update. The default is <code>false</code>
37+
*/
38+
public boolean isPreserveExisting() {
39+
return preserveExisting;
40+
}
41+
42+
/**
43+
* Iff set to <code>true</code> this settings update will only add settings not already set on an index. Existing settings remain
44+
* unchanged.
45+
*/
46+
public UpdateSettingsClusterStateUpdateRequest setPreserveExisting(boolean preserveExisting) {
47+
this.preserveExisting = preserveExisting;
48+
return this;
3449
}
3550

3651
/**

core/src/main/java/org/elasticsearch/action/admin/indices/settings/put/UpdateSettingsRequest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class UpdateSettingsRequest extends AcknowledgedRequest<UpdateSettingsReq
4747
private String[] indices;
4848
private IndicesOptions indicesOptions = IndicesOptions.fromOptions(false, false, true, true);
4949
private Settings settings = EMPTY_SETTINGS;
50+
private boolean preserveExisting = false;
5051

5152
public UpdateSettingsRequest() {
5253
}
@@ -127,6 +128,23 @@ public UpdateSettingsRequest settings(String source) {
127128
return this;
128129
}
129130

131+
/**
132+
* Returns <code>true</code> iff the settings update should only add but not update settings. If the setting already exists
133+
* it should not be overwritten by this update. The default is <code>false</code>
134+
*/
135+
public boolean isPreserveExisting() {
136+
return preserveExisting;
137+
}
138+
139+
/**
140+
* Iff set to <code>true</code> this settings update will only add settings not already set on an index. Existing settings remain
141+
* unchanged.
142+
*/
143+
public UpdateSettingsRequest setPreserveExisting(boolean preserveExisting) {
144+
this.preserveExisting = preserveExisting;
145+
return this;
146+
}
147+
130148
/**
131149
* Sets the settings to be updated (either json/yaml/properties format)
132150
*/
@@ -149,6 +167,7 @@ public void readFrom(StreamInput in) throws IOException {
149167
indicesOptions = IndicesOptions.readIndicesOptions(in);
150168
settings = readSettingsFromStream(in);
151169
readTimeout(in);
170+
preserveExisting = in.readBoolean();
152171
}
153172

154173
@Override
@@ -158,5 +177,6 @@ public void writeTo(StreamOutput out) throws IOException {
158177
indicesOptions.writeIndicesOptions(out);
159178
writeSettingsToStream(settings, out);
160179
writeTimeout(out);
180+
out.writeBoolean(preserveExisting);
161181
}
162182
}

core/src/main/java/org/elasticsearch/action/admin/indices/settings/put/UpdateSettingsRequestBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,9 @@ public UpdateSettingsRequestBuilder setSettings(Map<String, Object> source) {
8484
request.settings(source);
8585
return this;
8686
}
87+
88+
public UpdateSettingsRequestBuilder setPreserveExisting(boolean preserveExisting) {
89+
request.setPreserveExisting(preserveExisting);
90+
return this;
91+
}
8792
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public void updateSettings(final UpdateSettingsClusterStateUpdateRequest request
176176
final Settings skippedSettigns = skipppedSettings.build();
177177
final Settings closedSettings = settingsForClosedIndices.build();
178178
final Settings openSettings = settingsForOpenIndices.build();
179+
final boolean preserveExisting = request.isPreserveExisting();
179180

180181
clusterService.submitStateUpdateTask("update-settings",
181182
new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(Priority.URGENT, request, listener) {
@@ -221,7 +222,7 @@ public ClusterState execute(ClusterState currentState) {
221222
}
222223

223224
int updatedNumberOfReplicas = openSettings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, -1);
224-
if (updatedNumberOfReplicas != -1) {
225+
if (updatedNumberOfReplicas != -1 && preserveExisting == false) {
225226
routingTableBuilder.updateNumberOfReplicas(updatedNumberOfReplicas, actualIndices);
226227
metaDataBuilder.updateNumberOfReplicas(updatedNumberOfReplicas, actualIndices);
227228
logger.info("updating number_of_replicas to [{}] for indices {}", updatedNumberOfReplicas, actualIndices);
@@ -239,6 +240,9 @@ public ClusterState execute(ClusterState currentState) {
239240
Settings.Builder updates = Settings.builder();
240241
Settings.Builder indexSettings = Settings.builder().put(indexMetaData.getSettings());
241242
if (indexScopedSettings.updateDynamicSettings(openSettings, indexSettings, updates, index.getName())) {
243+
if (preserveExisting) {
244+
indexSettings.put(indexMetaData.getSettings());
245+
}
242246
metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(indexSettings));
243247
}
244248
}
@@ -250,6 +254,9 @@ public ClusterState execute(ClusterState currentState) {
250254
Settings.Builder updates = Settings.builder();
251255
Settings.Builder indexSettings = Settings.builder().put(indexMetaData.getSettings());
252256
if (indexScopedSettings.updateSettings(closedSettings, indexSettings, updates, index.getName())) {
257+
if (preserveExisting) {
258+
indexSettings.put(indexMetaData.getSettings());
259+
}
253260
metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(indexSettings));
254261
}
255262
}

core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Set;
33+
import java.util.SortedMap;
34+
import java.util.SortedSet;
35+
import java.util.TreeMap;
3336
import java.util.concurrent.CopyOnWriteArrayList;
3437
import java.util.function.BiConsumer;
3538
import java.util.function.Consumer;
@@ -221,9 +224,17 @@ public final void validate(Settings.Builder settingsBuilder) {
221224
* * Validates that all given settings are registered and valid
222225
*/
223226
public final void validate(Settings settings) {
224-
for (Map.Entry<String, String> entry : settings.getAsMap().entrySet()) {
225-
validate(entry.getKey(), settings);
227+
List<RuntimeException> exceptions = new ArrayList<>();
228+
// we want them sorted for deterministic error messages
229+
SortedMap<String, String> sortedSettings = new TreeMap<>(settings.getAsMap());
230+
for (Map.Entry<String, String> entry : sortedSettings.entrySet()) {
231+
try {
232+
validate(entry.getKey(), settings);
233+
} catch (RuntimeException ex) {
234+
exceptions.add(ex);
235+
}
226236
}
237+
ExceptionsHelper.rethrowAndSuppress(exceptions);
227238
}
228239

229240

core/src/main/java/org/elasticsearch/common/settings/SettingsModule.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@
2020
package org.elasticsearch.common.settings;
2121

2222
import org.elasticsearch.common.inject.AbstractModule;
23+
import org.elasticsearch.common.logging.ESLogger;
24+
import org.elasticsearch.common.logging.Loggers;
25+
import org.elasticsearch.common.xcontent.ToXContent;
26+
import org.elasticsearch.common.xcontent.XContentBuilder;
27+
import org.elasticsearch.common.xcontent.XContentType;
2328
import org.elasticsearch.tribe.TribeService;
2429

30+
import java.io.IOException;
31+
import java.util.Collections;
2532
import java.util.HashMap;
2633
import java.util.HashSet;
2734
import java.util.Map;
2835
import java.util.Set;
2936
import java.util.function.Predicate;
37+
import java.util.stream.Collectors;
38+
import java.util.stream.IntStream;
3039

3140
/**
3241
* A module that binds the provided settings to the {@link Settings} interface.
@@ -37,9 +46,12 @@ public class SettingsModule extends AbstractModule {
3746
private final Set<String> settingsFilterPattern = new HashSet<>();
3847
private final Map<String, Setting<?>> nodeSettings = new HashMap<>();
3948
private final Map<String, Setting<?>> indexSettings = new HashMap<>();
40-
private static final Predicate<String> TRIBE_CLIENT_NODE_SETTINGS_PREDICATE = (s) -> s.startsWith("tribe.") && TribeService.TRIBE_SETTING_KEYS.contains(s) == false;
49+
private static final Predicate<String> TRIBE_CLIENT_NODE_SETTINGS_PREDICATE = (s) -> s.startsWith("tribe.")
50+
&& TribeService.TRIBE_SETTING_KEYS.contains(s) == false;
51+
private final ESLogger logger;
4152

4253
public SettingsModule(Settings settings) {
54+
logger = Loggers.getLogger(getClass(), settings);
4355
this.settings = settings;
4456
for (Setting<?> setting : ClusterSettings.BUILT_IN_CLUSTER_SETTINGS) {
4557
registerSetting(setting);
@@ -53,6 +65,56 @@ public SettingsModule(Settings settings) {
5365
protected void configure() {
5466
final IndexScopedSettings indexScopedSettings = new IndexScopedSettings(settings, new HashSet<>(this.indexSettings.values()));
5567
final ClusterSettings clusterSettings = new ClusterSettings(settings, new HashSet<>(this.nodeSettings.values()));
68+
Settings indexSettings = settings.filter((s) -> s.startsWith("index.") && clusterSettings.get(s) == null);
69+
if (indexSettings.isEmpty() == false) {
70+
try {
71+
String separator = IntStream.range(0, 85).mapToObj(s -> "*").collect(Collectors.joining("")).trim();
72+
StringBuilder builder = new StringBuilder();
73+
builder.append(System.lineSeparator());
74+
builder.append(separator);
75+
builder.append(System.lineSeparator());
76+
builder.append("Found index level settings on node level configuration.");
77+
builder.append(System.lineSeparator());
78+
builder.append(System.lineSeparator());
79+
int count = 0;
80+
for (String word : ("Since elasticsearch 5.x index level settings can NOT be set on the nodes configuration like " +
81+
"the elasticsearch.yaml, in system properties or command line arguments." +
82+
"In order to upgrade all indices the settings must be updated via the /${index}/_settings API. " +
83+
"Unless all settings are dynamic all indices must be closed in order to apply the upgrade" +
84+
"Indices created in the future should use index templates to set default values."
85+
).split(" ")) {
86+
if (count + word.length() > 85) {
87+
builder.append(System.lineSeparator());
88+
count = 0;
89+
}
90+
count += word.length() + 1;
91+
builder.append(word).append(" ");
92+
}
93+
94+
builder.append(System.lineSeparator());
95+
builder.append(System.lineSeparator());
96+
builder.append("Please ensure all required values are updated on all indices by executing: ");
97+
builder.append(System.lineSeparator());
98+
builder.append(System.lineSeparator());
99+
builder.append("curl -XPUT 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '");
100+
try (XContentBuilder xContentBuilder = XContentBuilder.builder(XContentType.JSON.xContent())) {
101+
xContentBuilder.prettyPrint();
102+
xContentBuilder.startObject();
103+
indexSettings.toXContent(xContentBuilder, new ToXContent.MapParams(Collections.singletonMap("flat_settings", "true")));
104+
xContentBuilder.endObject();
105+
builder.append(xContentBuilder.string());
106+
}
107+
builder.append("'");
108+
builder.append(System.lineSeparator());
109+
builder.append(separator);
110+
builder.append(System.lineSeparator());
111+
112+
logger.warn(builder.toString());
113+
throw new IllegalArgumentException("node settings must not contain any index level settings");
114+
} catch (IOException e) {
115+
throw new RuntimeException(e);
116+
}
117+
}
56118
// by now we are fully configured, lets check node level settings for unregistered index settings
57119
final Predicate<String> acceptOnlyClusterSettings = TRIBE_CLIENT_NODE_SETTINGS_PREDICATE.negate();
58120
clusterSettings.validate(settings.filter(acceptOnlyClusterSettings));

core/src/main/java/org/elasticsearch/rest/action/admin/indices/settings/RestUpdateSettingsAction.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class RestUpdateSettingsAction extends BaseRestHandler {
4747
"timeout",
4848
"master_timeout",
4949
"index",
50+
"preserve_existing",
5051
"expand_wildcards",
5152
"ignore_unavailable",
5253
"allow_no_indices"));
@@ -62,6 +63,7 @@ public RestUpdateSettingsAction(Settings settings, RestController controller, Cl
6263
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
6364
UpdateSettingsRequest updateSettingsRequest = updateSettingsRequest(Strings.splitStringByCommaToArray(request.param("index")));
6465
updateSettingsRequest.timeout(request.paramAsTime("timeout", updateSettingsRequest.timeout()));
66+
updateSettingsRequest.setPreserveExisting(request.paramAsBoolean("preserve_existing", updateSettingsRequest.isPreserveExisting()));
6567
updateSettingsRequest.masterNodeTimeout(request.paramAsTime("master_timeout", updateSettingsRequest.masterNodeTimeout()));
6668
updateSettingsRequest.indicesOptions(IndicesOptions.fromRequest(request, updateSettingsRequest.indicesOptions()));
6769

core/src/test/java/org/elasticsearch/common/settings/SettingsModuleTests.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,35 @@ public void testValidate() {
3636
{
3737
Settings settings = Settings.builder().put("cluster.routing.allocation.balance.shard", "[2.0]").build();
3838
SettingsModule module = new SettingsModule(settings);
39-
try {
40-
assertInstanceBinding(module, Settings.class, (s) -> s == settings);
41-
fail();
42-
} catch (IllegalArgumentException ex) {
43-
assertEquals("Failed to parse value [[2.0]] for setting [cluster.routing.allocation.balance.shard]", ex.getMessage());
44-
}
39+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
40+
() -> assertInstanceBinding(module, Settings.class, (s) -> s == settings));
41+
assertEquals("Failed to parse value [[2.0]] for setting [cluster.routing.allocation.balance.shard]", ex.getMessage());
42+
}
43+
44+
{
45+
Settings settings = Settings.builder().put("cluster.routing.allocation.balance.shard", "[2.0]")
46+
.put("some.foo.bar", 1).build();
47+
SettingsModule module = new SettingsModule(settings);
48+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
49+
() -> assertInstanceBinding(module, Settings.class, (s) -> s == settings));
50+
assertEquals("Failed to parse value [[2.0]] for setting [cluster.routing.allocation.balance.shard]", ex.getMessage());
51+
assertEquals(1, ex.getSuppressed().length);
52+
assertEquals("unknown setting [some.foo.bar]", ex.getSuppressed()[0].getMessage());
53+
}
54+
55+
{
56+
Settings settings = Settings.builder().put("index.codec", "default")
57+
.put("index.foo.bar", 1).build();
58+
SettingsModule module = new SettingsModule(settings);
59+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
60+
() -> assertInstanceBinding(module, Settings.class, (s) -> s == settings));
61+
assertEquals("node settings must not contain any index level settings", ex.getMessage());
62+
}
63+
64+
{
65+
Settings settings = Settings.builder().put("index.codec", "default").build();
66+
SettingsModule module = new SettingsModule(settings);
67+
assertInstanceBinding(module, Settings.class, (s) -> s == settings);
4568
}
4669
}
4770

docs/reference/migration/migrate_5_0/settings.asciidoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ that plugins that define custom settings must register all of their settings
1212
during plugin loading using the `SettingsModule#registerSettings(Setting)`
1313
method.
1414

15+
==== Index Level Settings
16+
17+
In previous versions Elasticsearch allowed to specify index level setting
18+
as _defaults_ on the node level, inside the `elasticsearch.yaml` file or even via
19+
command-line parameters. From Elasticsearch 5.0 on only selected settings like
20+
for instance `index.codec` can be set on the node level. All other settings must be
21+
set on each individual index. To set default values on every index, index templates
22+
should be used instead.
23+
1524
==== Node settings
1625

1726
The `name` setting has been removed and is replaced by `node.name`. Usage of

0 commit comments

Comments
 (0)