Skip to content

Commit 825329a

Browse files
committed
Settings: Introduce settings updater for a list of settings (#28338)
This introduces a settings updater that allows to specify a list of settings. Whenever one of those settings changes, the whole block of settings is passed to the consumer. This also fixes an issue with affix settings, when used in combination with group settings, which could result in no found settings when used to get a setting for a namespace. Lastly logging has been slightly changed, so that filtered settings now only log the setting key. Another bug has been fixed for the mock log appender, which did not work, when checking for the exact message. Closes #28047
1 parent 02c8715 commit 825329a

File tree

5 files changed

+187
-11
lines changed

5 files changed

+187
-11
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ public synchronized <T> void addSettingsUpdateConsumer(Setting<T> setting, Consu
194194
addSettingsUpdater(setting.newUpdater(consumer, logger, validator));
195195
}
196196

197+
/**
198+
* Adds a settings consumer that is only executed if any setting in the supplied list of settings is changed. In that case all the
199+
* settings are specified in the argument are returned.
200+
*
201+
* Also automatically adds empty consumers for all settings in order to activate logging
202+
*/
203+
public synchronized void addSettingsUpdateConsumer(Consumer<Settings> consumer, List<? extends Setting<?>> settings) {
204+
addSettingsUpdater(Setting.groupedSettingsUpdater(consumer, logger, settings));
205+
}
206+
197207
/**
198208
* Adds a settings consumer for affix settings. Affix settings have a namespace associated to it that needs to be available to the
199209
* consumer in order to be processed correctly.

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

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -509,10 +509,10 @@ public Tuple<A, B> getValue(Settings current, Settings previous) {
509509
@Override
510510
public void apply(Tuple<A, B> value, Settings current, Settings previous) {
511511
if (aSettingUpdater.hasChanged(current, previous)) {
512-
logger.info("updating [{}] from [{}] to [{}]", aSetting.key, aSetting.getRaw(previous), aSetting.getRaw(current));
512+
logSettingUpdate(aSetting, current, previous, logger);
513513
}
514514
if (bSettingUpdater.hasChanged(current, previous)) {
515-
logger.info("updating [{}] from [{}] to [{}]", bSetting.key, bSetting.getRaw(previous), bSetting.getRaw(current));
515+
logSettingUpdate(bSetting, current, previous, logger);
516516
}
517517
consumer.accept(value.v1(), value.v2());
518518
}
@@ -524,6 +524,46 @@ public String toString() {
524524
};
525525
}
526526

527+
static AbstractScopedSettings.SettingUpdater<Settings> groupedSettingsUpdater(Consumer<Settings> consumer, Logger logger,
528+
final List<? extends Setting<?>> configuredSettings) {
529+
530+
return new AbstractScopedSettings.SettingUpdater<Settings>() {
531+
532+
private Settings get(Settings settings) {
533+
return settings.filter(s -> {
534+
for (Setting<?> setting : configuredSettings) {
535+
if (setting.key.match(s)) {
536+
return true;
537+
}
538+
}
539+
return false;
540+
});
541+
}
542+
543+
@Override
544+
public boolean hasChanged(Settings current, Settings previous) {
545+
Settings currentSettings = get(current);
546+
Settings previousSettings = get(previous);
547+
return currentSettings.equals(previousSettings) == false;
548+
}
549+
550+
@Override
551+
public Settings getValue(Settings current, Settings previous) {
552+
return get(current);
553+
}
554+
555+
@Override
556+
public void apply(Settings value, Settings current, Settings previous) {
557+
consumer.accept(value);
558+
}
559+
560+
@Override
561+
public String toString() {
562+
return "Updater grouped: " + configuredSettings.stream().map(Setting::getKey).collect(Collectors.joining(", "));
563+
}
564+
};
565+
}
566+
527567
public static class AffixSetting<T> extends Setting<T> {
528568
private final AffixKey key;
529569
private final Function<String, Setting<T>> delegateFactory;
@@ -541,7 +581,7 @@ boolean isGroupSetting() {
541581
}
542582

543583
private Stream<String> matchStream(Settings settings) {
544-
return settings.keySet().stream().filter((key) -> match(key)).map(settingKey -> key.getConcreteString(settingKey));
584+
return settings.keySet().stream().filter(this::match).map(key::getConcreteString);
545585
}
546586

547587
public Set<String> getSettingsDependencies(String settingsKey) {
@@ -812,9 +852,7 @@ public Settings getValue(Settings current, Settings previous) {
812852

813853
@Override
814854
public void apply(Settings value, Settings current, Settings previous) {
815-
if (logger.isInfoEnabled()) { // getRaw can create quite some objects
816-
logger.info("updating [{}] from [{}] to [{}]", key, getRaw(previous), getRaw(current));
817-
}
855+
Setting.logSettingUpdate(GroupSetting.this, current, previous, logger);
818856
consumer.accept(value);
819857
}
820858

@@ -902,7 +940,7 @@ public T getValue(Settings current, Settings previous) {
902940

903941
@Override
904942
public void apply(T value, Settings current, Settings previous) {
905-
logger.info("updating [{}] from [{}] to [{}]", key, getRaw(previous), getRaw(current));
943+
logSettingUpdate(Setting.this, current, previous, logger);
906944
consumer.accept(value);
907945
}
908946
}
@@ -1138,6 +1176,16 @@ private static String arrayToParsableString(List<String> array) {
11381176
}
11391177
}
11401178

1179+
static void logSettingUpdate(Setting setting, Settings current, Settings previous, Logger logger) {
1180+
if (logger.isInfoEnabled()) {
1181+
if (setting.isFiltered()) {
1182+
logger.info("updating [{}]", setting.key);
1183+
} else {
1184+
logger.info("updating [{}] from [{}] to [{}]", setting.key, setting.getRaw(previous), setting.getRaw(current));
1185+
}
1186+
}
1187+
}
1188+
11411189
public static Setting<Settings> groupSetting(String key, Property... properties) {
11421190
return groupSetting(key, (s) -> {}, properties);
11431191
}
@@ -1308,8 +1356,8 @@ public static final class AffixKey implements Key {
13081356
if (suffix == null) {
13091357
pattern = Pattern.compile("(" + Pattern.quote(prefix) + "((?:[-\\w]+[.])*[-\\w]+$))");
13101358
} else {
1311-
// the last part of this regexp is for lists since they are represented as x.${namespace}.y.1, x.${namespace}.y.2
1312-
pattern = Pattern.compile("(" + Pattern.quote(prefix) + "([-\\w]+)\\." + Pattern.quote(suffix) + ")(?:\\.\\d+)?");
1359+
// the last part of this regexp is to support both list and group keys
1360+
pattern = Pattern.compile("(" + Pattern.quote(prefix) + "([-\\w]+)\\." + Pattern.quote(suffix) + ")(?:\\..*)?");
13131361
}
13141362
}
13151363

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
import static org.hamcrest.Matchers.containsString;
4040
import static org.hamcrest.Matchers.equalTo;
41+
import static org.hamcrest.Matchers.hasSize;
4142
import static org.hamcrest.Matchers.hasToString;
4243
import static org.hamcrest.Matchers.instanceOf;
4344
import static org.hamcrest.Matchers.is;
@@ -712,4 +713,79 @@ public void testTimeValue() {
712713
assertThat(setting.get(Settings.EMPTY).getMillis(), equalTo(random.getMillis() * factor));
713714
}
714715

716+
public void testSettingsGroupUpdater() {
717+
Setting<Integer> intSetting = Setting.intSetting("prefix.foo", 1, Property.NodeScope, Property.Dynamic);
718+
Setting<Integer> intSetting2 = Setting.intSetting("prefix.same", 1, Property.NodeScope, Property.Dynamic);
719+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
720+
Arrays.asList(intSetting, intSetting2));
721+
722+
Settings current = Settings.builder().put("prefix.foo", 123).put("prefix.same", 5555).build();
723+
Settings previous = Settings.builder().put("prefix.foo", 321).put("prefix.same", 5555).build();
724+
assertTrue(updater.apply(current, previous));
725+
}
726+
727+
public void testSettingsGroupUpdaterRemoval() {
728+
Setting<Integer> intSetting = Setting.intSetting("prefix.foo", 1, Property.NodeScope, Property.Dynamic);
729+
Setting<Integer> intSetting2 = Setting.intSetting("prefix.same", 1, Property.NodeScope, Property.Dynamic);
730+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
731+
Arrays.asList(intSetting, intSetting2));
732+
733+
Settings current = Settings.builder().put("prefix.same", 5555).build();
734+
Settings previous = Settings.builder().put("prefix.foo", 321).put("prefix.same", 5555).build();
735+
assertTrue(updater.apply(current, previous));
736+
}
737+
738+
public void testSettingsGroupUpdaterWithAffixSetting() {
739+
Setting<Integer> intSetting = Setting.intSetting("prefix.foo", 1, Property.NodeScope, Property.Dynamic);
740+
Setting.AffixSetting<String> prefixKeySetting =
741+
Setting.prefixKeySetting("prefix.foo.bar.", key -> Setting.simpleString(key, Property.NodeScope, Property.Dynamic));
742+
Setting.AffixSetting<String> affixSetting =
743+
Setting.affixKeySetting("prefix.foo.", "suffix", key -> Setting.simpleString(key,Property.NodeScope, Property.Dynamic));
744+
745+
AbstractScopedSettings.SettingUpdater<Settings> updater = Setting.groupedSettingsUpdater(s -> {}, logger,
746+
Arrays.asList(intSetting, prefixKeySetting, affixSetting));
747+
748+
Settings.Builder currentSettingsBuilder = Settings.builder()
749+
.put("prefix.foo.bar.baz", "foo")
750+
.put("prefix.foo.infix.suffix", "foo");
751+
Settings.Builder previousSettingsBuilder = Settings.builder()
752+
.put("prefix.foo.bar.baz", "foo")
753+
.put("prefix.foo.infix.suffix", "foo");
754+
boolean removePrefixKeySetting = randomBoolean();
755+
boolean changePrefixKeySetting = randomBoolean();
756+
boolean removeAffixKeySetting = randomBoolean();
757+
boolean changeAffixKeySetting = randomBoolean();
758+
boolean removeAffixNamespace = randomBoolean();
759+
760+
if (removePrefixKeySetting) {
761+
previousSettingsBuilder.remove("prefix.foo.bar.baz");
762+
}
763+
if (changePrefixKeySetting) {
764+
currentSettingsBuilder.put("prefix.foo.bar.baz", "bar");
765+
}
766+
if (removeAffixKeySetting) {
767+
previousSettingsBuilder.remove("prefix.foo.infix.suffix");
768+
}
769+
if (changeAffixKeySetting) {
770+
currentSettingsBuilder.put("prefix.foo.infix.suffix", "bar");
771+
}
772+
if (removeAffixKeySetting == false && changeAffixKeySetting == false && removeAffixNamespace) {
773+
currentSettingsBuilder.remove("prefix.foo.infix.suffix");
774+
currentSettingsBuilder.put("prefix.foo.infix2.suffix", "bar");
775+
previousSettingsBuilder.put("prefix.foo.infix2.suffix", "bar");
776+
}
777+
778+
boolean expectedChange = removeAffixKeySetting || removePrefixKeySetting || changeAffixKeySetting || changePrefixKeySetting
779+
|| removeAffixNamespace;
780+
assertThat(updater.apply(currentSettingsBuilder.build(), previousSettingsBuilder.build()), is(expectedChange));
781+
}
782+
783+
public void testAffixNamespacesWithGroupSetting() {
784+
final Setting.AffixSetting<Settings> affixSetting =
785+
Setting.affixKeySetting("prefix.","suffix",
786+
(key) -> Setting.groupSetting(key + ".", Setting.Property.Dynamic, Setting.Property.NodeScope));
787+
788+
assertThat(affixSetting.getNamespaces(Settings.builder().put("prefix.infix.suffix", "anything").build()), hasSize(1));
789+
assertThat(affixSetting.getNamespaces(Settings.builder().put("prefix.infix.suffix.anything", "anything").build()), hasSize(1));
790+
}
715791
}

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,22 @@
1818
*/
1919
package org.elasticsearch.common.settings;
2020

21-
import org.elasticsearch.common.Strings;
21+
import org.apache.logging.log4j.Level;
22+
import org.apache.logging.log4j.Logger;
23+
import org.elasticsearch.common.logging.Loggers;
24+
import org.elasticsearch.common.logging.ServerLoggers;
25+
import org.elasticsearch.common.settings.Setting.Property;
2226
import org.elasticsearch.common.xcontent.XContentBuilder;
2327
import org.elasticsearch.common.xcontent.json.JsonXContent;
2428
import org.elasticsearch.rest.RestRequest;
2529
import org.elasticsearch.test.ESTestCase;
30+
import org.elasticsearch.test.MockLogAppender;
2631
import org.elasticsearch.test.rest.FakeRestRequest;
2732

2833
import java.io.IOException;
2934
import java.util.Arrays;
3035
import java.util.HashSet;
36+
import java.util.function.Consumer;
3137

3238
import static org.hamcrest.CoreMatchers.equalTo;
3339

@@ -100,7 +106,43 @@ public void testSettingsFiltering() throws IOException {
100106
.build(),
101107
"a.b.*.d"
102108
);
109+
}
110+
111+
public void testFilteredSettingIsNotLogged() throws Exception {
112+
Settings oldSettings = Settings.builder().put("key", "old").build();
113+
Settings newSettings = Settings.builder().put("key", "new").build();
114+
115+
Setting<String> filteredSetting = Setting.simpleString("key", Property.Filtered);
116+
assertExpectedLogMessages((testLogger) -> Setting.logSettingUpdate(filteredSetting, newSettings, oldSettings, testLogger),
117+
new MockLogAppender.SeenEventExpectation("secure logging", "org.elasticsearch.test", Level.INFO, "updating [key]"),
118+
new MockLogAppender.UnseenEventExpectation("unwanted old setting name", "org.elasticsearch.test", Level.INFO, "*old*"),
119+
new MockLogAppender.UnseenEventExpectation("unwanted new setting name", "org.elasticsearch.test", Level.INFO, "*new*")
120+
);
121+
}
122+
123+
public void testRegularSettingUpdateIsFullyLogged() throws Exception {
124+
Settings oldSettings = Settings.builder().put("key", "old").build();
125+
Settings newSettings = Settings.builder().put("key", "new").build();
126+
127+
Setting<String> regularSetting = Setting.simpleString("key");
128+
assertExpectedLogMessages((testLogger) -> Setting.logSettingUpdate(regularSetting, newSettings, oldSettings, testLogger),
129+
new MockLogAppender.SeenEventExpectation("regular logging", "org.elasticsearch.test", Level.INFO,
130+
"updating [key] from [old] to [new]"));
131+
}
103132

133+
private void assertExpectedLogMessages(Consumer<Logger> consumer,
134+
MockLogAppender.LoggingExpectation ... expectations) throws IllegalAccessException {
135+
Logger testLogger = Loggers.getLogger("org.elasticsearch.test");
136+
MockLogAppender appender = new MockLogAppender();
137+
ServerLoggers.addAppender(testLogger, appender);
138+
try {
139+
appender.start();
140+
Arrays.stream(expectations).forEach(appender::addExpectation);
141+
consumer.accept(testLogger);
142+
appender.assertAllExpectationsMatched();
143+
} finally {
144+
ServerLoggers.removeAppender(testLogger, appender);
145+
}
104146
}
105147

106148
private void testFiltering(Settings source, Settings filtered, String... patterns) throws IOException {

test/framework/src/main/java/org/elasticsearch/test/MockLogAppender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public void match(LogEvent event) {
9292
saw = true;
9393
}
9494
} else {
95-
if (event.getMessage().toString().contains(message)) {
95+
if (event.getMessage().getFormattedMessage().contains(message)) {
9696
saw = true;
9797
}
9898
}

0 commit comments

Comments
 (0)