Skip to content

Commit 6952c0e

Browse files
committed
Add infrastructure to upgrade settings
In some cases we want to deprecate a setting, and then automatically upgrade uses of that setting to a replacement setting. This commit adds infrastructure for this so that we can upgrade settings when recovering the cluster state, as well as when such settings are dynamically applied on cluster update settings requests. This commit only focuses on cluster settings, index settings can build on this infrastructure in a follow-up.
1 parent 9a404f3 commit 6952c0e

File tree

6 files changed

+325
-5
lines changed

6 files changed

+325
-5
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,12 @@ public void onFailure(String source, Exception e) {
179179

180180
@Override
181181
public ClusterState execute(final ClusterState currentState) {
182-
ClusterState clusterState =
183-
updater.updateSettings(currentState, request.transientSettings(), request.persistentSettings(), logger);
182+
final ClusterState clusterState =
183+
updater.updateSettings(
184+
currentState,
185+
clusterSettings.upgradeSettings(request.transientSettings()),
186+
clusterSettings.upgradeSettings(request.persistentSettings()),
187+
logger);
184188
changed = clusterState != currentState;
185189
return clusterState;
186190
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.logging.log4j.message.ParameterizedMessage;
2323
import org.apache.lucene.search.spell.LevenshteinDistance;
2424
import org.apache.lucene.util.CollectionUtil;
25+
import org.elasticsearch.Assertions;
2526
import org.elasticsearch.ExceptionsHelper;
2627
import org.elasticsearch.common.collect.Tuple;
2728
import org.elasticsearch.common.component.AbstractComponent;
@@ -34,10 +35,12 @@
3435
import java.util.List;
3536
import java.util.Locale;
3637
import java.util.Map;
38+
import java.util.Objects;
3739
import java.util.Set;
3840
import java.util.concurrent.CopyOnWriteArrayList;
3941
import java.util.function.BiConsumer;
4042
import java.util.function.Consumer;
43+
import java.util.function.Function;
4144
import java.util.function.Predicate;
4245
import java.util.regex.Pattern;
4346
import java.util.stream.Collectors;
@@ -757,6 +760,23 @@ private static Setting<?> findOverlappingSetting(Setting<?> newSetting, Map<Stri
757760
return null;
758761
}
759762

763+
public Settings upgradeSettings(final Settings settings) {
764+
final Settings.Builder builder = Settings.builder();
765+
boolean changed = false;
766+
for (final String key : settings.keySet()) {
767+
final Setting<?> setting = getRaw(key);
768+
final Function<Map.Entry<String, String>, Map.Entry<String, String>> upgrader = upgraders.get(setting);
769+
if (upgrader == null) {
770+
builder.copy(key, settings);
771+
} else {
772+
changed = true;
773+
final Map.Entry<String, String> upgrade = upgrader.apply(new Entry(key, settings));
774+
builder.put(upgrade.getKey(), upgrade.getValue());
775+
}
776+
}
777+
return changed ? builder.build() : settings;
778+
}
779+
760780
/**
761781
* Archives invalid or unknown settings. Any setting that is not recognized or fails validation
762782
* will be archived. This means the setting is prefixed with {@value ARCHIVED_SETTINGS_PREFIX}
@@ -847,4 +867,12 @@ public String setValue(String value) {
847867
public boolean isPrivateSetting(String key) {
848868
return false;
849869
}
870+
871+
private Map<Setting<?>, Function<Map.Entry<String, String>, Map.Entry<String, String>>> upgraders = new HashMap<>();
872+
873+
public synchronized void addSettingsUpgrader(
874+
final Setting<?> setting, Function<Map.Entry<String, String>, Map.Entry<String, String>> upgrader) {
875+
upgraders.put(Objects.requireNonNull(setting, "setting"), Objects.requireNonNull(upgrader, "upgrader"));
876+
}
877+
850878
}

server/src/main/java/org/elasticsearch/gateway/Gateway.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,25 @@ public void performStateRecovery(final GatewayStateRecoveredListener listener) t
137137
}
138138
}
139139
}
140+
final ClusterState.Builder builder = upgradeAndArchiveUnknownOrInvalidSettings(metaDataBuilder);
141+
listener.onSuccess(builder.build());
142+
}
143+
144+
ClusterState.Builder upgradeAndArchiveUnknownOrInvalidSettings(MetaData.Builder metaDataBuilder) {
140145
final ClusterSettings clusterSettings = clusterService.getClusterSettings();
141146
metaDataBuilder.persistentSettings(
142147
clusterSettings.archiveUnknownOrInvalidSettings(
143-
metaDataBuilder.persistentSettings(),
148+
clusterSettings.upgradeSettings(metaDataBuilder.persistentSettings()),
144149
e -> logUnknownSetting("persistent", e),
145150
(e, ex) -> logInvalidSetting("persistent", e, ex)));
146151
metaDataBuilder.transientSettings(
147152
clusterSettings.archiveUnknownOrInvalidSettings(
148-
metaDataBuilder.transientSettings(),
153+
clusterSettings.upgradeSettings(metaDataBuilder.transientSettings()),
149154
e -> logUnknownSetting("transient", e),
150155
(e, ex) -> logInvalidSetting("transient", e, ex)));
151156
ClusterState.Builder builder = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(settings));
152157
builder.metaData(metaDataBuilder);
153-
listener.onSuccess(builder.build());
158+
return builder;
154159
}
155160

156161
private void logUnknownSetting(String settingType, Map.Entry<String, String> e) {

server/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.elasticsearch.transport.TransportService;
3333

3434
import java.io.IOException;
35+
import java.util.AbstractMap;
36+
import java.util.ArrayList;
3537
import java.util.Arrays;
3638
import java.util.Collections;
3739
import java.util.HashMap;
@@ -53,6 +55,7 @@
5355
import static org.hamcrest.CoreMatchers.startsWith;
5456
import static org.hamcrest.Matchers.arrayWithSize;
5557
import static org.hamcrest.Matchers.hasToString;
58+
import static org.hamcrest.Matchers.sameInstance;
5659

5760
public class ScopedSettingsTests extends ESTestCase {
5861

@@ -1045,4 +1048,79 @@ public void testPrivateIndexSettingsSkipValidation() {
10451048
indexScopedSettings.validate(settings, false, /* validateInternalOrPrivateIndex */ false);
10461049
}
10471050

1051+
public void testUpgradeSetting() {
1052+
final Setting<?> oldSetting = Setting.simpleString("foo.old", Property.NodeScope);
1053+
final Setting<?> newSetting = Setting.simpleString("foo.new", Property.NodeScope);
1054+
final Setting<?> remainingSetting = Setting.simpleString("foo.remaining", Property.NodeScope);
1055+
1056+
final AbstractScopedSettings service =
1057+
new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList(oldSetting, newSetting, remainingSetting)));
1058+
service.addSettingsUpgrader(oldSetting, entry -> new AbstractMap.SimpleEntry<>("foo.new", entry.getValue()));
1059+
1060+
final Settings settings =
1061+
Settings.builder()
1062+
.put("foo.old", randomAlphaOfLength(8))
1063+
.put("foo.remaining", randomAlphaOfLength(8))
1064+
.build();
1065+
final Settings upgradedSettings = service.upgradeSettings(settings);
1066+
assertFalse(oldSetting.exists(upgradedSettings));
1067+
assertTrue(newSetting.exists(upgradedSettings));
1068+
assertThat(newSetting.get(upgradedSettings), equalTo(oldSetting.get(settings)));
1069+
assertTrue(remainingSetting.exists(upgradedSettings));
1070+
assertThat(remainingSetting.get(upgradedSettings), equalTo(remainingSetting.get(settings)));
1071+
}
1072+
1073+
public void testUpgradeSettingsNoChangesPreservesInstance() {
1074+
final Setting<?> oldSetting = Setting.simpleString("foo.old", Property.NodeScope);
1075+
final Setting<?> newSetting = Setting.simpleString("foo.new", Property.NodeScope);
1076+
final Setting<?> remainingSetting = Setting.simpleString("foo.remaining", Property.NodeScope);
1077+
1078+
final AbstractScopedSettings service =
1079+
new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList(oldSetting, newSetting, remainingSetting)));
1080+
service.addSettingsUpgrader(oldSetting, entry -> new AbstractMap.SimpleEntry<>("foo.new", entry.getValue()));
1081+
1082+
final Settings settings =
1083+
Settings.builder().put("foo.remaining", randomAlphaOfLength(8)).build();
1084+
final Settings upgradedSettings = service.upgradeSettings(settings);
1085+
assertThat(upgradedSettings, sameInstance(settings));
1086+
}
1087+
1088+
public void testUpgradeComplexSetting() {
1089+
final Setting.AffixSetting<String> oldSetting =
1090+
Setting.affixKeySetting("foo.old.", "suffix", key -> Setting.simpleString(key, Property.NodeScope));
1091+
final Setting.AffixSetting<String> newSetting =
1092+
Setting.affixKeySetting("foo.new.", "suffix", key -> Setting.simpleString(key, Property.NodeScope));
1093+
final Setting.AffixSetting<String> remainingSetting =
1094+
Setting.affixKeySetting("foo.remaining.", "suffix", key -> Setting.simpleString(key, Property.NodeScope));
1095+
1096+
final AbstractScopedSettings service =
1097+
new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList(oldSetting, newSetting, remainingSetting)));
1098+
service.addSettingsUpgrader(
1099+
oldSetting,
1100+
entry -> new AbstractMap.SimpleEntry<>(entry.getKey().replaceFirst("^foo.old", "foo.new"), entry.getValue()));
1101+
1102+
final int count = randomIntBetween(1, 8);
1103+
final List<String> concretes = new ArrayList<>(count);
1104+
final Settings.Builder builder = Settings.builder();
1105+
for (int i = 0; i < count; i++) {
1106+
final String concrete = randomAlphaOfLength(8);
1107+
concretes.add(concrete);
1108+
builder.put("foo.old." + concrete + ".suffix", randomAlphaOfLength(8));
1109+
builder.put("foo.remaining." + concrete + ".suffix", randomAlphaOfLength(8));
1110+
}
1111+
final Settings settings = builder.build();
1112+
final Settings upgradedSettings = service.upgradeSettings(settings);
1113+
for (final String concrete : concretes) {
1114+
assertFalse(oldSetting.getConcreteSettingForNamespace(concrete).exists(upgradedSettings));
1115+
assertTrue(newSetting.getConcreteSettingForNamespace(concrete).exists(upgradedSettings));
1116+
assertThat(
1117+
newSetting.getConcreteSettingForNamespace(concrete).get(upgradedSettings),
1118+
equalTo(oldSetting.getConcreteSettingForNamespace(concrete).get(settings)));
1119+
assertTrue(remainingSetting.getConcreteSettingForNamespace(concrete).exists(upgradedSettings));
1120+
assertThat(
1121+
remainingSetting.getConcreteSettingForNamespace(concrete).get(upgradedSettings),
1122+
equalTo(remainingSetting.getConcreteSettingForNamespace(concrete).get(settings)));
1123+
}
1124+
}
1125+
10481126
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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.common.settings;
21+
22+
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequestBuilder;
23+
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
24+
import org.elasticsearch.client.Client;
25+
import org.elasticsearch.cluster.metadata.MetaData;
26+
import org.elasticsearch.cluster.service.ClusterService;
27+
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
28+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
29+
import org.elasticsearch.env.Environment;
30+
import org.elasticsearch.env.NodeEnvironment;
31+
import org.elasticsearch.plugins.Plugin;
32+
import org.elasticsearch.script.ScriptService;
33+
import org.elasticsearch.test.ESSingleNodeTestCase;
34+
import org.elasticsearch.threadpool.ThreadPool;
35+
import org.elasticsearch.watcher.ResourceWatcherService;
36+
import org.junit.After;
37+
38+
import java.util.AbstractMap;
39+
import java.util.Arrays;
40+
import java.util.Collection;
41+
import java.util.Collections;
42+
import java.util.List;
43+
import java.util.function.BiConsumer;
44+
import java.util.function.Function;
45+
46+
import static org.hamcrest.Matchers.equalTo;
47+
48+
public class UpgradeSettingsIT extends ESSingleNodeTestCase {
49+
50+
@After
51+
public void cleanup() throws Exception {
52+
client()
53+
.admin()
54+
.cluster()
55+
.prepareUpdateSettings()
56+
.setPersistentSettings(Settings.builder().putNull("*"))
57+
.setTransientSettings(Settings.builder().putNull("*"))
58+
.get();
59+
}
60+
61+
@Override
62+
protected Collection<Class<? extends Plugin>> getPlugins() {
63+
return Collections.singletonList(UpgradeSettingsPlugin.class);
64+
}
65+
66+
public static class UpgradeSettingsPlugin extends Plugin {
67+
68+
static final Setting<?> oldSetting = Setting.simpleString("foo.old", Setting.Property.Dynamic, Setting.Property.NodeScope);
69+
static final Setting<?> newSetting = Setting.simpleString("foo.new", Setting.Property.Dynamic, Setting.Property.NodeScope);
70+
71+
public UpgradeSettingsPlugin(){
72+
73+
}
74+
75+
@Override
76+
public Collection<Object> createComponents(
77+
final Client client,
78+
final ClusterService clusterService,
79+
final ThreadPool threadPool,
80+
final ResourceWatcherService resourceWatcherService,
81+
final ScriptService scriptService,
82+
final NamedXContentRegistry xContentRegistry,
83+
final Environment environment,
84+
final NodeEnvironment nodeEnvironment,
85+
final NamedWriteableRegistry namedWriteableRegistry) {
86+
clusterService.getClusterSettings().addSettingsUpgrader(
87+
oldSetting,
88+
entry -> new AbstractMap.SimpleEntry<>("foo.new", entry.getValue()));
89+
return Collections.emptyList();
90+
}
91+
92+
@Override
93+
public List<Setting<?>> getSettings() {
94+
return Arrays.asList(oldSetting, newSetting);
95+
}
96+
97+
}
98+
99+
public void testUpgradePersistentSettingsOnUpdate() {
100+
runUpgradeSettingsOnUpdateTest((settings, builder) -> builder.setPersistentSettings(settings), MetaData::persistentSettings);
101+
}
102+
103+
public void testUpgradeTransientSettingsOnUpdate() {
104+
runUpgradeSettingsOnUpdateTest((settings, builder) -> builder.setTransientSettings(settings), MetaData::transientSettings);
105+
}
106+
107+
private void runUpgradeSettingsOnUpdateTest(
108+
final BiConsumer<Settings, ClusterUpdateSettingsRequestBuilder> consumer,
109+
final Function<MetaData, Settings> settingsFunction) {
110+
final String value = randomAlphaOfLength(8);
111+
final ClusterUpdateSettingsRequestBuilder builder =
112+
client()
113+
.admin()
114+
.cluster()
115+
.prepareUpdateSettings();
116+
consumer.accept(Settings.builder().put("foo.old", value).build(), builder);
117+
builder.get();
118+
119+
final ClusterStateResponse response = client()
120+
.admin()
121+
.cluster()
122+
.prepareState()
123+
.clear()
124+
.setMetaData(true)
125+
.get();
126+
127+
assertFalse(UpgradeSettingsPlugin.oldSetting.exists(settingsFunction.apply(response.getState().metaData())));
128+
assertTrue(UpgradeSettingsPlugin.newSetting.exists(settingsFunction.apply(response.getState().metaData())));
129+
assertThat(UpgradeSettingsPlugin.newSetting.get(settingsFunction.apply(response.getState().metaData())), equalTo(value));
130+
}
131+
132+
}

0 commit comments

Comments
 (0)