Browse Source

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
Simon Willnauer 7 years ago
parent
commit
70f8ea367b

+ 8 - 5
core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java

@@ -38,6 +38,7 @@ import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.collect.Tuple;
 import org.elasticsearch.common.collect.Tuple;
 import org.elasticsearch.common.component.AbstractComponent;
 import org.elasticsearch.common.component.AbstractComponent;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.regex.Regex;
 import org.elasticsearch.common.settings.IndexScopedSettings;
 import org.elasticsearch.common.settings.IndexScopedSettings;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
@@ -54,7 +55,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
-import java.util.function.Predicate;
 
 
 import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
 import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
 
 
@@ -164,13 +164,16 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements
         Settings.Builder settingsForOpenIndices = Settings.builder();
         Settings.Builder settingsForOpenIndices = Settings.builder();
         final Set<String> skippedSettings = new HashSet<>();
         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()) {
         for (String key : normalizedSettings.keySet()) {
             Setting setting = indexScopedSettings.get(key);
             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);
             settingsForClosedIndices.copy(key, normalizedSettings);
-            if (setting.isDynamic()) {
+            if (isWildcard || setting.isDynamic()) {
                 settingsForOpenIndices.copy(key, normalizedSettings);
                 settingsForOpenIndices.copy(key, normalizedSettings);
             } else {
             } else {
                 skippedSettings.add(key);
                 skippedSettings.add(key);

+ 14 - 9
core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java

@@ -500,6 +500,16 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
         return updateSettings(toApply, target, updates, type, false);
         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.
      * 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) -> (
         final Predicate<String> canUpdate = (key) -> (
             isFinalSetting(key) == false && // it's not a final setting
             isFinalSetting(key) == false && // it's not a final setting
                 ((onlyDynamic == false && get(key) != null) || isDynamicSetting(key)));
                 ((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()) {
         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 *)
                 // 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
                 // we don't validate if there is any dynamic setting with that prefix yet we could do in the future
                 toRemove.add(key);
                 toRemove.add(key);
                 // we don't set changed here it's set after we apply deletes below if something actually changed
                 // we don't set changed here it's set after we apply deletes below if something actually changed
             } else if (get(key) == null) {
             } else if (get(key) == null) {
                 throw new IllegalArgumentException(type + " setting [" + key + "], not recognized");
                 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
                 validate(key, toApply, false); // we might not have a full picture here do to a dependency validation
                 settingsBuilder.copy(key, toApply);
                 settingsBuilder.copy(key, toApply);
                 updates.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());
         target.put(settingsBuilder.build());
         return changed;
         return changed;
     }
     }

+ 10 - 2
core/src/main/java/org/elasticsearch/common/settings/Settings.java

@@ -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
      * 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
      * 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();
             Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
             while(iterator.hasNext()) {
             while(iterator.hasNext()) {
                 Map.Entry<String, Object> entry = iterator.next();
                 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();
                     iterator.remove();
                 }
                 }
             }
             }

+ 36 - 1
core/src/test/java/org/elasticsearch/indices/settings/UpdateSettingsIT.java

@@ -241,10 +241,45 @@ public class UpdateSettingsIT extends ESIntegTestCase {
                 .actionGet();
                 .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() {
     public void testResetDefault() {
         createIndex("test");
         createIndex("test");
-
         client()
         client()
             .admin()
             .admin()
             .indices()
             .indices()