Skip to content

Commit 87517d9

Browse files
authored
Enable dependent settings values to be validated (#49942)
Today settings can declare dependencies on another setting. This declaration is implemented so that if the declared setting is not set when the declaring setting is, settings validation fails. Yet, in some cases we want not only that the setting is set, but that it also has a specific value. For example, with the monitoring exporter settings, if xpack.monitoring.exporters.my_exporter.host is set, we not only want that xpack.monitoring.exporters.my_exporter.type is set, but that it is also set to local. This commit extends the settings infrastructure so that this declaration is possible. The use of this in the monitoring exporter settings will be implemented in a follow-up.
1 parent d12637a commit 87517d9

File tree

8 files changed

+137
-32
lines changed

8 files changed

+137
-32
lines changed

plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java

+13-8
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,34 @@ final class AzureStorageSettings {
6262
public static final AffixSetting<Integer> MAX_RETRIES_SETTING =
6363
Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "max_retries",
6464
(key) -> Setting.intSetting(key, RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT, Setting.Property.NodeScope),
65-
ACCOUNT_SETTING, KEY_SETTING);
65+
() -> ACCOUNT_SETTING, () -> KEY_SETTING);
6666
/**
6767
* Azure endpoint suffix. Default to core.windows.net (CloudStorageAccount.DEFAULT_DNS).
6868
*/
6969
public static final AffixSetting<String> ENDPOINT_SUFFIX_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "endpoint_suffix",
70-
key -> Setting.simpleString(key, Property.NodeScope), ACCOUNT_SETTING, KEY_SETTING);
70+
key -> Setting.simpleString(key, Property.NodeScope), () -> ACCOUNT_SETTING, () -> KEY_SETTING);
7171

7272
public static final AffixSetting<TimeValue> TIMEOUT_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "timeout",
73-
(key) -> Setting.timeSetting(key, TimeValue.timeValueMinutes(-1), Property.NodeScope), ACCOUNT_SETTING, KEY_SETTING);
73+
(key) -> Setting.timeSetting(key, TimeValue.timeValueMinutes(-1), Property.NodeScope), () -> ACCOUNT_SETTING, () -> KEY_SETTING);
7474

7575
/** The type of the proxy to connect to azure through. Can be direct (no proxy, default), http or socks */
7676
public static final AffixSetting<Proxy.Type> PROXY_TYPE_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "proxy.type",
7777
(key) -> new Setting<>(key, "direct", s -> Proxy.Type.valueOf(s.toUpperCase(Locale.ROOT)), Property.NodeScope)
78-
, ACCOUNT_SETTING, KEY_SETTING);
78+
, () -> ACCOUNT_SETTING, () -> KEY_SETTING);
7979

8080
/** The host name of a proxy to connect to azure through. */
8181
public static final AffixSetting<String> PROXY_HOST_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "proxy.host",
82-
(key) -> Setting.simpleString(key, Property.NodeScope), KEY_SETTING, ACCOUNT_SETTING, PROXY_TYPE_SETTING);
82+
(key) -> Setting.simpleString(key, Property.NodeScope), () -> KEY_SETTING, () -> ACCOUNT_SETTING, () -> PROXY_TYPE_SETTING);
8383

8484
/** The port of a proxy to connect to azure through. */
85-
public static final Setting<Integer> PROXY_PORT_SETTING = Setting.affixKeySetting(AZURE_CLIENT_PREFIX_KEY, "proxy.port",
86-
(key) -> Setting.intSetting(key, 0, 0, 65535, Setting.Property.NodeScope), ACCOUNT_SETTING, KEY_SETTING, PROXY_TYPE_SETTING,
87-
PROXY_HOST_SETTING);
85+
public static final Setting<Integer> PROXY_PORT_SETTING = Setting.affixKeySetting(
86+
AZURE_CLIENT_PREFIX_KEY,
87+
"proxy.port",
88+
(key) -> Setting.intSetting(key, 0, 0, 65535, Setting.Property.NodeScope),
89+
() -> ACCOUNT_SETTING,
90+
() -> KEY_SETTING,
91+
() -> PROXY_TYPE_SETTING,
92+
() -> PROXY_HOST_SETTING);
8893

8994
private final String account;
9095
private final String connectString;

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -530,20 +530,24 @@ void validate(
530530
}
531531
throw new IllegalArgumentException(msg);
532532
} else {
533-
Set<Setting<?>> settingsDependencies = setting.getSettingsDependencies(key);
533+
Set<Setting.SettingDependency> settingsDependencies = setting.getSettingsDependencies(key);
534534
if (setting.hasComplexMatcher()) {
535535
setting = setting.getConcreteSetting(key);
536536
}
537537
if (validateDependencies && settingsDependencies.isEmpty() == false) {
538-
for (final Setting<?> settingDependency : settingsDependencies) {
539-
if (settingDependency.existsOrFallbackExists(settings) == false) {
538+
for (final Setting.SettingDependency settingDependency : settingsDependencies) {
539+
final Setting<?> dependency = settingDependency.getSetting();
540+
// validate the dependent setting is set
541+
if (dependency.existsOrFallbackExists(settings) == false) {
540542
final String message = String.format(
541543
Locale.ROOT,
542544
"missing required setting [%s] for setting [%s]",
543-
settingDependency.getKey(),
545+
dependency.getKey(),
544546
setting.getKey());
545547
throw new IllegalArgumentException(message);
546548
}
549+
// validate the dependent setting value
550+
settingDependency.validate(setting.getKey(), setting.get(settings), dependency.get(settings));
547551
}
548552
}
549553
// the only time that validateInternalOrPrivateIndex should be true is if this call is coming via the update settings API

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

+57-8
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,37 @@ public Setting<T> getConcreteSetting(String key) {
564564
return this;
565565
}
566566

567+
/**
568+
* Allows a setting to declare a dependency on another setting being set. Optionally, a setting can validate the value of the dependent
569+
* setting.
570+
*/
571+
public interface SettingDependency {
572+
573+
/**
574+
* The setting to declare a dependency on.
575+
*
576+
* @return the setting
577+
*/
578+
Setting getSetting();
579+
580+
/**
581+
* Validates the dependent setting value.
582+
*
583+
* @param key the key for this setting
584+
* @param value the value of this setting
585+
* @param dependency the value of the dependent setting
586+
*/
587+
default void validate(String key, Object value, Object dependency) {
588+
589+
}
590+
591+
}
592+
567593
/**
568594
* Returns a set of settings that are required at validation time. Unless all of the dependencies are present in the settings
569595
* object validation of setting must fail.
570596
*/
571-
public Set<Setting<?>> getSettingsDependencies(String key) {
597+
public Set<SettingDependency> getSettingsDependencies(final String key) {
572598
return Collections.emptySet();
573599
}
574600

@@ -671,13 +697,23 @@ public String toString() {
671697
};
672698
}
673699

700+
/**
701+
* Allows an affix setting to declare a dependency on another affix setting.
702+
*/
703+
public interface AffixSettingDependency extends SettingDependency {
704+
705+
@Override
706+
AffixSetting getSetting();
707+
708+
}
709+
674710
public static class AffixSetting<T> extends Setting<T> {
675711
private final AffixKey key;
676712
private final BiFunction<String, String, Setting<T>> delegateFactory;
677-
private final Set<AffixSetting> dependencies;
713+
private final Set<AffixSettingDependency> dependencies;
678714

679715
public AffixSetting(AffixKey key, Setting<T> delegate, BiFunction<String, String, Setting<T>> delegateFactory,
680-
AffixSetting... dependencies) {
716+
AffixSettingDependency... dependencies) {
681717
super(key, delegate.defaultValue, delegate.parser, delegate.properties.toArray(new Property[0]));
682718
this.key = key;
683719
this.delegateFactory = delegateFactory;
@@ -693,12 +729,25 @@ private Stream<String> matchStream(Settings settings) {
693729
}
694730

695731
@Override
696-
public Set<Setting<?>> getSettingsDependencies(String settingsKey) {
732+
public Set<SettingDependency> getSettingsDependencies(String settingsKey) {
697733
if (dependencies.isEmpty()) {
698734
return Collections.emptySet();
699735
} else {
700736
String namespace = key.getNamespace(settingsKey);
701-
return dependencies.stream().map(s -> (Setting<?>)s.getConcreteSettingForNamespace(namespace)).collect(Collectors.toSet());
737+
return dependencies.stream()
738+
.map(s ->
739+
new SettingDependency() {
740+
@Override
741+
public Setting<Object> getSetting() {
742+
return s.getSetting().getConcreteSettingForNamespace(namespace);
743+
}
744+
745+
@Override
746+
public void validate(final String key, final Object value, final Object dependency) {
747+
s.validate(key, value, dependency);
748+
};
749+
})
750+
.collect(Collectors.toSet());
702751
}
703752
}
704753

@@ -1635,19 +1684,19 @@ public static <T> AffixSetting<T> prefixKeySetting(String prefix, Function<Strin
16351684
* out of the box unless {@link #getConcreteSetting(String)} is used to pull the updater.
16361685
*/
16371686
public static <T> AffixSetting<T> affixKeySetting(String prefix, String suffix, Function<String, Setting<T>> delegateFactory,
1638-
AffixSetting... dependencies) {
1687+
AffixSettingDependency... dependencies) {
16391688
BiFunction<String, String, Setting<T>> delegateFactoryWithNamespace = (ns, k) -> delegateFactory.apply(k);
16401689
return affixKeySetting(new AffixKey(prefix, suffix), delegateFactoryWithNamespace, dependencies);
16411690
}
16421691

16431692
public static <T> AffixSetting<T> affixKeySetting(String prefix, String suffix, BiFunction<String, String, Setting<T>> delegateFactory,
1644-
AffixSetting... dependencies) {
1693+
AffixSettingDependency... dependencies) {
16451694
Setting<T> delegate = delegateFactory.apply("_na_", "_na_");
16461695
return new AffixSetting<>(new AffixKey(prefix, suffix), delegate, delegateFactory, dependencies);
16471696
}
16481697

16491698
private static <T> AffixSetting<T> affixKeySetting(AffixKey key, BiFunction<String, String, Setting<T>> delegateFactory,
1650-
AffixSetting... dependencies) {
1699+
AffixSettingDependency... dependencies) {
16511700
Setting<T> delegate = delegateFactory.apply("_na_", "_na_");
16521701
return new AffixSetting<>(key, delegate, delegateFactory, dependencies);
16531702
}

server/src/main/java/org/elasticsearch/transport/RemoteClusterService.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,19 @@ public final class RemoteClusterService extends RemoteClusterAware implements Cl
100100
false,
101101
Setting.Property.Dynamic,
102102
Setting.Property.NodeScope),
103-
SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
103+
() -> SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
104104

105105
public static final Setting.AffixSetting<TimeValue> REMOTE_CLUSTER_PING_SCHEDULE = Setting.affixKeySetting(
106106
"cluster.remote.",
107107
"transport.ping_schedule",
108108
key -> timeSetting(key, TransportSettings.PING_SCHEDULE, Setting.Property.Dynamic, Setting.Property.NodeScope),
109-
SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
109+
() -> SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
110110

111111
public static final Setting.AffixSetting<Boolean> REMOTE_CLUSTER_COMPRESS = Setting.affixKeySetting(
112112
"cluster.remote.",
113113
"transport.compress",
114114
key -> boolSetting(key, TransportSettings.TRANSPORT_COMPRESS, Setting.Property.Dynamic, Setting.Property.NodeScope),
115-
SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
115+
() -> SniffConnectionStrategy.REMOTE_CLUSTER_SEEDS);
116116

117117
private final TransportService transportService;
118118
private final Map<String, RemoteClusterConnection> remoteClusters = ConcurrentCollections.newConcurrentMap();

server/src/main/java/org/elasticsearch/transport/SniffConnectionStrategy.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public class SniffConnectionStrategy extends RemoteConnectionStrategy {
116116
}),
117117
Setting.Property.Dynamic,
118118
Setting.Property.NodeScope),
119-
REMOTE_CLUSTER_SEEDS);
119+
() -> REMOTE_CLUSTER_SEEDS);
120120

121121
/**
122122
* The maximum number of connections that will be established to a remote cluster. For instance if there is only a single

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

+51-4
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ public void testDependentSettings() {
179179
Setting.AffixSetting<String> stringSetting = Setting.affixKeySetting("foo.", "name",
180180
(k) -> Setting.simpleString(k, Property.Dynamic, Property.NodeScope));
181181
Setting.AffixSetting<Integer> intSetting = Setting.affixKeySetting("foo.", "bar",
182-
(k) -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope), stringSetting);
182+
(k) -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope), () -> stringSetting);
183183

184-
AbstractScopedSettings service = new ClusterSettings(Settings.EMPTY,new HashSet<>(Arrays.asList(intSetting, stringSetting)));
184+
AbstractScopedSettings service = new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList(intSetting, stringSetting)));
185185

186186
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
187187
() -> service.validate(Settings.builder().put("foo.test.bar", 7).build(), true));
@@ -195,6 +195,50 @@ public void testDependentSettings() {
195195
service.validate(Settings.builder().put("foo.test.bar", 7).build(), false);
196196
}
197197

198+
public void testDependentSettingsValidate() {
199+
Setting.AffixSetting<String> stringSetting = Setting.affixKeySetting(
200+
"foo.",
201+
"name",
202+
(k) -> Setting.simpleString(k, Property.Dynamic, Property.NodeScope));
203+
Setting.AffixSetting<Integer> intSetting = Setting.affixKeySetting(
204+
"foo.",
205+
"bar",
206+
(k) -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope),
207+
new Setting.AffixSettingDependency() {
208+
209+
@Override
210+
public Setting.AffixSetting getSetting() {
211+
return stringSetting;
212+
}
213+
214+
@Override
215+
public void validate(final String key, final Object value, final Object dependency) {
216+
if ("valid".equals(dependency) == false) {
217+
throw new SettingsException("[" + key + "] is set but [name] is [" + dependency + "]");
218+
}
219+
}
220+
});
221+
222+
AbstractScopedSettings service = new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList(intSetting, stringSetting)));
223+
224+
SettingsException iae = expectThrows(
225+
SettingsException.class,
226+
() -> service.validate(Settings.builder().put("foo.test.bar", 7).put("foo.test.name", "invalid").build(), true));
227+
assertEquals("[foo.test.bar] is set but [name] is [invalid]", iae.getMessage());
228+
229+
service.validate(Settings.builder()
230+
.put("foo.test.bar", 7)
231+
.put("foo.test.name", "valid")
232+
.build(),
233+
true);
234+
235+
service.validate(Settings.builder()
236+
.put("foo.test.bar", 7)
237+
.put("foo.test.name", "invalid")
238+
.build(),
239+
false);
240+
}
241+
198242
public void testDependentSettingsWithFallback() {
199243
Setting.AffixSetting<String> nameFallbackSetting =
200244
Setting.affixKeySetting("fallback.", "name", k -> Setting.simpleString(k, Property.Dynamic, Property.NodeScope));
@@ -208,8 +252,11 @@ public void testDependentSettingsWithFallback() {
208252
: nameFallbackSetting.getConcreteSetting(k.replaceAll("^foo", "fallback")),
209253
Property.Dynamic,
210254
Property.NodeScope));
211-
Setting.AffixSetting<Integer> barSetting =
212-
Setting.affixKeySetting("foo.", "bar", k -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope), nameSetting);
255+
Setting.AffixSetting<Integer> barSetting = Setting.affixKeySetting(
256+
"foo.",
257+
"bar",
258+
k -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope),
259+
() -> nameSetting);
213260

214261
final AbstractScopedSettings service =
215262
new ClusterSettings(Settings.EMPTY,new HashSet<>(Arrays.asList(nameFallbackSetting, nameSetting, barSetting)));

server/src/test/java/org/elasticsearch/indices/settings/UpdateSettingsIT.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ public static class DummySettingPlugin extends Plugin {
9393
public static final Setting.AffixSetting<String> DUMMY_ACCOUNT_USER = Setting.affixKeySetting("index.acc.", "user",
9494
k -> Setting.simpleString(k, Setting.Property.IndexScope, Setting.Property.Dynamic));
9595
public static final Setting<String> DUMMY_ACCOUNT_PW = Setting.affixKeySetting("index.acc.", "pw",
96-
k -> Setting.simpleString(k, Setting.Property.IndexScope, Setting.Property.Dynamic), DUMMY_ACCOUNT_USER);
96+
k -> Setting.simpleString(k, Setting.Property.IndexScope, Setting.Property.Dynamic), () -> DUMMY_ACCOUNT_USER);
9797

9898
public static final Setting.AffixSetting<String> DUMMY_ACCOUNT_USER_CLUSTER = Setting.affixKeySetting("cluster.acc.", "user",
9999
k -> Setting.simpleString(k, Setting.Property.NodeScope, Setting.Property.Dynamic));
100100
public static final Setting<String> DUMMY_ACCOUNT_PW_CLUSTER = Setting.affixKeySetting("cluster.acc.", "pw",
101-
k -> Setting.simpleString(k, Setting.Property.NodeScope, Setting.Property.Dynamic), DUMMY_ACCOUNT_USER_CLUSTER);
101+
k -> Setting.simpleString(k, Setting.Property.NodeScope, Setting.Property.Dynamic), () -> DUMMY_ACCOUNT_USER_CLUSTER);
102102

103103
@Override
104104
public void onIndexModule(IndexModule indexModule) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmSettings.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
/**
2222
* Provides a number of utility methods for interacting with {@link Settings} and {@link Setting} inside a {@link Realm}.
23-
* Settings for realms use an {@link Setting#affixKeySetting(String, String, Function, Setting.AffixSetting[]) affix} style,
23+
* Settings for realms use an {@link Setting#affixKeySetting(String, String, Function, Setting.AffixSettingDependency[]) affix} style,
2424
* where the <em>type</em> of the realm is part of the prefix, and name of the realm is the variable portion
2525
* (That is to set the order in a file realm named "file1", then full setting key would be
2626
* {@code xpack.security.authc.realms.file.file1.order}.
@@ -74,7 +74,7 @@ public static Setting.AffixSetting<SecureString> secureString(String realmType,
7474
* The {@code Function} takes the <em>realm-type</em> as an argument.
7575
* @param suffix The suffix of the setting (everything following the realm name in the affix setting)
7676
* @param delegateFactory A factory to produce the concrete setting.
77-
* See {@link Setting#affixKeySetting(String, String, Function, Setting.AffixSetting[])}
77+
* See {@link Setting#affixKeySetting(String, String, Function, Setting.AffixSettingDependency[])}
7878
*/
7979
public static <T> Function<String, Setting.AffixSetting<T>> affixSetting(String suffix, Function<String, Setting<T>> delegateFactory) {
8080
return realmType -> Setting.affixKeySetting(realmSettingPrefix(realmType), suffix, delegateFactory);

0 commit comments

Comments
 (0)