Browse Source

Added `action.destructive_requires_name` that controls whether wildcard expressions and `_all` is allowed to be used for destructive operat Also the delete index api requires always an index to be specified (either concrete index, alias or wildcard expression)

Closes #4549 #4481
Martijn van Groningen 11 years ago
parent
commit
eb63bb259d
18 changed files with 397 additions and 124 deletions
  1. 7 5
      docs/reference/indices/delete-index.asciidoc
  2. 3 2
      docs/reference/indices/open-close.asciidoc
  3. 1 1
      rest-api-spec/api/indices.delete.json
  4. 7 17
      src/main/java/org/elasticsearch/action/admin/indices/close/TransportCloseIndexAction.java
  5. 3 13
      src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java
  6. 8 21
      src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java
  7. 8 2
      src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java
  8. 7 3
      src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java
  9. 15 2
      src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java
  10. 85 0
      src/main/java/org/elasticsearch/action/support/DestructiveOperations.java
  11. 2 0
      src/main/java/org/elasticsearch/cluster/settings/ClusterDynamicSettingsModule.java
  12. 44 48
      src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java
  13. 179 0
      src/test/java/org/elasticsearch/operateAllIndices/DestructiveOperationsIntegrationTests.java
  14. 1 1
      src/test/java/org/elasticsearch/percolator/PercolatorTests.java
  15. 2 2
      src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java
  16. 1 1
      src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java
  17. 1 1
      src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java
  18. 23 5
      src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java

+ 7 - 5
docs/reference/indices/delete-index.asciidoc

@@ -8,10 +8,12 @@ The delete index API allows to delete an existing index.
 $ curl -XDELETE 'http://localhost:9200/twitter/'
 --------------------------------------------------
 
-The above example deletes an index called `twitter`.
+The above example deletes an index called `twitter`. Specifying an index,
+alias or wildcard expression is required.
 
 The delete index API can also be applied to more than one index, or on
-`_all` indices (be careful!). All indices will also be deleted when no
-specific index is provided. In order to disable allowing to delete all
-indices, set `action.disable_delete_all_indices` setting in the config
-to `true`.
+all indices (be careful!) by using `_all` or `*` as index.
+
+In order to disable allowing to delete indices via wildcards or `_all`,
+set `action.destructive_requires_name` setting in the config to `true`.
+This setting can also be changed via the cluster update settings api.

+ 3 - 2
docs/reference/indices/open-close.asciidoc

@@ -23,6 +23,7 @@ disabled using the `ignore_unavailable=true` parameter.
 
 All indices can be opened or closed at once using `_all` as the index name
 or specifying patterns that identify them all (e.g. `*`).
-Closing all indices can be disabled by setting the `action.disable_close_all_indices`
-flag in the config file to `true`.
 
+Identifying indices via wildcards or `_all` can be disabled by setting the
+`action.destructive_requires_name` flag in the config file to `true`.
+This setting can also be changed via the cluster update settings api.

+ 1 - 1
rest-api-spec/api/indices.delete.json

@@ -8,7 +8,7 @@
       "parts": {
         "index": {
           "type" : "list",
-          "description" : "A comma-separated list of indices to delete; use `_all` or empty string to delete all indices"
+          "description" : "A comma-separated list of indices to delete; use `_all` or `*` string to delete all indices"
         }
       },
       "params": {

+ 7 - 17
src/main/java/org/elasticsearch/action/admin/indices/close/TransportCloseIndexAction.java

@@ -20,8 +20,8 @@
 package org.elasticsearch.action.admin.indices.close;
 
 import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction;
 import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
@@ -32,6 +32,7 @@ import org.elasticsearch.cluster.block.ClusterBlockLevel;
 import org.elasticsearch.cluster.metadata.MetaDataIndexStateService;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.settings.NodeSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
@@ -41,15 +42,14 @@ import org.elasticsearch.transport.TransportService;
 public class TransportCloseIndexAction extends TransportMasterNodeOperationAction<CloseIndexRequest, CloseIndexResponse> {
 
     private final MetaDataIndexStateService indexStateService;
-    private final boolean disableCloseAllIndices;
-
+    private final DestructiveOperations destructiveOperations;
 
     @Inject
     public TransportCloseIndexAction(Settings settings, TransportService transportService, ClusterService clusterService,
-                                     ThreadPool threadPool, MetaDataIndexStateService indexStateService) {
+                                     ThreadPool threadPool, MetaDataIndexStateService indexStateService, NodeSettingsService nodeSettingsService) {
         super(settings, transportService, clusterService, threadPool);
         this.indexStateService = indexStateService;
-        this.disableCloseAllIndices = settings.getAsBoolean("action.disable_close_all_indices", false);
+        this.destructiveOperations = new DestructiveOperations(logger, settings, nodeSettingsService);
     }
 
     @Override
@@ -75,17 +75,7 @@ public class TransportCloseIndexAction extends TransportMasterNodeOperationActio
 
     @Override
     protected void doExecute(CloseIndexRequest request, ActionListener<CloseIndexResponse> listener) {
-        ClusterState state = clusterService.state();
-        String[] indicesOrAliases = request.indices();
-        request.indices(state.metaData().concreteIndices(indicesOrAliases, request.indicesOptions()));
-
-        if (disableCloseAllIndices) {
-            if (state.metaData().isExplicitAllIndices(indicesOrAliases) ||
-                    state.metaData().isPatternMatchingAllIndices(indicesOrAliases, request.indices())) {
-                throw new ElasticsearchIllegalArgumentException("closing all indices is disabled");
-            }
-        }
-
+        destructiveOperations.failDestructive(request.indices());
         super.doExecute(request, listener);
     }
 
@@ -96,7 +86,7 @@ public class TransportCloseIndexAction extends TransportMasterNodeOperationActio
 
     @Override
     protected void masterOperation(final CloseIndexRequest request, final ClusterState state, final ActionListener<CloseIndexResponse> listener) throws ElasticsearchException {
-
+        request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions()));
         CloseIndexClusterStateUpdateRequest updateRequest = new CloseIndexClusterStateUpdateRequest()
                 .ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
                 .indices(request.indices());

+ 3 - 13
src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java

@@ -68,7 +68,7 @@ public class DeleteIndexRequest extends MasterNodeOperationRequest<DeleteIndexRe
     @Override
     public ActionRequestValidationException validate() {
         ActionRequestValidationException validationException = null;
-        if (indices == null) {
+        if (indices == null || indices.length == 0) {
             validationException = addValidationError("index / indices is missing", validationException);
         }
         return validationException;
@@ -114,10 +114,7 @@ public class DeleteIndexRequest extends MasterNodeOperationRequest<DeleteIndexRe
     @Override
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
-        indices = new String[in.readVInt()];
-        for (int i = 0; i < indices.length; i++) {
-            indices[i] = in.readString();
-        }
+        indices = in.readStringArray();
         indicesOptions = IndicesOptions.readIndicesOptions(in);
         timeout = readTimeValue(in);
     }
@@ -125,14 +122,7 @@ public class DeleteIndexRequest extends MasterNodeOperationRequest<DeleteIndexRe
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         super.writeTo(out);
-        if (indices == null) {
-            out.writeVInt(0);
-        } else {
-            out.writeVInt(indices.length);
-            for (String index : indices) {
-                out.writeString(index);
-            }
-        }
+        out.writeStringArray(indices);
         indicesOptions.writeIndicesOptions(out);
         timeout.writeTo(out);
     }

+ 8 - 21
src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java

@@ -20,9 +20,8 @@
 package org.elasticsearch.action.admin.indices.delete;
 
 import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.ActionListener;
-import org.elasticsearch.action.admin.indices.mapping.delete.TransportDeleteMappingAction;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction;
 import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
@@ -32,6 +31,7 @@ import org.elasticsearch.cluster.metadata.MetaDataDeleteIndexService;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.concurrent.CountDown;
+import org.elasticsearch.node.settings.NodeSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
@@ -41,19 +41,15 @@ import org.elasticsearch.transport.TransportService;
 public class TransportDeleteIndexAction extends TransportMasterNodeOperationAction<DeleteIndexRequest, DeleteIndexResponse> {
 
     private final MetaDataDeleteIndexService deleteIndexService;
-
-    private final TransportDeleteMappingAction deleteMappingAction;
-
-    private final boolean disableDeleteAllIndices;
+    private final DestructiveOperations destructiveOperations;
 
     @Inject
     public TransportDeleteIndexAction(Settings settings, TransportService transportService, ClusterService clusterService,
-                                      ThreadPool threadPool, MetaDataDeleteIndexService deleteIndexService, TransportDeleteMappingAction deleteMappingAction) {
+                                      ThreadPool threadPool, MetaDataDeleteIndexService deleteIndexService,
+                                      NodeSettingsService nodeSettingsService) {
         super(settings, transportService, clusterService, threadPool);
         this.deleteIndexService = deleteIndexService;
-        this.deleteMappingAction = deleteMappingAction;
-
-        this.disableDeleteAllIndices = settings.getAsBoolean("action.disable_delete_all_indices", false);
+        this.destructiveOperations = new DestructiveOperations(logger, settings, nodeSettingsService);
     }
 
     @Override
@@ -78,17 +74,7 @@ public class TransportDeleteIndexAction extends TransportMasterNodeOperationActi
 
     @Override
     protected void doExecute(DeleteIndexRequest request, ActionListener<DeleteIndexResponse> listener) {
-        ClusterState state = clusterService.state();
-        String[] indicesOrAliases = request.indices();
-
-        request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions()));
-
-        if (disableDeleteAllIndices) {
-            if (state.metaData().isAllIndices(indicesOrAliases) ||
-                    state.metaData().isPatternMatchingAllIndices(indicesOrAliases, request.indices())) {
-                throw new ElasticsearchIllegalArgumentException("deleting all indices is disabled");
-            }
-        }
+        destructiveOperations.failDestructive(request.indices());
         super.doExecute(request, listener);
     }
 
@@ -99,6 +85,7 @@ public class TransportDeleteIndexAction extends TransportMasterNodeOperationActi
 
     @Override
     protected void masterOperation(final DeleteIndexRequest request, final ClusterState state, final ActionListener<DeleteIndexResponse> listener) throws ElasticsearchException {
+        request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions()));
         if (request.indices().length == 0) {
             listener.onResponse(new DeleteIndexResponse(true));
             return;

+ 8 - 2
src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java

@@ -27,6 +27,7 @@ import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
 import org.elasticsearch.action.admin.indices.refresh.TransportRefreshAction;
 import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse;
 import org.elasticsearch.action.deletebyquery.TransportDeleteByQueryAction;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.action.support.QuerySourceBuilder;
 import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction;
 import org.elasticsearch.client.Requests;
@@ -41,6 +42,7 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.query.FilterBuilders;
 import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.node.settings.NodeSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
@@ -53,16 +55,19 @@ public class TransportDeleteMappingAction extends TransportMasterNodeOperationAc
     private final TransportFlushAction flushAction;
     private final TransportDeleteByQueryAction deleteByQueryAction;
     private final TransportRefreshAction refreshAction;
+    private final DestructiveOperations destructiveOperations;
 
     @Inject
     public TransportDeleteMappingAction(Settings settings, TransportService transportService, ClusterService clusterService,
                                         ThreadPool threadPool, MetaDataMappingService metaDataMappingService,
-                                        TransportDeleteByQueryAction deleteByQueryAction, TransportRefreshAction refreshAction, TransportFlushAction flushAction) {
+                                        TransportDeleteByQueryAction deleteByQueryAction, TransportRefreshAction refreshAction,
+                                        TransportFlushAction flushAction, NodeSettingsService nodeSettingsService) {
         super(settings, transportService, clusterService, threadPool);
         this.metaDataMappingService = metaDataMappingService;
         this.deleteByQueryAction = deleteByQueryAction;
         this.refreshAction = refreshAction;
         this.flushAction = flushAction;
+        this.destructiveOperations = new DestructiveOperations(logger, settings, nodeSettingsService);
     }
 
     @Override
@@ -88,7 +93,7 @@ public class TransportDeleteMappingAction extends TransportMasterNodeOperationAc
 
     @Override
     protected void doExecute(DeleteMappingRequest request, ActionListener<DeleteMappingResponse> listener) {
-        request.indices(clusterService.state().metaData().concreteIndices(request.indices(), request.indicesOptions()));
+        destructiveOperations.failDestructive(request.indices());
         super.doExecute(request, listener);
     }
 
@@ -99,6 +104,7 @@ public class TransportDeleteMappingAction extends TransportMasterNodeOperationAc
 
     @Override
     protected void masterOperation(final DeleteMappingRequest request, final ClusterState state, final ActionListener<DeleteMappingResponse> listener) throws ElasticsearchException {
+        request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions()));
         flushAction.execute(Requests.flushRequest(request.indices()), new ActionListener<FlushResponse>() {
             @Override
             public void onResponse(FlushResponse flushResponse) {

+ 7 - 3
src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java

@@ -21,6 +21,7 @@ package org.elasticsearch.action.admin.indices.open;
 
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction;
 import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
@@ -31,6 +32,7 @@ import org.elasticsearch.cluster.block.ClusterBlockLevel;
 import org.elasticsearch.cluster.metadata.MetaDataIndexStateService;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.settings.NodeSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
@@ -40,12 +42,14 @@ import org.elasticsearch.transport.TransportService;
 public class TransportOpenIndexAction extends TransportMasterNodeOperationAction<OpenIndexRequest, OpenIndexResponse> {
 
     private final MetaDataIndexStateService indexStateService;
+    private final DestructiveOperations destructiveOperations;
 
     @Inject
     public TransportOpenIndexAction(Settings settings, TransportService transportService, ClusterService clusterService,
-                                    ThreadPool threadPool, MetaDataIndexStateService indexStateService) {
+                                    ThreadPool threadPool, MetaDataIndexStateService indexStateService, NodeSettingsService nodeSettingsService) {
         super(settings, transportService, clusterService, threadPool);
         this.indexStateService = indexStateService;
+        this.destructiveOperations = new DestructiveOperations(logger, settings, nodeSettingsService);
     }
 
     @Override
@@ -71,7 +75,7 @@ public class TransportOpenIndexAction extends TransportMasterNodeOperationAction
 
     @Override
     protected void doExecute(OpenIndexRequest request, ActionListener<OpenIndexResponse> listener) {
-        request.indices(clusterService.state().metaData().concreteIndices(request.indices(), request.indicesOptions()));
+        destructiveOperations.failDestructive(request.indices());
         super.doExecute(request, listener);
     }
 
@@ -82,7 +86,7 @@ public class TransportOpenIndexAction extends TransportMasterNodeOperationAction
 
     @Override
     protected void masterOperation(final OpenIndexRequest request, final ClusterState state, final ActionListener<OpenIndexResponse> listener) throws ElasticsearchException {
-
+        request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions()));
         OpenIndexClusterStateUpdateRequest updateRequest = new OpenIndexClusterStateUpdateRequest()
                 .ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
                 .indices(request.indices());

+ 15 - 2
src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java

@@ -20,6 +20,8 @@
 package org.elasticsearch.action.deletebyquery;
 
 import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.action.support.replication.TransportIndicesReplicationOperationAction;
 import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
@@ -27,6 +29,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException;
 import org.elasticsearch.cluster.block.ClusterBlockLevel;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.settings.NodeSettingsService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
@@ -38,10 +41,20 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
  */
 public class TransportDeleteByQueryAction extends TransportIndicesReplicationOperationAction<DeleteByQueryRequest, DeleteByQueryResponse, IndexDeleteByQueryRequest, IndexDeleteByQueryResponse, ShardDeleteByQueryRequest, ShardDeleteByQueryRequest, ShardDeleteByQueryResponse> {
 
+    private final DestructiveOperations destructiveOperations;
+
     @Inject
     public TransportDeleteByQueryAction(Settings settings, ClusterService clusterService, TransportService transportService,
-                                        ThreadPool threadPool, TransportIndexDeleteByQueryAction indexDeleteByQueryAction) {
+                                        ThreadPool threadPool, TransportIndexDeleteByQueryAction indexDeleteByQueryAction,
+                                        NodeSettingsService nodeSettingsService) {
         super(settings, transportService, clusterService, threadPool, indexDeleteByQueryAction);
+        this.destructiveOperations = new DestructiveOperations(logger, settings, nodeSettingsService);
+    }
+
+    @Override
+    protected void doExecute(DeleteByQueryRequest request, ActionListener<DeleteByQueryResponse> listener) {
+        destructiveOperations.failDestructive(request.indices());
+        super.doExecute(request, listener);
     }
 
     @Override
@@ -82,7 +95,7 @@ public class TransportDeleteByQueryAction extends TransportIndicesReplicationOpe
     }
 
     @Override
-    protected ClusterBlockException checkRequestBlock(ClusterState state, DeleteByQueryRequest replicationPingRequest, String[] concreteIndices) {
+    protected ClusterBlockException checkRequestBlock(ClusterState state, DeleteByQueryRequest request, String[] concreteIndices) {
         return state.blocks().indicesBlockedException(ClusterBlockLevel.WRITE, concreteIndices);
     }
 

+ 85 - 0
src/main/java/org/elasticsearch/action/support/DestructiveOperations.java

@@ -0,0 +1,85 @@
+/*
+ * 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.support;
+
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.node.settings.NodeSettingsService;
+
+/**
+ * Helper for dealing with destructive operations and wildcard usage.
+ */
+public final class DestructiveOperations implements NodeSettingsService.Listener {
+
+    /**
+     * Setting which controls whether wildcard usage (*, prefix*, _all) is allowed.
+     */
+    public static final String REQUIRES_NAME = "action.destructive_requires_name";
+
+    private final ESLogger logger;
+    private volatile boolean destructiveRequiresName;
+
+    // TODO: Turn into a component that can be reused and wired up into all the transport actions where
+    // this helper logic is required. Note: also added the logger as argument, otherwise the same log
+    // statement is printed several times, this can removed once this becomes a component.
+    public DestructiveOperations(ESLogger logger, Settings settings, NodeSettingsService nodeSettingsService) {
+        this.logger = logger;
+        destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false);
+        nodeSettingsService.addListener(this);
+    }
+
+    /**
+     * Fail if there is wildcard usage in indices and the named is required for destructive operations.
+     */
+    public void failDestructive(String[] aliasesOrIndices) {
+        if (!destructiveRequiresName) {
+            return;
+        }
+
+        if (aliasesOrIndices == null || aliasesOrIndices.length == 0) {
+            throw new ElasticsearchIllegalArgumentException("Wildcard expressions or all indices are not allowed");
+        } else if (aliasesOrIndices.length == 1) {
+            if (hasWildcardUsage(aliasesOrIndices[0])) {
+                throw new ElasticsearchIllegalArgumentException("Wildcard expressions or all indices are not allowed");
+            }
+        } else {
+            for (String aliasesOrIndex : aliasesOrIndices) {
+                if (hasWildcardUsage(aliasesOrIndex)) {
+                    throw new ElasticsearchIllegalArgumentException("Wildcard expressions or all indices are not allowed");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onRefreshSettings(Settings settings) {
+        boolean newValue = settings.getAsBoolean("action.destructive_requires_name", destructiveRequiresName);
+        if (destructiveRequiresName != newValue) {
+            logger.info("updating [action.operate_all_indices] from [{}] to [{}]", destructiveRequiresName, newValue);
+            this.destructiveRequiresName = newValue;
+        }
+    }
+
+    private static boolean hasWildcardUsage(String aliasOrIndex) {
+        return "_all".equals(aliasOrIndex) || aliasOrIndex.indexOf('*') != -1;
+    }
+
+}

+ 2 - 0
src/main/java/org/elasticsearch/cluster/settings/ClusterDynamicSettingsModule.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.cluster.settings;
 
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.cluster.InternalClusterInfoService;
 import org.elasticsearch.cluster.metadata.MetaData;
 import org.elasticsearch.cluster.routing.allocation.allocator.BalancedShardsAllocator;
@@ -79,6 +80,7 @@ public class ClusterDynamicSettingsModule extends AbstractModule {
         clusterDynamicSettings.addDynamicSetting(SnapshotInProgressAllocationDecider.CLUSTER_ROUTING_ALLOCATION_SNAPSHOT_RELOCATION_ENABLED);
         clusterDynamicSettings.addDynamicSetting(InternalCircuitBreakerService.CIRCUIT_BREAKER_MAX_BYTES_SETTING, Validator.BYTES_SIZE);
         clusterDynamicSettings.addDynamicSetting(InternalCircuitBreakerService.CIRCUIT_BREAKER_OVERHEAD_SETTING, Validator.NON_NEGATIVE_DOUBLE);
+        clusterDynamicSettings.addDynamicSetting(DestructiveOperations.REQUIRES_NAME);
     }
 
     public void addDynamicSettings(String... settings) {

+ 44 - 48
src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java

@@ -22,6 +22,7 @@ import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
 import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
 import org.elasticsearch.action.admin.indices.close.CloseIndexResponse;
+import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.settings.ImmutableSettings;
 import org.elasticsearch.common.settings.Settings;
@@ -30,68 +31,63 @@ import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
 import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
 import org.junit.Test;
 
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 
-@ClusterScope(scope=Scope.SUITE, numNodes=2)
+@ClusterScope(scope=Scope.TEST, numNodes=2)
 public class CloseIndexDisableCloseAllTests extends ElasticsearchIntegrationTest {
 
-    @Override
-    protected Settings nodeSettings(int nodeOrdinal) {
-        return ImmutableSettings.settingsBuilder().put("action.disable_close_all_indices", true).put(super.nodeSettings(nodeOrdinal)).build();
-    }
-
-
-    @Test(expected = ElasticsearchIllegalArgumentException.class)
-    public void testCloseAllExplicitly() {
+    @Test
+    // Combined multiple tests into one, because cluster scope is test.
+    // The cluster scope is test b/c we can't clear cluster settings.
+    public void testCloseAllRequiresName() {
+        Settings clusterSettings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, true)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(clusterSettings));
         createIndex("test1", "test2", "test3");
         ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
         assertThat(healthResponse.isTimedOut(), equalTo(false));
-        client().admin().indices().prepareClose("_all").execute().actionGet();
-    }
 
-    @Test(expected = ElasticsearchIllegalArgumentException.class)
-    public void testCloseAllWildcard() {
-        createIndex("test1", "test2", "test3");
-        ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
-        assertThat(healthResponse.isTimedOut(), equalTo(false));
-        client().admin().indices().prepareClose("*").execute().actionGet();
-    }
+        // Close all explicitly
+        try {
+            client().admin().indices().prepareClose("_all").execute().actionGet();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
 
-    @Test(expected = ElasticsearchIllegalArgumentException.class)
-    public void testCloseAllWildcard2() {
-        createIndex("test1", "test2", "test3");
-        ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
-        assertThat(healthResponse.isTimedOut(), equalTo(false));
-        client().admin().indices().prepareClose("test*").execute().actionGet();
-    }
+        // Close all wildcard
+        try {
+            client().admin().indices().prepareClose("*").execute().actionGet();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
 
-    @Test
-    public void testCloseWildcardNonMatchingAll() {
-        createIndex("test1", "test2", "test3");
-        ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
-        assertThat(healthResponse.isTimedOut(), equalTo(false));
-        CloseIndexResponse closeIndexResponse = client().admin().indices().prepareClose("*", "-test1").execute().actionGet();
-        assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
-        assertIndexIsClosed("test2", "test3");
-    }
+        // Close all wildcard
+        try {
+            client().admin().indices().prepareClose("test*").execute().actionGet();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
 
-    @Test(expected = ElasticsearchIllegalArgumentException.class)
-    public void testCloseWildcardMatchingAll() {
-        createIndex("test1", "test2", "test3");
-        ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
-        assertThat(healthResponse.isTimedOut(), equalTo(false));
-        client().admin().indices().prepareClose("*", "-test1", "+test1").execute().actionGet();
-    }
+        // Close all wildcard
+        try {
+            client().admin().indices().prepareClose("*", "-test1").execute().actionGet();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
 
-    @Test
-    public void testCloseWildcardNonMatchingAll2() {
-        createIndex( "test1", "test2", "test3", "a");
-        ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
-        assertThat(healthResponse.isTimedOut(), equalTo(false));
-        CloseIndexResponse closeIndexResponse = client().admin().indices().prepareClose("test*").execute().actionGet();
+        // Close all wildcard
+        try {
+            client().admin().indices().prepareClose("*", "-test1", "+test1").execute().actionGet();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
+
+        CloseIndexResponse closeIndexResponse = client().admin().indices().prepareClose("test3", "test2").execute().actionGet();
         assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
-        assertIndexIsClosed("test1", "test2", "test3");
+        assertIndexIsClosed("test2", "test3");
     }
 
     private void assertIndexIsClosed(String... indices) {

+ 179 - 0
src/test/java/org/elasticsearch/operateAllIndices/DestructiveOperationsIntegrationTests.java

@@ -0,0 +1,179 @@
+/*
+ * 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.operateAllIndices;
+
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
+import org.elasticsearch.action.support.DestructiveOperations;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.test.ElasticsearchIntegrationTest;
+import org.junit.Test;
+
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ */
+@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST)
+public class DestructiveOperationsIntegrationTests extends ElasticsearchIntegrationTest {
+
+    @Test
+    // One test for test performance, since cluster scope is test
+    // The cluster scope is test b/c we can't clear cluster settings.
+    public void testDestructiveOperations() throws Exception {
+        Settings settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, true)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        assertAcked(client().admin().indices().prepareCreate("index1").get());
+        assertAcked(client().admin().indices().prepareCreate("1index").get());
+
+        // Should succeed, since no wildcards
+        assertAcked(client().admin().indices().prepareDelete("1index").get());
+
+        try {
+            // should fail since index1 is the only index.
+            client().admin().indices().prepareDelete("i*").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        try {
+            client().admin().indices().prepareDelete("_all").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, false)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        assertAcked(client().admin().indices().prepareDelete("_all").get());
+        assertThat(client().admin().indices().prepareExists("_all").get().isExists(), equalTo(false));
+
+        // end delete index:
+        // close index:
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, true)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        assertAcked(client().admin().indices().prepareCreate("index1").get());
+        assertAcked(client().admin().indices().prepareCreate("1index").get());
+
+        // Should succeed, since no wildcards
+        assertAcked(client().admin().indices().prepareClose("1index").get());
+
+        try {
+            client().admin().indices().prepareClose("_all").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+        try {
+            assertAcked(client().admin().indices().prepareOpen("_all").get());
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
+        try {
+            client().admin().indices().prepareClose("*").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+        try {
+            assertAcked(client().admin().indices().prepareOpen("*").get());
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {
+        }
+
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, false)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+        assertAcked(client().admin().indices().prepareClose("_all").get());
+        assertAcked(client().admin().indices().prepareOpen("_all").get());
+
+        // end close index:
+        client().admin().indices().prepareDelete("_all").get();
+        // delete_by_query:
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, true)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        assertAcked(client().admin().indices().prepareCreate("index1").get());
+        assertAcked(client().admin().indices().prepareCreate("1index").get());
+
+        // Should succeed, since no wildcards
+        client().prepareDeleteByQuery("1index").setQuery(QueryBuilders.matchAllQuery()).get();
+
+        try {
+            client().prepareDeleteByQuery("_all").setQuery(QueryBuilders.matchAllQuery()).get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        try {
+            client().prepareDeleteByQuery().setQuery(QueryBuilders.matchAllQuery()).get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, false)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        client().prepareDeleteByQuery().setQuery(QueryBuilders.matchAllQuery()).get();
+        client().prepareDeleteByQuery("_all").setQuery(QueryBuilders.matchAllQuery()).get();
+
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+        client().prepareDeleteByQuery().setQuery(QueryBuilders.matchAllQuery()).get();
+        // end delete_by_query:
+        client().admin().indices().prepareDelete("_all").get();
+        // delete mapping:
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, true)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        assertAcked(client().admin().indices().prepareCreate("index1").addMapping("1", "field1", "type=string").get());
+        assertAcked(client().admin().indices().prepareCreate("1index").addMapping("1", "field1", "type=string").get());
+
+        // Should succeed, since no wildcards
+        client().admin().indices().prepareDeleteMapping("1index").setType("1").get();
+        try {
+            client().admin().indices().prepareDeleteMapping("_all").setType("1").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        try {
+            client().admin().indices().prepareDeleteMapping().setType("1").get();
+            assert false;
+        } catch (ElasticsearchIllegalArgumentException e) {}
+
+        settings = ImmutableSettings.builder()
+                .put(DestructiveOperations.REQUIRES_NAME, false)
+                .build();
+        assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
+
+        client().admin().indices().preparePutMapping("1index").setType("1").setSource("field1", "type=string").get();
+        client().admin().indices().prepareDeleteMapping().setType("1").get();
+        client().admin().indices().preparePutMapping("1index").setType("1").setSource("field1", "type=string").get();
+        client().admin().indices().prepareDeleteMapping("_all").setType("1").get();
+    }
+
+}

+ 1 - 1
src/test/java/org/elasticsearch/percolator/PercolatorTests.java

@@ -1504,7 +1504,7 @@ public class PercolatorTests extends ElasticsearchIntegrationTest {
 
     @Test
     public void testDeletePercolatorType() throws Exception {
-        DeleteIndexResponse deleteIndexResponse = client().admin().indices().prepareDelete().execute().actionGet();
+        DeleteIndexResponse deleteIndexResponse = client().admin().indices().prepareDelete("_all").execute().actionGet();
         assertThat("Delete Index failed - not acked", deleteIndexResponse.isAcknowledged(), equalTo(true));
         ensureGreen();
 

+ 2 - 2
src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java

@@ -196,7 +196,7 @@ public class RecoveryPercolatorTests extends ElasticsearchIntegrationTest {
         cluster().startNode(settings);
         cluster().startNode(settings);
 
-        client().admin().indices().prepareDelete().execute().actionGet();
+        client().admin().indices().prepareDelete("_all").execute().actionGet();
         ensureGreen();
 
         client().admin().indices().prepareCreate("test")
@@ -265,7 +265,7 @@ public class RecoveryPercolatorTests extends ElasticsearchIntegrationTest {
         logger.info("--> Adding 3th node");
         cluster().startNode(settingsBuilder().put("node.stay", true));
 
-        client().admin().indices().prepareDelete().execute().actionGet();
+        client().admin().indices().prepareDelete("_all").execute().actionGet();
         ensureGreen();
 
         client().admin().indices().prepareCreate("test")

+ 1 - 1
src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java

@@ -55,7 +55,7 @@ public class TTLPercolatorTests extends ElasticsearchIntegrationTest {
     @Test
     public void testPercolatingWithTimeToLive() throws Exception {
         Client client = client();
-        client.admin().indices().prepareDelete().execute().actionGet();
+        client.admin().indices().prepareDelete("_all").execute().actionGet();
         ensureGreen();
 
         String precolatorMapping = XContentFactory.jsonBuilder().startObject().startObject(PercolatorService.TYPE_NAME)

+ 1 - 1
src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java

@@ -610,7 +610,7 @@ public class CompletionSuggestSearchTests extends ElasticsearchIntegrationTest {
     public void testThatStatsAreWorking() throws Exception {
         String otherField = "testOtherField";
 
-        client().admin().indices().prepareDelete().get();
+        client().admin().indices().prepareDelete("_all").get();
         client().admin().indices().prepareCreate(INDEX)
                 .setSettings(createDefaultSettings())
                 .get();

+ 23 - 5
src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java

@@ -18,14 +18,17 @@
  */
 package org.elasticsearch.test;
 
+import com.carrotsearch.hppc.ObjectArrayList;
 import com.google.common.base.Joiner;
 import org.apache.lucene.util.AbstractRandomizedTest;
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ShardOperationFailedException;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
 import org.elasticsearch.action.admin.indices.flush.FlushResponse;
@@ -42,6 +45,7 @@ import org.elasticsearch.client.AdminClient;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Requests;
 import org.elasticsearch.cluster.ClusterService;
+import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.MetaData;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.collect.Tuple;
@@ -182,7 +186,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
                assert false : "Unknown Scope: [" + currentClusterScope + "]";
             }
             currentCluster.beforeTest(getRandom(), getPerTestTransportClientRatio());
-            wipeIndices();
+            wipeIndices("_all");
             wipeTemplates();
             randomIndexTemplate();
             logger.info("[{}#{}]: before test", getTestClass().getSimpleName(), getTestName());
@@ -230,7 +234,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
                         .transientSettings().getAsMap().size(), equalTo(0));
             
             }
-            wipeIndices(); // wipe after to make sure we fail in the test that
+            wipeIndices("_all"); // wipe after to make sure we fail in the test that
                            // didn't ack the delete
             wipeTemplates();
             ensureAllSearchersClosed();
@@ -319,12 +323,26 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
      * Deletes the given indices from the tests cluster. If no index name is passed to this method
      * all indices are removed.
      */
-    public static void wipeIndices(String... names) {
+    public static void wipeIndices(String... indices) {
+        assert indices != null && indices.length > 0;
         if (cluster().size() > 0) {
             try {
-                assertAcked(client().admin().indices().prepareDelete(names));
+                assertAcked(client().admin().indices().prepareDelete(indices));
             } catch (IndexMissingException e) {
                 // ignore
+            } catch (ElasticsearchIllegalArgumentException e) {
+                // Happens if `action.destructive_requires_name` is set to true
+                // which is the case in the CloseIndexDisableCloseAllTests
+                if ("_all".equals(indices[0])) {
+                    ClusterStateResponse clusterStateResponse = client().admin().cluster().prepareState().execute().actionGet();
+                    ObjectArrayList<String> concreteIndices = new ObjectArrayList<String>();
+                    for (IndexMetaData indexMetaData : clusterStateResponse.getState().metaData()) {
+                        concreteIndices.add(indexMetaData.getIndex());
+                    }
+                    if (!concreteIndices.isEmpty()) {
+                        assertAcked(client().admin().indices().prepareDelete(concreteIndices.toArray(String.class)));
+                    }
+                }
             }
         }
     }
@@ -363,7 +381,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
                 created.add(name);
                 success = true;
             } finally {
-                if (!success) {
+                if (!success && !created.isEmpty()) {
                     wipeIndices(created.toArray(new String[created.size()]));
                 }
             }