Skip to content

Commit ead198b

Browse files
authored
Add settings updater for 2 affix settings (#33050)
Today we can only have non-affix settings updated and consumed _together_. Yet, there are use-cases where two affix settings depend on each other which makes using the hard without consuming updates together. Unfortunately, there is not straight forward way to have N settings updated together in a type-safe way having 2 still serves a large portion of use-cases.
1 parent 262d3c0 commit ead198b

File tree

4 files changed

+168
-7
lines changed

4 files changed

+168
-7
lines changed

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

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public synchronized <T> void addSettingsUpdateConsumer(Setting<T> setting, Consu
199199
* Also automatically adds empty consumers for all settings in order to activate logging
200200
*/
201201
public synchronized void addSettingsUpdateConsumer(Consumer<Settings> consumer, List<? extends Setting<?>> settings) {
202-
addSettingsUpdater(Setting.groupedSettingsUpdater(consumer, logger, settings));
202+
addSettingsUpdater(Setting.groupedSettingsUpdater(consumer, settings));
203203
}
204204

205205
/**
@@ -208,11 +208,78 @@ public synchronized void addSettingsUpdateConsumer(Consumer<Settings> consumer,
208208
*/
209209
public synchronized <T> void addAffixUpdateConsumer(Setting.AffixSetting<T> setting, BiConsumer<String, T> consumer,
210210
BiConsumer<String, T> validator) {
211+
ensureSettingIsRegistered(setting);
212+
addSettingsUpdater(setting.newAffixUpdater(consumer, logger, validator));
213+
}
214+
215+
/**
216+
* Adds a affix settings consumer that accepts the values for two settings. The consumer is only notified if one or both settings change
217+
* and if the provided validator succeeded.
218+
* <p>
219+
* Note: Only settings registered in {@link SettingsModule} can be changed dynamically.
220+
* </p>
221+
* This method registers a compound updater that is useful if two settings are depending on each other.
222+
* The consumer is always provided with both values even if only one of the two changes.
223+
*/
224+
public synchronized <A,B> void addAffixUpdateConsumer(Setting.AffixSetting<A> settingA, Setting.AffixSetting<B> settingB,
225+
BiConsumer<String, Tuple<A, B>> consumer,
226+
BiConsumer<String, Tuple<A, B>> validator) {
227+
// it would be awesome to have a generic way to do that ie. a set of settings that map to an object with a builder
228+
// down the road this would be nice to have!
229+
ensureSettingIsRegistered(settingA);
230+
ensureSettingIsRegistered(settingB);
231+
SettingUpdater<Map<SettingUpdater<A>, A>> affixUpdaterA = settingA.newAffixUpdater((a,b)-> {}, logger, (a,b)-> {});
232+
SettingUpdater<Map<SettingUpdater<B>, B>> affixUpdaterB = settingB.newAffixUpdater((a,b)-> {}, logger, (a,b)-> {});
233+
234+
addSettingsUpdater(new SettingUpdater<Map<String, Tuple<A, B>>>() {
235+
236+
@Override
237+
public boolean hasChanged(Settings current, Settings previous) {
238+
return affixUpdaterA.hasChanged(current, previous) || affixUpdaterB.hasChanged(current, previous);
239+
}
240+
241+
@Override
242+
public Map<String, Tuple<A, B>> getValue(Settings current, Settings previous) {
243+
Map<String, Tuple<A, B>> map = new HashMap<>();
244+
BiConsumer<String, A> aConsumer = (key, value) -> {
245+
assert map.containsKey(key) == false : "duplicate key: " + key;
246+
map.put(key, new Tuple<>(value, settingB.getConcreteSettingForNamespace(key).get(current)));
247+
};
248+
BiConsumer<String, B> bConsumer = (key, value) -> {
249+
Tuple<A, B> abTuple = map.get(key);
250+
if (abTuple != null) {
251+
map.put(key, new Tuple<>(abTuple.v1(), value));
252+
} else {
253+
assert settingA.getConcreteSettingForNamespace(key).get(current).equals(settingA.getConcreteSettingForNamespace
254+
(key).get(previous)) : "expected: " + settingA.getConcreteSettingForNamespace(key).get(current)
255+
+ " but was " + settingA.getConcreteSettingForNamespace(key).get(previous);
256+
map.put(key, new Tuple<>(settingA.getConcreteSettingForNamespace(key).get(current), value));
257+
}
258+
};
259+
SettingUpdater<Map<SettingUpdater<A>, A>> affixUpdaterA = settingA.newAffixUpdater(aConsumer, logger, (a,b) ->{});
260+
SettingUpdater<Map<SettingUpdater<B>, B>> affixUpdaterB = settingB.newAffixUpdater(bConsumer, logger, (a,b) ->{});
261+
affixUpdaterA.apply(current, previous);
262+
affixUpdaterB.apply(current, previous);
263+
for (Map.Entry<String, Tuple<A, B>> entry : map.entrySet()) {
264+
validator.accept(entry.getKey(), entry.getValue());
265+
}
266+
return Collections.unmodifiableMap(map);
267+
}
268+
269+
@Override
270+
public void apply(Map<String, Tuple<A, B>> values, Settings current, Settings previous) {
271+
for (Map.Entry<String, Tuple<A, B>> entry : values.entrySet()) {
272+
consumer.accept(entry.getKey(), entry.getValue());
273+
}
274+
}
275+
});
276+
}
277+
278+
private void ensureSettingIsRegistered(Setting.AffixSetting<?> setting) {
211279
final Setting<?> registeredSetting = this.complexMatchers.get(setting.getKey());
212280
if (setting != registeredSetting) {
213281
throw new IllegalArgumentException("Setting is not registered for key [" + setting.getKey() + "]");
214282
}
215-
addSettingsUpdater(setting.newAffixUpdater(consumer, logger, validator));
216283
}
217284

218285
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ public String toString() {
547547
};
548548
}
549549

550-
static AbstractScopedSettings.SettingUpdater<Settings> groupedSettingsUpdater(Consumer<Settings> consumer, Logger logger,
550+
static AbstractScopedSettings.SettingUpdater<Settings> groupedSettingsUpdater(Consumer<Settings> consumer,
551551
final List<? extends Setting<?>> configuredSettings) {
552552

553553
return new AbstractScopedSettings.SettingUpdater<Settings>() {

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

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.cluster.metadata.IndexMetaData;
2424
import org.elasticsearch.cluster.routing.allocation.decider.FilterAllocationDecider;
2525
import org.elasticsearch.cluster.routing.allocation.decider.ShardsLimitAllocationDecider;
26+
import org.elasticsearch.common.collect.Tuple;
2627
import org.elasticsearch.common.logging.ESLoggerFactory;
2728
import org.elasticsearch.common.logging.Loggers;
2829
import org.elasticsearch.common.settings.Setting.Property;
@@ -180,6 +181,99 @@ public void testDependentSettings() {
180181
service.validate(Settings.builder().put("foo.test.bar", 7).build(), false);
181182
}
182183

184+
public void testTupleAffixUpdateConsumer() {
185+
String prefix = randomAlphaOfLength(3) + "foo.";
186+
String intSuffix = randomAlphaOfLength(3);
187+
String listSuffix = randomAlphaOfLength(4);
188+
Setting.AffixSetting<Integer> intSetting = Setting.affixKeySetting(prefix, intSuffix,
189+
(k) -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope));
190+
Setting.AffixSetting<List<Integer>> listSetting = Setting.affixKeySetting(prefix, listSuffix,
191+
(k) -> Setting.listSetting(k, Arrays.asList("1"), Integer::parseInt, Property.Dynamic, Property.NodeScope));
192+
AbstractScopedSettings service = new ClusterSettings(Settings.EMPTY,new HashSet<>(Arrays.asList(intSetting, listSetting)));
193+
Map<String, Tuple<List<Integer>, Integer>> results = new HashMap<>();
194+
Function<String, String> listBuilder = g -> (prefix + g + "." + listSuffix);
195+
Function<String, String> intBuilder = g -> (prefix + g + "." + intSuffix);
196+
String group1 = randomAlphaOfLength(3);
197+
String group2 = randomAlphaOfLength(4);
198+
String group3 = randomAlphaOfLength(5);
199+
BiConsumer<String, Tuple<List<Integer>, Integer>> listConsumer = results::put;
200+
201+
service.addAffixUpdateConsumer(listSetting, intSetting, listConsumer, (s, k) -> {
202+
if (k.v1().isEmpty() && k.v2() == 2) {
203+
throw new IllegalArgumentException("boom");
204+
}
205+
});
206+
assertEquals(0, results.size());
207+
service.applySettings(Settings.builder()
208+
.put(intBuilder.apply(group1), 2)
209+
.put(intBuilder.apply(group2), 7)
210+
.putList(listBuilder.apply(group1), "16", "17")
211+
.putList(listBuilder.apply(group2), "18", "19", "20")
212+
.build());
213+
assertEquals(2, results.get(group1).v2().intValue());
214+
assertEquals(7, results.get(group2).v2().intValue());
215+
assertEquals(Arrays.asList(16, 17), results.get(group1).v1());
216+
assertEquals(Arrays.asList(18, 19, 20), results.get(group2).v1());
217+
assertEquals(2, results.size());
218+
219+
results.clear();
220+
221+
service.applySettings(Settings.builder()
222+
.put(intBuilder.apply(group1), 2)
223+
.put(intBuilder.apply(group2), 7)
224+
.putList(listBuilder.apply(group1), "16", "17")
225+
.putNull(listBuilder.apply(group2)) // removed
226+
.build());
227+
228+
assertNull(group1 + " wasn't changed", results.get(group1));
229+
assertEquals(1, results.get(group2).v1().size());
230+
assertEquals(Arrays.asList(1), results.get(group2).v1());
231+
assertEquals(7, results.get(group2).v2().intValue());
232+
assertEquals(1, results.size());
233+
results.clear();
234+
235+
service.applySettings(Settings.builder()
236+
.put(intBuilder.apply(group1), 2)
237+
.put(intBuilder.apply(group2), 7)
238+
.putList(listBuilder.apply(group1), "16", "17")
239+
.putList(listBuilder.apply(group3), "5", "6") // added
240+
.build());
241+
assertNull(group1 + " wasn't changed", results.get(group1));
242+
assertNull(group2 + " wasn't changed", results.get(group2));
243+
244+
assertEquals(2, results.get(group3).v1().size());
245+
assertEquals(Arrays.asList(5, 6), results.get(group3).v1());
246+
assertEquals(1, results.get(group3).v2().intValue());
247+
assertEquals(1, results.size());
248+
results.clear();
249+
250+
service.applySettings(Settings.builder()
251+
.put(intBuilder.apply(group1), 4) // modified
252+
.put(intBuilder.apply(group2), 7)
253+
.putList(listBuilder.apply(group1), "16", "17")
254+
.putList(listBuilder.apply(group3), "5", "6")
255+
.build());
256+
assertNull(group2 + " wasn't changed", results.get(group2));
257+
assertNull(group3 + " wasn't changed", results.get(group3));
258+
259+
assertEquals(2, results.get(group1).v1().size());
260+
assertEquals(Arrays.asList(16, 17), results.get(group1).v1());
261+
assertEquals(4, results.get(group1).v2().intValue());
262+
assertEquals(1, results.size());
263+
results.clear();
264+
265+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
266+
service.applySettings(Settings.builder()
267+
.put(intBuilder.apply(group1), 2) // modified to trip validator
268+
.put(intBuilder.apply(group2), 7)
269+
.putList(listBuilder.apply(group1)) // modified to trip validator
270+
.putList(listBuilder.apply(group3), "5", "6")
271+
.build())
272+
);
273+
assertEquals("boom", iae.getMessage());
274+
assertEquals(0, results.size());
275+
}
276+
183277
public void testAddConsumerAffix() {
184278
Setting.AffixSetting<Integer> intSetting = Setting.affixKeySetting("foo.", "bar",
185279
(k) -> Setting.intSetting(k, 1, Property.Dynamic, Property.NodeScope));
@@ -893,7 +987,7 @@ public void testInternalIndexSettingsFailsValidation() {
893987

894988
public void testInternalIndexSettingsSkipValidation() {
895989
final Setting<String> internalIndexSetting = Setting.simpleString("index.internal", Property.InternalIndex, Property.IndexScope);
896-
final IndexScopedSettings indexScopedSettings =
990+
final IndexScopedSettings indexScopedSettings =
897991
new IndexScopedSettings(Settings.EMPTY, Collections.singleton(internalIndexSetting));
898992
// nothing should happen, validation should not throw an exception
899993
final Settings settings = Settings.builder().put("index.internal", "internal").build();

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ public void testTimeValue() {
757757
public void testSettingsGroupUpdater() {
758758
Setting<Integer> intSetting = Setting.intSetting("prefix.foo", 1, Property.NodeScope, Property.Dynamic);
759759
Setting<Integer> intSetting2 = Setting.intSetting("prefix.same", 1, Property.NodeScope, Property.Dynamic);
760-
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
760+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {},
761761
Arrays.asList(intSetting, intSetting2));
762762

763763
Settings current = Settings.builder().put("prefix.foo", 123).put("prefix.same", 5555).build();
@@ -768,7 +768,7 @@ public void testSettingsGroupUpdater() {
768768
public void testSettingsGroupUpdaterRemoval() {
769769
Setting<Integer> intSetting = Setting.intSetting("prefix.foo", 1, Property.NodeScope, Property.Dynamic);
770770
Setting<Integer> intSetting2 = Setting.intSetting("prefix.same", 1, Property.NodeScope, Property.Dynamic);
771-
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
771+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {},
772772
Arrays.asList(intSetting, intSetting2));
773773

774774
Settings current = Settings.builder().put("prefix.same", 5555).build();
@@ -783,7 +783,7 @@ public void testSettingsGroupUpdaterWithAffixSetting() {
783783
Setting.AffixSetting<String> affixSetting =
784784
Setting.affixKeySetting("prefix.foo.", "suffix", key -> Setting.simpleString(key,Property.NodeScope, Property.Dynamic));
785785

786-
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
786+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {},
787787
Arrays.asList(intSetting, prefixKeySetting, affixSetting));
788788

789789
Settings.Builder currentSettingsBuilder = Settings.builder()

0 commit comments

Comments
 (0)