Skip to content

Commit 36bd666

Browse files
authored
Return 429 status code on read_only_allow_delete index block (#50166)
We consider index level read_only_allow_delete blocks temporary since the DiskThresholdMonitor can automatically release those when an index is no longer allocated on nodes above high threshold. The rest status has therefore been changed to 429 when encountering this index block to signal retryability to clients. Related to #49393
1 parent b3afbe7 commit 36bd666

File tree

6 files changed

+127
-12
lines changed

6 files changed

+127
-12
lines changed

modules/reindex/src/test/java/org/elasticsearch/index/reindex/DeleteByQueryBasicTests.java

+65-4
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@
2121

2222
import org.elasticsearch.action.admin.indices.alias.Alias;
2323
import org.elasticsearch.action.index.IndexRequestBuilder;
24+
import org.elasticsearch.cluster.ClusterInfoService;
25+
import org.elasticsearch.cluster.InternalClusterInfoService;
26+
import org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings;
2427
import org.elasticsearch.common.settings.Settings;
28+
import org.elasticsearch.common.unit.TimeValue;
2529
import org.elasticsearch.index.IndexNotFoundException;
2630
import org.elasticsearch.index.query.QueryBuilders;
2731
import org.elasticsearch.plugins.Plugin;
2832
import org.elasticsearch.search.sort.SortOrder;
2933
import org.elasticsearch.test.InternalSettingsPlugin;
34+
import org.elasticsearch.test.InternalTestCluster;
35+
import org.elasticsearch.threadpool.ThreadPool;
3036

3137
import java.util.ArrayList;
3238
import java.util.Collection;
@@ -214,18 +220,65 @@ public void testDeleteByQueryOnReadOnlyIndex() throws Exception {
214220
}
215221
indexRandom(true, true, true, builders);
216222

217-
String block = randomFrom(SETTING_READ_ONLY, SETTING_READ_ONLY_ALLOW_DELETE);
218223
try {
219-
enableIndexBlock("test", block);
224+
enableIndexBlock("test", SETTING_READ_ONLY);
220225
assertThat(deleteByQuery().source("test").filter(QueryBuilders.matchAllQuery()).refresh(true).get(),
221-
matcher().deleted(0).failures(docs));
226+
matcher().deleted(0).failures(docs));
222227
} finally {
223-
disableIndexBlock("test", block);
228+
disableIndexBlock("test", SETTING_READ_ONLY);
224229
}
225230

226231
assertHitCount(client().prepareSearch("test").setSize(0).get(), docs);
227232
}
228233

234+
public void testDeleteByQueryOnReadOnlyAllowDeleteIndex() throws Exception {
235+
createIndex("test");
236+
237+
final int docs = randomIntBetween(1, 50);
238+
List<IndexRequestBuilder> builders = new ArrayList<>();
239+
for (int i = 0; i < docs; i++) {
240+
builders.add(client().prepareIndex("test").setId(Integer.toString(i)).setSource("field", 1));
241+
}
242+
indexRandom(true, true, true, builders);
243+
244+
// Because the index level read_only_allow_delete block can be automatically released by disk allocation decider,
245+
// so we should test both case of disk allocation decider is enabled and disabled
246+
boolean diskAllocationDeciderEnabled = randomBoolean();
247+
try {
248+
// When a read_only_allow_delete block is set on the index,
249+
// it will trigger a retry policy in the delete by query request because the rest status of the block is 429
250+
enableIndexBlock("test", SETTING_READ_ONLY_ALLOW_DELETE);
251+
if (diskAllocationDeciderEnabled) {
252+
InternalTestCluster internalTestCluster = internalCluster();
253+
InternalClusterInfoService infoService = (InternalClusterInfoService) internalTestCluster
254+
.getInstance(ClusterInfoService.class, internalTestCluster.getMasterName());
255+
ThreadPool threadPool = internalTestCluster.getInstance(ThreadPool.class, internalTestCluster.getMasterName());
256+
// Refresh the cluster info after a random delay to check the disk threshold and release the block on the index
257+
threadPool.schedule(infoService::refresh, TimeValue.timeValueMillis(randomIntBetween(1, 100)), ThreadPool.Names.MANAGEMENT);
258+
// The delete by query request will be executed successfully because the block will be released
259+
assertThat(deleteByQuery().source("test").filter(QueryBuilders.matchAllQuery()).refresh(true).get(),
260+
matcher().deleted(docs));
261+
} else {
262+
// Disable the disk allocation decider to ensure the read_only_allow_delete block cannot be released
263+
setDiskAllocationDeciderEnabled(false);
264+
// The delete by query request will not be executed successfully because the block cannot be released
265+
assertThat(deleteByQuery().source("test").filter(QueryBuilders.matchAllQuery()).refresh(true)
266+
.setMaxRetries(2).setRetryBackoffInitialTime(TimeValue.timeValueMillis(50)).get(),
267+
matcher().deleted(0).failures(docs));
268+
}
269+
} finally {
270+
disableIndexBlock("test", SETTING_READ_ONLY_ALLOW_DELETE);
271+
if (diskAllocationDeciderEnabled == false) {
272+
setDiskAllocationDeciderEnabled(true);
273+
}
274+
}
275+
if (diskAllocationDeciderEnabled) {
276+
assertHitCount(client().prepareSearch("test").setSize(0).get(), 0);
277+
} else {
278+
assertHitCount(client().prepareSearch("test").setSize(0).get(), docs);
279+
}
280+
}
281+
229282
public void testSlices() throws Exception {
230283
indexRandom(true,
231284
client().prepareIndex("test").setId("1").setSource("foo", "a"),
@@ -315,4 +368,12 @@ public void testMissingSources() {
315368
assertThat(response, matcher().deleted(0).slices(hasSize(0)));
316369
}
317370

371+
/** Enables or disables the cluster disk allocation decider **/
372+
private void setDiskAllocationDeciderEnabled(boolean value) {
373+
Settings settings = value ? Settings.builder().putNull(
374+
DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey()).build() :
375+
Settings.builder().put(
376+
DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), value).build();
377+
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings).get());
378+
}
318379
}

server/src/main/java/org/elasticsearch/cluster/block/ClusterBlockException.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,23 @@ private static String buildMessageForIndexBlocks(Map<String, Set<ClusterBlock>>
110110
@Override
111111
public RestStatus status() {
112112
RestStatus status = null;
113+
boolean onlyRetryableBlocks = true;
113114
for (ClusterBlock block : blocks) {
114-
if (status == null) {
115-
status = block.status();
116-
} else if (status.getStatus() < block.status().getStatus()) {
117-
status = block.status();
115+
boolean isRetryableBlock = block.status() == RestStatus.TOO_MANY_REQUESTS;
116+
if (isRetryableBlock == false) {
117+
if (status == null) {
118+
status = block.status();
119+
} else if (status.getStatus() < block.status().getStatus()) {
120+
status = block.status();
121+
}
118122
}
123+
onlyRetryableBlocks = onlyRetryableBlocks && isRetryableBlock;
119124
}
125+
// return retryable status if there are only retryable blocks
126+
if (onlyRetryableBlocks) {
127+
return RestStatus.TOO_MANY_REQUESTS;
128+
}
129+
// return status which has the maximum code of all status except the retryable blocks'
120130
return status;
121131
}
122132
}

server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class IndexMetaData implements Diffable<IndexMetaData>, ToXContentFragmen
9595
RestStatus.FORBIDDEN, EnumSet.of(ClusterBlockLevel.METADATA_WRITE, ClusterBlockLevel.METADATA_READ));
9696
public static final ClusterBlock INDEX_READ_ONLY_ALLOW_DELETE_BLOCK =
9797
new ClusterBlock(12, "index read-only / allow delete (api)", false, false,
98-
true, RestStatus.FORBIDDEN, EnumSet.of(ClusterBlockLevel.METADATA_WRITE, ClusterBlockLevel.WRITE));
98+
true, RestStatus.TOO_MANY_REQUESTS, EnumSet.of(ClusterBlockLevel.METADATA_WRITE, ClusterBlockLevel.WRITE));
9999

100100
public enum State {
101101
OPEN((byte) 0),

server/src/test/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexBlocksIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void testClusterBlockMessageHasIndexName() {
7272
client().admin().indices().prepareUpdateSettings("test").setSettings(settings).get();
7373
ClusterBlockException e = expectThrows(ClusterBlockException.class, () ->
7474
client().prepareIndex().setIndex("test").setId("1").setSource("foo", "bar").get());
75-
assertEquals("index [test] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];", e.getMessage());
75+
assertEquals("index [test] blocked by: [TOO_MANY_REQUESTS/12/index read-only / allow delete (api)];", e.getMessage());
7676
} finally {
7777
assertAcked(client().admin().indices().prepareUpdateSettings("test")
7878
.setSettings(Settings.builder().putNull(IndexMetaData.SETTING_READ_ONLY_ALLOW_DELETE).build()).get());

test/framework/src/main/java/org/elasticsearch/test/hamcrest/ElasticsearchAssertions.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.elasticsearch.action.support.master.AcknowledgedResponse;
4444
import org.elasticsearch.cluster.block.ClusterBlock;
4545
import org.elasticsearch.cluster.block.ClusterBlockException;
46+
import org.elasticsearch.cluster.metadata.IndexMetaData;
4647
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
4748
import org.elasticsearch.common.Nullable;
4849
import org.elasticsearch.common.bytes.BytesReference;
@@ -150,7 +151,9 @@ public static void assertBlocked(BroadcastResponse replicatedBroadcastResponse)
150151
assertNotNull("expected the cause of failure to be a ClusterBlockException but got " + exception.getCause().getMessage(),
151152
clusterBlockException);
152153
assertThat(clusterBlockException.blocks().size(), greaterThan(0));
153-
assertThat(clusterBlockException.status(), CoreMatchers.equalTo(RestStatus.FORBIDDEN));
154+
155+
RestStatus status = checkRetryableBlock(clusterBlockException.blocks()) ? RestStatus.TOO_MANY_REQUESTS : RestStatus.FORBIDDEN;
156+
assertThat(clusterBlockException.status(), CoreMatchers.equalTo(status));
154157
}
155158
}
156159

@@ -166,7 +169,8 @@ public static void assertBlocked(final ActionRequestBuilder builder, @Nullable f
166169
fail("Request executed with success but a ClusterBlockException was expected");
167170
} catch (ClusterBlockException e) {
168171
assertThat(e.blocks().size(), greaterThan(0));
169-
assertThat(e.status(), equalTo(RestStatus.FORBIDDEN));
172+
RestStatus status = checkRetryableBlock(e.blocks()) ? RestStatus.TOO_MANY_REQUESTS : RestStatus.FORBIDDEN;
173+
assertThat(e.status(), equalTo(status));
170174

171175
if (expectedBlockId != null) {
172176
boolean found = false;
@@ -191,6 +195,16 @@ public static void assertBlocked(final ActionRequestBuilder builder, @Nullable f
191195
assertBlocked(builder, expectedBlock != null ? expectedBlock.id() : null);
192196
}
193197

198+
private static boolean checkRetryableBlock(Set<ClusterBlock> clusterBlocks){
199+
// check only retryable blocks exist in the set
200+
for (ClusterBlock clusterBlock : clusterBlocks) {
201+
if (clusterBlock.id() != IndexMetaData.INDEX_READ_ONLY_ALLOW_DELETE_BLOCK.id()) {
202+
return false;
203+
}
204+
}
205+
return true;
206+
}
207+
194208
public static String formatShardStatus(BroadcastResponse response) {
195209
StringBuilder msg = new StringBuilder();
196210
msg.append(" Total shards: ").append(response.getTotalShards())

test/framework/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchAssertionsTests.java

+30
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919

2020
package org.elasticsearch.test.hamcrest;
2121

22+
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
23+
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
24+
import org.elasticsearch.cluster.block.ClusterBlock;
25+
import org.elasticsearch.cluster.block.ClusterBlockException;
26+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2227
import org.elasticsearch.common.bytes.BytesReference;
2328
import org.elasticsearch.common.xcontent.XContentBuilder;
2429
import org.elasticsearch.common.xcontent.XContentParser;
@@ -27,7 +32,12 @@
2732
import org.elasticsearch.test.RandomObjects;
2833

2934
import java.io.IOException;
35+
import java.util.HashMap;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Set;
3039

40+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBlocked;
3141
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
3242
import static org.hamcrest.Matchers.containsString;
3343

@@ -188,4 +198,24 @@ public void testAssertXContentEquivalentErrors() throws IOException {
188198
assertThat(error.getMessage(), containsString("expected [1] more entries"));
189199
}
190200
}
201+
202+
public void testAssertBlocked() {
203+
Map<String, Set<ClusterBlock>> indexLevelBlocks = new HashMap<>();
204+
205+
indexLevelBlocks.put("test", Set.of(IndexMetaData.INDEX_READ_ONLY_BLOCK));
206+
assertBlocked(new BroadcastResponse(1, 0, 1, List.of(new DefaultShardOperationFailedException("test", 0,
207+
new ClusterBlockException(indexLevelBlocks)))));
208+
209+
indexLevelBlocks.put("test", Set.of(IndexMetaData.INDEX_READ_ONLY_ALLOW_DELETE_BLOCK));
210+
assertBlocked(new BroadcastResponse(1, 0, 1, List.of(new DefaultShardOperationFailedException("test", 0,
211+
new ClusterBlockException(indexLevelBlocks)))));
212+
213+
indexLevelBlocks.put("test", Set.of(IndexMetaData.INDEX_READ_BLOCK, IndexMetaData.INDEX_METADATA_BLOCK));
214+
assertBlocked(new BroadcastResponse(1, 0, 1, List.of(new DefaultShardOperationFailedException("test", 0,
215+
new ClusterBlockException(indexLevelBlocks)))));
216+
217+
indexLevelBlocks.put("test", Set.of(IndexMetaData.INDEX_READ_ONLY_BLOCK, IndexMetaData.INDEX_READ_ONLY_ALLOW_DELETE_BLOCK));
218+
assertBlocked(new BroadcastResponse(1, 0, 1, List.of(new DefaultShardOperationFailedException("test", 0,
219+
new ClusterBlockException(indexLevelBlocks)))));
220+
}
191221
}

0 commit comments

Comments
 (0)