diff --git a/docs/changelog/119743.yaml b/docs/changelog/119743.yaml new file mode 100644 index 0000000000000..b6f53c0dd1aed --- /dev/null +++ b/docs/changelog/119743.yaml @@ -0,0 +1,5 @@ +pr: 119743 +summary: POC mark read-only +area: Engine +type: enhancement +issues: [] diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AddIndexBlockRollingUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AddIndexBlockRollingUpgradeIT.java new file mode 100644 index 0000000000000..f8d185dbaabdd --- /dev/null +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AddIndexBlockRollingUpgradeIT.java @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.upgrades; + +import io.netty.handler.codec.http.HttpMethod; + +import com.carrotsearch.randomizedtesting.annotations.Name; + +import org.elasticsearch.TransportVersions; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.cluster.metadata.MetadataIndexStateService; +import org.hamcrest.Matchers; + +import java.io.IOException; +import java.util.Map; + +public class AddIndexBlockRollingUpgradeIT extends AbstractRollingUpgradeTestCase { + + private static final String INDEX_NAME = "test_add_block"; + + public AddIndexBlockRollingUpgradeIT(@Name("upgradedNodes") int upgradedNodes) { + super(upgradedNodes); + } + + public void testAddBlock() throws Exception { + if (isOldCluster()) { + createIndex(INDEX_NAME); + } else if (isMixedCluster()) { + blockWrites(); + // this is used both for upgrading from 9.0.0 to current and from 8.18 to current. + if (minimumTransportVersion().before(TransportVersions.ADD_INDEX_BLOCK_TWO_PHASE)) { + assertNull(verifiedSettingValue()); + } else { + assertThat(verifiedSettingValue(), Matchers.equalTo("true")); + } + } else { + assertTrue(isUpgradedCluster()); + blockWrites(); + assertThat(verifiedSettingValue(), Matchers.equalTo("true")); + } + } + + private static void blockWrites() throws IOException { + client().performRequest(new Request(HttpMethod.PUT.name(), "/" + INDEX_NAME + "/_block/write")); + + expectThrows( + ResponseException.class, + () -> client().performRequest( + newXContentRequest(HttpMethod.PUT, "/" + INDEX_NAME + "/_doc/test", (builder, params) -> builder.field("test", "test")) + ) + ); + } + + @SuppressWarnings("unchecked") + private static String verifiedSettingValue() throws IOException { + final var settingsRequest = new Request(HttpMethod.GET.name(), "/" + INDEX_NAME + "/_settings?flat_settings"); + final Map settingsResponse = entityAsMap(client().performRequest(settingsRequest)); + return (String) ((Map) ((Map) settingsResponse.get(INDEX_NAME)).get("settings")).get( + MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.getKey() + ); + } +} diff --git a/server/src/internalClusterTest/java/org/elasticsearch/blocks/SimpleBlocksIT.java b/server/src/internalClusterTest/java/org/elasticsearch/blocks/SimpleBlocksIT.java index bb4d579f6bed2..93f8997ff24a1 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/blocks/SimpleBlocksIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/blocks/SimpleBlocksIT.java @@ -16,14 +16,21 @@ import org.elasticsearch.action.admin.indices.readonly.AddIndexBlockResponse; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ClusterStateTaskListener; +import org.elasticsearch.cluster.SimpleBatchedExecutor; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexMetadata.APIBlock; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.MetadataIndexStateService; import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.common.Priority; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Tuple; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.test.BackgroundIndexer; import org.elasticsearch.test.ESIntegTestCase; @@ -266,6 +273,74 @@ public void testAddIndexBlock() throws Exception { assertHitCount(prepareSearch(indexName).setSize(0), nbDocs); } + public void testReAddUnverifiedIndexBlock() { + 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 = APIBlock.WRITE; + try { + AddIndexBlockResponse response = indicesAdmin().prepareAddBlock(block, indexName).get(); + assertTrue("Add block [" + block + "] to index [" + indexName + "] not acknowledged: " + response, response.isAcknowledged()); + assertIndexHasBlock(block, indexName); + + removeVerified(indexName); + + AddIndexBlockResponse response2 = indicesAdmin().prepareAddBlock(block, indexName).get(); + assertTrue("Add block [" + block + "] to index [" + indexName + "] not acknowledged: " + response, response2.isAcknowledged()); + assertIndexHasBlock(block, indexName); + } finally { + disableIndexBlock(indexName, block); + } + + } + + private static void removeVerified(String indexName) { + PlainActionFuture listener = new PlainActionFuture<>(); + internalCluster().clusterService(internalCluster().getMasterName()) + .createTaskQueue("test", Priority.NORMAL, new SimpleBatchedExecutor<>() { + @Override + public Tuple executeTask( + ClusterStateTaskListener clusterStateTaskListener, + ClusterState clusterState + ) { + + IndexMetadata indexMetadata = clusterState.metadata().index(indexName); + Settings.Builder settingsBuilder = Settings.builder().put(indexMetadata.getSettings()); + settingsBuilder.remove(MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.getKey()); + return Tuple.tuple( + ClusterState.builder(clusterState) + .metadata( + Metadata.builder(clusterState.metadata()) + .put( + IndexMetadata.builder(indexMetadata) + .settings(settingsBuilder) + .settingsVersion(indexMetadata.getSettingsVersion() + 1) + ) + ) + .build(), + null + ); + } + + @Override + public void taskSucceeded(ClusterStateTaskListener clusterStateTaskListener, Object ignored) { + listener.onResponse(null); + } + }) + .submitTask("test", e -> fail(e), null); + + listener.actionGet(); + } + public void testSameBlockTwice() throws Exception { final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); createIndex(indexName); @@ -452,6 +527,9 @@ static void assertIndexHasBlock(APIBlock block, final String... indices) { .count(), equalTo(1L) ); + if (block.getBlock().contains(ClusterBlockLevel.WRITE)) { + assertThat(MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.get(indexSettings), is(true)); + } } } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index a95eafb1fe76e..b37cabdad627b 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -164,6 +164,8 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_PROFILE_ROWS_PROCESSED = def(8_824_00_0); public static final TransportVersion BYTE_SIZE_VALUE_ALWAYS_USES_BYTES_1 = def(8_825_00_0); public static final TransportVersion REVERT_BYTE_SIZE_VALUE_ALWAYS_USES_BYTES_1 = def(8_826_00_0); + public static final TransportVersion ESQL_SKIP_ES_INDEX_SERIALIZATION = def(8_827_00_0); + public static final TransportVersion ADD_INDEX_BLOCK_TWO_PHASE = def(8_828_00_0); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockClusterStateUpdateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockClusterStateUpdateRequest.java index 50bd3b37b4cb3..f48c550d73efd 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockClusterStateUpdateRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockClusterStateUpdateRequest.java @@ -21,6 +21,7 @@ public record AddIndexBlockClusterStateUpdateRequest( TimeValue masterNodeTimeout, TimeValue ackTimeout, APIBlock block, + boolean markVerified, long taskId, Index[] indices ) { diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockRequest.java index 5f9bd6399fe7d..20201bf7fe058 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/readonly/AddIndexBlockRequest.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.admin.indices.readonly; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; @@ -32,12 +33,18 @@ public class AddIndexBlockRequest extends AcknowledgedRequest { private final ClusterBlock clusterBlock; + private final boolean phase1; ShardRequest(StreamInput in) throws IOException { super(in); clusterBlock = new ClusterBlock(in); + if (in.getTransportVersion().onOrAfter(TransportVersions.ADD_INDEX_BLOCK_TWO_PHASE)) { + phase1 = in.readBoolean(); + } else { + phase1 = true; // does not matter, not verified anyway + } } - public ShardRequest(final ShardId shardId, final ClusterBlock clusterBlock, final TaskId parentTaskId) { + public ShardRequest(final ShardId shardId, final ClusterBlock clusterBlock, boolean phase1, final TaskId parentTaskId) { super(shardId); this.clusterBlock = Objects.requireNonNull(clusterBlock); + this.phase1 = phase1; setParentTask(parentTaskId); } @Override public String toString() { - return "verify shard " + shardId + " before block with " + clusterBlock; + return "verify shard " + shardId + " before block with " + clusterBlock + " phase1=" + phase1; } @Override public void writeTo(final StreamOutput out) throws IOException { super.writeTo(out); clusterBlock.writeTo(out); + if (out.getTransportVersion().onOrAfter(TransportVersions.ADD_INDEX_BLOCK_TWO_PHASE)) { + out.writeBoolean(phase1); + } } public ClusterBlock clusterBlock() { return clusterBlock; } + + public boolean phase1() { + return phase1; + } } } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java index 2c5053c406f97..9e60e95791f7f 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.Build; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.action.admin.indices.close.CloseIndexClusterStateUpdateRequest; @@ -387,11 +388,18 @@ private static Tuple> addIndexBlock( ) { final Metadata.Builder metadata = Metadata.builder(currentState.metadata()); + final ClusterBlocks.Builder blocks = ClusterBlocks.builder(currentState.blocks()); final Set indicesToAddBlock = new HashSet<>(); for (Index index : indices) { - metadata.getSafe(index); // to check if index exists + IndexMetadata indexMetadata = metadata.getSafe(index);// to check if index exists if (currentState.blocks().hasIndexBlock(index.getName(), block.block)) { - logger.debug("index {} already has block {}, ignoring", index, block.block); + if (block.block.contains(ClusterBlockLevel.WRITE) && isIndexWriteBlockVerified(indexMetadata)) { + logger.debug("index {} already has block {}, ignoring", index, block.block); + } else { + // remove the block, we'll add a uuid based block below instead, never leaving it unblocked. + blocks.removeIndexBlock(index.getName(), block.block); + indicesToAddBlock.add(index); + } } else { indicesToAddBlock.add(index); } @@ -401,7 +409,6 @@ private static Tuple> addIndexBlock( return Tuple.tuple(currentState, Map.of()); } - final ClusterBlocks.Builder blocks = ClusterBlocks.builder(currentState.blocks()); final Map blockedIndices = new HashMap<>(); for (Index index : indicesToAddBlock) { @@ -409,7 +416,7 @@ private static Tuple> addIndexBlock( final Set clusterBlocks = currentState.blocks().indices().get(index.getName()); if (clusterBlocks != null) { for (ClusterBlock clusterBlock : clusterBlocks) { - if (clusterBlock.id() == block.block.id()) { + if (clusterBlock.id() == block.block.id() && clusterBlock.uuid() != null) { // Reuse the existing UUID-based block indexBlock = clusterBlock; break; @@ -442,6 +449,10 @@ private static Tuple> addIndexBlock( return Tuple.tuple(ClusterState.builder(currentState).blocks(blocks).metadata(metadata).build(), blockedIndices); } + private static boolean isIndexWriteBlockVerified(IndexMetadata indexMetadata) { + return VERIFIED_READ_ONLY_SETTING.get(indexMetadata.getSettings()); + } + /** * Adds an index block based on the given request, and notifies the listener upon completion. * Adding blocks is done in three steps: @@ -450,7 +461,7 @@ private static Tuple> addIndexBlock( * - Second, shards are checked to have properly applied the UUID-based block. * (see {@link WaitForBlocksApplied}). * - Third, the temporary UUID-based block is turned into a full block - * (see {@link #finalizeBlock(ClusterState, Map, Map, APIBlock)}. + * (see {@link #finalizeBlock(ClusterState, Map, Map, APIBlock, boolean)}. * Using this three-step process ensures non-interference by other operations in case where * we notify successful completion here. */ @@ -511,7 +522,16 @@ public void taskSucceeded(AddBlocksTask task, Map blockedIn + "]-[" + blockedIndices.keySet().stream().map(Index::getName).collect(Collectors.joining(", ")) + "]", - new FinalizeBlocksTask(task.request, blockedIndices, verifyResults, delegate2), + new FinalizeBlocksTask( + task.request, + blockedIndices, + verifyResults, + task.request().markVerified() + && clusterService.state() + .getMinTransportVersion() + .onOrAfter(TransportVersions.ADD_INDEX_BLOCK_TWO_PHASE), + delegate2 + ), null ) ) @@ -539,7 +559,8 @@ public Tuple> executeTask(FinalizeBlocksTask clusterState, task.blockedIndices, task.verifyResults, - task.request.block() + task.request.block(), + task.markVerified() ); assert finalizeResult.v2().size() == task.verifyResults.size(); return finalizeResult; @@ -556,6 +577,7 @@ private record FinalizeBlocksTask( AddIndexBlockClusterStateUpdateRequest request, Map blockedIndices, Map verifyResults, + boolean markVerified, ActionListener listener ) implements ClusterStateTaskListener { @Override @@ -805,10 +827,21 @@ private void sendVerifyShardBlockRequest( final TransportVerifyShardIndexBlockAction.ShardRequest shardRequest = new TransportVerifyShardIndexBlockAction.ShardRequest( shardId, block, + true, parentTaskId ); shardRequest.timeout(request.ackTimeout()); - client.executeLocally(TransportVerifyShardIndexBlockAction.TYPE, shardRequest, listener); + client.executeLocally( + TransportVerifyShardIndexBlockAction.TYPE, + shardRequest, + listener.delegateFailure((delegate, replicationResponse) -> { + final var phase2 = new TransportVerifyShardIndexBlockAction.ShardRequest(shardId, block, false, parentTaskId); + if (request.ackTimeout() != null) { + phase2.timeout(request.ackTimeout()); + } + client.executeLocally(TransportVerifyShardIndexBlockAction.TYPE, phase2, delegate); + }) + ); } } @@ -959,15 +992,18 @@ private void onlyOpenIndices(final OpenIndexClusterStateUpdateRequest request, f * @param blockedIndices the indices and their temporary UUID-based blocks to convert * @param verifyResult the index-level results for adding the block * @param block the full block to convert to + * @param markVerified if the index should be marked verified in case of a write-level block. * @return the updated cluster state, as well as the (failed and successful) index-level results for adding the block */ private static Tuple> finalizeBlock( final ClusterState currentState, final Map blockedIndices, final Map verifyResult, - final APIBlock block + final APIBlock block, + final boolean markVerified ) { final ClusterBlocks.Builder blocks = ClusterBlocks.builder(currentState.blocks()); + final Metadata.Builder metadata = Metadata.builder(currentState.metadata()); final Set effectivelyBlockedIndices = new HashSet<>(); Map blockingResults = new HashMap<>(verifyResult); @@ -1015,12 +1051,25 @@ private static Tuple> finalizeBlock( logger.debug("add block {} to index {} succeeded", block.block, index); effectivelyBlockedIndices.add(index.getName()); + + if (block.getBlock().contains(ClusterBlockLevel.WRITE) && markVerified) { + final IndexMetadata indexMetadata = metadata.getSafe(index); + if (VERIFIED_READ_ONLY_SETTING.get(indexMetadata.getSettings()) == false) { + final IndexMetadata.Builder updatedMetadata = IndexMetadata.builder(indexMetadata) + .settings(Settings.builder().put(indexMetadata.getSettings()).put(VERIFIED_READ_ONLY_SETTING.getKey(), true)) + .settingsVersion(indexMetadata.getSettingsVersion() + 1); + metadata.put(updatedMetadata); + } + } } catch (final IndexNotFoundException e) { logger.debug("index {} has been deleted since blocking it started, ignoring", index); } } logger.info("completed adding [index.blocks.{}] block to indices {}", block.name, effectivelyBlockedIndices); - return Tuple.tuple(ClusterState.builder(currentState).blocks(blocks).build(), List.copyOf(blockingResults.values())); + return Tuple.tuple( + ClusterState.builder(currentState).metadata(metadata).blocks(blocks).build(), + List.copyOf(blockingResults.values()) + ); } /** diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataUpdateSettingsService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataUpdateSettingsService.java index 4fcbd4165423b..e984768277d27 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataUpdateSettingsService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataUpdateSettingsService.java @@ -19,6 +19,7 @@ import org.elasticsearch.cluster.ClusterStateAckListener; import org.elasticsearch.cluster.ClusterStateTaskListener; import org.elasticsearch.cluster.block.ClusterBlock; +import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -329,7 +330,7 @@ ClusterState execute(ClusterState currentState) { final ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks()); boolean changedBlocks = false; for (IndexMetadata.APIBlock block : IndexMetadata.APIBlock.values()) { - changedBlocks |= maybeUpdateClusterBlock(actualIndices, blocks, block.block, block.setting, openSettings); + changedBlocks |= maybeUpdateClusterBlock(actualIndices, blocks, block.block, block.setting, openSettings, metadataBuilder); } changed |= changedBlocks; @@ -424,7 +425,8 @@ private static boolean maybeUpdateClusterBlock( ClusterBlocks.Builder blocks, ClusterBlock block, Setting setting, - Settings openSettings + Settings openSettings, + Metadata.Builder metadataBuilder ) { boolean changed = false; if (setting.exists(openSettings)) { @@ -439,6 +441,12 @@ private static boolean maybeUpdateClusterBlock( if (blocks.hasIndexBlock(index, block)) { blocks.removeIndexBlock(index, block); changed = true; + if (block.contains(ClusterBlockLevel.WRITE)) { + IndexMetadata indexMetadata = metadataBuilder.get(index); + Settings.Builder indexSettings = Settings.builder().put(indexMetadata.getSettings()); + indexSettings.remove(MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.getKey()); + metadataBuilder.put(IndexMetadata.builder(indexMetadata).settings(indexSettings)); + } } } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecatedIndexPredicate.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecatedIndexPredicate.java index 26f769cd08766..540ffbc1b83e6 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecatedIndexPredicate.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecatedIndexPredicate.java @@ -9,6 +9,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.MetadataIndexStateService; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.index.IndexVersions; @@ -33,7 +34,14 @@ public static Predicate getReindexRequiredPredicate(Metadata metadata) { } public static boolean reindexRequired(IndexMetadata indexMetadata) { - return creationVersionBeforeMinimumWritableVersion(indexMetadata) && isNotSearchableSnapshot(indexMetadata); + return creationVersionBeforeMinimumWritableVersion(indexMetadata) + && isNotSearchableSnapshot(indexMetadata) + && isNotVerifiedReadOnly(indexMetadata); + } + + private static boolean isNotVerifiedReadOnly(IndexMetadata indexMetadata) { + // no need to check blocks. + return MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.get(indexMetadata.getSettings()) == false; } private static boolean isNotSearchableSnapshot(IndexMetadata indexMetadata) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DownsampleAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DownsampleAction.java index 6ce9e05e4a464..8cd1a4bd03eab 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DownsampleAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DownsampleAction.java @@ -203,7 +203,7 @@ public List toSteps(Client client, String phase, StepKey nextStepKey) { Instant::now ); // Mark source index as read-only - ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, generateDownsampleIndexNameKey, client); + ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, generateDownsampleIndexNameKey, client, true); // Before the downsample action was retry-able, we used to generate a unique downsample index name and delete the previous index in // case a failure occurred. The downsample action can now retry execution in case of failure and start where it left off, so no diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyAction.java index b36156842acf5..b93bf652b84b4 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyAction.java @@ -69,7 +69,7 @@ public List toSteps(Client client, String phase, StepKey nextStepKey) { readOnlyKey, Instant::now ); - ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, nextStepKey, client); + ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, nextStepKey, client, true); return List.of(checkNotWriteIndexStep, waitUntilTimeSeriesEndTimeStep, readOnlyStep); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyStep.java index 208b6bb1b4fd0..2f142d832fc3e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ReadOnlyStep.java @@ -23,9 +23,15 @@ */ public class ReadOnlyStep extends AsyncActionStep { public static final String NAME = "readonly"; + private final boolean markVerified; - public ReadOnlyStep(StepKey key, StepKey nextStepKey, Client client) { + /** + * @param markVerified whether the index should be marked verified after becoming read-only, ensuring that N-2 is supported without + * manual intervention. Should be set to true when the read-only block is not temporary. + */ + public ReadOnlyStep(StepKey key, StepKey nextStepKey, Client client, boolean markVerified) { super(key, nextStepKey, client); + this.markVerified = markVerified; } @Override @@ -39,7 +45,8 @@ public void performAction( .indices() .execute( TransportAddIndexBlockAction.TYPE, - new AddIndexBlockRequest(WRITE, indexMetadata.getIndex().getName()).masterNodeTimeout(TimeValue.MAX_VALUE), + new AddIndexBlockRequest(WRITE, indexMetadata.getIndex().getName()).masterNodeTimeout(TimeValue.MAX_VALUE) + .markVerified(markVerified), listener.delegateFailureAndWrap((l, response) -> { if (response.isAcknowledged() == false) { throw new ElasticsearchException("read only add block index request failed to be acknowledged"); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java index f7478518613e2..4e7bdcb1197bc 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java @@ -233,7 +233,7 @@ public List toSteps(Client client, String phase, Step.StepKey nextStepKey) readOnlyKey, Instant::now ); - ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, checkTargetShardsCountKey, client); + ReadOnlyStep readOnlyStep = new ReadOnlyStep(readOnlyKey, checkTargetShardsCountKey, client, false); CheckTargetShardsCountStep checkTargetShardsCountStep = new CheckTargetShardsCountStep( checkTargetShardsCountKey, cleanupShrinkIndexKey,