Skip to content

Commit 9a404f3

Browse files
authored
Include fallback settings when checking dependencies (#33522)
Today when checking settings dependencies, we do not check if fallback settings are present. This means, for example, that if cluster.remote.*.seeds falls back to search.remote.*.seeds, and cluster.remote.*.skip_unavailable and search.remote.*.skip_unavailable depend on cluster.remote.*.seeds, and we have set search.remote.*.seeds and search.remote.*.skip_unavailable, then validation will fail because it is expected that cluster.ermote.*.seeds is set here. This commit addresses this by also checking fallback settings when validating dependencies. To do this, we adjust the settings exist method to also check for fallback settings, a case that it was not handling previously.
1 parent 6b780e9 commit 9a404f3

File tree

8 files changed

+171
-69
lines changed

8 files changed

+171
-69
lines changed

qa/ccs-unavailable-clusters/src/test/java/org/elasticsearch/search/CrossClusterSearchUnavailableClusterIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public void testSkipUnavailableDependsOnSeeds() throws IOException {
235235
() -> client().performRequest(request));
236236
assertEquals(400, responseException.getResponse().getStatusLine().getStatusCode());
237237
assertThat(responseException.getMessage(),
238-
containsString("Missing required setting [cluster.remote.remote1.seeds] " +
238+
containsString("missing required setting [cluster.remote.remote1.seeds] " +
239239
"for setting [cluster.remote.remote1.skip_unavailable]"));
240240
}
241241

@@ -251,7 +251,7 @@ public void testSkipUnavailableDependsOnSeeds() throws IOException {
251251
ResponseException responseException = expectThrows(ResponseException.class,
252252
() -> client().performRequest(request));
253253
assertEquals(400, responseException.getResponse().getStatusLine().getStatusCode());
254-
assertThat(responseException.getMessage(), containsString("Missing required setting [cluster.remote.remote1.seeds] " +
254+
assertThat(responseException.getMessage(), containsString("missing required setting [cluster.remote.remote1.seeds] " +
255255
"for setting [cluster.remote.remote1.skip_unavailable]"));
256256
}
257257

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.HashMap;
3333
import java.util.HashSet;
3434
import java.util.List;
35+
import java.util.Locale;
3536
import java.util.Map;
3637
import java.util.Set;
3738
import java.util.concurrent.CopyOnWriteArrayList;
@@ -461,16 +462,19 @@ void validate(
461462
}
462463
throw new IllegalArgumentException(msg);
463464
} else {
464-
Set<String> settingsDependencies = setting.getSettingsDependencies(key);
465+
Set<Setting<?>> settingsDependencies = setting.getSettingsDependencies(key);
465466
if (setting.hasComplexMatcher()) {
466467
setting = setting.getConcreteSetting(key);
467468
}
468469
if (validateDependencies && settingsDependencies.isEmpty() == false) {
469-
Set<String> settingKeys = settings.keySet();
470-
for (String requiredSetting : settingsDependencies) {
471-
if (settingKeys.contains(requiredSetting) == false) {
472-
throw new IllegalArgumentException("Missing required setting ["
473-
+ requiredSetting + "] for setting [" + setting.getKey() + "]");
470+
for (final Setting<?> settingDependency : settingsDependencies) {
471+
if (settingDependency.existsOrFallbackExists(settings) == false) {
472+
final String message = String.format(
473+
Locale.ROOT,
474+
"missing required setting [%s] for setting [%s]",
475+
settingDependency.getKey(),
476+
setting.getKey());
477+
throw new IllegalArgumentException(message);
474478
}
475479
}
476480
}

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

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -366,12 +366,25 @@ public T getDefault(Settings settings) {
366366
}
367367

368368
/**
369-
* Returns <code>true</code> iff this setting is present in the given settings object. Otherwise <code>false</code>
369+
* Returns true if and only if this setting is present in the given settings instance. Note that fallback settings are excluded.
370+
*
371+
* @param settings the settings
372+
* @return true if the setting is present in the given settings instance, otherwise false
370373
*/
371-
public boolean exists(Settings settings) {
374+
public boolean exists(final Settings settings) {
372375
return settings.keySet().contains(getKey());
373376
}
374377

378+
/**
379+
* Returns true if and only if this setting including fallback settings is present in the given settings instance.
380+
*
381+
* @param settings the settings
382+
* @return true if the setting including fallback settings is present in the given settings instance, otherwise false
383+
*/
384+
public boolean existsOrFallbackExists(final Settings settings) {
385+
return settings.keySet().contains(getKey()) || (fallbackSetting != null && fallbackSetting.existsOrFallbackExists(settings));
386+
}
387+
375388
/**
376389
* Returns the settings value. If the setting is not present in the given settings object the default value is returned
377390
* instead.
@@ -511,7 +524,7 @@ public Setting<T> getConcreteSetting(String key) {
511524
* Returns a set of settings that are required at validation time. Unless all of the dependencies are present in the settings
512525
* object validation of setting must fail.
513526
*/
514-
public Set<String> getSettingsDependencies(String key) {
527+
public Set<Setting<?>> getSettingsDependencies(String key) {
515528
return Collections.emptySet();
516529
}
517530

@@ -634,12 +647,12 @@ private Stream<String> matchStream(Settings settings) {
634647
return settings.keySet().stream().filter(this::match).map(key::getConcreteString);
635648
}
636649

637-
public Set<String> getSettingsDependencies(String settingsKey) {
650+
public Set<Setting<?>> getSettingsDependencies(String settingsKey) {
638651
if (dependencies.isEmpty()) {
639652
return Collections.emptySet();
640653
} else {
641654
String namespace = key.getNamespace(settingsKey);
642-
return dependencies.stream().map(s -> s.key.toConcreteKey(namespace).key).collect(Collectors.toSet());
655+
return dependencies.stream().map(s -> (Setting<?>)s.getConcreteSettingForNamespace(namespace)).collect(Collectors.toSet());
643656
}
644657
}
645658

@@ -914,40 +927,6 @@ public String toString() {
914927
}
915928
}
916929

917-
private static class ListSetting<T> extends Setting<List<T>> {
918-
private final Function<Settings, List<String>> defaultStringValue;
919-
920-
private ListSetting(String key, Function<Settings, List<String>> defaultStringValue, Function<String, List<T>> parser,
921-
Property... properties) {
922-
super(new ListKey(key), (s) -> Setting.arrayToParsableString(defaultStringValue.apply(s)), parser,
923-
properties);
924-
this.defaultStringValue = defaultStringValue;
925-
}
926-
927-
@Override
928-
String innerGetRaw(final Settings settings) {
929-
List<String> array = settings.getAsList(getKey(), null);
930-
return array == null ? defaultValue.apply(settings) : arrayToParsableString(array);
931-
}
932-
933-
@Override
934-
boolean hasComplexMatcher() {
935-
return true;
936-
}
937-
938-
@Override
939-
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
940-
if (exists(source) == false) {
941-
List<String> asList = defaultSettings.getAsList(getKey(), null);
942-
if (asList == null) {
943-
builder.putList(getKey(), defaultStringValue.apply(defaultSettings));
944-
} else {
945-
builder.putList(getKey(), asList);
946-
}
947-
}
948-
}
949-
}
950-
951930
private final class Updater implements AbstractScopedSettings.SettingUpdater<T> {
952931
private final Consumer<T> consumer;
953932
private final Logger logger;
@@ -1209,26 +1188,44 @@ public static Setting<ByteSizeValue> memorySizeSetting(String key, String defaul
12091188
return new Setting<>(key, (s) -> defaultPercentage, (s) -> MemorySizeValue.parseBytesSizeValueOrHeapRatio(s, key), properties);
12101189
}
12111190

1212-
public static <T> Setting<List<T>> listSetting(String key, List<String> defaultStringValue, Function<String, T> singleValueParser,
1213-
Property... properties) {
1214-
return listSetting(key, (s) -> defaultStringValue, singleValueParser, properties);
1191+
public static <T> Setting<List<T>> listSetting(
1192+
final String key,
1193+
final List<String> defaultStringValue,
1194+
final Function<String, T> singleValueParser,
1195+
final Property... properties) {
1196+
return listSetting(key, null, singleValueParser, (s) -> defaultStringValue, properties);
12151197
}
12161198

12171199
// TODO this one's two argument get is still broken
1218-
public static <T> Setting<List<T>> listSetting(String key, Setting<List<T>> fallbackSetting, Function<String, T> singleValueParser,
1219-
Property... properties) {
1220-
return listSetting(key, (s) -> parseableStringToList(fallbackSetting.getRaw(s)), singleValueParser, properties);
1200+
public static <T> Setting<List<T>> listSetting(
1201+
final String key,
1202+
final Setting<List<T>> fallbackSetting,
1203+
final Function<String, T> singleValueParser,
1204+
final Property... properties) {
1205+
return listSetting(key, fallbackSetting, singleValueParser, (s) -> parseableStringToList(fallbackSetting.getRaw(s)), properties);
1206+
}
1207+
1208+
public static <T> Setting<List<T>> listSetting(
1209+
final String key,
1210+
final Function<String, T> singleValueParser,
1211+
final Function<Settings, List<String>> defaultStringValue,
1212+
final Property... properties) {
1213+
return listSetting(key, null, singleValueParser, defaultStringValue, properties);
12211214
}
12221215

1223-
public static <T> Setting<List<T>> listSetting(String key, Function<Settings, List<String>> defaultStringValue,
1224-
Function<String, T> singleValueParser, Property... properties) {
1216+
public static <T> Setting<List<T>> listSetting(
1217+
final String key,
1218+
final @Nullable Setting<List<T>> fallbackSetting,
1219+
final Function<String, T> singleValueParser,
1220+
final Function<Settings, List<String>> defaultStringValue,
1221+
final Property... properties) {
12251222
if (defaultStringValue.apply(Settings.EMPTY) == null) {
12261223
throw new IllegalArgumentException("default value function must not return null");
12271224
}
12281225
Function<String, List<T>> parser = (s) ->
12291226
parseableStringToList(s).stream().map(singleValueParser).collect(Collectors.toList());
12301227

1231-
return new ListSetting<>(key, defaultStringValue, parser, properties);
1228+
return new ListSetting<>(key, fallbackSetting, defaultStringValue, parser, properties);
12321229
}
12331230

12341231
private static List<String> parseableStringToList(String parsableString) {
@@ -1266,6 +1263,51 @@ private static String arrayToParsableString(List<String> array) {
12661263
}
12671264
}
12681265

1266+
private static class ListSetting<T> extends Setting<List<T>> {
1267+
1268+
private final Function<Settings, List<String>> defaultStringValue;
1269+
1270+
private ListSetting(
1271+
final String key,
1272+
final @Nullable Setting<List<T>> fallbackSetting,
1273+
final Function<Settings, List<String>> defaultStringValue,
1274+
final Function<String, List<T>> parser,
1275+
final Property... properties) {
1276+
super(
1277+
new ListKey(key),
1278+
fallbackSetting,
1279+
(s) -> Setting.arrayToParsableString(defaultStringValue.apply(s)),
1280+
parser,
1281+
(v,s) -> {},
1282+
properties);
1283+
this.defaultStringValue = defaultStringValue;
1284+
}
1285+
1286+
@Override
1287+
String innerGetRaw(final Settings settings) {
1288+
List<String> array = settings.getAsList(getKey(), null);
1289+
return array == null ? defaultValue.apply(settings) : arrayToParsableString(array);
1290+
}
1291+
1292+
@Override
1293+
boolean hasComplexMatcher() {
1294+
return true;
1295+
}
1296+
1297+
@Override
1298+
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
1299+
if (exists(source) == false) {
1300+
List<String> asList = defaultSettings.getAsList(getKey(), null);
1301+
if (asList == null) {
1302+
builder.putList(getKey(), defaultStringValue.apply(defaultSettings));
1303+
} else {
1304+
builder.putList(getKey(), asList);
1305+
}
1306+
}
1307+
}
1308+
1309+
}
1310+
12691311
static void logSettingUpdate(Setting setting, Settings current, Settings previous, Logger logger) {
12701312
if (logger.isInfoEnabled()) {
12711313
if (setting.isFiltered()) {

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void testDependentSettings() {
171171

172172
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
173173
() -> service.validate(Settings.builder().put("foo.test.bar", 7).build(), true));
174-
assertEquals("Missing required setting [foo.test.name] for setting [foo.test.bar]", iae.getMessage());
174+
assertEquals("missing required setting [foo.test.name] for setting [foo.test.bar]", iae.getMessage());
175175

176176
service.validate(Settings.builder()
177177
.put("foo.test.name", "test")
@@ -181,6 +181,34 @@ public void testDependentSettings() {
181181
service.validate(Settings.builder().put("foo.test.bar", 7).build(), false);
182182
}
183183

184+
public void testDependentSettingsWithFallback() {
185+
Setting.AffixSetting<String> nameFallbackSetting =
186+
Setting.affixKeySetting("fallback.", "name", k -> Setting.simpleString(k, Property.Dynamic, Property.NodeScope));
187+
Setting.AffixSetting<String> nameSetting = Setting.affixKeySetting(
188+
"foo.",
189+
"name",
190+
k -> Setting.simpleString(
191+
k,
192+
"_na_".equals(k)
193+
? nameFallbackSetting.getConcreteSettingForNamespace(k)
194+
: nameFallbackSetting.getConcreteSetting(k.replaceAll("^foo", "fallback")),
195+
Property.Dynamic,
196+
Property.NodeScope));
197+
Setting.AffixSetting<Integer> barSetting =
198+
Setting.affixKeySetting("foo.", "bar", k -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope), nameSetting);
199+
200+
final AbstractScopedSettings service =
201+
new ClusterSettings(Settings.EMPTY,new HashSet<>(Arrays.asList(nameFallbackSetting, nameSetting, barSetting)));
202+
203+
final IllegalArgumentException e = expectThrows(
204+
IllegalArgumentException.class,
205+
() -> service.validate(Settings.builder().put("foo.test.bar", 7).build(), true));
206+
assertThat(e, hasToString(containsString("missing required setting [foo.test.name] for setting [foo.test.bar]")));
207+
208+
service.validate(Settings.builder().put("foo.test.name", "test").put("foo.test.bar", 7).build(), true);
209+
service.validate(Settings.builder().put("fallback.test.name", "test").put("foo.test.bar", 7).build(), true);
210+
}
211+
184212
public void testTupleAffixUpdateConsumer() {
185213
String prefix = randomAlphaOfLength(3) + "foo.";
186214
String intSuffix = randomAlphaOfLength(3);

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,30 @@ public void testAffixNamespacesWithGroupSetting() {
856856
assertThat(affixSetting.getNamespaces(Settings.builder().put("prefix.infix.suffix", "anything").build()), hasSize(1));
857857
assertThat(affixSetting.getNamespaces(Settings.builder().put("prefix.infix.suffix.anything", "anything").build()), hasSize(1));
858858
}
859+
860+
public void testExists() {
861+
final Setting<?> fooSetting = Setting.simpleString("foo", Property.NodeScope);
862+
assertFalse(fooSetting.exists(Settings.EMPTY));
863+
assertTrue(fooSetting.exists(Settings.builder().put("foo", "bar").build()));
864+
}
865+
866+
public void testExistsWithFallback() {
867+
final int count = randomIntBetween(1, 16);
868+
Setting<String> current = Setting.simpleString("fallback0", Property.NodeScope);
869+
for (int i = 1; i < count; i++) {
870+
final Setting<String> next =
871+
new Setting<>(new Setting.SimpleKey("fallback" + i), current, Function.identity(), Property.NodeScope);
872+
current = next;
873+
}
874+
final Setting<String> fooSetting = new Setting<>(new Setting.SimpleKey("foo"), current, Function.identity(), Property.NodeScope);
875+
assertFalse(fooSetting.exists(Settings.EMPTY));
876+
if (randomBoolean()) {
877+
assertTrue(fooSetting.exists(Settings.builder().put("foo", "bar").build()));
878+
} else {
879+
final String setting = "fallback" + randomIntBetween(0, count - 1);
880+
assertFalse(fooSetting.exists(Settings.builder().put(setting, "bar").build()));
881+
assertTrue(fooSetting.existsOrFallbackExists(Settings.builder().put(setting, "bar").build()));
882+
}
883+
}
884+
859885
}

0 commit comments

Comments
 (0)