Skip to content

Commit acf031c

Browse files
authored
Forbid read-only-allow-delete block in blocks API (#58727)
* Forbid read-only-allow-delete block in blocks API The read-only-allow-delete block is not really under the user's control since Elasticsearch adds/removes it automatically. This commit removes support for it from the new API for adding blocks to indices that was introduced in #58094. * Missing xref * Reword paragraph on read-only-allow-delete block
1 parent dcd723a commit acf031c

File tree

6 files changed

+58
-28
lines changed

6 files changed

+58
-28
lines changed

docs/reference/index-modules/blocks.asciidoc

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@ index:
2222
Set to `true` to make the index and index metadata read only, `false` to
2323
allow writes and metadata changes.
2424

25-
`index.blocks.read_only_allow_delete`::
26-
27-
Similar to `index.blocks.read_only`, but also allows deleting the index to
28-
make more resources available. The <<disk-based-shard-allocation,disk-based shard
29-
allocator>> may add and remove this block automatically.
30-
31-
Deleting documents from an index to release resources - rather than deleting the index itself - can increase the index size over time. When `index.blocks.read_only_allow_delete` is set to `true`, deleting documents is not permitted. However, deleting the index itself releases the read-only index block and makes resources available almost immediately.
32-
33-
IMPORTANT: {es} adds and removes the read-only index block automatically when the disk utilization falls below the high watermark, controlled by <<cluster-routing-flood_stage,cluster.routing.allocation.disk.watermark.flood_stage>>.
34-
3525
`index.blocks.read`::
3626

3727
Set to `true` to disable read operations against the index.
@@ -46,6 +36,26 @@ IMPORTANT: {es} adds and removes the read-only index block automatically when th
4636

4737
Set to `true` to disable index metadata reads and writes.
4838

39+
`index.blocks.read_only_allow_delete`::
40+
41+
Similar to `index.blocks.read_only`, but also allows deleting the index to
42+
make more resources available. The <<disk-based-shard-allocation,disk-based shard
43+
allocator>> adds and removes this block automatically.
44+
45+
Deleting documents from an index - rather than deleting the index itself - can
46+
in fact increase the index size. When you are running out of disk space
47+
`index.blocks.read_only_allow_delete` is set to `true`, preventing you from
48+
consuming more disk space by deleting some documents. However, this block does
49+
permit you to delete the index itself since this does not require any extra
50+
disk space. When you delete an index the data is removed from disk almost
51+
immediately, freeing the space it consumes.
52+
53+
IMPORTANT: {es} adds the read-only-allow-delete index block automatically when
54+
disk utilisation exceeds the <<cluster-routing-flood_stage,flood-stage
55+
watermark>> and removes it again when disk utilisation is below the
56+
<<cluster-routing-watermark-high,high watermark>>. You should not apply this
57+
block yourself.
58+
4959
[discrete]
5060
[[add-index-block]]
5161
=== Add index block API
@@ -94,11 +104,6 @@ Disable read operations.
94104
`read_only`::
95105
Disable write operations and metadata changes.
96106
97-
`read_only_allow_delete`::
98-
Disable write operations and metadata changes.
99-
Document deletion is disabled.
100-
However, index deletion is still allowed.
101-
102107
`write`::
103108
Disable write operations. However, metadata changes are still allowed.
104109
====

docs/reference/modules/cluster/disk_allocator.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ file or updated dynamically on a live cluster with the
1313

1414
Defaults to `true`. Set to `false` to disable the disk allocation decider.
1515

16+
[[cluster-routing-watermark-low]]
1617
`cluster.routing.allocation.disk.watermark.low`::
1718

1819
Controls the low watermark for disk usage. It defaults to `85%`, meaning
@@ -22,6 +23,7 @@ file or updated dynamically on a live cluster with the
2223
amount of space is available. This setting has no effect on the primary
2324
shards of newly-created indices but will prevent their replicas from being allocated.
2425

26+
[[cluster-routing-watermark-high]]
2527
`cluster.routing.allocation.disk.watermark.high`::
2628

2729
Controls the high watermark. It defaults to `90%`, meaning that

rest-api-spec/src/main/resources/rest-api-spec/api/indices.add_block.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
"block":{
2121
"type":"string",
22-
"description":"The block to add (one of read, write, read_only, metadata, read_only_allow_delete)"
22+
"description":"The block to add (one of read, write, read_only or metadata)"
2323
}
2424
}
2525
}

server/src/internalClusterTest/java/org/elasticsearch/blocks/SimpleBlocksIT.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.ExceptionsHelper;
2323
import org.elasticsearch.action.ActionRequestValidationException;
2424
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
25+
import org.elasticsearch.action.admin.indices.readonly.AddIndexBlockRequestBuilder;
2526
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequestBuilder;
2627
import org.elasticsearch.action.index.IndexRequestBuilder;
2728
import org.elasticsearch.action.index.IndexResponse;
@@ -177,6 +178,9 @@ public void testAddBlocksWhileExistingBlocks() {
177178
ensureGreen("test");
178179

179180
for (APIBlock otherBlock : APIBlock.values()) {
181+
if (otherBlock == APIBlock.READ_ONLY_ALLOW_DELETE) {
182+
continue;
183+
}
180184

181185
for (APIBlock block : Arrays.asList(APIBlock.READ, APIBlock.WRITE)) {
182186
try {
@@ -216,20 +220,20 @@ public void testAddBlocksWhileExistingBlocks() {
216220

217221
public void testAddBlockToMissingIndex() {
218222
IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().admin().indices()
219-
.prepareAddBlock(randomFrom(APIBlock.values()),"test").get());
223+
.prepareAddBlock(randomAddableBlock(), "test").get());
220224
assertThat(e.getMessage(), is("no such index [test]"));
221225
}
222226

223227
public void testAddBlockToOneMissingIndex() {
224228
createIndex("test1");
225229
final IndexNotFoundException e = expectThrows(IndexNotFoundException.class,
226-
() -> client().admin().indices().prepareAddBlock(randomFrom(APIBlock.values()),"test1", "test2").get());
230+
() -> client().admin().indices().prepareAddBlock(randomAddableBlock(), "test1", "test2").get());
227231
assertThat(e.getMessage(), is("no such index [test2]"));
228232
}
229233

230234
public void testCloseOneMissingIndexIgnoreMissing() throws Exception {
231235
createIndex("test1");
232-
final APIBlock block = randomFrom(APIBlock.values());
236+
final APIBlock block = randomAddableBlock();
233237
try {
234238
assertBusy(() -> assertAcked(client().admin().indices().prepareAddBlock(block, "test1", "test2")
235239
.setIndicesOptions(lenientExpandOpen())));
@@ -241,13 +245,20 @@ public void testCloseOneMissingIndexIgnoreMissing() throws Exception {
241245

242246
public void testAddBlockNoIndex() {
243247
final ActionRequestValidationException e = expectThrows(ActionRequestValidationException.class,
244-
() -> client().admin().indices().prepareAddBlock(randomFrom(APIBlock.values())).get());
248+
() -> client().admin().indices().prepareAddBlock(randomAddableBlock()).get());
245249
assertThat(e.getMessage(), containsString("index is missing"));
246250
}
247251

248252
public void testAddBlockNullIndex() {
249253
expectThrows(NullPointerException.class,
250-
() -> client().admin().indices().prepareAddBlock(randomFrom(APIBlock.values()), (String[])null));
254+
() -> client().admin().indices().prepareAddBlock(randomAddableBlock(), (String[])null));
255+
}
256+
257+
public void testCannotAddReadOnlyAllowDeleteBlock() {
258+
createIndex("test1");
259+
final AddIndexBlockRequestBuilder request = client().admin().indices().prepareAddBlock(APIBlock.READ_ONLY_ALLOW_DELETE, "test1");
260+
final ActionRequestValidationException e = expectThrows(ActionRequestValidationException.class, request::get);
261+
assertThat(e.getMessage(), containsString("read_only_allow_delete block is for internal use only"));
251262
}
252263

253264
public void testAddIndexBlock() throws Exception {
@@ -258,7 +269,7 @@ public void testAddIndexBlock() throws Exception {
258269
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, nbDocs)
259270
.mapToObj(i -> client().prepareIndex(indexName).setId(String.valueOf(i)).setSource("num", i)).collect(toList()));
260271

261-
final APIBlock block = randomFrom(APIBlock.values());
272+
final APIBlock block = randomAddableBlock();
262273
try {
263274
assertAcked(client().admin().indices().prepareAddBlock(block, indexName));
264275
assertIndexHasBlock(block, indexName);
@@ -278,7 +289,7 @@ public void testSameBlockTwice() throws Exception {
278289
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, randomIntBetween(1, 10))
279290
.mapToObj(i -> client().prepareIndex(indexName).setId(String.valueOf(i)).setSource("num", i)).collect(toList()));
280291
}
281-
final APIBlock block = randomFrom(APIBlock.values());
292+
final APIBlock block = randomAddableBlock();
282293
try {
283294
assertAcked(client().admin().indices().prepareAddBlock(block, indexName));
284295
assertIndexHasBlock(block, indexName);
@@ -300,7 +311,7 @@ public void testAddBlockToUnassignedIndex() throws Exception {
300311
assertThat(clusterState.metadata().indices().get(indexName).getState(), is(IndexMetadata.State.OPEN));
301312
assertThat(clusterState.routingTable().allShards().stream().allMatch(ShardRouting::unassigned), is(true));
302313

303-
final APIBlock block = randomFrom(APIBlock.values());
314+
final APIBlock block = randomAddableBlock();
304315
try {
305316
assertAcked(client().admin().indices().prepareAddBlock(block, indexName));
306317
assertIndexHasBlock(block, indexName);
@@ -321,7 +332,7 @@ public void testConcurrentAddBlock() throws InterruptedException {
321332
final CountDownLatch startClosing = new CountDownLatch(1);
322333
final Thread[] threads = new Thread[randomIntBetween(2, 5)];
323334

324-
final APIBlock block = randomFrom(APIBlock.values());
335+
final APIBlock block = randomAddableBlock();
325336

326337
try {
327338
for (int i = 0; i < threads.length; i++) {
@@ -356,7 +367,7 @@ public void testAddBlockWhileIndexingDocuments() throws Exception {
356367
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
357368
createIndex(indexName);
358369

359-
final APIBlock block = randomFrom(APIBlock.values());
370+
final APIBlock block = randomAddableBlock();
360371

361372
int nbDocs = 0;
362373

@@ -400,7 +411,7 @@ public void testAddBlockWhileDeletingIndices() throws Exception {
400411
final List<Thread> threads = new ArrayList<>();
401412
final CountDownLatch latch = new CountDownLatch(1);
402413

403-
final APIBlock block = randomFrom(APIBlock.values());
414+
final APIBlock block = randomAddableBlock();
404415

405416
Consumer<Exception> exceptionConsumer = t -> {
406417
Throwable cause = ExceptionsHelper.unwrapCause(t);
@@ -478,4 +489,12 @@ static void assertIndexHasBlock(APIBlock block, final String... indices) {
478489
public static void disableIndexBlock(String index, APIBlock block) {
479490
disableIndexBlock(index, block.settingName());
480491
}
492+
493+
/**
494+
* The read-only-allow-delete block cannot be added via the add index block API; this method chooses randomly from the values that
495+
* the add index block API does support.
496+
*/
497+
private static APIBlock randomAddableBlock() {
498+
return randomValueOtherThan(APIBlock.READ_ONLY_ALLOW_DELETE, () -> randomFrom(APIBlock.values()));
499+
}
481500
}

server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public ActionRequestValidationException validate() {
6363
if (CollectionUtils.isEmpty(indices)) {
6464
validationException = addValidationError("index is missing", validationException);
6565
}
66+
if (block == APIBlock.READ_ONLY_ALLOW_DELETE) {
67+
validationException = addValidationError("read_only_allow_delete block is for internal use only", validationException);
68+
}
6669
return validationException;
6770
}
6871

test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1470,7 +1470,8 @@ public static void disableIndexBlock(String index, String block) {
14701470

14711471
/** Enables an index block for the specified index */
14721472
public static void enableIndexBlock(String index, String block) {
1473-
if (randomBoolean()) {
1473+
if (IndexMetadata.APIBlock.fromSetting(block) == IndexMetadata.APIBlock.READ_ONLY_ALLOW_DELETE || randomBoolean()) {
1474+
// the read-only-allow-delete block isn't supported by the add block API so we must use the update settings API here.
14741475
Settings settings = Settings.builder().put(block, true).build();
14751476
client().admin().indices().prepareUpdateSettings(index).setSettings(settings).get();
14761477
} else {

0 commit comments

Comments
 (0)