Browse Source

add rest API to reset settings

Simon Willnauer 9 years ago
parent
commit
2e27ee393f

+ 1 - 19
core/src/main/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsRequest.java

@@ -44,8 +44,6 @@ public class ClusterUpdateSettingsRequest extends AcknowledgedRequest<ClusterUpd
 
     private Settings transientSettings = EMPTY_SETTINGS;
     private Settings persistentSettings = EMPTY_SETTINGS;
-    private Set<String> transientReset = new HashSet<>();
-    private Set<String> persistentReset = new HashSet<>();
 
     public ClusterUpdateSettingsRequest() {
     }
@@ -53,7 +51,7 @@ public class ClusterUpdateSettingsRequest extends AcknowledgedRequest<ClusterUpd
     @Override
     public ActionRequestValidationException validate() {
         ActionRequestValidationException validationException = null;
-        if (transientSettings.getAsMap().isEmpty() && persistentSettings.getAsMap().isEmpty() && transientReset.isEmpty() && persistentReset.isEmpty()) {
+        if (transientSettings.getAsMap().isEmpty() && persistentSettings.getAsMap().isEmpty()) {
             validationException = addValidationError("no settings to update", validationException);
         }
         return validationException;
@@ -67,18 +65,6 @@ public class ClusterUpdateSettingsRequest extends AcknowledgedRequest<ClusterUpd
         return persistentSettings;
     }
 
-    public Set<String> getTransientReset() { return Collections.unmodifiableSet(transientReset); }
-
-    public Set<String> getPersistentReset() { return Collections.unmodifiableSet(persistentReset); }
-
-    public void addTransientResetKeys(Collection<String> keys) {
-        transientReset.addAll(keys);
-    }
-
-    public void addPersistentResetKeys(Collection<String> keys) {
-        persistentReset.addAll(keys);
-    }
-
     /**
      * Sets the transient settings to be updated. They will not survive a full cluster restart
      */
@@ -162,8 +148,6 @@ public class ClusterUpdateSettingsRequest extends AcknowledgedRequest<ClusterUpd
         super.readFrom(in);
         transientSettings = readSettingsFromStream(in);
         persistentSettings = readSettingsFromStream(in);
-        transientReset = new HashSet<>(Arrays.asList(in.readStringArray()));
-        persistentReset = new HashSet<>(Arrays.asList(in.readStringArray()));
         readTimeout(in);
     }
 
@@ -172,8 +156,6 @@ public class ClusterUpdateSettingsRequest extends AcknowledgedRequest<ClusterUpd
         super.writeTo(out);
         writeSettingsToStream(transientSettings, out);
         writeSettingsToStream(persistentSettings, out);
-        out.writeStringArray(transientReset.toArray(new String[0]));
-        out.writeStringArray(persistentReset.toArray(new String[0]));
         writeTimeout(out);
     }
 }

+ 0 - 20
core/src/main/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsRequestBuilder.java

@@ -99,24 +99,4 @@ public class ClusterUpdateSettingsRequestBuilder extends AcknowledgedRequestBuil
         request.persistentSettings(settings);
         return this;
     }
-
-    public ClusterUpdateSettingsRequestBuilder addTransientResetKeys(Collection<String> keys) {
-        request.addTransientResetKeys(keys);
-        return this;
-    }
-
-    public ClusterUpdateSettingsRequestBuilder addPersistentResetKeys(Collection<String> keys) {
-        request.addPersistentResetKeys(keys);
-        return this;
-    }
-
-    public ClusterUpdateSettingsRequestBuilder addTransientResetKeys(String... keys) {
-        request.addTransientResetKeys(Arrays.asList(keys));
-        return this;
-    }
-
-    public ClusterUpdateSettingsRequestBuilder addPersistentResetKeys(String... keys) {
-        request.addPersistentResetKeys(Arrays.asList(keys));
-        return this;
-    }
 }

+ 130 - 0
core/src/main/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdater.java

@@ -0,0 +1,130 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.action.admin.cluster.settings;
+
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlocks;
+import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.common.regex.Regex;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.ClusterSettingsService;
+import org.elasticsearch.common.settings.Settings;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.cluster.ClusterState.builder;
+
+/**
+ * Updates transient and persistent cluster state settings if there are any changes
+ * due to the update.
+ */
+final class SettingsUpdater {
+    final Settings.Builder transientUpdates = Settings.settingsBuilder();
+    final Settings.Builder persistentUpdates = Settings.settingsBuilder();
+    private final ClusterSettings dynamicSettings;
+    private final ClusterSettingsService clusterSettingsService;
+
+    SettingsUpdater(ClusterSettingsService clusterSettingsService) {
+        this.dynamicSettings = clusterSettingsService.getClusterSettings();
+        this.clusterSettingsService = clusterSettingsService;
+    }
+
+    synchronized Settings getTransientUpdates() {
+        return transientUpdates.build();
+    }
+
+    synchronized Settings getPersistentUpdate() {
+        return persistentUpdates.build();
+    }
+
+    synchronized ClusterState updateSettings(final ClusterState currentState, Settings transientToApply, Settings persistentToApply) {
+        boolean changed = false;
+        Settings.Builder transientSettings = Settings.settingsBuilder();
+        transientSettings.put(currentState.metaData().transientSettings());
+        changed |= apply(transientToApply, transientSettings, transientUpdates, "transient");
+
+        Settings.Builder persistentSettings = Settings.settingsBuilder();
+        persistentSettings.put(currentState.metaData().persistentSettings());
+        changed |= apply(persistentToApply, persistentSettings, persistentUpdates, "persistent");
+
+        if (!changed) {
+            return currentState;
+        }
+
+        MetaData.Builder metaData = MetaData.builder(currentState.metaData())
+            .persistentSettings(persistentSettings.build())
+            .transientSettings(transientSettings.build());
+
+        ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
+        boolean updatedReadOnly = MetaData.SETTING_READ_ONLY_SETTING.get(metaData.persistentSettings()) || MetaData.SETTING_READ_ONLY_SETTING.get(metaData.transientSettings());
+        if (updatedReadOnly) {
+            blocks.addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
+        } else {
+            blocks.removeGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
+        }
+        ClusterState build = builder(currentState).metaData(metaData).blocks(blocks).build();
+        Settings settings = build.metaData().settings();
+        // now we try to apply things and if they are invalid we fail
+        // this dryRun will validate & parse settings but won't actually apply them.
+        clusterSettingsService.dryRun(settings);
+        return build;
+    }
+
+    private boolean apply(Settings toApply, Settings.Builder target, Settings.Builder updates, String type) {
+        boolean changed = false;
+        final Set<String> toRemove = new HashSet<>();
+        Settings.Builder settingsBuilder = Settings.settingsBuilder();
+        for (Map.Entry<String, String> entry : toApply.getAsMap().entrySet()) {
+            if (entry.getValue() == null) {
+                toRemove.add(entry.getKey());
+            } else if (dynamicSettings.isLoggerSetting(entry.getKey()) || dynamicSettings.hasDynamicSetting(entry.getKey())) {
+                settingsBuilder.put(entry.getKey(), entry.getValue());
+                updates.put(entry.getKey(), entry.getValue());
+                changed = true;
+            } else {
+                throw new IllegalArgumentException(type + " setting [" + entry.getKey() + "], not dynamically updateable");
+            }
+
+        }
+        changed |= applyDeletes(toRemove, target);
+        target.put(settingsBuilder.build());
+        return changed;
+    }
+
+    private final boolean applyDeletes(Set<String> deletes, Settings.Builder builder) {
+        boolean changed = false;
+        for (String entry : deletes) {
+            Set<String> keysToRemove = new HashSet<>();
+            Set<String> keySet = builder.internalMap().keySet();
+            for (String key : keySet) {
+                if (Regex.simpleMatch(entry, key)) {
+                    keysToRemove.add(key);
+                }
+            }
+            for (String key : keysToRemove) {
+                builder.remove(key);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+}

+ 10 - 94
core/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java

@@ -28,26 +28,19 @@ import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.block.ClusterBlockException;
 import org.elasticsearch.cluster.block.ClusterBlockLevel;
-import org.elasticsearch.cluster.block.ClusterBlocks;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.metadata.MetaData;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.cluster.routing.allocation.AllocationService;
 import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
-import org.elasticsearch.common.settings.ClusterSettings;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.regex.Regex;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.ClusterSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
 import static org.elasticsearch.cluster.ClusterState.builder;
 
 /**
@@ -57,16 +50,13 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
 
     private final AllocationService allocationService;
 
-    private final ClusterSettings dynamicSettings;
     private final ClusterSettingsService clusterSettingsService;
 
     @Inject
     public TransportClusterUpdateSettingsAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
-                                                AllocationService allocationService, ClusterSettings dynamicSettings,
-                                                ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver, ClusterSettingsService clusterSettingsService) {
+                                                AllocationService allocationService, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver, ClusterSettingsService clusterSettingsService) {
         super(settings, ClusterUpdateSettingsAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, ClusterUpdateSettingsRequest::new);
         this.allocationService = allocationService;
-        this.dynamicSettings = dynamicSettings;
         this.clusterSettingsService = clusterSettingsService;
     }
 
@@ -93,9 +83,7 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
 
     @Override
     protected void masterOperation(final ClusterUpdateSettingsRequest request, final ClusterState state, final ActionListener<ClusterUpdateSettingsResponse> listener) {
-        final Settings.Builder transientUpdates = Settings.settingsBuilder();
-        final Settings.Builder persistentUpdates = Settings.settingsBuilder();
-
+        final SettingsUpdater updater = new SettingsUpdater(clusterSettingsService);
         clusterService.submitStateUpdateTask("cluster_update_settings",
                 new AckedClusterStateUpdateTask<ClusterUpdateSettingsResponse>(Priority.IMMEDIATE, request, listener) {
 
@@ -103,7 +91,7 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
 
             @Override
             protected ClusterUpdateSettingsResponse newResponse(boolean acknowledged) {
-                return new ClusterUpdateSettingsResponse(acknowledged, transientUpdates.build(), persistentUpdates.build());
+                return new ClusterUpdateSettingsResponse(acknowledged, updater.getTransientUpdates(), updater.getPersistentUpdate());
             }
 
             @Override
@@ -130,7 +118,7 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
                 // so we should *not* execute the reroute.
                 if (!clusterService.state().nodes().localNodeMaster()) {
                     logger.debug("Skipping reroute after cluster update settings, because node is no longer master");
-                    listener.onResponse(new ClusterUpdateSettingsResponse(updateSettingsAcked, transientUpdates.build(), persistentUpdates.build()));
+                    listener.onResponse(new ClusterUpdateSettingsResponse(updateSettingsAcked, updater.getTransientUpdates(), updater.getPersistentUpdate()));
                     return;
                 }
 
@@ -150,13 +138,13 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
                     @Override
                     //we return when the cluster reroute is acked or it times out but the acknowledged flag depends on whether the update settings was acknowledged
                     protected ClusterUpdateSettingsResponse newResponse(boolean acknowledged) {
-                        return new ClusterUpdateSettingsResponse(updateSettingsAcked && acknowledged, transientUpdates.build(), persistentUpdates.build());
+                        return new ClusterUpdateSettingsResponse(updateSettingsAcked && acknowledged, updater.getTransientUpdates(), updater.getPersistentUpdate());
                     }
 
                     @Override
                     public void onNoLongerMaster(String source) {
                         logger.debug("failed to preform reroute after cluster settings were updated - current node is no longer a master");
-                        listener.onResponse(new ClusterUpdateSettingsResponse(updateSettingsAcked, transientUpdates.build(), persistentUpdates.build()));
+                        listener.onResponse(new ClusterUpdateSettingsResponse(updateSettingsAcked, updater.getTransientUpdates(), updater.getPersistentUpdate()));
                     }
 
                     @Override
@@ -186,83 +174,11 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeAct
 
             @Override
             public ClusterState execute(final ClusterState currentState) {
-                Settings.Builder transientSettings = Settings.settingsBuilder();
-                transientSettings.put(currentState.metaData().transientSettings());
-                for (Map.Entry<String, String> entry : request.transientSettings().getAsMap().entrySet()) {
-                    if (dynamicSettings.isLoggerSetting(entry.getKey()) || dynamicSettings.hasDynamicSetting(entry.getKey())) {
-                        transientSettings.put(entry.getKey(), entry.getValue());
-                        transientUpdates.put(entry.getKey(), entry.getValue());
-                        changed = true;
-                    } else {
-                        throw new IllegalArgumentException("transient setting [" + entry.getKey() + "], not dynamically updateable");
-                    }
-                }
-
-                Settings.Builder persistentSettings = Settings.settingsBuilder();
-                persistentSettings.put(currentState.metaData().persistentSettings());
-                for (Map.Entry<String, String> entry : request.persistentSettings().getAsMap().entrySet()) {
-                    if (dynamicSettings.isLoggerSetting(entry.getKey()) || dynamicSettings.hasDynamicSetting(entry.getKey())) {
-                        persistentSettings.put(entry.getKey(), entry.getValue());
-                        persistentUpdates.put(entry.getKey(), entry.getValue());
-                        changed = true;
-                    } else {
-                        throw new IllegalArgumentException("persistent setting [" + entry.getKey() + "], not dynamically updateable");
-                    }
-                }
-
-                for (String entry : request.getPersistentReset()) {
-                    Set<String> strings = persistentSettings.internalMap().keySet();
-                    Set<String> keysToRemove = new HashSet<String>();
-                    for (String key : strings) {
-                        if (Regex.simpleMatch(entry, key)) {
-                            keysToRemove.add(key);
-                        }
-                    }
-                    for (String keyToRemove : keysToRemove) {
-                        persistentSettings.remove(keyToRemove);
-                        persistentUpdates.remove(keyToRemove);
-                    }
-                    changed |= keysToRemove.isEmpty() == false;
-                }
-
-                for (String entry : request.getTransientReset()) {
-                    Set<String> strings = transientSettings.internalMap().keySet();
-                    Set<String> keysToRemove = new HashSet<>();
-                    for (String key : strings) {
-                        if (Regex.simpleMatch(entry, key)) {
-                            keysToRemove.add(key);
-                        }
-                    }
-                    for (String keyToRemove : keysToRemove) {
-                        transientSettings.remove(keyToRemove);
-                        transientUpdates.remove(keyToRemove);
-                    }
-                    changed |= keysToRemove.isEmpty() == false;
-                }
-
-
-                if (!changed) {
-                    return currentState;
-                }
-
-                MetaData.Builder metaData = MetaData.builder(currentState.metaData())
-                        .persistentSettings(persistentSettings.build())
-                        .transientSettings(transientSettings.build());
-
-                ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
-                boolean updatedReadOnly = MetaData.SETTING_READ_ONLY_SETTING.get(metaData.persistentSettings()) || MetaData.SETTING_READ_ONLY_SETTING.get(metaData.transientSettings());
-                if (updatedReadOnly) {
-                    blocks.addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
-                } else {
-                    blocks.removeGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
-                }
-                ClusterState build = builder(currentState).metaData(metaData).blocks(blocks).build();
-                Settings settings = build.metaData().settings();
-                // now we try to apply things and if they are invalid we fail
-                // this dryRun will validate & parse settings but won't actually apply them.
-                clusterSettingsService.dryRun(settings);
-                return build;
+                ClusterState clusterState = updater.updateSettings(currentState, request.transientSettings(), request.persistentSettings());
+                changed = clusterState != currentState;
+                return clusterState;
             }
         });
     }
+
 }

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

@@ -715,7 +715,7 @@ public final class Settings implements ToXContent {
         Builder builder = new Builder();
         int numberOfSettings = in.readVInt();
         for (int i = 0; i < numberOfSettings; i++) {
-            builder.put(in.readString(), in.readString());
+            builder.put(in.readString(), in.readOptionalString());
         }
         return builder.build();
     }
@@ -724,7 +724,7 @@ public final class Settings implements ToXContent {
         out.writeVInt(settings.getAsMap().size());
         for (Map.Entry<String, String> entry : settings.getAsMap().entrySet()) {
             out.writeString(entry.getKey());
-            out.writeString(entry.getValue());
+            out.writeOptionalString(entry.getValue());
         }
     }
 
@@ -827,6 +827,10 @@ public final class Settings implements ToXContent {
             return this;
         }
 
+        public Builder putNull(String key) {
+            return put(key, (String) null);
+        }
+
         /**
          * Sets a setting with the provided setting key and class as value.
          *

+ 10 - 8
core/src/main/java/org/elasticsearch/common/settings/loader/XContentSettingsLoader.java

@@ -103,9 +103,9 @@ public abstract class XContentSettingsLoader implements SettingsLoader {
             } else if (token == XContentParser.Token.FIELD_NAME) {
                 currentFieldName = parser.currentName();
             } else if (token == XContentParser.Token.VALUE_NULL) {
-                // ignore this
+                serializeValue(settings, sb, path, parser, currentFieldName, true);
             } else {
-                serializeValue(settings, sb, path, parser, currentFieldName);
+                serializeValue(settings, sb, path, parser, currentFieldName, false);
 
             }
         }
@@ -126,31 +126,33 @@ public abstract class XContentSettingsLoader implements SettingsLoader {
             } else if (token == XContentParser.Token.FIELD_NAME) {
                 fieldName = parser.currentName();
             } else if (token == XContentParser.Token.VALUE_NULL) {
+                serializeValue(settings, sb, path, parser, fieldName + '.' + (counter++), true);
                 // ignore
             } else {
-                serializeValue(settings, sb, path, parser, fieldName + '.' + (counter++));
+                serializeValue(settings, sb, path, parser, fieldName + '.' + (counter++), false);
             }
         }
     }
 
-    private void serializeValue(Map<String, String> settings, StringBuilder sb, List<String> path, XContentParser parser, String fieldName) throws IOException {
+    private void serializeValue(Map<String, String> settings, StringBuilder sb, List<String> path, XContentParser parser, String fieldName, boolean isNull) throws IOException {
         sb.setLength(0);
         for (String pathEle : path) {
             sb.append(pathEle).append('.');
         }
         sb.append(fieldName);
         String key = sb.toString();
-        String currentValue = parser.text();
-        String previousValue = settings.put(key, currentValue);
-        if (previousValue != null) {
+        String currentValue = isNull ? null : parser.text();
+
+        if (settings.containsKey(key)) {
             throw new ElasticsearchParseException(
                     "duplicate settings key [{}] found at line number [{}], column number [{}], previous value [{}], current value [{}]",
                     key,
                     parser.getTokenLocation().lineNumber,
                     parser.getTokenLocation().columnNumber,
-                    previousValue,
+                    settings.get(key),
                     currentValue
             );
         }
+        settings.put(key, currentValue);
     }
 }

+ 127 - 0
core/src/test/java/org/elasticsearch/action/admin/cluster/settings/SettingsUpdaterTests.java

@@ -0,0 +1,127 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.action.admin.cluster.settings;
+
+import org.elasticsearch.cluster.ClusterName;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlocks;
+import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.cluster.routing.allocation.allocator.BalancedShardsAllocator;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.ClusterSettingsService;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+public class SettingsUpdaterTests extends ESTestCase {
+
+
+    public void testUpdateSetting() {
+        AtomicReference<Float> index = new AtomicReference<>();
+        AtomicReference<Float> shard = new AtomicReference<>();
+        ClusterState.Builder builder = ClusterState.builder(new ClusterName("foo"));
+        ClusterSettingsService settingsService = new ClusterSettingsService(Settings.EMPTY, new ClusterSettings(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING, index::set);
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING, shard::set);
+        SettingsUpdater updater = new SettingsUpdater(settingsService);
+        MetaData.Builder metaData = MetaData.builder()
+            .persistentSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 2.5).build())
+            .transientSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 3.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 4.5).build());
+        ClusterState build = builder.metaData(metaData).build();
+        ClusterState clusterState = updater.updateSettings(build, Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 0.5).build(),
+            Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 0.4).build());
+        assertNotSame(clusterState, build);
+        assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 0.4, 0.1);
+        assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 2.5, 0.1);
+        assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().transientSettings()), 0.5, 0.1);
+        assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().transientSettings()), 4.5, 0.1);
+
+        clusterState = updater.updateSettings(clusterState, Settings.builder().putNull("cluster.routing.*").build(),
+            Settings.EMPTY);
+        assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 0.4, 0.1);
+        assertEquals(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 2.5, 0.1);
+        assertFalse(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings()));
+        assertFalse(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings()));
+
+        clusterState = updater.updateSettings(clusterState,
+            Settings.EMPTY,  Settings.builder().putNull("cluster.routing.*").put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 10.0).build());
+
+        assertEquals(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.get(clusterState.metaData().persistentSettings()), 10.0, 0.1);
+        assertFalse(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().persistentSettings()));
+        assertFalse(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings()));
+        assertFalse(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.exists(clusterState.metaData().transientSettings()));
+        assertNull("updater only does a dryRun", index.get());
+        assertNull("updater only does a dryRun", shard.get());
+    }
+
+    public void testAllOrNothing() {
+        ClusterState.Builder builder = ClusterState.builder(new ClusterName("foo"));
+        ClusterSettingsService settingsService = new ClusterSettingsService(Settings.EMPTY, new ClusterSettings(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
+        AtomicReference<Float> index = new AtomicReference<>();
+        AtomicReference<Float> shard = new AtomicReference<>();
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING, index::set);
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING, shard::set);
+        SettingsUpdater updater = new SettingsUpdater(settingsService);
+        MetaData.Builder metaData = MetaData.builder()
+            .persistentSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 2.5).build())
+            .transientSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 3.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 4.5).build());
+        ClusterState build = builder.metaData(metaData).build();
+
+        try {
+            updater.updateSettings(build, Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), "not a float").build(),
+                Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), "not a float").put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build());
+            fail("all or nothing");
+        } catch (IllegalArgumentException ex) {
+            assertEquals("Failed to parse value [not a float] for setting [cluster.routing.allocation.balance.index]", ex.getMessage());
+        }
+        assertNull("updater only does a dryRun", index.get());
+        assertNull("updater only does a dryRun", shard.get());
+    }
+
+    public void testClusterBlock() {
+        ClusterState.Builder builder = ClusterState.builder(new ClusterName("foo"));
+        ClusterSettingsService settingsService = new ClusterSettingsService(Settings.EMPTY, new ClusterSettings(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
+        AtomicReference<Float> index = new AtomicReference<>();
+        AtomicReference<Float> shard = new AtomicReference<>();
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING, index::set);
+        settingsService.addSettingsUpdateConsumer(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING, shard::set);
+        SettingsUpdater updater = new SettingsUpdater(settingsService);
+        MetaData.Builder metaData = MetaData.builder()
+            .persistentSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 2.5).build())
+            .transientSettings(Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 3.5)
+                .put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 4.5).build());
+        ClusterState build = builder.metaData(metaData).build();
+
+        ClusterState clusterState = updater.updateSettings(build, Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), true).build(),
+            Settings.builder().put(BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING.getKey(), 1.6).put(BalancedShardsAllocator.SHARD_BALANCE_FACTOR_SETTING.getKey(), 1.0f).build());
+        assertEquals(clusterState.blocks().global().size(), 1);
+        assertEquals(clusterState.blocks().global().iterator().next(), MetaData.CLUSTER_READ_ONLY_BLOCK);
+
+        clusterState = updater.updateSettings(build, Settings.EMPTY,
+            Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), false).build());
+        assertEquals(clusterState.blocks().global().size(), 0);
+
+    }
+}

+ 31 - 4
core/src/test/java/org/elasticsearch/cluster/settings/ClusterSettingsIT.java

@@ -34,6 +34,7 @@ import org.elasticsearch.index.store.IndexStoreConfig;
 import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
 
+import static org.elasticsearch.common.inject.matcher.Matchers.not;
 import static org.elasticsearch.common.settings.Settings.settingsBuilder;
 import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
@@ -59,6 +60,32 @@ public class ClusterSettingsIT extends ESIntegTestCase {
         }
     }
 
+    public void testDeleteIsAppliedFirst() {
+        DiscoverySettings discoverySettings = internalCluster().getInstance(DiscoverySettings.class);
+
+        assertEquals(discoverySettings.getPublishTimeout(), DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY));
+        assertTrue(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY));
+
+        ClusterUpdateSettingsResponse response = client().admin().cluster()
+            .prepareUpdateSettings()
+            .setTransientSettings(Settings.builder()
+                .put(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), false)
+                .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s").build())
+            .get();
+
+        assertAcked(response);
+        assertEquals(response.getTransientSettings().getAsMap().get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()), "1s");
+        assertTrue(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY));
+        assertFalse(response.getTransientSettings().getAsBoolean(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), null));
+
+        response = client().admin().cluster()
+            .prepareUpdateSettings()
+            .setTransientSettings(Settings.builder().putNull((randomBoolean() ? "discovery.zen.*" : "*")).put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "2s"))
+            .get();
+        assertEquals(response.getTransientSettings().getAsMap().get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()), "2s");
+        assertNull(response.getTransientSettings().getAsBoolean(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), null));
+    }
+
     public void testResetClusterSetting() {
         DiscoverySettings discoverySettings = internalCluster().getInstance(DiscoverySettings.class);
 
@@ -78,7 +105,7 @@ public class ClusterSettingsIT extends ESIntegTestCase {
 
         response = client().admin().cluster()
                 .prepareUpdateSettings()
-                .addTransientResetKeys(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey())
+                .setTransientSettings(Settings.builder().putNull(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()))
                 .get();
 
         assertAcked(response);
@@ -99,7 +126,7 @@ public class ClusterSettingsIT extends ESIntegTestCase {
         assertFalse(discoverySettings.getPublishDiff());
         response = client().admin().cluster()
                 .prepareUpdateSettings()
-                .addTransientResetKeys(randomBoolean() ? "discovery.zen.*" : "*")
+                .setTransientSettings(Settings.builder().putNull((randomBoolean() ? "discovery.zen.*" : "*")))
                 .get();
 
         assertNull(response.getTransientSettings().getAsMap().get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));
@@ -121,7 +148,7 @@ public class ClusterSettingsIT extends ESIntegTestCase {
 
         response = client().admin().cluster()
                 .prepareUpdateSettings()
-                .addPersistentResetKeys(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey())
+                .setPersistentSettings(Settings.builder().putNull((DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey())))
                 .get();
 
         assertAcked(response);
@@ -143,7 +170,7 @@ public class ClusterSettingsIT extends ESIntegTestCase {
         assertFalse(discoverySettings.getPublishDiff());
         response = client().admin().cluster()
                 .prepareUpdateSettings()
-                .addPersistentResetKeys(randomBoolean() ? "discovery.zen.*" : "*")
+                .setPersistentSettings(Settings.builder().putNull((randomBoolean() ? "discovery.zen.*" : "*")))
                 .get();
 
         assertNull(response.getPersistentSettings().getAsMap().get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));

+ 0 - 1
core/src/test/java/org/elasticsearch/common/settings/SettingsServiceTests.java

@@ -19,7 +19,6 @@
 package org.elasticsearch.common.settings;
 
 import org.elasticsearch.test.ESTestCase;
-
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class SettingsServiceTests extends ESTestCase {

+ 25 - 0
docs/reference/cluster/update-settings.asciidoc

@@ -38,6 +38,31 @@ last example will be:
 }'
 --------------------------------------------------
 
+Resetting persistent or transient settings can be done by assigning a
+`null` value. If a transient setting is reset, the persistent setting
+is applied if available. Otherwise Elasticsearch will fallback to the setting
+defined at the configuration file or, if not existent, to the default
+value. Here is an example:
+
+[source,js]
+--------------------------------------------------
+curl -XPUT localhost:9200/_cluster/settings -d '{
+    "transient" : {
+        "discovery.zen.minimum_master_nodes" : null
+    }
+}'
+--------------------------------------------------
+
+Reset settings will not be included in the cluster response. So
+the response for the last example will be:
+
+[source,js]
+--------------------------------------------------
+{
+    "persistent" : {},
+    "transient" : {}
+}
+
 Cluster wide settings can be returned using:
 
 [source,js]

+ 31 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/cluster.put_settings/11_reset.yaml

@@ -0,0 +1,31 @@
+---
+"Test reset cluster settings":
+  - do:
+      cluster.put_settings:
+        body:
+          persistent:
+            cluster.routing.allocation.disk.threshold_enabled: false
+        flat_settings: true
+
+  - match: {persistent: {cluster.routing.allocation.disk.threshold_enabled: "false"}}
+
+  - do:
+      cluster.get_settings:
+        flat_settings: true
+
+  - match: {persistent: {cluster.routing.allocation.disk.threshold_enabled: "false"}}
+
+  - do:
+      cluster.put_settings:
+        body:
+          persistent:
+            cluster.routing.allocation.disk.threshold_enabled: null
+        flat_settings: true
+
+  - match: {persistent: {}}
+
+  - do:
+      cluster.get_settings:
+        flat_settings: true
+
+  - match: {persistent: {}}