Do not start snapshots that are deleted during initialization (#27931)
When a new snapshot is created it is added to the cluster state as a snapshot-in-progress in INIT state, and the initialization is kicked off in a new runnable task by SnapshotService.beginSnapshot(). The initialization writes multiple files before updating the cluster state to change the snapshot-in-progress to STARTED state. This leaves a short window in which the snapshot could be deleted (let's say, because the snapshot is stuck in INIT or because it takes too much time to upload all the initialization files for all snapshotted indices). If the INIT snapshot is deleted, the snapshot-in-progress becomes ABORTED but once the initialization in SnapshotService.beginSnapshot() finished it is change back to STARTED state again. This commit avoids an ABORTED snapshot to be started if it has been deleted during initialization. It also adds a test that would have failed with the previous behavior, and changes few method names here and there.
This commit is contained in:
parent
012ea03f54
commit
bd9daf422e
|
@ -66,7 +66,7 @@ public class SnapshotException extends ElasticsearchException {
|
|||
}
|
||||
|
||||
public SnapshotException(final String repositoryName, final String snapshotName, final String msg, final Throwable cause) {
|
||||
super("[" + repositoryName + ":" + snapshotName + "]" + msg, cause);
|
||||
super("[" + repositoryName + ":" + snapshotName + "] " + msg, cause);
|
||||
this.repositoryName = repositoryName;
|
||||
this.snapshotName = snapshotName;
|
||||
}
|
||||
|
|
|
@ -30,15 +30,15 @@ import java.io.IOException;
|
|||
public class SnapshotMissingException extends SnapshotException {
|
||||
|
||||
public SnapshotMissingException(final String repositoryName, final SnapshotId snapshotId, final Throwable cause) {
|
||||
super(repositoryName, snapshotId, " is missing", cause);
|
||||
super(repositoryName, snapshotId, "is missing", cause);
|
||||
}
|
||||
|
||||
public SnapshotMissingException(final String repositoryName, final SnapshotId snapshotId) {
|
||||
super(repositoryName, snapshotId, " is missing");
|
||||
super(repositoryName, snapshotId, "is missing");
|
||||
}
|
||||
|
||||
public SnapshotMissingException(final String repositoryName, final String snapshotName) {
|
||||
super(repositoryName, snapshotName, " is missing");
|
||||
super(repositoryName, snapshotName, "is missing");
|
||||
}
|
||||
|
||||
public SnapshotMissingException(StreamInput in) throws IOException {
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.snapshots;
|
|||
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
|
@ -66,7 +67,6 @@ import org.elasticsearch.indices.IndicesService;
|
|||
import org.elasticsearch.repositories.IndexId;
|
||||
import org.elasticsearch.repositories.Repository;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.EmptyTransportResponseHandler;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -85,6 +85,7 @@ import java.util.stream.Collectors;
|
|||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import static org.elasticsearch.cluster.SnapshotsInProgress.completed;
|
||||
import static org.elasticsearch.transport.EmptyTransportResponseHandler.INSTANCE_SAME;
|
||||
|
||||
/**
|
||||
* This service runs on data and master nodes and controls currently snapshotted shards on these nodes. It is responsible for
|
||||
|
@ -151,7 +152,6 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
} finally {
|
||||
shutdownLock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -162,14 +162,16 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
@Override
|
||||
public void clusterChanged(ClusterChangedEvent event) {
|
||||
try {
|
||||
SnapshotsInProgress prev = event.previousState().custom(SnapshotsInProgress.TYPE);
|
||||
SnapshotsInProgress curr = event.state().custom(SnapshotsInProgress.TYPE);
|
||||
|
||||
if ((prev == null && curr != null) || (prev != null && prev.equals(curr) == false)) {
|
||||
SnapshotsInProgress previousSnapshots = event.previousState().custom(SnapshotsInProgress.TYPE);
|
||||
SnapshotsInProgress currentSnapshots = event.state().custom(SnapshotsInProgress.TYPE);
|
||||
if ((previousSnapshots == null && currentSnapshots != null)
|
||||
|| (previousSnapshots != null && previousSnapshots.equals(currentSnapshots) == false)) {
|
||||
processIndexShardSnapshots(event);
|
||||
}
|
||||
String masterNodeId = event.state().nodes().getMasterNodeId();
|
||||
if (masterNodeId != null && masterNodeId.equals(event.previousState().nodes().getMasterNodeId()) == false) {
|
||||
|
||||
String previousMasterNodeId = event.previousState().nodes().getMasterNodeId();
|
||||
String currentMasterNodeId = event.state().nodes().getMasterNodeId();
|
||||
if (currentMasterNodeId != null && currentMasterNodeId.equals(previousMasterNodeId) == false) {
|
||||
syncShardStatsOnNewMaster(event);
|
||||
}
|
||||
|
||||
|
@ -240,7 +242,6 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
Map<Snapshot, Map<ShardId, IndexShardSnapshotStatus>> newSnapshots = new HashMap<>();
|
||||
// Now go through all snapshots and update existing or create missing
|
||||
final String localNodeId = event.state().nodes().getLocalNodeId();
|
||||
final DiscoveryNode masterNode = event.state().nodes().getMasterNode();
|
||||
final Map<Snapshot, Map<String, IndexId>> snapshotIndices = new HashMap<>();
|
||||
if (snapshotsInProgress != null) {
|
||||
for (SnapshotsInProgress.Entry entry : snapshotsInProgress.entries()) {
|
||||
|
@ -286,17 +287,18 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
snapshotStatus.abort();
|
||||
break;
|
||||
case FINALIZE:
|
||||
logger.debug("[{}] trying to cancel snapshot on shard [{}] that is finalizing, letting it finish", entry.snapshot(), shard.key);
|
||||
logger.debug("[{}] trying to cancel snapshot on shard [{}] that is finalizing, " +
|
||||
"letting it finish", entry.snapshot(), shard.key);
|
||||
break;
|
||||
case DONE:
|
||||
logger.debug("[{}] trying to cancel snapshot on the shard [{}] that is already done, updating status on the master", entry.snapshot(), shard.key);
|
||||
updateIndexShardSnapshotStatus(entry.snapshot(), shard.key,
|
||||
new ShardSnapshotStatus(localNodeId, State.SUCCESS), masterNode);
|
||||
logger.debug("[{}] trying to cancel snapshot on the shard [{}] that is already done, " +
|
||||
"updating status on the master", entry.snapshot(), shard.key);
|
||||
notifySuccessfulSnapshotShard(entry.snapshot(), shard.key, localNodeId);
|
||||
break;
|
||||
case FAILURE:
|
||||
logger.debug("[{}] trying to cancel snapshot on the shard [{}] that has already failed, updating status on the master", entry.snapshot(), shard.key);
|
||||
updateIndexShardSnapshotStatus(entry.snapshot(), shard.key,
|
||||
new ShardSnapshotStatus(localNodeId, State.FAILED, snapshotStatus.failure()), masterNode);
|
||||
logger.debug("[{}] trying to cancel snapshot on the shard [{}] that has already failed, " +
|
||||
"updating status on the master", entry.snapshot(), shard.key);
|
||||
notifyFailedSnapshotShard(entry.snapshot(), shard.key, localNodeId, snapshotStatus.failure());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown snapshot shard stage " + snapshotStatus.stage());
|
||||
|
@ -325,34 +327,46 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
if (newSnapshots.isEmpty() == false) {
|
||||
Executor executor = threadPool.executor(ThreadPool.Names.SNAPSHOT);
|
||||
for (final Map.Entry<Snapshot, Map<ShardId, IndexShardSnapshotStatus>> entry : newSnapshots.entrySet()) {
|
||||
Map<String, IndexId> indicesMap = snapshotIndices.get(entry.getKey());
|
||||
final Snapshot snapshot = entry.getKey();
|
||||
final Map<String, IndexId> indicesMap = snapshotIndices.get(snapshot);
|
||||
assert indicesMap != null;
|
||||
|
||||
for (final Map.Entry<ShardId, IndexShardSnapshotStatus> shardEntry : entry.getValue().entrySet()) {
|
||||
final ShardId shardId = shardEntry.getKey();
|
||||
try {
|
||||
final IndexShard indexShard = indicesService.indexServiceSafe(shardId.getIndex()).getShardOrNull(shardId.id());
|
||||
final IndexId indexId = indicesMap.get(shardId.getIndexName());
|
||||
assert indexId != null;
|
||||
executor.execute(new AbstractRunnable() {
|
||||
|
||||
final SetOnce<Exception> failure = new SetOnce<>();
|
||||
|
||||
@Override
|
||||
public void doRun() {
|
||||
snapshot(indexShard, entry.getKey(), indexId, shardEntry.getValue());
|
||||
updateIndexShardSnapshotStatus(entry.getKey(), shardId,
|
||||
new ShardSnapshotStatus(localNodeId, State.SUCCESS), masterNode);
|
||||
snapshot(indexShard, snapshot, indexId, shardEntry.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
logger.warn((Supplier<?>) () -> new ParameterizedMessage("[{}] [{}] failed to create snapshot", shardId, entry.getKey()), e);
|
||||
updateIndexShardSnapshotStatus(entry.getKey(), shardId,
|
||||
new ShardSnapshotStatus(localNodeId, State.FAILED, ExceptionsHelper.detailedMessage(e)), masterNode);
|
||||
logger.warn((Supplier<?>) () ->
|
||||
new ParameterizedMessage("[{}][{}] failed to snapshot shard", shardId, snapshot), e);
|
||||
failure.set(e);
|
||||
}
|
||||
|
||||
});
|
||||
} catch (Exception e) {
|
||||
updateIndexShardSnapshotStatus(entry.getKey(), shardId,
|
||||
new ShardSnapshotStatus(localNodeId, State.FAILED, ExceptionsHelper.detailedMessage(e)), masterNode);
|
||||
@Override
|
||||
public void onRejection(Exception e) {
|
||||
failure.set(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfter() {
|
||||
final Exception exception = failure.get();
|
||||
if (exception != null) {
|
||||
notifyFailedSnapshotShard(snapshot, shardId, localNodeId, ExceptionsHelper.detailedMessage(exception));
|
||||
} else {
|
||||
notifySuccessfulSnapshotShard(snapshot, shardId, localNodeId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -365,34 +379,36 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
* @param snapshotStatus snapshot status
|
||||
*/
|
||||
private void snapshot(final IndexShard indexShard, final Snapshot snapshot, final IndexId indexId, final IndexShardSnapshotStatus snapshotStatus) {
|
||||
Repository repository = snapshotsService.getRepositoriesService().repository(snapshot.getRepository());
|
||||
ShardId shardId = indexShard.shardId();
|
||||
if (!indexShard.routingEntry().primary()) {
|
||||
final ShardId shardId = indexShard.shardId();
|
||||
if (indexShard.routingEntry().primary() == false) {
|
||||
throw new IndexShardSnapshotFailedException(shardId, "snapshot should be performed only on primary");
|
||||
}
|
||||
if (indexShard.routingEntry().relocating()) {
|
||||
// do not snapshot when in the process of relocation of primaries so we won't get conflicts
|
||||
throw new IndexShardSnapshotFailedException(shardId, "cannot snapshot while relocating");
|
||||
}
|
||||
if (indexShard.state() == IndexShardState.CREATED || indexShard.state() == IndexShardState.RECOVERING) {
|
||||
|
||||
final IndexShardState indexShardState = indexShard.state();
|
||||
if (indexShardState == IndexShardState.CREATED || indexShardState == IndexShardState.RECOVERING) {
|
||||
// shard has just been created, or still recovering
|
||||
throw new IndexShardSnapshotFailedException(shardId, "shard didn't fully recover yet");
|
||||
}
|
||||
|
||||
final Repository repository = snapshotsService.getRepositoriesService().repository(snapshot.getRepository());
|
||||
try {
|
||||
// we flush first to make sure we get the latest writes snapshotted
|
||||
try (Engine.IndexCommitRef snapshotRef = indexShard.acquireIndexCommit(true)) {
|
||||
repository.snapshotShard(indexShard, snapshot.getSnapshotId(), indexId, snapshotRef.getIndexCommit(), snapshotStatus);
|
||||
if (logger.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" index : version [").append(snapshotStatus.indexVersion()).append("], number_of_files [").append(snapshotStatus.numberOfFiles()).append("] with total_size [").append(new ByteSizeValue(snapshotStatus.totalSize())).append("]\n");
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(" index : version [").append(snapshotStatus.indexVersion());
|
||||
details.append("], number_of_files [").append(snapshotStatus.numberOfFiles());
|
||||
details.append("] with total_size [").append(new ByteSizeValue(snapshotStatus.totalSize())).append("]\n");
|
||||
logger.debug("snapshot ({}) completed to {}, took [{}]\n{}", snapshot, repository,
|
||||
TimeValue.timeValueMillis(snapshotStatus.time()), sb);
|
||||
TimeValue.timeValueMillis(snapshotStatus.time()), details);
|
||||
}
|
||||
}
|
||||
} catch (SnapshotFailedEngineException e) {
|
||||
throw e;
|
||||
} catch (IndexShardSnapshotFailedException e) {
|
||||
} catch (SnapshotFailedEngineException | IndexShardSnapshotFailedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IndexShardSnapshotFailedException(shardId, "Failed to snapshot", e);
|
||||
|
@ -407,8 +423,8 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
if (snapshotsInProgress == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String localNodeId = event.state().nodes().getLocalNodeId();
|
||||
final DiscoveryNode masterNode = event.state().nodes().getMasterNode();
|
||||
for (SnapshotsInProgress.Entry snapshot : snapshotsInProgress.entries()) {
|
||||
if (snapshot.state() == State.STARTED || snapshot.state() == State.ABORTED) {
|
||||
Map<ShardId, IndexShardSnapshotStatus> localShards = currentSnapshotShards(snapshot.snapshot());
|
||||
|
@ -422,15 +438,15 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
// Master knows about the shard and thinks it has not completed
|
||||
if (localShardStatus.stage() == Stage.DONE) {
|
||||
// but we think the shard is done - we need to make new master know that the shard is done
|
||||
logger.debug("[{}] new master thinks the shard [{}] is not completed but the shard is done locally, updating status on the master", snapshot.snapshot(), shardId);
|
||||
updateIndexShardSnapshotStatus(snapshot.snapshot(), shardId,
|
||||
new ShardSnapshotStatus(localNodeId, State.SUCCESS), masterNode);
|
||||
logger.debug("[{}] new master thinks the shard [{}] is not completed but the shard is done locally, " +
|
||||
"updating status on the master", snapshot.snapshot(), shardId);
|
||||
notifySuccessfulSnapshotShard(snapshot.snapshot(), shardId, localNodeId);
|
||||
|
||||
} else if (localShard.getValue().stage() == Stage.FAILURE) {
|
||||
// but we think the shard failed - we need to make new master know that the shard failed
|
||||
logger.debug("[{}] new master thinks the shard [{}] is not completed but the shard failed locally, updating status on master", snapshot.snapshot(), shardId);
|
||||
updateIndexShardSnapshotStatus(snapshot.snapshot(), shardId,
|
||||
new ShardSnapshotStatus(localNodeId, State.FAILED, localShardStatus.failure()), masterNode);
|
||||
|
||||
logger.debug("[{}] new master thinks the shard [{}] is not completed but the shard failed locally, " +
|
||||
"updating status on master", snapshot.snapshot(), shardId);
|
||||
notifyFailedSnapshotShard(snapshot.snapshot(), shardId, localNodeId, localShardStatus.failure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -450,7 +466,6 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal request that is used to send changes in snapshot status to master
|
||||
*/
|
||||
|
@ -510,13 +525,21 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the shard status
|
||||
*/
|
||||
public void updateIndexShardSnapshotStatus(Snapshot snapshot, ShardId shardId, ShardSnapshotStatus status, DiscoveryNode master) {
|
||||
/** Notify the master node that the given shard has been successfully snapshotted **/
|
||||
void notifySuccessfulSnapshotShard(final Snapshot snapshot, final ShardId shardId, final String localNodeId) {
|
||||
sendSnapshotShardUpdate(snapshot, shardId, new ShardSnapshotStatus(localNodeId, State.SUCCESS));
|
||||
}
|
||||
|
||||
/** Notify the master node that the given shard failed to be snapshotted **/
|
||||
void notifyFailedSnapshotShard(final Snapshot snapshot, final ShardId shardId, final String localNodeId, final String failure) {
|
||||
sendSnapshotShardUpdate(snapshot, shardId, new ShardSnapshotStatus(localNodeId, State.FAILED, failure));
|
||||
}
|
||||
|
||||
/** Updates the shard snapshot status by sending a {@link UpdateIndexShardSnapshotStatusRequest} to the master node */
|
||||
void sendSnapshotShardUpdate(final Snapshot snapshot, final ShardId shardId, final ShardSnapshotStatus status) {
|
||||
try {
|
||||
UpdateIndexShardSnapshotStatusRequest request = new UpdateIndexShardSnapshotStatusRequest(snapshot, shardId, status);
|
||||
transportService.sendRequest(transportService.getLocalNode(), UPDATE_SNAPSHOT_STATUS_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME);
|
||||
transportService.sendRequest(transportService.getLocalNode(), UPDATE_SNAPSHOT_STATUS_ACTION_NAME, request, INSTANCE_SAME);
|
||||
} catch (Exception e) {
|
||||
logger.warn((Supplier<?>) () -> new ParameterizedMessage("[{}] [{}] failed to update snapshot state", snapshot, status), e);
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ import static org.elasticsearch.cluster.SnapshotsInProgress.completed;
|
|||
* kicks in and initializes the snapshot in the repository and then populates list of shards that needs to be snapshotted in cluster state</li>
|
||||
* <li>Each data node is watching for these shards and when new shards scheduled for snapshotting appear in the cluster state, data nodes
|
||||
* start processing them through {@link SnapshotShardsService#processIndexShardSnapshots(ClusterChangedEvent)} method</li>
|
||||
* <li>Once shard snapshot is created data node updates state of the shard in the cluster state using the {@link SnapshotShardsService#updateIndexShardSnapshotStatus} method</li>
|
||||
* <li>Once shard snapshot is created data node updates state of the shard in the cluster state using the {@link SnapshotShardsService#sendSnapshotShardUpdate(Snapshot, ShardId, ShardSnapshotStatus)} method</li>
|
||||
* <li>When last shard is completed master node in {@link SnapshotShardsService#innerUpdateSnapshotState} method marks the snapshot as completed</li>
|
||||
* <li>After cluster state is updated, the {@link #endSnapshot(SnapshotsInProgress.Entry)} finalizes snapshot in the repository,
|
||||
* notifies all {@link #snapshotCompletionListeners} that snapshot is completed, and finally calls {@link #removeSnapshotFromClusterState(Snapshot, SnapshotInfo, Exception)} to remove snapshot from cluster state</li>
|
||||
|
@ -381,7 +381,7 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
|
|||
SnapshotsInProgress snapshots = currentState.custom(SnapshotsInProgress.TYPE);
|
||||
List<SnapshotsInProgress.Entry> entries = new ArrayList<>();
|
||||
for (SnapshotsInProgress.Entry entry : snapshots.entries()) {
|
||||
if (entry.snapshot().equals(snapshot.snapshot())) {
|
||||
if (entry.snapshot().equals(snapshot.snapshot()) && entry.state() != State.ABORTED) {
|
||||
// Replace the snapshot that was just created
|
||||
ImmutableOpenMap<ShardId, SnapshotsInProgress.ShardSnapshotStatus> shards = shards(currentState, entry.indices());
|
||||
if (!partial) {
|
||||
|
@ -392,11 +392,11 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
|
|||
StringBuilder failureMessage = new StringBuilder();
|
||||
updatedSnapshot = new SnapshotsInProgress.Entry(entry, State.FAILED, shards);
|
||||
entries.add(updatedSnapshot);
|
||||
if (missing.isEmpty() == false ) {
|
||||
if (missing.isEmpty() == false) {
|
||||
failureMessage.append("Indices don't have primary shards ");
|
||||
failureMessage.append(missing);
|
||||
}
|
||||
if (closed.isEmpty() == false ) {
|
||||
if (closed.isEmpty() == false) {
|
||||
if (failureMessage.length() > 0) {
|
||||
failureMessage.append("; ");
|
||||
}
|
||||
|
@ -1237,7 +1237,7 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
|
|||
"could not be found after failing to abort.",
|
||||
smex.getSnapshotName()), e);
|
||||
listener.onFailure(new SnapshotException(snapshot,
|
||||
"Tried deleting in-progress snapshot [{}], but it " +
|
||||
"Tried deleting in-progress snapshot [" + smex.getSnapshotName() + "], but it " +
|
||||
"could not be found after failing to abort.", smex));
|
||||
}
|
||||
});
|
||||
|
@ -1292,6 +1292,8 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
|
|||
try {
|
||||
Repository repository = repositoriesService.repository(snapshot.getRepository());
|
||||
repository.deleteSnapshot(snapshot.getSnapshotId(), repositoryStateId);
|
||||
logger.info("snapshot [{}] deleted", snapshot);
|
||||
|
||||
removeSnapshotDeletionFromClusterState(snapshot, null, listener);
|
||||
} catch (Exception ex) {
|
||||
removeSnapshotDeletionFromClusterState(snapshot, ex, listener);
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.Version;
|
|||
import org.elasticsearch.action.ActionFuture;
|
||||
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse;
|
||||
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
|
||||
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse;
|
||||
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
|
||||
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
|
||||
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotIndexShardStage;
|
||||
|
@ -46,6 +47,7 @@ import org.elasticsearch.action.search.SearchResponse;
|
|||
import org.elasticsearch.action.support.ActiveShardCount;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.ClusterStateListener;
|
||||
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||
import org.elasticsearch.cluster.RestoreInProgress;
|
||||
import org.elasticsearch.cluster.SnapshotsInProgress;
|
||||
|
@ -102,6 +104,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -3143,6 +3146,74 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
|
|||
assertThat(shardStats.getSeqNoStats().getMaxSeqNo(), equalTo(15L));
|
||||
}
|
||||
|
||||
public void testAbortedSnapshotDuringInitDoesNotStart() throws Exception {
|
||||
final Client client = client();
|
||||
|
||||
// Blocks on initialization
|
||||
assertAcked(client.admin().cluster().preparePutRepository("repository")
|
||||
.setType("mock").setSettings(Settings.builder()
|
||||
.put("location", randomRepoPath())
|
||||
.put("block_on_init", true)
|
||||
));
|
||||
|
||||
createIndex("test-idx");
|
||||
final int nbDocs = scaledRandomIntBetween(1, 100);
|
||||
for (int i = 0; i < nbDocs; i++) {
|
||||
index("test-idx", "_doc", Integer.toString(i), "foo", "bar" + i);
|
||||
}
|
||||
refresh();
|
||||
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().getTotalHits(), equalTo((long) nbDocs));
|
||||
|
||||
// Create a snapshot
|
||||
client.admin().cluster().prepareCreateSnapshot("repository", "snap").execute();
|
||||
waitForBlock(internalCluster().getMasterName(), "repository", TimeValue.timeValueMinutes(1));
|
||||
boolean blocked = true;
|
||||
|
||||
// Snapshot is initializing (and is blocked at this stage)
|
||||
SnapshotsStatusResponse snapshotsStatus = client.admin().cluster().prepareSnapshotStatus("repository").setSnapshots("snap").get();
|
||||
assertThat(snapshotsStatus.getSnapshots().iterator().next().getState(), equalTo(State.INIT));
|
||||
|
||||
final List<State> states = new CopyOnWriteArrayList<>();
|
||||
final ClusterStateListener listener = event -> {
|
||||
SnapshotsInProgress snapshotsInProgress = event.state().custom(SnapshotsInProgress.TYPE);
|
||||
for (SnapshotsInProgress.Entry entry : snapshotsInProgress.entries()) {
|
||||
if ("snap".equals(entry.snapshot().getSnapshotId().getName())) {
|
||||
states.add(entry.state());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Record the upcoming states of the snapshot on all nodes
|
||||
internalCluster().getInstances(ClusterService.class).forEach(clusterService -> clusterService.addListener(listener));
|
||||
|
||||
// Delete the snapshot while it is being initialized
|
||||
ActionFuture<DeleteSnapshotResponse> delete = client.admin().cluster().prepareDeleteSnapshot("repository", "snap").execute();
|
||||
|
||||
// The deletion must set the snapshot in the ABORTED state
|
||||
assertBusy(() -> {
|
||||
SnapshotsStatusResponse status = client.admin().cluster().prepareSnapshotStatus("repository").setSnapshots("snap").get();
|
||||
assertThat(status.getSnapshots().iterator().next().getState(), equalTo(State.ABORTED));
|
||||
});
|
||||
|
||||
// Now unblock the repository
|
||||
unblockNode("repository", internalCluster().getMasterName());
|
||||
blocked = false;
|
||||
|
||||
assertAcked(delete.get());
|
||||
expectThrows(SnapshotMissingException.class, () ->
|
||||
client.admin().cluster().prepareGetSnapshots("repository").setSnapshots("snap").get());
|
||||
|
||||
assertFalse("Expecting snapshot state to be updated", states.isEmpty());
|
||||
assertFalse("Expecting snapshot to be aborted and not started at all", states.contains(State.STARTED));
|
||||
} finally {
|
||||
internalCluster().getInstances(ClusterService.class).forEach(clusterService -> clusterService.removeListener(listener));
|
||||
if (blocked) {
|
||||
unblockNode("repository", internalCluster().getMasterName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySnapshotInfo(final GetSnapshotsResponse response, final Map<String, List<String>> indicesPerSnapshot) {
|
||||
for (SnapshotInfo snapshotInfo : response.getSnapshots()) {
|
||||
final List<String> expected = snapshotInfo.indices();
|
||||
|
|
Loading…
Reference in New Issue