Allow index settings to be reset by wildcards (#27671)

Index settings didn't support reset by wildcard which also causes
issues like #27537 where archived settings can't be reset. This change
adds support for wildcards like `archived.*` to be used to reset setting to their
defaults or remove them from an index.

Closes #27537
This commit is contained in:
Simon Willnauer 2017-12-06 07:35:37 +01:00 committed by GitHub
parent 234e09a105
commit 70f8ea367b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 17 deletions

View File

@ -38,6 +38,7 @@ import org.elasticsearch.common.Priority;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
@ -54,7 +55,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
@ -164,13 +164,16 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements
Settings.Builder settingsForOpenIndices = Settings.builder();
final Set<String> skippedSettings = new HashSet<>();
indexScopedSettings.validate(normalizedSettings, false); // don't validate dependencies here we check it below
// never allow to change the number of shards
indexScopedSettings.validate(normalizedSettings.filter(s -> Regex.isSimpleMatchPattern(s) == false /* don't validate wildcards */),
false); //don't validate dependencies here we check it below never allow to change the number of shards
for (String key : normalizedSettings.keySet()) {
Setting setting = indexScopedSettings.get(key);
assert setting != null; // we already validated the normalized settings
boolean isWildcard = setting == null && Regex.isSimpleMatchPattern(key);
assert setting != null // we already validated the normalized settings
|| (isWildcard && normalizedSettings.hasValue(key) == false)
: "unknown setting: " + key + " isWildcard: " + isWildcard + " hasValue: " + normalizedSettings.hasValue(key);
settingsForClosedIndices.copy(key, normalizedSettings);
if (setting.isDynamic()) {
if (isWildcard || setting.isDynamic()) {
settingsForOpenIndices.copy(key, normalizedSettings);
} else {
skippedSettings.add(key);

View File

@ -500,6 +500,16 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
return updateSettings(toApply, target, updates, type, false);
}
/**
* Returns <code>true</code> if the given key is a valid delete key
*/
private boolean isValidDelete(String key, boolean onlyDynamic) {
return isFinalSetting(key) == false && // it's not a final setting
(onlyDynamic && isDynamicSetting(key) // it's a dynamicSetting and we only do dynamic settings
|| get(key) == null && key.startsWith(ARCHIVED_SETTINGS_PREFIX) // the setting is not registered AND it's been archived
|| (onlyDynamic == false && get(key) != null)); // if it's not dynamic AND we have a key
}
/**
* Updates a target settings builder with new, updated or deleted settings from a given settings builder.
*
@ -519,21 +529,16 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
final Predicate<String> canUpdate = (key) -> (
isFinalSetting(key) == false && // it's not a final setting
((onlyDynamic == false && get(key) != null) || isDynamicSetting(key)));
final Predicate<String> canRemove = (key) ->(// we can delete if
isFinalSetting(key) == false && // it's not a final setting
(onlyDynamic && isDynamicSetting(key) // it's a dynamicSetting and we only do dynamic settings
|| get(key) == null && key.startsWith(ARCHIVED_SETTINGS_PREFIX) // the setting is not registered AND it's been archived
|| (onlyDynamic == false && get(key) != null))); // if it's not dynamic AND we have a key
for (String key : toApply.keySet()) {
boolean isNull = toApply.get(key) == null;
if (isNull && (canRemove.test(key) || key.endsWith("*"))) {
boolean isDelete = toApply.hasValue(key) == false;
if (isDelete && (isValidDelete(key, onlyDynamic) || key.endsWith("*"))) {
// this either accepts null values that suffice the canUpdate test OR wildcard expressions (key ends with *)
// we don't validate if there is any dynamic setting with that prefix yet we could do in the future
toRemove.add(key);
// we don't set changed here it's set after we apply deletes below if something actually changed
} else if (get(key) == null) {
throw new IllegalArgumentException(type + " setting [" + key + "], not recognized");
} else if (isNull == false && canUpdate.test(key)) {
} else if (isDelete == false && canUpdate.test(key)) {
validate(key, toApply, false); // we might not have a full picture here do to a dependency validation
settingsBuilder.copy(key, toApply);
updates.copy(key, toApply);
@ -546,7 +551,7 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
}
}
}
changed |= applyDeletes(toRemove, target, canRemove);
changed |= applyDeletes(toRemove, target, k -> isValidDelete(k, onlyDynamic));
target.put(settingsBuilder.build());
return changed;
}

View File

@ -306,6 +306,13 @@ public final class Settings implements ToXContentFragment {
}
}
/**
* Returns <code>true</code> iff the given key has a value in this settings object
*/
public boolean hasValue(String key) {
return settings.get(key) != null;
}
/**
* We have to lazy initialize the deprecation logger as otherwise a static logger here would be constructed before logging is configured
* leading to a runtime failure (see {@link LogConfigurator#checkErrorListener()} ). The premature construction would come from any
@ -1229,8 +1236,9 @@ public final class Settings implements ToXContentFragment {
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
while(iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
if (entry.getKey().startsWith(prefix) == false) {
replacements.put(prefix + entry.getKey(), entry.getValue());
String key = entry.getKey();
if (key.startsWith(prefix) == false && key.endsWith("*") == false) {
replacements.put(prefix + key, entry.getValue());
iterator.remove();
}
}

View File

@ -241,10 +241,45 @@ public class UpdateSettingsIT extends ESIntegTestCase {
.actionGet();
}
}
public void testResetDefaultWithWildcard() {
createIndex("test");
client()
.admin()
.indices()
.prepareUpdateSettings("test")
.setSettings(
Settings.builder()
.put("index.refresh_interval", -1))
.execute()
.actionGet();
IndexMetaData indexMetaData = client().admin().cluster().prepareState().execute().actionGet().getState().metaData().index("test");
assertEquals(indexMetaData.getSettings().get("index.refresh_interval"), "-1");
for (IndicesService service : internalCluster().getInstances(IndicesService.class)) {
IndexService indexService = service.indexService(resolveIndex("test"));
if (indexService != null) {
assertEquals(indexService.getIndexSettings().getRefreshInterval().millis(), -1);
}
}
client()
.admin()
.indices()
.prepareUpdateSettings("test")
.setSettings(Settings.builder().putNull("index.ref*"))
.execute()
.actionGet();
indexMetaData = client().admin().cluster().prepareState().execute().actionGet().getState().metaData().index("test");
assertNull(indexMetaData.getSettings().get("index.refresh_interval"));
for (IndicesService service : internalCluster().getInstances(IndicesService.class)) {
IndexService indexService = service.indexService(resolveIndex("test"));
if (indexService != null) {
assertEquals(indexService.getIndexSettings().getRefreshInterval().millis(), 1000);
}
}
}
public void testResetDefault() {
createIndex("test");
client()
.admin()
.indices()