Skip to content

Commit 9baea80

Browse files
committed
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 dc44158 commit 9baea80

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.logging.log4j.LogManager;
2323
import org.apache.logging.log4j.Logger;
2424
import org.elasticsearch.Version;
25+
import org.elasticsearch.cluster.metadata.IndexGraveyard;
2526
import org.elasticsearch.cluster.metadata.IndexMetaData;
2627
import org.elasticsearch.cluster.metadata.Manifest;
2728
import org.elasticsearch.cluster.metadata.MetaData;
@@ -116,11 +117,14 @@ private Tuple<Manifest, MetaData> loadFullStateBWC() throws IOException {
116117
MetaData globalMetaData = metaDataAndGeneration.v1();
117118
long globalStateGeneration = metaDataAndGeneration.v2();
118119

120+
final IndexGraveyard indexGraveyard;
119121
if (globalMetaData != null) {
120122
metaDataBuilder = MetaData.builder(globalMetaData);
123+
indexGraveyard = globalMetaData.custom(IndexGraveyard.TYPE);
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()) {
@@ -131,8 +135,13 @@ private Tuple<Manifest, MetaData> loadFullStateBWC() throws IOException {
131135
IndexMetaData indexMetaData = indexMetaDataAndGeneration.v1();
132136
long generation = indexMetaDataAndGeneration.v2();
133137
if (indexMetaData != null) {
134-
indices.put(indexMetaData.getIndex(), generation);
135-
metaDataBuilder.put(indexMetaData, false);
138+
if (indexGraveyard.containsIndex(indexMetaData.getIndex())) {
139+
logger.debug("[{}] found metadata for deleted index [{}]", indexFolderName, indexMetaData.getIndex());
140+
// this index folder is cleared up when state is recovered
141+
} else {
142+
indices.put(indexMetaData.getIndex(), generation);
143+
metaDataBuilder.put(indexMetaData, false);
144+
}
136145
} else {
137146
logger.debug("[{}] failed to find metadata for existing index location", indexFolderName);
138147
}
@@ -290,4 +299,4 @@ public void writeGlobalStateAndUpdateManifest(String reason, MetaData metaData)
290299
writeManifestAndCleanup(reason, manifest);
291300
cleanupGlobalState(generation);
292301
}
293-
}
302+
}

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;
@@ -509,6 +516,42 @@ public void testArchiveBrokenClusterSettings() throws Exception {
509516
assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L);
510517
}
511518

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

0 commit comments

Comments
 (0)