#1452 closed: block writes or metadata changes if {index,cluster}.read_only is set.

This commit is contained in:
bbgordonn 2011-12-14 17:40:21 -05:00 committed by Shay Banon
parent cc3f44473f
commit 661d04e9de
6 changed files with 260 additions and 4 deletions

View File

@ -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);

View File

@ -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<String> dynamicSettings = ImmutableSet.<String>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<String> 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;
}

View File

@ -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<IndexMetaData> {
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<String> dynamicSettings = ImmutableSet.<String>builder()
.add("cluster.read_only")
.build();
public static ImmutableSet<String> dynamicSettings() {

View File

@ -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<String> 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);

View File

@ -258,11 +258,19 @@ public class GatewayService extends AbstractLifecycleComponent<GatewayService> 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

View File

@ -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<String, Object> newSettings = new HashMap<String, Object>();
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<String, Object> newSettings = new HashMap<String, Object>();
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());
}
}