Skip to content

Commit ab00c71

Browse files
original-brownbearDaveCTurner
authored andcommitted
Fix Bug with Concurrent Snapshot and Index Delete (#73456)
Fixes the incorrect assumption in the snapshot state machine that a finished snapshot delete could only start shard snapshots: in fact it can also move snapshots to a completed state.
1 parent 4dee9ee commit ab00c71

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

server/src/internalClusterTest/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.carrotsearch.hppc.IntHashSet;
1212
import com.carrotsearch.hppc.IntSet;
1313
import org.elasticsearch.action.ActionFuture;
14+
import org.elasticsearch.action.ActionListener;
1415
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
1516
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
1617
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
@@ -20,6 +21,7 @@
2021
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse;
2122
import org.elasticsearch.action.admin.indices.stats.ShardStats;
2223
import org.elasticsearch.action.index.IndexRequestBuilder;
24+
import org.elasticsearch.action.support.ActionTestUtils;
2325
import org.elasticsearch.action.support.ActiveShardCount;
2426
import org.elasticsearch.action.support.PlainActionFuture;
2527
import org.elasticsearch.action.support.master.AcknowledgedResponse;
@@ -35,6 +37,7 @@
3537
import org.elasticsearch.common.settings.SettingsFilter;
3638
import org.elasticsearch.common.util.set.Sets;
3739
import org.elasticsearch.env.Environment;
40+
import org.elasticsearch.index.IndexNotFoundException;
3841
import org.elasticsearch.index.seqno.RetentionLeaseActions;
3942
import org.elasticsearch.index.seqno.RetentionLeases;
4043
import org.elasticsearch.index.shard.ShardId;
@@ -75,6 +78,7 @@
7578
import java.util.List;
7679
import java.util.Locale;
7780
import java.util.concurrent.CountDownLatch;
81+
import java.util.concurrent.Future;
7882
import java.util.concurrent.TimeUnit;
7983
import java.util.concurrent.atomic.AtomicBoolean;
8084
import java.util.concurrent.atomic.AtomicReference;
@@ -90,6 +94,7 @@
9094
import static org.hamcrest.Matchers.greaterThan;
9195
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
9296
import static org.hamcrest.Matchers.hasSize;
97+
import static org.hamcrest.Matchers.instanceOf;
9398
import static org.hamcrest.Matchers.is;
9499
import static org.hamcrest.Matchers.lessThan;
95100
import static org.hamcrest.Matchers.not;
@@ -1045,6 +1050,65 @@ public void testPartialSnapshotsDoNotRecordDeletedShardFailures() throws Excepti
10451050
assertThat(snapshotInfo.shardFailures(), empty());
10461051
}
10471052

1053+
public void testDeleteIndexDuringSnapshot() throws Exception {
1054+
final String indexName = "test-idx";
1055+
assertAcked(prepareCreate(indexName, 1, indexSettingsNoReplicas(1)));
1056+
ensureGreen();
1057+
indexRandomDocs(indexName, 100);
1058+
1059+
final String repoName = "test-repo";
1060+
createRepository(repoName, "fs");
1061+
1062+
final String firstSnapshotName = "test-snap";
1063+
createSnapshot(repoName, firstSnapshotName, Collections.singletonList(indexName));
1064+
final int concurrentLoops = randomIntBetween(2, 5);
1065+
final List<Future<Void>> futures = new ArrayList<>(concurrentLoops);
1066+
for (int i = 0; i < concurrentLoops; i++) {
1067+
final PlainActionFuture<Void> future = PlainActionFuture.newFuture();
1068+
futures.add(future);
1069+
startSnapshotDeleteLoop(repoName, indexName, "test-snap-" + i, future);
1070+
}
1071+
1072+
Thread.sleep(200);
1073+
1074+
logger.info("--> delete index");
1075+
assertAcked(admin().indices().prepareDelete(indexName));
1076+
1077+
for (Future<Void> future : futures) {
1078+
future.get();
1079+
}
1080+
1081+
logger.info("--> restore snapshot 1");
1082+
clusterAdmin().prepareRestoreSnapshot(repoName, firstSnapshotName).get();
1083+
ensureGreen(indexName);
1084+
}
1085+
1086+
// create and delete a snapshot of the given name and for the given single index in a loop until the index is removed from the cluster
1087+
// at which point doneListener is resolved
1088+
private void startSnapshotDeleteLoop(String repoName, String indexName, String snapshotName, ActionListener<Void> doneListener) {
1089+
clusterAdmin().prepareCreateSnapshot(repoName, snapshotName)
1090+
.setWaitForCompletion(true)
1091+
.setPartial(true)
1092+
.setIndices(indexName)
1093+
.execute(new ActionListener<CreateSnapshotResponse>() {
1094+
@Override
1095+
public void onResponse(CreateSnapshotResponse createSnapshotResponse) {
1096+
clusterAdmin().prepareDeleteSnapshot(repoName, snapshotName).execute(
1097+
ActionTestUtils.assertNoFailureListener(acknowledgedResponse -> {
1098+
assertAcked(acknowledgedResponse);
1099+
startSnapshotDeleteLoop(repoName, indexName, snapshotName, doneListener);
1100+
}));
1101+
}
1102+
1103+
@Override
1104+
public void onFailure(Exception e) {
1105+
assertThat(e, instanceOf(IndexNotFoundException.class));
1106+
doneListener.onResponse(null);
1107+
}
1108+
});
1109+
}
1110+
1111+
10481112
public void testGetReposWithWildcard() {
10491113
internalCluster().startMasterOnlyNode();
10501114
List<RepositoryMetadata> repositoryMetadata = client().admin().cluster().prepareGetRepositories("*").get().repositories();

server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2782,8 +2782,12 @@ && alreadyReassigned(value.key.getIndexName(), value.key.getId(), reassignedShar
27822782
updatedAssignmentsBuilder.put(shardId, updated);
27832783
}
27842784
}
2785-
snapshotEntries.add(entry.withStartedShards(updatedAssignmentsBuilder.build()));
2785+
final SnapshotsInProgress.Entry updatedEntry = entry.withShardStates(updatedAssignmentsBuilder.build());
2786+
snapshotEntries.add(updatedEntry);
27862787
changed = true;
2788+
if (updatedEntry.state().completed()) {
2789+
newFinalizations.add(entry);
2790+
}
27872791
}
27882792
}
27892793
} else {

0 commit comments

Comments
 (0)