Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
20e57fe
Add RemoveBlock core service implementation
HyunSangHan Jun 8, 2025
4f223be
Add RemoveBlock transport layer and client APIs
HyunSangHan Jun 8, 2025
0b25da3
Add REST interface for RemoveBlock API
HyunSangHan Jun 8, 2025
4bc5893
Restore settings test and add new remove block test
HyunSangHan Jun 11, 2025
383d1ce
Edit variable name and remove unnecessary overrides
HyunSangHan Jun 11, 2025
017a4f0
Pass timeout parameters directly in RemoveIndexBlockRequest constructor
HyunSangHan Jun 11, 2025
53895fc
Move cluster task logic to transport action
nielsbauman Jun 11, 2025
a5c86d0
Use `ProjectState`
nielsbauman Jun 11, 2025
9f57645
Make request cancelleable
nielsbauman Jun 13, 2025
38b06aa
Fix broken test cases using prepareRemoveBlock
HyunSangHan Jun 15, 2025
131bfb0
Update remove_block API test to use capabilities feature
HyunSangHan Jun 15, 2025
7c602bc
Remove redundant description in index-block.md
HyunSangHan Jun 15, 2025
2d50a14
Add new line to the end of indices.remove_block.json
HyunSangHan Jun 15, 2025
b6f408f
Reorder test assertions in testRemoveIndexBlockIsRejected
HyunSangHan Jun 15, 2025
3231236
Fix bug for getting block name
HyunSangHan Jun 15, 2025
7c0b881
Fix assertion for blockedIndexCountListener
HyunSangHan Jun 15, 2025
8ba7e68
Remove unused constant
HyunSangHan Jun 15, 2025
0ff2031
Merge branch 'main' into api-remove-index-block
nielsbauman Jun 18, 2025
15461ad
Add changelog
nielsbauman Jun 18, 2025
1a2e208
Merge branch 'main' into api-remove-index-block
nielsbauman Jun 19, 2025
bc938f8
Remove VERIFIED_READ_ONLY_SETTING when write blocks are cleared
HyunSangHan Jun 24, 2025
6f59e01
Refactor `hasBlock`
nielsbauman Jun 24, 2025
9fe16a4
Merge branch 'main' into api-remove-index-block
nielsbauman Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/reference/elasticsearch/index-settings/index-block.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,106 @@ The API returns following response:
}
```


## Remove index block API [remove-index-block]

Removes an index block from an index. Unlike the add index block API, this operation doesn't require shard-level verification and completes immediately after updating the cluster metadata.

```console
DELETE /my-index-000001/_block/write
```


### {{api-request-title}} [remove-index-block-api-request]

`DELETE /<index>/_block/<block>`


### {{api-path-parms-title}} [remove-index-block-api-path-params]

`<index>`
: (Optional, string) Comma-separated list or wildcard expression of index names used to limit the request.

By default, you must explicitly name the indices you are removing blocks from. To allow the removal of blocks from indices with `_all`, `*`, or other wildcard expressions, change the `action.destructive_requires_name` setting to `false`. You can update this setting in the `elasticsearch.yml` file or using the [cluster update settings](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-cluster-put-settings) API.


`<block>`
: (Required, string) Block type to remove from the index.

**Valid values**:

`metadata`
: Remove metadata block, allowing metadata changes.

`read`
: Remove read block, allowing read operations.

`read_only`
: Remove read-only block, allowing write operations and metadata changes.

`write`
: Remove write block, allowing write operations.

::::



### {{api-query-parms-title}} [remove-index-block-api-query-params]

`allow_no_indices`
: (Optional, Boolean) If `false`, the request returns an error if any wildcard expression, [index alias](docs-content://manage-data/data-store/aliases.md), or `_all` value targets only missing or closed indices. This behavior applies even if the request targets other open indices. For example, a request targeting `foo*,bar*` returns an error if an index starts with `foo` but no index starts with `bar`.

Defaults to `true`.


`expand_wildcards`
: (Optional, string) Type of index that wildcard patterns can match. If the request can target data streams, this argument determines whether wildcard expressions match hidden data streams. Supports comma-separated values, such as `open,hidden`. Valid values are:

`all`
: Match any data stream or index, including [hidden](/reference/elasticsearch/rest-apis/api-conventions.md#multi-hidden) ones.

`open`
: Match open, non-hidden indices. Also matches any non-hidden data stream.

`closed`
: Match closed, non-hidden indices. Also matches any non-hidden data stream. Data streams cannot be closed.

`hidden`
: Match hidden data streams and hidden indices. Must be combined with `open`, `closed`, or both.

`none`
: Wildcard patterns are not accepted.

Defaults to `open`.


`ignore_unavailable`
: (Optional, Boolean) If `false`, the request returns an error if it targets a missing or closed index. Defaults to `false`.

`master_timeout`
: (Optional, [time units](/reference/elasticsearch/rest-apis/api-conventions.md#time-units)) Period to wait for the master node. If the master node is not available before the timeout expires, the request fails and returns an error. Defaults to `30s`. Can also be set to `-1` to indicate that the request should never timeout.

`timeout`
: (Optional, [time units](/reference/elasticsearch/rest-apis/api-conventions.md#time-units)) Period to wait for a response from all relevant nodes in the cluster after updating the cluster metadata. If no response is received before the timeout expires, the cluster metadata update still applies but the response will indicate that it was not completely acknowledged. Defaults to `30s`. Can also be set to `-1` to indicate that the request should never timeout.


### {{api-examples-title}} [remove-index-block-api-example]

The following example shows how to remove an index block:

```console
DELETE /my-index-000001/_block/write
```

The API returns following response:

```console-result
{
"acknowledged" : true,
"indices" : [ {
"name" : "my-index-000001",
"unblocked" : true
} ]
}
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"indices.remove_block": {
"documentation": {
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/index-modules-blocks.html",
"description": "Removes a block from an index."
},
"stability": "stable",
"visibility": "public",
"headers": {
"accept": [
"application/json"
]
},
"url": {
"paths": [
{
"path": "/{index}/_block/{block}",
"methods": [
"DELETE"
],
"parts": {
"index": {
"type": "list",
"description": "A comma separated list of indices to remove a block from"
},
"block": {
"type": "string",
"description": "The block to remove (one of read, write, read_only or metadata)"
}
}
}
]
},
"params": {
"timeout": {
"type": "time",
"description": "Explicit operation timeout"
},
"master_timeout": {
"type": "time",
"description": "Specify timeout for connection to master"
},
"ignore_unavailable": {
"type": "boolean",
"description": "Whether specified concrete indices should be ignored when unavailable (missing or closed)"
},
"allow_no_indices": {
"type": "boolean",
"description": "Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)"
},
"expand_wildcards": {
"type": "enum",
"options": [
"open",
"closed",
"hidden",
"none",
"all"
],
"default": "open",
"description": "Whether to expand wildcard expression to concrete indices that are open, closed or both."
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,43 @@
index: test_index
body:
index.blocks.write: false

---
"Test removing block via remove_block API":
- requires:
cluster_features: ["gte_v9.1.0"]
reason: "index block APIs have only been made available in 9.1.0"
- do:
indices.create:
index: test_index_2

- do:
indices.add_block:
index: test_index_2
block: write
- is_true: acknowledged

- do:
catch: /cluster_block_exception/
index:
index: test_index_2
body: { foo: bar }

- do:
search:
index: test_index_2

- do:
indices.remove_block:
index: test_index_2
block: write
- is_true: acknowledged

- do:
index:
index: test_index_2
body: { foo: bar }

- do:
search:
index: test_index_2
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.readonly.AddIndexBlockResponse;
import org.elasticsearch.action.admin.indices.readonly.RemoveIndexBlockResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.PlainActionFuture;
Expand Down Expand Up @@ -549,6 +550,143 @@ public static void disableIndexBlock(String index, APIBlock block) {
disableIndexBlock(index, block.settingName());
}

public void testRemoveBlockToMissingIndex() {
IndexNotFoundException e = expectThrows(
IndexNotFoundException.class,
indicesAdmin().prepareRemoveBlock(randomAddableBlock(), "test")
);
assertThat(e.getMessage(), is("no such index [test]"));
}

public void testRemoveBlockToOneMissingIndex() {
createIndex("test1");
final IndexNotFoundException e = expectThrows(
IndexNotFoundException.class,
indicesAdmin().prepareRemoveBlock(randomAddableBlock(), "test1", "test2")
);
assertThat(e.getMessage(), is("no such index [test2]"));
}

public void testRemoveBlockNoIndex() {
final ActionRequestValidationException e = expectThrows(
ActionRequestValidationException.class,
indicesAdmin().prepareRemoveBlock(randomAddableBlock())
);
assertThat(e.getMessage(), containsString("index is missing"));
}

public void testRemoveBlockNullIndex() {
expectThrows(NullPointerException.class, () -> indicesAdmin().prepareRemoveBlock(randomAddableBlock(), (String[]) null));
}

public void testCannotRemoveReadOnlyAllowDeleteBlock() {
createIndex("test1");
final ActionRequestValidationException e = expectThrows(
ActionRequestValidationException.class,
indicesAdmin().prepareRemoveBlock(APIBlock.READ_ONLY_ALLOW_DELETE, "test1")
);
assertThat(e.getMessage(), containsString("read_only_allow_delete block is for internal use only"));
}

public void testRemoveIndexBlock() throws Exception {
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
createIndex(indexName);
ensureGreen(indexName);

final int nbDocs = randomIntBetween(0, 50);
indexRandom(
randomBoolean(),
false,
randomBoolean(),
IntStream.range(0, nbDocs).mapToObj(i -> prepareIndex(indexName).setId(String.valueOf(i)).setSource("num", i)).collect(toList())
);

final APIBlock block = randomAddableBlock();
try {
// First add the block
AddIndexBlockResponse addResponse = indicesAdmin().prepareAddBlock(block, indexName).get();
assertTrue(
"Add block [" + block + "] to index [" + indexName + "] not acknowledged: " + addResponse,
addResponse.isAcknowledged()
);
assertIndexHasBlock(block, indexName);

// Then remove the block
RemoveIndexBlockResponse removeResponse = indicesAdmin().prepareRemoveBlock(block, indexName).get();
assertTrue(
"Remove block [" + block + "] from index [" + indexName + "] not acknowledged: " + removeResponse,
removeResponse.isAcknowledged()
);
assertIndexDoesNotHaveBlock(block, indexName);
} finally {
// Ensure cleanup
disableIndexBlock(indexName, block);
}

indicesAdmin().prepareRefresh(indexName).get();
assertHitCount(prepareSearch(indexName).setSize(0), nbDocs);
}

public void testRemoveBlockIdempotent() throws Exception {
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
createIndex(indexName);
ensureGreen(indexName);

final APIBlock block = randomAddableBlock();
try {
// First add the block
assertAcked(indicesAdmin().prepareAddBlock(block, indexName));
assertIndexHasBlock(block, indexName);

// Remove the block
assertAcked(indicesAdmin().prepareRemoveBlock(block, indexName));
assertIndexDoesNotHaveBlock(block, indexName);

// Second remove should be acked too (idempotent behavior)
assertAcked(indicesAdmin().prepareRemoveBlock(block, indexName));
assertIndexDoesNotHaveBlock(block, indexName);
} finally {
disableIndexBlock(indexName, block);
}
}

public void testRemoveBlockOneMissingIndexIgnoreMissing() throws Exception {
createIndex("test1");
final APIBlock block = randomAddableBlock();
try {
// First add the block to test1
assertAcked(indicesAdmin().prepareAddBlock(block, "test1"));
assertIndexHasBlock(block, "test1");

// Remove from both test1 and test2 (missing), with lenient options
assertBusy(
() -> assertAcked(indicesAdmin().prepareRemoveBlock(block, "test1", "test2").setIndicesOptions(lenientExpandOpen()))
);
assertIndexDoesNotHaveBlock(block, "test1");
} finally {
disableIndexBlock("test1", block);
}
}

static void assertIndexDoesNotHaveBlock(APIBlock block, final String... indices) {
final ClusterState clusterState = clusterAdmin().prepareState(TEST_REQUEST_TIMEOUT).get().getState();
final ProjectId projectId = Metadata.DEFAULT_PROJECT_ID;
for (String index : indices) {
final IndexMetadata indexMetadata = clusterState.metadata().getProject(projectId).indices().get(index);
final Settings indexSettings = indexMetadata.getSettings();
assertThat(
"Index " + index + " should not have block setting [" + block.settingName() + "]",
indexSettings.getAsBoolean(block.settingName(), false),
is(false)
);
assertThat(
"Index " + index + " should not have block [" + block.getBlock() + "]",
clusterState.blocks().hasIndexBlock(projectId, index, block.getBlock()),
is(false)
);
}
}

/**
* The read-only-allow-delete block cannot be added via the add index block API; this method chooses randomly from the values that
* the add index block API does support.
Expand Down
Loading
Loading