diff --git a/docs/reference/indices/delete-index.asciidoc b/docs/reference/indices/delete-index.asciidoc index 3dbfaee12b485..25d176668cc66 100644 --- a/docs/reference/indices/delete-index.asciidoc +++ b/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. \ No newline at end of file diff --git a/docs/reference/indices/open-close.asciidoc b/docs/reference/indices/open-close.asciidoc index 858508c4012be..29259d2b33112 100644 --- a/docs/reference/indices/open-close.asciidoc +++ b/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. \ No newline at end of file diff --git a/rest-api-spec/api/indices.delete.json b/rest-api-spec/api/indices.delete.json index 03c2350c12efe..7eb8c5eaf586d 100644 --- a/rest-api-spec/api/indices.delete.json +++ b/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": { diff --git a/src/main/java/org/elasticsearch/action/admin/indices/close/TransportCloseIndexAction.java b/src/main/java/org/elasticsearch/action/admin/indices/close/TransportCloseIndexAction.java index 0de502b395cf5..021b8071ffe51 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/close/TransportCloseIndexAction.java +++ b/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,24 +32,25 @@ 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; /** * Close index action */ -public class TransportCloseIndexAction extends TransportMasterNodeOperationAction { +public class TransportCloseIndexAction extends TransportMasterNodeOperationAction implements NodeSettingsService.Listener { private final MetaDataIndexStateService indexStateService; - private final boolean disableCloseAllIndices; - + private volatile boolean destructiveRequiresName; @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.destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false); + nodeSettingsService.addListener(this); } @Override @@ -75,17 +76,7 @@ protected CloseIndexResponse newResponse() { @Override protected void doExecute(CloseIndexRequest request, ActionListener 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(), destructiveRequiresName); super.doExecute(request, listener); } @@ -96,7 +87,7 @@ protected ClusterBlockException checkBlock(CloseIndexRequest request, ClusterSta @Override protected void masterOperation(final CloseIndexRequest request, final ClusterState state, final ActionListener 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()); @@ -115,4 +106,13 @@ public void onFailure(Throwable t) { } }); } + + @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; + } + } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java b/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java index 2e1402af1e903..6164aaee3090b 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexRequest.java @@ -68,7 +68,7 @@ public DeleteIndexRequest indicesOptions(IndicesOptions indicesOptions) { @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 DeleteIndexRequest timeout(String timeout) { @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 void readFrom(StreamInput in) throws IOException { @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); } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java b/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java index a262980e186d9..229acb01f230d 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java +++ b/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,28 +31,26 @@ 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; /** * Delete index action. */ -public class TransportDeleteIndexAction extends TransportMasterNodeOperationAction { +public class TransportDeleteIndexAction extends TransportMasterNodeOperationAction implements NodeSettingsService.Listener { private final MetaDataDeleteIndexService deleteIndexService; - - private final TransportDeleteMappingAction deleteMappingAction; - - private final boolean disableDeleteAllIndices; + private volatile boolean destructiveRequiresName; @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.destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false); + nodeSettingsService.addListener(this); } @Override @@ -78,17 +75,7 @@ protected DeleteIndexResponse newResponse() { @Override protected void doExecute(DeleteIndexRequest request, ActionListener 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(), destructiveRequiresName); super.doExecute(request, listener); } @@ -99,6 +86,7 @@ protected ClusterBlockException checkBlock(DeleteIndexRequest request, ClusterSt @Override protected void masterOperation(final DeleteIndexRequest request, final ClusterState state, final ActionListener listener) throws ElasticsearchException { + request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions())); if (request.indices().length == 0) { listener.onResponse(new DeleteIndexResponse(true)); return; @@ -136,4 +124,13 @@ public void onFailure(Throwable t) { }); } } + + @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; + } + } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java b/src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java index 75da00c974f36..806df048b9305 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/mapping/delete/TransportDeleteMappingAction.java @@ -27,6 +27,7 @@ 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,28 +42,33 @@ 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; /** * Delete mapping action. */ -public class TransportDeleteMappingAction extends TransportMasterNodeOperationAction { +public class TransportDeleteMappingAction extends TransportMasterNodeOperationAction implements NodeSettingsService.Listener { private final MetaDataMappingService metaDataMappingService; private final TransportFlushAction flushAction; private final TransportDeleteByQueryAction deleteByQueryAction; private final TransportRefreshAction refreshAction; + private volatile boolean destructiveRequiresName; @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.destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false); + nodeSettingsService.addListener(this); } @Override @@ -88,7 +94,7 @@ protected DeleteMappingResponse newResponse() { @Override protected void doExecute(DeleteMappingRequest request, ActionListener listener) { - request.indices(clusterService.state().metaData().concreteIndices(request.indices(), request.indicesOptions())); + DestructiveOperations.failDestructive(request.indices(), destructiveRequiresName); super.doExecute(request, listener); } @@ -99,6 +105,7 @@ protected ClusterBlockException checkBlock(DeleteMappingRequest request, Cluster @Override protected void masterOperation(final DeleteMappingRequest request, final ClusterState state, final ActionListener listener) throws ElasticsearchException { + request.indices(state.metaData().concreteIndices(request.indices(), request.indicesOptions())); flushAction.execute(Requests.flushRequest(request.indices()), new ActionListener() { @Override public void onResponse(FlushResponse flushResponse) { @@ -152,4 +159,13 @@ public void onFailure(Throwable t) { } }); } + + @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; + } + } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java b/src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java index 517174ad2c9eb..6254c51c5ce2b 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/open/TransportOpenIndexAction.java @@ -21,6 +21,7 @@ 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,21 +32,25 @@ 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; /** * Open index action */ -public class TransportOpenIndexAction extends TransportMasterNodeOperationAction { +public class TransportOpenIndexAction extends TransportMasterNodeOperationAction implements NodeSettingsService.Listener { private final MetaDataIndexStateService indexStateService; + private volatile boolean destructiveRequiresName; @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.destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false); + nodeSettingsService.addListener(this); } @Override @@ -71,7 +76,7 @@ protected OpenIndexResponse newResponse() { @Override protected void doExecute(OpenIndexRequest request, ActionListener listener) { - request.indices(clusterService.state().metaData().concreteIndices(request.indices(), request.indicesOptions())); + DestructiveOperations.failDestructive(request.indices(), destructiveRequiresName); super.doExecute(request, listener); } @@ -82,7 +87,7 @@ protected ClusterBlockException checkBlock(OpenIndexRequest request, ClusterStat @Override protected void masterOperation(final OpenIndexRequest request, final ClusterState state, final ActionListener 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()); @@ -101,4 +106,13 @@ public void onFailure(Throwable t) { } }); } + + @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; + } + } } diff --git a/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java b/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java index c79a466b80e28..d3ee5fad54f16 100644 --- a/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java +++ b/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.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; @@ -36,12 +39,23 @@ /** */ -public class TransportDeleteByQueryAction extends TransportIndicesReplicationOperationAction { +public class TransportDeleteByQueryAction extends TransportIndicesReplicationOperationAction implements NodeSettingsService.Listener { + + private volatile boolean destructiveRequiresName; @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.destructiveRequiresName = settings.getAsBoolean(DestructiveOperations.REQUIRES_NAME, false); + nodeSettingsService.addListener(this); + } + + @Override + protected void doExecute(DeleteByQueryRequest request, ActionListener listener) { + DestructiveOperations.failDestructive(request.indices(), destructiveRequiresName); + super.doExecute(request, listener); } @Override @@ -82,7 +96,7 @@ protected ClusterBlockException checkGlobalBlock(ClusterState state, DeleteByQue } @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); } @@ -91,4 +105,13 @@ protected IndexDeleteByQueryRequest newIndexRequestInstance(DeleteByQueryRequest String[] filteringAliases = clusterService.state().metaData().filteringAliases(index, request.indices()); return new IndexDeleteByQueryRequest(request, index, routing, filteringAliases); } + + @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; + } + } } diff --git a/src/main/java/org/elasticsearch/action/support/DestructiveOperations.java b/src/main/java/org/elasticsearch/action/support/DestructiveOperations.java new file mode 100644 index 0000000000000..af5f21fb9e8db --- /dev/null +++ b/src/main/java/org/elasticsearch/action/support/DestructiveOperations.java @@ -0,0 +1,63 @@ +/* + * 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; + +/** + * Static helper for dealing with destructive operations and wildcards. + */ +public final class DestructiveOperations { + + public static final String REQUIRES_NAME = "action.destructive_requires_name"; + + private DestructiveOperations() { + } + + /** + * Fail if there is wildcard usage in indices and the named is required for destructive operations. + * + * @param destructiveRequiresName Controls whether wildcard usage (*, prefix*, _all) is allowed + */ + public static void failDestructive(String[] aliasesOrIndices, boolean destructiveRequiresName) { + 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"); + } + } + } + } + + private static boolean hasWildcardUsage(String aliasOrIndex) { + return "_all".equals(aliasOrIndex) || aliasOrIndex.indexOf('*') != -1; + } + +} diff --git a/src/main/java/org/elasticsearch/cluster/settings/ClusterDynamicSettingsModule.java b/src/main/java/org/elasticsearch/cluster/settings/ClusterDynamicSettingsModule.java index d4a8881e71120..4322b20948514 100644 --- a/src/main/java/org/elasticsearch/cluster/settings/ClusterDynamicSettingsModule.java +++ b/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 ClusterDynamicSettingsModule() { 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) { diff --git a/src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java b/src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java index 47e68874c9fd8..e3b04e25c56ef 100644 --- a/src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java +++ b/src/test/java/org/elasticsearch/indices/state/CloseIndexDisableCloseAllTests.java @@ -22,6 +22,7 @@ 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.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) { diff --git a/src/test/java/org/elasticsearch/operateAllIndices/DestructiveOperationsIntegrationTests.java b/src/test/java/org/elasticsearch/operateAllIndices/DestructiveOperationsIntegrationTests.java new file mode 100644 index 0000000000000..b7a26dd185afc --- /dev/null +++ b/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(); + } + +} diff --git a/src/test/java/org/elasticsearch/percolator/PercolatorTests.java b/src/test/java/org/elasticsearch/percolator/PercolatorTests.java index fe81be5f900b9..80423ba47ffa9 100644 --- a/src/test/java/org/elasticsearch/percolator/PercolatorTests.java +++ b/src/test/java/org/elasticsearch/percolator/PercolatorTests.java @@ -1504,7 +1504,7 @@ public int compare(PercolateResponse.Match a, PercolateResponse.Match b) { @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(); diff --git a/src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java b/src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java index d720cb223be9a..de63c3c2f4a0a 100644 --- a/src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java +++ b/src/test/java/org/elasticsearch/percolator/RecoveryPercolatorTests.java @@ -196,7 +196,7 @@ public void testLoadingPercolateQueriesDuringCloseAndOpen() throws Exception { 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 @@ private void percolatorRecovery(final boolean multiPercolate) throws Exception { 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") diff --git a/src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java b/src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java index 988b80569ad8a..d049e7bbbbe45 100644 --- a/src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java +++ b/src/test/java/org/elasticsearch/percolator/TTLPercolatorTests.java @@ -55,7 +55,7 @@ protected Settings nodeSettings(int nodeOrdinal) { @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) diff --git a/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java b/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java index 9c24de5201b1d..a600bbcde4805 100644 --- a/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java +++ b/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchTests.java @@ -610,7 +610,7 @@ public void testThatFuzzySuggesterIsUnicodeAware() throws Exception { 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(); diff --git a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java b/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java index 22c1011f0fbe7..21a609a1a45ba 100644 --- a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java +++ b/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.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 final void before() throws IOException { 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 final void after() throws IOException { .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 Settings indexSettings() { * 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 concreteIndices = new ObjectArrayList(); + 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 final void createIndex(String... names) { created.add(name); success = true; } finally { - if (!success) { + if (!success && !created.isEmpty()) { wipeIndices(created.toArray(new String[created.size()])); } }