Skip to content

Commit 13170a7

Browse files
authored
Ignore metadata of deleted indices at start (#48918)
Today in 6.x it is possible to add an index tombstone to the graveyard without deleting the corresponding index metadata, because the deletion is slightly deferred. If you shut down the node and upgrade to 7.x when in this state then the node will fail to apply any cluster states, reporting java.lang.IllegalStateException: Cannot delete index [...], it is still part of the cluster state. This commit addresses this situation by skipping over any index metadata with a corresponding tombstone, allowing this metadata to be cleaned up by the 7.x node.
1 parent 049e122 commit 13170a7

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

server/src/main/java/org/elasticsearch/gateway/MetaStateService.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.logging.log4j.LogManager;
2323
import org.apache.logging.log4j.Logger;
24+
import org.elasticsearch.cluster.metadata.IndexGraveyard;
2425
import org.elasticsearch.cluster.metadata.IndexMetaData;
2526
import org.elasticsearch.cluster.metadata.Manifest;
2627
import org.elasticsearch.cluster.metadata.MetaData;
@@ -115,12 +116,15 @@ private Tuple<Manifest, MetaData> loadFullStateBWC() throws IOException {
115116
MetaData globalMetaData = metaDataAndGeneration.v1();
116117
long globalStateGeneration = metaDataAndGeneration.v2();
117118

119+
final IndexGraveyard indexGraveyard;
118120
if (globalMetaData != null) {
119121
metaDataBuilder = MetaData.builder(globalMetaData);
122+
indexGraveyard = globalMetaData.custom(IndexGraveyard.TYPE);
120123
// TODO https://github.com/elastic/elasticsearch/issues/38556
121124
// assert Version.CURRENT.major < 8 : "failed to find manifest file, which is mandatory staring with Elasticsearch version 8.0";
122125
} else {
123126
metaDataBuilder = MetaData.builder();
127+
indexGraveyard = IndexGraveyard.builder().build();
124128
}
125129

126130
for (String indexFolderName : nodeEnv.availableIndexFolders()) {
@@ -132,8 +136,13 @@ private Tuple<Manifest, MetaData> loadFullStateBWC() throws IOException {
132136
IndexMetaData indexMetaData = indexMetaDataAndGeneration.v1();
133137
long generation = indexMetaDataAndGeneration.v2();
134138
if (indexMetaData != null) {
135-
indices.put(indexMetaData.getIndex(), generation);
136-
metaDataBuilder.put(indexMetaData, false);
139+
if (indexGraveyard.containsIndex(indexMetaData.getIndex())) {
140+
logger.debug("[{}] found metadata for deleted index [{}]", indexFolderName, indexMetaData.getIndex());
141+
// this index folder is cleared up when state is recovered
142+
} else {
143+
indices.put(indexMetaData.getIndex(), generation);
144+
metaDataBuilder.put(indexMetaData, false);
145+
}
137146
} else {
138147
logger.debug("[{}] failed to find metadata for existing index location", indexFolderName);
139148
}

server/src/test/java/org/elasticsearch/gateway/GatewayIndexStateIT.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,24 @@
3030
import org.elasticsearch.client.Client;
3131
import org.elasticsearch.client.Requests;
3232
import org.elasticsearch.cluster.ClusterState;
33+
import org.elasticsearch.cluster.coordination.CoordinationMetaData;
3334
import org.elasticsearch.cluster.metadata.IndexGraveyard;
3435
import org.elasticsearch.cluster.metadata.IndexMetaData;
36+
import org.elasticsearch.cluster.metadata.Manifest;
3537
import org.elasticsearch.cluster.metadata.MappingMetaData;
3638
import org.elasticsearch.cluster.metadata.MetaData;
3739
import org.elasticsearch.cluster.routing.IndexRoutingTable;
3840
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
3941
import org.elasticsearch.cluster.routing.RoutingTable;
4042
import org.elasticsearch.cluster.routing.ShardRoutingState;
4143
import org.elasticsearch.cluster.routing.UnassignedInfo;
44+
import org.elasticsearch.cluster.service.ClusterService;
4245
import org.elasticsearch.common.CheckedConsumer;
4346
import org.elasticsearch.common.Priority;
4447
import org.elasticsearch.common.settings.Settings;
4548
import org.elasticsearch.common.xcontent.XContentFactory;
4649
import org.elasticsearch.common.xcontent.XContentType;
50+
import org.elasticsearch.core.internal.io.IOUtils;
4751
import org.elasticsearch.env.NodeEnvironment;
4852
import org.elasticsearch.index.mapper.MapperParsingException;
4953
import org.elasticsearch.indices.IndexClosedException;
@@ -54,6 +58,8 @@
5458
import org.elasticsearch.test.InternalTestCluster.RestartCallback;
5559

5660
import java.io.IOException;
61+
import java.nio.file.Files;
62+
import java.nio.file.Path;
5763
import java.util.List;
5864
import java.util.Map;
5965
import java.util.concurrent.TimeUnit;
@@ -66,6 +72,7 @@
6672
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
6773
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
6874
import static org.hamcrest.Matchers.containsString;
75+
import static org.hamcrest.Matchers.empty;
6976
import static org.hamcrest.Matchers.equalTo;
7077
import static org.hamcrest.Matchers.greaterThan;
7178
import static org.hamcrest.Matchers.notNullValue;
@@ -508,6 +515,42 @@ public void testArchiveBrokenClusterSettings() throws Exception {
508515
assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L);
509516
}
510517

518+
public void testHalfDeletedIndexImport() throws Exception {
519+
// It's possible for a 6.x node to add a tombstone for an index but not actually delete the index metadata from disk since that
520+
// deletion is slightly deferred and may race against the node being shut down; if you upgrade to 7.x when in this state then the
521+
// node won't start.
522+
523+
internalCluster().startNode();
524+
createIndex("test", Settings.builder()
525+
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
526+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
527+
.build());
528+
ensureGreen("test");
529+
530+
final MetaData metaData = internalCluster().getInstance(ClusterService.class).state().metaData();
531+
final Path[] paths = internalCluster().getInstance(NodeEnvironment.class).nodeDataPaths();
532+
writeBrokenMeta(metaStateService -> {
533+
metaStateService.writeGlobalState("test", MetaData.builder(metaData)
534+
// we remove the manifest file, resetting the term and making this look like an upgrade from 6.x, so must also reset the
535+
// term in the coordination metadata
536+
.coordinationMetaData(CoordinationMetaData.builder(metaData.coordinationMetaData()).term(0L).build())
537+
// add a tombstone but do not delete the index metadata from disk
538+
.putCustom(IndexGraveyard.TYPE, IndexGraveyard.builder().addTombstone(metaData.index("test").getIndex()).build()).build());
539+
for (final Path path : paths) {
540+
try (Stream<Path> stateFiles = Files.list(path.resolve(MetaDataStateFormat.STATE_DIR_NAME))) {
541+
for (final Path manifestPath : stateFiles
542+
.filter(p -> p.getFileName().toString().startsWith(Manifest.FORMAT.getPrefix())).collect(Collectors.toList())) {
543+
IOUtils.rm(manifestPath);
544+
}
545+
}
546+
}
547+
});
548+
549+
ensureGreen();
550+
551+
assertBusy(() -> assertThat(internalCluster().getInstance(NodeEnvironment.class).availableIndexFolders(), empty()));
552+
}
553+
511554
private void writeBrokenMeta(CheckedConsumer<MetaStateService, IOException> writer) throws Exception {
512555
Map<String, MetaStateService> metaStateServices = Stream.of(internalCluster().getNodeNames())
513556
.collect(Collectors.toMap(Function.identity(), nodeName -> internalCluster().getInstance(MetaStateService.class, nodeName)));

0 commit comments

Comments
 (0)