Skip to content

Commit 76a4a05

Browse files
authored
[8.x] Forbid the removal of the write block if the index is read-only (#120647)
Forbids on 8.x the removal/update of a write block if at least one node of the cluster cannot write the index. Backport of #120648 for 8.18. Relates ES-10320
1 parent e171fe6 commit 76a4a05

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public boolean hasIndexBlock(String index, ClusterBlock block) {
146146
return indicesBlocks.containsKey(index) && indicesBlocks.get(index).contains(block);
147147
}
148148

149+
public boolean hasIndexBlockLevel(String index, ClusterBlockLevel level) {
150+
return blocksForIndex(level, index).isEmpty() == false;
151+
}
152+
149153
public boolean hasIndexBlockWithId(String index, int blockId) {
150154
final Set<ClusterBlock> clusterBlocks = indicesBlocks.get(index);
151155
if (clusterBlocks != null) {
@@ -398,6 +402,10 @@ public boolean hasIndexBlock(String index, ClusterBlock block) {
398402
return indices.getOrDefault(index, Set.of()).contains(block);
399403
}
400404

405+
public boolean hasIndexBlockLevel(String index, ClusterBlockLevel level) {
406+
return indices.getOrDefault(index, Set.of()).stream().anyMatch(clusterBlock -> clusterBlock.contains(level));
407+
}
408+
401409
public Builder removeIndexBlock(String index, ClusterBlock block) {
402410
if (indices.containsKey(index) == false) {
403411
return this;

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

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.elasticsearch.common.settings.IndexScopedSettings;
3737
import org.elasticsearch.common.settings.Setting;
3838
import org.elasticsearch.common.settings.Settings;
39+
import org.elasticsearch.core.Nullable;
3940
import org.elasticsearch.core.TimeValue;
4041
import org.elasticsearch.index.Index;
4142
import org.elasticsearch.index.IndexSettings;
@@ -51,7 +52,9 @@
5152
import java.util.Objects;
5253
import java.util.Set;
5354
import java.util.function.BiFunction;
55+
import java.util.function.Function;
5456

57+
import static org.elasticsearch.cluster.metadata.MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING;
5558
import static org.elasticsearch.index.IndexSettings.same;
5659

5760
/**
@@ -181,11 +184,14 @@ ClusterState execute(ClusterState currentState) {
181184

182185
RoutingTable.Builder routingTableBuilder = null;
183186
Metadata.Builder metadataBuilder = Metadata.builder(currentState.metadata());
187+
final var minSupportedIndexVersion = currentState.nodes().getMinSupportedIndexVersion();
184188

185189
// allow to change any settings to a closed index, and only allow dynamic settings to be changed
186190
// on an open index
187191
Set<Index> openIndices = new HashSet<>();
188192
Set<Index> closedIndices = new HashSet<>();
193+
Set<Index> readOnlyIndices = null;
194+
189195
final String[] actualIndices = new String[request.indices().length];
190196
for (int i = 0; i < request.indices().length; i++) {
191197
Index index = request.indices()[i];
@@ -197,6 +203,12 @@ ClusterState execute(ClusterState currentState) {
197203
} else {
198204
closedIndices.add(index);
199205
}
206+
if (metadata.getCompatibilityVersion().before(minSupportedIndexVersion)) {
207+
if (readOnlyIndices == null) {
208+
readOnlyIndices = new HashSet<>();
209+
}
210+
readOnlyIndices.add(index);
211+
}
200212
}
201213

202214
if (skippedSettings.isEmpty() == false && openIndices.isEmpty() == false) {
@@ -327,10 +339,23 @@ ClusterState execute(ClusterState currentState) {
327339
}
328340
}
329341

342+
// provides the value of VERIFIED_READ_ONLY_SETTING before block changes
343+
final Function<String, Boolean> verifiedReadOnly = indexName -> VERIFIED_READ_ONLY_SETTING.get(
344+
currentState.metadata().index(indexName).getSettings()
345+
);
346+
330347
final ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
331348
boolean changedBlocks = false;
332349
for (IndexMetadata.APIBlock block : IndexMetadata.APIBlock.values()) {
333-
changedBlocks |= maybeUpdateClusterBlock(actualIndices, blocks, block.block, block.setting, openSettings, metadataBuilder);
350+
changedBlocks |= maybeUpdateClusterBlock(
351+
actualIndices,
352+
blocks,
353+
block.block,
354+
block.setting,
355+
openSettings,
356+
metadataBuilder,
357+
verifiedReadOnly
358+
);
334359
}
335360
changed |= changedBlocks;
336361

@@ -359,6 +384,7 @@ ClusterState execute(ClusterState currentState) {
359384
// This step is mandatory since we allow to update non-dynamic settings on closed indices.
360385
indicesService.verifyIndexMetadata(updatedMetadata, updatedMetadata);
361386
}
387+
verifyReadOnlyIndices(readOnlyIndices, updatedState.blocks());
362388
} catch (IOException ex) {
363389
throw ExceptionsHelper.convertToElastic(ex);
364390
}
@@ -417,6 +443,18 @@ public static void updateIndexSettings(
417443
}
418444
}
419445

446+
private static void verifyReadOnlyIndices(@Nullable Set<Index> readOnlyIndices, ClusterBlocks blocks) {
447+
if (readOnlyIndices != null) {
448+
for (Index readOnlyIndex : readOnlyIndices) {
449+
if (blocks.hasIndexBlockLevel(readOnlyIndex.getName(), ClusterBlockLevel.WRITE) == false) {
450+
throw new IllegalArgumentException(
451+
String.format(Locale.ROOT, "Can't remove the write block on read-only compatible index %s", readOnlyIndex)
452+
);
453+
}
454+
}
455+
}
456+
}
457+
420458
/**
421459
* Updates the cluster block only iff the setting exists in the given settings
422460
*/
@@ -426,7 +464,8 @@ private static boolean maybeUpdateClusterBlock(
426464
ClusterBlock block,
427465
Setting<Boolean> setting,
428466
Settings openSettings,
429-
Metadata.Builder metadataBuilder
467+
Metadata.Builder metadataBuilder,
468+
Function<String, Boolean> verifiedReadOnlyBeforeBlockChanges
430469
) {
431470
boolean changed = false;
432471
if (setting.exists(openSettings)) {
@@ -436,16 +475,32 @@ private static boolean maybeUpdateClusterBlock(
436475
if (blocks.hasIndexBlock(index, block) == false) {
437476
blocks.addIndexBlock(index, block);
438477
changed = true;
478+
if (block.contains(ClusterBlockLevel.WRITE)) {
479+
var isVerifiedReadOnly = verifiedReadOnlyBeforeBlockChanges.apply(index);
480+
if (isVerifiedReadOnly) {
481+
var indexMetadata = metadataBuilder.get(index);
482+
metadataBuilder.put(
483+
IndexMetadata.builder(indexMetadata)
484+
.settings(
485+
Settings.builder()
486+
.put(indexMetadata.getSettings())
487+
.put(VERIFIED_READ_ONLY_SETTING.getKey(), true)
488+
)
489+
);
490+
}
491+
}
439492
}
440493
} else {
441494
if (blocks.hasIndexBlock(index, block)) {
442495
blocks.removeIndexBlock(index, block);
443496
changed = true;
444497
if (block.contains(ClusterBlockLevel.WRITE)) {
445-
IndexMetadata indexMetadata = metadataBuilder.get(index);
446-
Settings.Builder indexSettings = Settings.builder().put(indexMetadata.getSettings());
447-
indexSettings.remove(MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.getKey());
448-
metadataBuilder.put(IndexMetadata.builder(indexMetadata).settings(indexSettings));
498+
if (blocks.hasIndexBlockLevel(index, ClusterBlockLevel.WRITE) == false) {
499+
var indexMetadata = metadataBuilder.get(index);
500+
var indexSettings = Settings.builder().put(indexMetadata.getSettings());
501+
indexSettings.remove(VERIFIED_READ_ONLY_SETTING.getKey());
502+
metadataBuilder.put(IndexMetadata.builder(indexMetadata).settings(indexSettings));
503+
}
449504
}
450505
}
451506
}

0 commit comments

Comments
 (0)