-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Archive unknown or invalid settings on updates #28888
Changes from 2 commits
ebebe19
293895c
b3781b4
d0cf14a
bf7f356
9e55fac
fe756a3
98a1b6b
2a1dc17
e5ceaac
d2bc7fd
3298664
e428728
e85ad01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,13 +19,19 @@ | |
|
||
package org.elasticsearch.action.admin.cluster.settings; | ||
|
||
import org.apache.logging.log4j.Logger; | ||
import org.apache.logging.log4j.message.ParameterizedMessage; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.block.ClusterBlocks; | ||
import org.elasticsearch.cluster.metadata.MetaData; | ||
import org.elasticsearch.common.collect.Tuple; | ||
import org.elasticsearch.common.settings.ClusterSettings; | ||
import org.elasticsearch.common.settings.Settings; | ||
|
||
import java.util.Map; | ||
|
||
import static org.elasticsearch.cluster.ClusterState.builder; | ||
import static org.elasticsearch.common.settings.AbstractScopedSettings.ARCHIVED_SETTINGS_PREFIX; | ||
|
||
/** | ||
* Updates transient and persistent cluster state settings if there are any changes | ||
|
@@ -48,15 +54,35 @@ synchronized Settings getPersistentUpdate() { | |
return persistentUpdates.build(); | ||
} | ||
|
||
synchronized ClusterState updateSettings(final ClusterState currentState, Settings transientToApply, Settings persistentToApply) { | ||
synchronized ClusterState updateSettings( | ||
final ClusterState currentState, | ||
final Settings transientToApply, | ||
final Settings persistentToApply, | ||
final Logger logger) { | ||
boolean changed = false; | ||
Settings.Builder transientSettings = Settings.builder(); | ||
transientSettings.put(currentState.metaData().transientSettings()); | ||
changed |= clusterSettings.updateDynamicSettings(transientToApply, transientSettings, transientUpdates, "transient"); | ||
|
||
/* | ||
* Our cluster state could have unknown or invalid settings that are known and valid in a previous version of Elasticsearch. We can | ||
* end up in this situation during a rolling upgrade where the previous version will infect the current version of Elasticsearch | ||
* with settings that the current version either no longer knows about or now considers to have invalid values. When the current | ||
* version of Elasticsearch becomes infected with a cluster state containing such settings, we need to skip validating such settings | ||
* and instead archive them. Consequently, for the current transient and persistent settings in the cluster state we do the | ||
* following: | ||
* - create a settings instance with that has archived all unknown or invalid settings | ||
* - split this settings instance into two with the known and valid settings in one, and the unknown or invalid in another | ||
* - validate the incoming settings update combined with the existing known and valid settings | ||
* - merge in the archived unknown or invalid settings | ||
*/ | ||
final Tuple<Settings, Settings> partitionedTransientSettings = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we have 2 methods There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a change. I kept the tuple but clarified its usage. I hope this helps. |
||
partitionKnownAndValidSettings(currentState.metaData().transientSettings(), "transient", logger); | ||
final Settings.Builder transientSettings = Settings.builder(); | ||
transientSettings.put(partitionedTransientSettings.v1()); | ||
changed |= clusterSettings.updateDynamicSettings(transientToApply, transientSettings, transientUpdates, "transient"); | ||
|
||
Settings.Builder persistentSettings = Settings.builder(); | ||
persistentSettings.put(currentState.metaData().persistentSettings()); | ||
final Tuple<Settings, Settings> partitionedPersistentSettings = | ||
partitionKnownAndValidSettings(currentState.metaData().persistentSettings(), "persistent", logger); | ||
final Settings.Builder persistentSettings = Settings.builder(); | ||
persistentSettings.put(partitionedPersistentSettings.v1()); | ||
changed |= clusterSettings.updateDynamicSettings(persistentToApply, persistentSettings, persistentUpdates, "persistent"); | ||
|
||
final ClusterState clusterState; | ||
|
@@ -69,8 +95,8 @@ synchronized ClusterState updateSettings(final ClusterState currentState, Settin | |
clusterSettings.validate(persistentFinalSettings, true); | ||
|
||
MetaData.Builder metaData = MetaData.builder(currentState.metaData()) | ||
.persistentSettings(persistentFinalSettings) | ||
.transientSettings(transientFinalSettings); | ||
.persistentSettings(Settings.builder().put(persistentFinalSettings).put(partitionedPersistentSettings.v2()).build()) | ||
.transientSettings(Settings.builder().put(transientFinalSettings).put(partitionedTransientSettings.v2()).build()); | ||
|
||
ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks()); | ||
boolean updatedReadOnly = MetaData.SETTING_READ_ONLY_SETTING.get(metaData.persistentSettings()) | ||
|
@@ -102,5 +128,44 @@ synchronized ClusterState updateSettings(final ClusterState currentState, Settin | |
return clusterState; | ||
} | ||
|
||
/** | ||
* Partitions the settings into those that are known and valid versus those that are unknown or invalid. The resulting tuple contains | ||
* the known and valid settings in the first component and the unknown or invalid settings in the second component. | ||
* | ||
* @param settings the settings to partition | ||
* @param settingsType a string to identify the settings (for logging) | ||
* @param logger a logger to sending warnings to | ||
* @return the partitioned settings | ||
*/ | ||
private Tuple<Settings, Settings> partitionKnownAndValidSettings( | ||
final Settings settings, | ||
final String settingsType, | ||
final Logger logger) { | ||
final Settings settingsWithUnknownOrInvalidArchived = clusterSettings.archiveUnknownOrInvalidSettings( | ||
settings, | ||
e -> logUnknownSetting(settingsType, e, logger), | ||
(e, ex) -> logInvalidSetting(settingsType, e, ex, logger)); | ||
return Tuple.tuple( | ||
settingsWithUnknownOrInvalidArchived.filter(k -> k.startsWith(ARCHIVED_SETTINGS_PREFIX) == false), | ||
settingsWithUnknownOrInvalidArchived.filter(k -> k.startsWith(ARCHIVED_SETTINGS_PREFIX))); | ||
} | ||
|
||
private void logUnknownSetting(final String settingType, final Map.Entry<String, String> e, final Logger logger) { | ||
logger.warn("ignoring existing unknown {} setting: [{}] with value [{}]; archiving", settingType, e.getKey(), e.getValue()); | ||
} | ||
|
||
private void logInvalidSetting( | ||
final String settingType, | ||
final Map.Entry<String, String> e, | ||
final IllegalArgumentException ex, | ||
final Logger logger) { | ||
logger.warn( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. man newlines?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about painting it yellow. 😛 |
||
(org.apache.logging.log4j.util.Supplier<?>) | ||
() -> new ParameterizedMessage("ignoring existing invalid {} setting: [{}] with value [{}]; archiving", | ||
settingType, | ||
e.getKey(), | ||
e.getValue()), | ||
ex); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,9 +30,14 @@ | |
|
||
import java.util.Set; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import static org.elasticsearch.common.settings.AbstractScopedSettings.ARCHIVED_SETTINGS_PREFIX; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.hamcrest.Matchers.hasItem; | ||
|
||
public class SettingsUpdaterTests extends ESTestCase { | ||
|
||
|
||
|
@@ -51,22 +56,22 @@ public void testUpdateSetting() { | |
.put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 4.5).build()); | ||
ClusterState build = builder.metaData(metaData).build(); | ||
ClusterState clusterState = updater.updateSettings(build, Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 0.5).build(), | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 0.4).build()); | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 0.4).build(), logger); | ||
assertNotSame(clusterState, build); | ||
assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 0.4, 0.1); | ||
assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 2.5, 0.1); | ||
assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().transientSettings()), 0.5, 0.1); | ||
assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().transientSettings()), 4.5, 0.1); | ||
|
||
clusterState = updater.updateSettings(clusterState, Settings.builder().putNull("cluster.routing.*").build(), | ||
Settings.EMPTY); | ||
Settings.EMPTY, logger); | ||
assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 0.4, 0.1); | ||
assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 2.5, 0.1); | ||
assertFalse(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings())); | ||
assertFalse(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings())); | ||
|
||
clusterState = updater.updateSettings(clusterState, | ||
Settings.EMPTY, Settings.builder().putNull("cluster.routing.*").put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 10.0).build()); | ||
Settings.EMPTY, Settings.builder().putNull("cluster.routing.*").put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 10.0).build(), logger); | ||
|
||
assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 10.0, 0.1); | ||
assertFalse(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().persistentSettings())); | ||
|
@@ -93,7 +98,7 @@ public void testAllOrNothing() { | |
|
||
try { | ||
updater.updateSettings(build, Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), "not a float").build(), | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), "not a float").put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build()); | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), "not a float").put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build(), logger); | ||
fail("all or nothing"); | ||
} catch (IllegalArgumentException ex) { | ||
logger.info("", ex); | ||
|
@@ -119,21 +124,21 @@ public void testClusterBlock() { | |
ClusterState build = builder.metaData(metaData).build(); | ||
|
||
ClusterState clusterState = updater.updateSettings(build, Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), true).build(), | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.6).put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build()); | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.6).put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build(), logger); | ||
assertEquals(clusterState.blocks().global().size(), 1); | ||
assertEquals(clusterState.blocks().global().iterator().next(), MetaData.CLUSTER_READ_ONLY_BLOCK); | ||
|
||
clusterState = updater.updateSettings(build, Settings.EMPTY, | ||
Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), false).build()); | ||
Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), false).build(), logger); | ||
assertEquals(clusterState.blocks().global().size(), 0); | ||
|
||
|
||
clusterState = updater.updateSettings(build, Settings.builder().put(MetaData.SETTING_READ_ONLY_ALLOW_DELETE_SETTING.getKey(), true).build(), | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.6).put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build()); | ||
Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.6).put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build(), logger); | ||
assertEquals(clusterState.blocks().global().size(), 1); | ||
assertEquals(clusterState.blocks().global().iterator().next(), MetaData.CLUSTER_READ_ONLY_ALLOW_DELETE_BLOCK); | ||
clusterState = updater.updateSettings(build, Settings.EMPTY, | ||
Settings.builder().put(MetaData.SETTING_READ_ONLY_ALLOW_DELETE_SETTING.getKey(), false).build()); | ||
Settings.builder().put(MetaData.SETTING_READ_ONLY_ALLOW_DELETE_SETTING.getKey(), false).build(), logger); | ||
assertEquals(clusterState.blocks().global().size(), 0); | ||
|
||
} | ||
|
@@ -151,16 +156,68 @@ public void testDeprecationLogging() { | |
ClusterState.builder(new ClusterName("foo")).metaData(MetaData.builder().persistentSettings(settings).build()).build(); | ||
|
||
final Settings toApplyDebug = Settings.builder().put("logger.org.elasticsearch", "debug").build(); | ||
final ClusterState afterDebug = settingsUpdater.updateSettings(clusterState, toApplyDebug, Settings.EMPTY); | ||
final ClusterState afterDebug = settingsUpdater.updateSettings(clusterState, toApplyDebug, Settings.EMPTY, logger); | ||
assertSettingDeprecationsAndWarnings(new Setting<?>[] { deprecatedSetting }); | ||
|
||
final Settings toApplyUnset = Settings.builder().putNull("logger.org.elasticsearch").build(); | ||
final ClusterState afterUnset = settingsUpdater.updateSettings(afterDebug, toApplyUnset, Settings.EMPTY); | ||
final ClusterState afterUnset = settingsUpdater.updateSettings(afterDebug, toApplyUnset, Settings.EMPTY, logger); | ||
assertSettingDeprecationsAndWarnings(new Setting<?>[] { deprecatedSetting }); | ||
|
||
// we also check that if no settings are changed, deprecation logging still occurs | ||
settingsUpdater.updateSettings(afterUnset, toApplyUnset, Settings.EMPTY); | ||
settingsUpdater.updateSettings(afterUnset, toApplyUnset, Settings.EMPTY, logger); | ||
assertSettingDeprecationsAndWarnings(new Setting<?>[] { deprecatedSetting }); | ||
} | ||
|
||
public void testUpdateWithUnknownAndSettings() { | ||
runUpdateWithUnknownAndInvalidSettingTest(builder -> builder::persistentSettings, MetaData::persistentSettings); | ||
runUpdateWithUnknownAndInvalidSettingTest(builder -> builder::transientSettings, MetaData::transientSettings); | ||
} | ||
|
||
private void runUpdateWithUnknownAndInvalidSettingTest( | ||
final Function<MetaData.Builder, Function<Settings, MetaData.Builder>> function, | ||
final Function<MetaData, Settings> settingsToTest) { | ||
final Setting<String> dynamicSetting = Setting.simpleString("dynamic.setting", Property.Dynamic, Property.NodeScope); | ||
final Setting<String> invalidSetting = Setting.simpleString( | ||
"invalid.setting", | ||
(setting, settings) -> { | ||
throw new IllegalArgumentException("invalid"); | ||
}, | ||
Property.NodeScope); | ||
final Settings settings = Settings.builder().put("invalid.setting", "value").put("unknown.setting", "value").build(); | ||
|
||
final Set<Setting<?>> knownSettings = | ||
Stream.concat( | ||
ClusterSettings.BUILT_IN_CLUSTER_SETTINGS.stream(), | ||
Stream.of(dynamicSetting, invalidSetting)) | ||
.collect(Collectors.toSet()); | ||
final ClusterSettings clusterSettings = new ClusterSettings(settings, knownSettings); | ||
clusterSettings.addSettingsUpdateConsumer(dynamicSetting, s -> {}); | ||
final SettingsUpdater settingsUpdater = new SettingsUpdater(clusterSettings); | ||
final ClusterState clusterState = | ||
ClusterState | ||
.builder(new ClusterName("cluster")) | ||
.metaData(function.apply(MetaData.builder()).apply(settings).build()) | ||
.build(); | ||
final Settings toApply = Settings.builder().put("dynamic.setting", "value").build(); | ||
final boolean applyTransient = randomBoolean(); | ||
final ClusterState clusterStateAfterUpdate; | ||
if (applyTransient) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took me quite a while to understand what's going on - on the one hand the persistent vs transient question is handled by parameters to this method (please rename the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed d2bc7fd. |
||
clusterStateAfterUpdate = settingsUpdater.updateSettings(clusterState, toApply, Settings.EMPTY, logger); | ||
} else { | ||
clusterStateAfterUpdate = settingsUpdater.updateSettings(clusterState, Settings.EMPTY, toApply, logger); | ||
} | ||
|
||
assertThat( | ||
settingsToTest.apply(clusterStateAfterUpdate.metaData()).keySet(), hasItem(ARCHIVED_SETTINGS_PREFIX + "invalid.setting")); | ||
assertThat( | ||
settingsToTest.apply(clusterStateAfterUpdate.metaData()).keySet(), hasItem(ARCHIVED_SETTINGS_PREFIX + "unknown.setting")); | ||
if (applyTransient) { | ||
assertThat(clusterStateAfterUpdate.metaData().transientSettings().keySet(), hasItem("dynamic.setting")); | ||
assertThat(clusterStateAfterUpdate.metaData().transientSettings().get("dynamic.setting"), equalTo("value")); | ||
} else { | ||
assertThat(clusterStateAfterUpdate.metaData().persistentSettings().keySet(), hasItem("dynamic.setting")); | ||
assertThat(clusterStateAfterUpdate.metaData().persistentSettings().get("dynamic.setting"), equalTo("value")); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get your newlines under control my friend