Skip to content

Commit 52b8251

Browse files
Support dependent validation for index settings (#70144) (#70275)
A setting validator can declare settings that the validation depends on, but when updating index settings, we eagerly validate the settings before submitting the cluster state update and here we do not know the existing settings. With this commit, we ensure that the pre-validation only validates the keys and not the values, leaving the value validation to after we have combined existing settings with the new settings on a per index basis.
1 parent b3fe609 commit 52b8251

File tree

4 files changed

+82
-25
lines changed

4 files changed

+82
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void updateSettings(final UpdateSettingsClusterStateUpdateRequest request
8585

8686
indexScopedSettings.validate(
8787
normalizedSettings.filter(s -> Regex.isSimpleMatchPattern(s) == false), // don't validate wildcards
88-
false, // don't validate dependencies here we check it below never allow to change the number of shards
88+
false, // don't validate values here we check it below never allow to change the number of shards
8989
true); // validate internal or private index settings
9090
for (String key : normalizedSettings.keySet()) {
9191
Setting setting = indexScopedSettings.get(key);

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -413,55 +413,55 @@ public synchronized <T> void addSettingsUpdateConsumer(Setting<T> setting, Consu
413413
* Validates that all settings are registered and valid.
414414
*
415415
* @param settings the settings to validate
416-
* @param validateDependencies true if dependent settings should be validated
416+
* @param validateValues true if values should be validated, otherwise only keys are validated
417417
* @see Setting#getSettingsDependencies(String)
418418
*/
419-
public final void validate(final Settings settings, final boolean validateDependencies) {
420-
validate(settings, validateDependencies, false, false);
419+
public final void validate(final Settings settings, final boolean validateValues) {
420+
validate(settings, validateValues, false, false);
421421
}
422422

423423
/**
424424
* Validates that all settings are registered and valid.
425425
*
426426
* @param settings the settings to validate
427-
* @param validateDependencies true if dependent settings should be validated
427+
* @param validateValues true if values should be validated, otherwise only keys are validated
428428
* @param validateInternalOrPrivateIndex true if internal index settings should be validated
429429
* @see Setting#getSettingsDependencies(String)
430430
*/
431-
public final void validate(final Settings settings, final boolean validateDependencies, final boolean validateInternalOrPrivateIndex) {
432-
validate(settings, validateDependencies, false, false, validateInternalOrPrivateIndex);
431+
public final void validate(final Settings settings, final boolean validateValues, final boolean validateInternalOrPrivateIndex) {
432+
validate(settings, validateValues, false, false, validateInternalOrPrivateIndex);
433433
}
434434

435435
/**
436436
* Validates that all settings are registered and valid.
437437
*
438438
* @param settings the settings
439-
* @param validateDependencies true if dependent settings should be validated
439+
* @param validateValues true if values should be validated, otherwise only keys are validated
440440
* @param ignorePrivateSettings true if private settings should be ignored during validation
441441
* @param ignoreArchivedSettings true if archived settings should be ignored during validation
442442
* @see Setting#getSettingsDependencies(String)
443443
*/
444444
public final void validate(
445445
final Settings settings,
446-
final boolean validateDependencies,
446+
final boolean validateValues,
447447
final boolean ignorePrivateSettings,
448448
final boolean ignoreArchivedSettings) {
449-
validate(settings, validateDependencies, ignorePrivateSettings, ignoreArchivedSettings, false);
449+
validate(settings, validateValues, ignorePrivateSettings, ignoreArchivedSettings, false);
450450
}
451451

452452
/**
453453
* Validates that all settings are registered and valid.
454454
*
455455
* @param settings the settings
456-
* @param validateDependencies true if dependent settings should be validated
456+
* @param validateValues true if values should be validated, otherwise only keys are validated
457457
* @param ignorePrivateSettings true if private settings should be ignored during validation
458458
* @param ignoreArchivedSettings true if archived settings should be ignored during validation
459459
* @param validateInternalOrPrivateIndex true if index internal settings should be validated
460460
* @see Setting#getSettingsDependencies(String)
461461
*/
462462
public final void validate(
463463
final Settings settings,
464-
final boolean validateDependencies,
464+
final boolean validateValues,
465465
final boolean ignorePrivateSettings,
466466
final boolean ignoreArchivedSettings,
467467
final boolean validateInternalOrPrivateIndex) {
@@ -475,7 +475,7 @@ public final void validate(
475475
continue;
476476
}
477477
try {
478-
validate(key, settings, validateDependencies, validateInternalOrPrivateIndex);
478+
validate(key, settings, validateValues, validateInternalOrPrivateIndex);
479479
} catch (final RuntimeException ex) {
480480
exceptions.add(ex);
481481
}
@@ -488,24 +488,24 @@ public final void validate(
488488
*
489489
* @param key the key of the setting to validate
490490
* @param settings the settings
491-
* @param validateDependencies true if dependent settings should be validated
491+
* @param validateValue true if value should be validated, otherwise only keys are validated
492492
* @throws IllegalArgumentException if the setting is invalid
493493
*/
494-
void validate(final String key, final Settings settings, final boolean validateDependencies) {
495-
validate(key, settings, validateDependencies, false);
494+
void validate(final String key, final Settings settings, final boolean validateValue) {
495+
validate(key, settings, validateValue, false);
496496
}
497497

498498
/**
499499
* Validates that the settings is valid.
500500
*
501501
* @param key the key of the setting to validate
502502
* @param settings the settings
503-
* @param validateDependencies true if dependent settings should be validated
503+
* @param validateValue true if value should be validated, otherwise only keys are validated
504504
* @param validateInternalOrPrivateIndex true if internal index settings should be validated
505505
* @throws IllegalArgumentException if the setting is invalid
506506
*/
507507
void validate(
508-
final String key, final Settings settings, final boolean validateDependencies, final boolean validateInternalOrPrivateIndex) {
508+
final String key, final Settings settings, final boolean validateValue, final boolean validateInternalOrPrivateIndex) {
509509
Setting setting = getRaw(key);
510510
if (setting == null) {
511511
LevenshteinDistance ld = new LevenshteinDistance();
@@ -536,7 +536,7 @@ void validate(
536536
if (setting.hasComplexMatcher()) {
537537
setting = setting.getConcreteSetting(key);
538538
}
539-
if (validateDependencies && settingsDependencies.isEmpty() == false) {
539+
if (validateValue && settingsDependencies.isEmpty() == false) {
540540
for (final Setting.SettingDependency settingDependency : settingsDependencies) {
541541
final Setting<?> dependency = settingDependency.getSetting();
542542
// validate the dependent setting is set
@@ -563,7 +563,9 @@ void validate(
563563
}
564564
}
565565
}
566-
setting.get(settings);
566+
if (validateValue) {
567+
setting.get(settings);
568+
}
567569
}
568570

569571
/**

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

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
import java.util.Collections;
2727
import java.util.HashMap;
2828
import java.util.HashSet;
29+
import java.util.Iterator;
2930
import java.util.LinkedHashSet;
3031
import java.util.List;
3132
import java.util.Map;
33+
import java.util.Objects;
3234
import java.util.Set;
3335
import java.util.concurrent.atomic.AtomicInteger;
3436
import java.util.concurrent.atomic.AtomicReference;
@@ -259,6 +261,59 @@ public void testDependentSettingsWithFallback() {
259261
service.validate(Settings.builder().put("fallback.test.name", "test").put("foo.test.bar", 7).build(), true);
260262
}
261263

264+
public void testValidateValue() {
265+
final boolean nodeSetting = randomBoolean();
266+
final String prefix = nodeSetting ? "" : "index.";
267+
final Property scopeProperty = nodeSetting ? Property.NodeScope : Property.IndexScope;
268+
final Setting.Validator<String> baseValidator = s -> {
269+
if (s.length() > 1) {
270+
throw new IllegalArgumentException("too long");
271+
}
272+
};
273+
final Setting<String> baseSetting = Setting.simpleString(prefix + "foo.base", baseValidator,
274+
Property.Dynamic, scopeProperty);
275+
final Setting.Validator<String> dependingValidator = new Setting.Validator<String>() {
276+
@Override
277+
public void validate(String value) {
278+
}
279+
280+
@Override
281+
public void validate(String value, Map<Setting<?>, Object> settings, boolean isPresent) {
282+
if (Objects.equals(value, settings.get(baseSetting)) == false) {
283+
throw new IllegalArgumentException("must have same value");
284+
}
285+
}
286+
287+
@Override
288+
public Iterator<Setting<?>> settings() {
289+
return org.elasticsearch.common.collect.List.<Setting<?>>of(baseSetting).iterator();
290+
}
291+
};
292+
final Setting<String> dependingSetting = Setting.simpleString(prefix + "foo.depending", dependingValidator, scopeProperty);
293+
294+
final AbstractScopedSettings service = nodeSetting ?
295+
new ClusterSettings(Settings.EMPTY, org.elasticsearch.common.collect.Set.of(baseSetting, dependingSetting)) :
296+
new IndexScopedSettings(Settings.EMPTY, org.elasticsearch.common.collect.Set.of(baseSetting, dependingSetting));
297+
298+
service.validate(Settings.builder().put(baseSetting.getKey(), "1").put(dependingSetting.getKey(), 1).build(), true);
299+
service.validate(Settings.builder().put(dependingSetting.getKey(), "1").build(), false);
300+
final IllegalArgumentException e = expectThrows(
301+
IllegalArgumentException.class,
302+
() -> service.validate(Settings.builder().put(dependingSetting.getKey(), "1").build(), true));
303+
assertThat(e.getMessage(), equalTo("must have same value"));
304+
305+
final IllegalArgumentException e2 = expectThrows(
306+
IllegalArgumentException.class,
307+
() -> service.validate(Settings.builder().put(baseSetting.getKey(), "2").put(dependingSetting.getKey(), "1").build(), true));
308+
assertThat(e2.getMessage(), equalTo("must have same value"));
309+
310+
service.validate(Settings.builder().put(baseSetting.getKey(), "22").build(), false);
311+
final IllegalArgumentException e3 = expectThrows(
312+
IllegalArgumentException.class,
313+
() -> service.validate(Settings.builder().put(baseSetting.getKey(), "22").build(), true));
314+
assertThat(e3.getMessage(), equalTo("too long"));
315+
}
316+
262317
public void testTupleAffixUpdateConsumer() {
263318
String prefix = randomAlphaOfLength(3) + "foo.";
264319
String intSuffix = randomAlphaOfLength(3);
@@ -850,16 +905,16 @@ public void testValidate() {
850905
assertEquals("unknown setting [i.am.not.a.setting]" + unknownMsgSuffix, e.getMessage());
851906

852907
e = expectThrows(IllegalArgumentException.class, () ->
853-
settings.validate(Settings.builder().put("index.store.type", "boom").put("index.number_of_replicas", true).build(), false));
908+
settings.validate(Settings.builder().put("index.store.type", "boom").put("index.number_of_replicas", true).build(), true));
854909
assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
855910

856911
e = expectThrows(IllegalArgumentException.class, () ->
857-
settings.validate("index.number_of_replicas", Settings.builder().put("index.number_of_replicas", "true").build(), false));
912+
settings.validate("index.number_of_replicas", Settings.builder().put("index.number_of_replicas", "true").build(), true));
858913
assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
859914

860915
e = expectThrows(IllegalArgumentException.class, () ->
861916
settings.validate("index.similarity.classic.type", Settings.builder().put("index.similarity.classic.type", "mine").build(),
862-
false));
917+
true));
863918
assertEquals("illegal value for [index.similarity.classic] cannot redefine built-in similarity", e.getMessage());
864919
}
865920

@@ -967,7 +1022,7 @@ public void testLoggingUpdates() {
9671022
IllegalArgumentException ex =
9681023
expectThrows(
9691024
IllegalArgumentException.class,
970-
() -> settings.validate(Settings.builder().put("logger._root", "boom").build(), false));
1025+
() -> settings.validate(Settings.builder().put("logger._root", "boom").build(), true));
9711026
assertEquals("Unknown level constant [BOOM].", ex.getMessage());
9721027
assertEquals(level, LogManager.getRootLogger().getLevel());
9731028
settings.applySettings(Settings.builder().put("logger._root", "TRACE").build());

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ public void testPKIRealmSettingsPassValidation() throws Exception {
464464
List<Setting<?>> settingList = new ArrayList<>();
465465
settingList.addAll(InternalRealmsSettings.getSettings());
466466
ClusterSettings clusterSettings = new ClusterSettings(settings, new HashSet<>(settingList));
467-
clusterSettings.validate(settings, false);
467+
clusterSettings.validate(settings, true);
468468

469469
assertSettingDeprecationsAndWarnings(new Setting[]{
470470
PkiRealmSettings.LEGACY_TRUST_STORE_PASSWORD.getConcreteSettingForNamespace("pki1")

0 commit comments

Comments
 (0)