From 661d04e9de03995a9b410943afc2a661f24da9fb Mon Sep 17 00:00:00 2001 From: bbgordonn Date: Wed, 14 Dec 2011 17:40:21 -0500 Subject: [PATCH] #1452 closed: block writes or metadata changes if {index,cluster}.read_only is set. --- .../TransportClusterUpdateSettingsAction.java | 17 +- .../cluster/metadata/IndexMetaData.java | 15 ++ .../cluster/metadata/MetaData.java | 6 + .../MetaDataUpdateSettingsService.java | 16 +- .../elasticsearch/gateway/GatewayService.java | 8 + .../cluster/ClusterBlockTests.java | 202 ++++++++++++++++++ 6 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/elasticsearch/test/integration/cluster/ClusterBlockTests.java diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java b/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java index 7531a1b22a5..29a88c7d416 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java @@ -26,6 +26,7 @@ import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.ProcessedClusterStateUpdateTask; +import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; @@ -111,12 +112,22 @@ public class TransportClusterUpdateSettingsAction extends TransportMasterNodeOpe return currentState; } + Settings persistentSettingsBuilt = persistentSettings.build(); + Settings transientSettingsBuilt = transientSettings.build(); MetaData.Builder metaData = MetaData.builder().metaData(currentState.metaData()) - .persistentSettings(persistentSettings.build()) - .transientSettings(transientSettings.build()); + .persistentSettings(persistentSettingsBuilt) + .transientSettings(transientSettingsBuilt); + ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks()); + Boolean updatedReadOnly = persistentSettingsBuilt.getAsBoolean(MetaData.SETTING_READ_ONLY, false) || transientSettingsBuilt.getAsBoolean(MetaData.SETTING_READ_ONLY, false); + if (updatedReadOnly) { + blocks.addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK); + } + else { + blocks.removeGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK); + } - return ClusterState.builder().state(currentState).metaData(metaData).build(); + return ClusterState.builder().state(currentState).metaData(metaData).blocks(blocks).build(); } catch (Exception e) { latch.countDown(); logger.warn("failed to update cluster settings", e); diff --git a/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java b/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java index 98486fd9d39..60ff5618160 100644 --- a/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java +++ b/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java @@ -22,6 +22,8 @@ package org.elasticsearch.cluster.metadata; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.elasticsearch.ElasticSearchIllegalStateException; +import org.elasticsearch.cluster.block.ClusterBlock; +import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.node.DiscoveryNodeFilters; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Preconditions; @@ -51,8 +53,11 @@ public class IndexMetaData { private static ImmutableSet dynamicSettings = ImmutableSet.builder() .add(IndexMetaData.SETTING_NUMBER_OF_REPLICAS) .add(IndexMetaData.SETTING_AUTO_EXPAND_REPLICAS) + .add(IndexMetaData.SETTING_READ_ONLY) .build(); + public static final ClusterBlock INDEX_READ_ONLY_BLOCK = new ClusterBlock(5, "index read-only", false, false, ClusterBlockLevel.WRITE, ClusterBlockLevel.METADATA); + public static ImmutableSet dynamicSettings() { return dynamicSettings; } @@ -111,6 +116,8 @@ public class IndexMetaData { public static final String SETTING_AUTO_EXPAND_REPLICAS = "index.auto_expand_replicas"; + public static final String SETTING_READ_ONLY = "index.read_only"; + private final String index; private final State state; @@ -191,6 +198,14 @@ public class IndexMetaData { return totalNumberOfShards(); } + public boolean readOnly() { + return settings.getAsBoolean(SETTING_READ_ONLY, false); + } + + public boolean getreadOnly() { + return readOnly(); + } + public Settings settings() { return settings; } diff --git a/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java b/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java index 793cb22fa1b..e4fdf844196 100644 --- a/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java +++ b/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java @@ -22,6 +22,8 @@ package org.elasticsearch.cluster.metadata; import com.google.common.collect.*; import gnu.trove.set.hash.THashSet; import org.elasticsearch.ElasticSearchIllegalArgumentException; +import org.elasticsearch.cluster.block.ClusterBlock; +import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; @@ -47,8 +49,12 @@ import static org.elasticsearch.common.settings.ImmutableSettings.*; * */ public class MetaData implements Iterable { + public static final String SETTING_READ_ONLY = "cluster.read_only"; + + public static final ClusterBlock CLUSTER_READ_ONLY_BLOCK = new ClusterBlock(6, "cluster read-only", false, false, ClusterBlockLevel.WRITE, ClusterBlockLevel.METADATA); private static ImmutableSet dynamicSettings = ImmutableSet.builder() + .add("cluster.read_only") .build(); public static ImmutableSet dynamicSettings() { diff --git a/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java b/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java index 50b039f23a7..feffb29c7c5 100644 --- a/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java +++ b/src/main/java/org/elasticsearch/cluster/metadata/MetaDataUpdateSettingsService.java @@ -22,6 +22,7 @@ package org.elasticsearch.cluster.metadata; import com.google.common.collect.Sets; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.cluster.*; +import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; @@ -163,6 +164,19 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements logger.info("updating number_of_replicas to [{}] for indices {}", updatedNumberOfReplicas, actualIndices); } + ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks()); + Boolean updatedReadOnly = openSettings.getAsBoolean(IndexMetaData.SETTING_READ_ONLY, null); + if (updatedReadOnly != null) { + for (String index : actualIndices) { + if (updatedReadOnly) { + blocks.addIndexBlock(index, IndexMetaData.INDEX_READ_ONLY_BLOCK); + } + else { + blocks.removeIndexBlock(index, IndexMetaData.INDEX_READ_ONLY_BLOCK); + } + } + } + // allow to change any settings to a close index, and only allow dynamic settings to be changed // on an open index Set openIndices = Sets.newHashSet(); @@ -189,7 +203,7 @@ public class MetaDataUpdateSettingsService extends AbstractComponent implements } - ClusterState updatedState = ClusterState.builder().state(currentState).metaData(metaDataBuilder).routingTable(routingTableBuilder).build(); + ClusterState updatedState = ClusterState.builder().state(currentState).metaData(metaDataBuilder).routingTable(routingTableBuilder).blocks(blocks).build(); // now, reroute in case things change that require it (like number of replicas) RoutingAllocation.Result routingResult = allocationService.reroute(updatedState); diff --git a/src/main/java/org/elasticsearch/gateway/GatewayService.java b/src/main/java/org/elasticsearch/gateway/GatewayService.java index 13e1a4722ca..508d8b64008 100644 --- a/src/main/java/org/elasticsearch/gateway/GatewayService.java +++ b/src/main/java/org/elasticsearch/gateway/GatewayService.java @@ -258,11 +258,19 @@ public class GatewayService extends AbstractLifecycleComponent i metaDataBuilder.put(entry.getValue()); } + + if (recoveredState.metaData().settings().getAsBoolean(MetaData.SETTING_READ_ONLY, false) || currentState.metaData().settings().getAsBoolean(MetaData.SETTING_READ_ONLY, false)) { + blocks.addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK); + } + for (IndexMetaData indexMetaData : recoveredState.metaData()) { metaDataBuilder.put(indexMetaData); if (indexMetaData.state() == IndexMetaData.State.CLOSE) { blocks.addIndexBlock(indexMetaData.index(), MetaDataStateIndexService.INDEX_CLOSED_BLOCK); } + if (indexMetaData.readOnly()) { + blocks.addIndexBlock(indexMetaData.index(), IndexMetaData.INDEX_READ_ONLY_BLOCK); + } } // update the state to reflect the new metadata and routing diff --git a/src/test/java/org/elasticsearch/test/integration/cluster/ClusterBlockTests.java b/src/test/java/org/elasticsearch/test/integration/cluster/ClusterBlockTests.java new file mode 100644 index 00000000000..94f17693c0d --- /dev/null +++ b/src/test/java/org/elasticsearch/test/integration/cluster/ClusterBlockTests.java @@ -0,0 +1,202 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.cluster.metadata; + +import java.util.HashMap; +import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; +import org.elasticsearch.action.admin.indices.exists.IndicesExistsResponse; +import org.elasticsearch.action.admin.indices.settings.UpdateSettingsResponse; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.action.admin.cluster.settings.ClusterUpdateSettingsRequestBuilder; +import org.elasticsearch.client.action.admin.indices.settings.UpdateSettingsRequestBuilder; +import org.elasticsearch.client.action.index.IndexRequestBuilder; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.node.Node; +import org.elasticsearch.node.NodeBuilder; +import org.testng.annotations.Test; + +import static org.elasticsearch.node.NodeBuilder.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@Test +public class ClusterBlockTests { + @Test public void testClusterReadOnly() throws Exception { + Node node = newNode(); + try { + Client client = node.client(); + try { + // cluster.read_only = null: write and metadata not blocked + canCreateIndex(client, "test1"); + canIndexDocument(client, "test1"); + setIndexReadOnly(client, "test1", "false"); + canIndexExists(client, "test1"); + + // cluster.read_only = true: block write and metadata + setClusterReadOnly(client, "true"); + canNotCreateIndex(client, "test2"); + // even if index has index.read_only = false + canNotIndexDocument(client, "test1"); + canNotIndexExists(client, "test1"); + + // cluster.read_only = false: removes the block + setClusterReadOnly(client, "false"); + canCreateIndex(client, "test2"); + canIndexDocument(client, "test2"); + canIndexDocument(client, "test1"); + canIndexExists(client, "test1"); + } + finally { + client.close(); + } + } + finally { + node.close(); + } + } + + @Test public void testIndexReadOnly() throws Exception { + Node node = newNode(); + try { + Client client = node.client(); + try { + // newly created an index has no blocks + canCreateIndex(client, "ro"); + canIndexDocument(client, "ro"); + canIndexExists(client, "ro"); + + // adds index write and metadata block + setIndexReadOnly(client, "ro", "true"); + canNotIndexDocument(client, "ro"); + canNotIndexExists(client, "ro"); + + // other indices not blocked + canCreateIndex(client, "rw"); + canIndexDocument(client, "rw"); + canIndexExists(client, "rw"); + + // blocks can be removed + setIndexReadOnly(client, "ro", "false"); + canIndexDocument(client, "ro"); + canIndexExists(client, "ro"); + } + finally { + client.close(); + } + } + finally { + node.close(); + } + } + + private Node newNode() { + ImmutableSettings.Builder settingsBuilder = ImmutableSettings.settingsBuilder().put("gateway.type", "none"); + NodeBuilder nodeBuilder = nodeBuilder().local(true).loadConfigSettings(false).clusterName("ClusterBlockTests").settings(settingsBuilder); + return nodeBuilder.node(); + } + + private void canCreateIndex(Client client, String index) { + try { + CreateIndexResponse r = client.admin().indices().prepareCreate(index).execute().actionGet(); + assertThat(r, notNullValue()); + } + catch (ClusterBlockException e) { + assert false; + } + } + + private void canNotCreateIndex(Client client, String index) { + try { + client.admin().indices().prepareCreate(index).execute().actionGet(); + assert false; + } + catch (ClusterBlockException e) { + // all is well + } + } + + private void canIndexDocument(Client client, String index) { + try { + IndexRequestBuilder builder = client.prepareIndex(index, "zzz"); + builder.setSource("foo", "bar"); + IndexResponse r = builder.execute().actionGet(); + assertThat(r, notNullValue()); + } + catch (ClusterBlockException e) { + assert false; + } + } + + private void canNotIndexDocument(Client client, String index) { + try { + IndexRequestBuilder builder = client.prepareIndex(index, "zzz"); + builder.setSource("foo", "bar"); + builder.execute().actionGet(); + assert false; + } + catch (ClusterBlockException e) { + // all is well + } + } + + private void canIndexExists(Client client, String index) { + try { + IndicesExistsResponse r = client.admin().indices().prepareExists(index).execute().actionGet(); + assertThat(r, notNullValue()); + } + catch (ClusterBlockException e) { + assert false; + } + } + + private void canNotIndexExists(Client client, String index) { + try { + IndicesExistsResponse r = client.admin().indices().prepareExists(index).execute().actionGet(); + assert false; + } + catch (ClusterBlockException e) { + // all is well + } + } + + private void setClusterReadOnly(Client client, String value) { + HashMap newSettings = new HashMap(); + newSettings.put(MetaData.SETTING_READ_ONLY, value); + + ClusterUpdateSettingsRequestBuilder settingsRequest = client.admin().cluster().prepareUpdateSettings(); + settingsRequest.setTransientSettings(newSettings); + ClusterUpdateSettingsResponse settingsResponse = settingsRequest.execute().actionGet(); + assertThat(settingsResponse, notNullValue()); + } + + private void setIndexReadOnly(Client client, String index, Object value) { + HashMap newSettings = new HashMap(); + newSettings.put(IndexMetaData.SETTING_READ_ONLY, value); + + UpdateSettingsRequestBuilder settingsRequest = client.admin().indices().prepareUpdateSettings(index); + settingsRequest.setSettings(newSettings); + UpdateSettingsResponse settingsResponse = settingsRequest.execute().actionGet(); + assertThat(settingsResponse, notNullValue()); + } +}