diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdater.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdater.java
index 5d1990a48d0..dc13913652a 100644
--- a/core/src/main/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdater.java
+++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdater.java
@@ -54,15 +54,23 @@ final class SettingsUpdater {
transientSettings.put(currentState.metaData().transientSettings());
changed |= clusterSettings.updateDynamicSettings(transientToApply, transientSettings, transientUpdates, "transient");
+
Settings.Builder persistentSettings = Settings.builder();
persistentSettings.put(currentState.metaData().persistentSettings());
changed |= clusterSettings.updateDynamicSettings(persistentToApply, persistentSettings, persistentUpdates, "persistent");
final ClusterState clusterState;
if (changed) {
+ Settings transientFinalSettings = transientSettings.build();
+ Settings persistentFinalSettings = persistentSettings.build();
+ // both transient and persistent settings must be consistent by itself we can't allow dependencies to be
+ // in either of them otherwise a full cluster restart will break the settings validation
+ clusterSettings.validate(transientFinalSettings, true);
+ clusterSettings.validate(persistentFinalSettings, true);
+
MetaData.Builder metaData = MetaData.builder(currentState.metaData())
- .persistentSettings(persistentSettings.build())
- .transientSettings(transientSettings.build());
+ .persistentSettings(persistentFinalSettings)
+ .transientSettings(transientFinalSettings);
ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
boolean updatedReadOnly = MetaData.SETTING_READ_ONLY_SETTING.get(metaData.persistentSettings())
diff --git a/core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java b/core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java
index 7d9897b112e..1624c7950e7 100644
--- a/core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java
+++ b/core/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java
@@ -77,7 +77,7 @@ public class TransportPutIndexTemplateAction extends TransportMasterNodeAction
listener) {
Settings.Builder updatedSettingsBuilder = Settings.builder();
- updatedSettingsBuilder.put(request.settings()).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX);
- indexScopedSettings.validate(updatedSettingsBuilder);
- request.settings(updatedSettingsBuilder.build());
-
+ Settings build = updatedSettingsBuilder.put(request.settings()).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX).build();
+ indexScopedSettings.validate(build, true); // we do validate here - index setting must be consistent
+ request.settings(build);
clusterService.submitStateUpdateTask("create-index [" + request.index() + "], cause [" + request.cause() + "]",
new IndexCreationTask(logger, allocationService, request, listener, indicesService, aliasValidator, xContentRegistry, settings,
this::validate));
@@ -420,7 +419,6 @@ public class MetaDataCreateIndexService extends AbstractComponent {
tmpImdBuilder.primaryTerm(shardId, primaryTerm);
}
}
-
// Set up everything, now locally create the index to see that things are ok, and apply
final IndexMetaData tmpImd = tmpImdBuilder.build();
ActiveShardCount waitForActiveShards = request.waitForActiveShards();
diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java
index c96895b94e7..883d7f2fc47 100644
--- a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java
+++ b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexTemplateService.java
@@ -276,7 +276,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
}
try {
- indexScopedSettings.validate(request.settings);
+ indexScopedSettings.validate(request.settings, true); // templates must be consistent with regards to dependencies
} catch (IllegalArgumentException iae) {
validationErrors.add(iae.getMessage());
for (Throwable t : iae.getSuppressed()) {
diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java
index abc0a4e8ea2..2c0bc929294 100644
--- a/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java
+++ b/core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java
@@ -54,6 +54,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
@@ -163,7 +164,7 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements
Settings.Builder settingsForOpenIndices = Settings.builder();
final Set skippedSettings = new HashSet<>();
- indexScopedSettings.validate(normalizedSettings);
+ indexScopedSettings.validate(normalizedSettings, false); // don't validate dependencies here we check it below
// never allow to change the number of shards
for (String key : normalizedSettings.keySet()) {
Setting setting = indexScopedSettings.get(key);
@@ -240,7 +241,9 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements
if (preserveExisting) {
indexSettings.put(indexMetaData.getSettings());
}
- metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(indexSettings));
+ Settings finalSettings = indexSettings.build();
+ indexScopedSettings.validate(finalSettings.filter(k -> indexScopedSettings.isPrivateSetting(k) == false), true);
+ metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(finalSettings));
}
}
}
@@ -254,7 +257,9 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements
if (preserveExisting) {
indexSettings.put(indexMetaData.getSettings());
}
- metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(indexSettings));
+ Settings finalSettings = indexSettings.build();
+ indexScopedSettings.validate(finalSettings.filter(k -> indexScopedSettings.isPrivateSetting(k) == false), true);
+ metaDataBuilder.put(IndexMetaData.builder(indexMetaData).settings(finalSettings));
}
}
}
diff --git a/core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java b/core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java
index 61f32c67c20..38eaef1d14d 100644
--- a/core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java
+++ b/core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java
@@ -264,20 +264,16 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
}
/**
- * Validates that all settings in the builder are registered and valid
+ * Validates that all given settings are registered and valid
+ * @param settings the settings to validate
+ * @param validateDependencies if true
settings dependencies are validated as well.
+ * @see Setting#getSettingsDependencies(String)
*/
- public final void validate(Settings.Builder settingsBuilder) {
- validate(settingsBuilder.build());
- }
-
- /**
- * * Validates that all given settings are registered and valid
- */
- public final void validate(Settings settings) {
+ public final void validate(Settings settings, boolean validateDependencies) {
List exceptions = new ArrayList<>();
for (String key : settings.keySet()) { // settings iterate in deterministic fashion
try {
- validate(key, settings);
+ validate(key, settings, validateDependencies);
} catch (RuntimeException ex) {
exceptions.add(ex);
}
@@ -285,12 +281,11 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
ExceptionsHelper.rethrowAndSuppress(exceptions);
}
-
/**
* Validates that the setting is valid
*/
- public final void validate(String key, Settings settings) {
- Setting setting = get(key);
+ void validate(String key, Settings settings, boolean validateDependencies) {
+ Setting setting = getRaw(key);
if (setting == null) {
LevensteinDistance ld = new LevensteinDistance();
List> scoredKeys = new ArrayList<>();
@@ -315,6 +310,20 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
"settings";
}
throw new IllegalArgumentException(msg);
+ } else {
+ Set settingsDependencies = setting.getSettingsDependencies(key);
+ if (setting.hasComplexMatcher()) {
+ setting = setting.getConcreteSetting(key);
+ }
+ if (validateDependencies && settingsDependencies.isEmpty() == false) {
+ Set settingKeys = settings.keySet();
+ for (String requiredSetting : settingsDependencies) {
+ if (settingKeys.contains(requiredSetting) == false) {
+ throw new IllegalArgumentException("Missing required setting ["
+ + requiredSetting + "] for setting [" + setting.getKey() + "]");
+ }
+ }
+ }
}
setting.get(settings);
}
@@ -375,7 +384,18 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
/**
* Returns the {@link Setting} for the given key or null
if the setting can not be found.
*/
- public Setting> get(String key) {
+ public final Setting> get(String key) {
+ Setting> raw = getRaw(key);
+ if (raw == null) {
+ return null;
+ } if (raw.hasComplexMatcher()) {
+ return raw.getConcreteSetting(key);
+ } else {
+ return raw;
+ }
+ }
+
+ private Setting> getRaw(String key) {
Setting> setting = keySettings.get(key);
if (setting != null) {
return setting;
@@ -383,7 +403,8 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
for (Map.Entry> entry : complexMatchers.entrySet()) {
if (entry.getValue().match(key)) {
assert assertMatcher(key, 1);
- return entry.getValue().getConcreteSetting(key);
+ assert entry.getValue().hasComplexMatcher();
+ return entry.getValue();
}
}
return null;
@@ -513,7 +534,7 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
} else if (get(key) == null) {
throw new IllegalArgumentException(type + " setting [" + key + "], not recognized");
} else if (isNull == false && canUpdate.test(key)) {
- validate(key, toApply);
+ validate(key, toApply, false); // we might not have a full picture here do to a dependency validation
settingsBuilder.copy(key, toApply);
updates.copy(key, toApply);
changed = true;
@@ -654,7 +675,7 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
* representation. Otherwise false
*/
// TODO this should be replaced by Setting.Property.HIDDEN or something like this.
- protected boolean isPrivateSetting(String key) {
+ public boolean isPrivateSetting(String key) {
return false;
}
}
diff --git a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java
index 962e61b5c3c..d40488eaa34 100644
--- a/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java
+++ b/core/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java
@@ -191,7 +191,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
}
@Override
- protected boolean isPrivateSetting(String key) {
+ public boolean isPrivateSetting(String key) {
switch (key) {
case IndexMetaData.SETTING_CREATION_DATE:
case IndexMetaData.SETTING_INDEX_UUID:
diff --git a/core/src/main/java/org/elasticsearch/common/settings/Setting.java b/core/src/main/java/org/elasticsearch/common/settings/Setting.java
index 9b99e67c8c4..abc589aedaf 100644
--- a/core/src/main/java/org/elasticsearch/common/settings/Setting.java
+++ b/core/src/main/java/org/elasticsearch/common/settings/Setting.java
@@ -42,6 +42,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
@@ -126,7 +127,7 @@ public class Setting implements ToXContentObject {
private static final EnumSet EMPTY_PROPERTIES = EnumSet.noneOf(Property.class);
private Setting(Key key, @Nullable Setting fallbackSetting, Function defaultValue, Function parser,
- Validator validator, Property... properties) {
+ Validator validator, Property... properties) {
assert this instanceof SecureSetting || this.isGroupSetting() || parser.apply(defaultValue.apply(Settings.EMPTY)) != null
: "parser returned null";
this.key = key;
@@ -457,6 +458,14 @@ public class Setting implements ToXContentObject {
return this;
}
+ /**
+ * Returns a set of settings that are required at validation time. Unless all of the dependencies are present in the settings
+ * object validation of setting must fail.
+ */
+ public Set getSettingsDependencies(String key) {
+ return Collections.emptySet();
+ }
+
/**
* Build a new updater with a noop validator.
*/
@@ -519,11 +528,13 @@ public class Setting implements ToXContentObject {
public static class AffixSetting extends Setting {
private final AffixKey key;
private final Function> delegateFactory;
+ private final Set dependencies;
- public AffixSetting(AffixKey key, Setting delegate, Function> delegateFactory) {
+ public AffixSetting(AffixKey key, Setting delegate, Function> delegateFactory, AffixSetting... dependencies) {
super(key, delegate.defaultValue, delegate.parser, delegate.properties.toArray(new Property[0]));
this.key = key;
this.delegateFactory = delegateFactory;
+ this.dependencies = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(dependencies)));
}
boolean isGroupSetting() {
@@ -534,6 +545,15 @@ public class Setting implements ToXContentObject {
return settings.keySet().stream().filter((key) -> match(key)).map(settingKey -> key.getConcreteString(settingKey));
}
+ public Set getSettingsDependencies(String settingsKey) {
+ if (dependencies.isEmpty()) {
+ return Collections.emptySet();
+ } else {
+ String namespace = key.getNamespace(settingsKey);
+ return dependencies.stream().map(s -> s.key.toConcreteKey(namespace).key).collect(Collectors.toSet());
+ }
+ }
+
AbstractScopedSettings.SettingUpdater