Skip to content

Commit 3a64ecb

Browse files
Allow Deleting Multiple Snapshots at Once (#55474) (#56083)
* Allow Deleting Multiple Snapshots at Once (#55474) Adds deleting multiple snapshots in one go without significantly changing the mechanics of snapshot deletes otherwise. This change does not yet allow mixing snapshot delete and abort. Abort is still only allowed for a single snapshot delete by exact name.
1 parent 2061652 commit 3a64ecb

File tree

32 files changed

+379
-247
lines changed

32 files changed

+379
-247
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ static Request restoreSnapshot(RestoreSnapshotRequest restoreSnapshotRequest) th
176176
static Request deleteSnapshot(DeleteSnapshotRequest deleteSnapshotRequest) {
177177
String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_snapshot")
178178
.addPathPart(deleteSnapshotRequest.repository())
179-
.addPathPart(deleteSnapshotRequest.snapshot())
179+
.addCommaSeparatedPathParts(deleteSnapshotRequest.snapshots())
180180
.build();
181181
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
182182

client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotRequestConvertersTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ public void testDeleteSnapshot() {
269269

270270
DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest();
271271
deleteSnapshotRequest.repository(repository);
272-
deleteSnapshotRequest.snapshot(snapshot);
272+
deleteSnapshotRequest.snapshots(snapshot);
273273
RequestConvertersTests.setRandomMasterTimeout(deleteSnapshotRequest, expectedParams);
274274

275275
Request request = SnapshotRequestConverters.deleteSnapshot(deleteSnapshotRequest);

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ public void testSnapshotDeleteSnapshot() throws IOException {
753753

754754
// tag::delete-snapshot-request
755755
DeleteSnapshotRequest request = new DeleteSnapshotRequest(repositoryName);
756-
request.snapshot(snapshotName);
756+
request.snapshots(snapshotName);
757757
// end::delete-snapshot-request
758758

759759
// tag::delete-snapshot-request-masterTimeout

docs/reference/snapshot-restore/take-snapshot.asciidoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,15 @@ created the snapshotting process will be aborted and all files created as part o
193193
cleaned. Therefore, the delete snapshot operation can be used to cancel long running snapshot operations that were
194194
started by mistake.
195195

196+
It is also possible to delete multiple snapshots from a repository in one go, for example:
197+
198+
[source,console]
199+
-----------------------------------
200+
DELETE /_snapshot/my_backup/my_backup,my_fs_backup
201+
DELETE /_snapshot/my_backup/snap*
202+
-----------------------------------
203+
// TEST[skip:no my_fs_backup]
204+
196205
A repository can be unregistered using the following command:
197206

198207
[source,console]

plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.elasticsearch.threadpool.Scheduler;
5353
import org.elasticsearch.threadpool.ThreadPool;
5454

55+
import java.util.Collection;
5556
import java.util.List;
5657
import java.util.Map;
5758
import java.util.concurrent.TimeUnit;
@@ -286,12 +287,12 @@ public void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations shardGenera
286287
}
287288

288289
@Override
289-
public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, Version repositoryMetaVersion,
290-
ActionListener<Void> listener) {
290+
public void deleteSnapshots(Collection<SnapshotId> snapshotIds, long repositoryStateId, Version repositoryMetaVersion,
291+
ActionListener<Void> listener) {
291292
if (SnapshotsService.useShardGenerations(repositoryMetaVersion) == false) {
292293
listener = delayedListener(listener);
293294
}
294-
super.deleteSnapshot(snapshotId, repositoryStateId, repositoryMetaVersion, listener);
295+
super.deleteSnapshots(snapshotIds, repositoryStateId, repositoryMetaVersion, listener);
295296
}
296297

297298
/**

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequest.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.action.support.master.MasterNodeRequest;
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.io.stream.StreamOutput;
26+
import org.elasticsearch.snapshots.SnapshotsService;
2627

2728
import java.io.IOException;
2829

@@ -31,15 +32,14 @@
3132
/**
3233
* Delete snapshot request
3334
* <p>
34-
* Delete snapshot request removes the snapshot record from the repository and cleans up all
35-
* files that are associated with this particular snapshot. All files that are shared with
36-
* at least one other existing snapshot are left intact.
35+
* Delete snapshot request removes snapshots from the repository and cleans up all files that are associated with the snapshots.
36+
* All files that are shared with at least one other existing snapshot are left intact.
3737
*/
3838
public class DeleteSnapshotRequest extends MasterNodeRequest<DeleteSnapshotRequest> {
3939

4040
private String repository;
4141

42-
private String snapshot;
42+
private String[] snapshots;
4343

4444
/**
4545
* Constructs a new delete snapshots request
@@ -48,14 +48,14 @@ public DeleteSnapshotRequest() {
4848
}
4949

5050
/**
51-
* Constructs a new delete snapshots request with repository and snapshot name
51+
* Constructs a new delete snapshots request with repository and snapshot names
5252
*
5353
* @param repository repository name
54-
* @param snapshot snapshot name
54+
* @param snapshots snapshot names
5555
*/
56-
public DeleteSnapshotRequest(String repository, String snapshot) {
56+
public DeleteSnapshotRequest(String repository, String... snapshots) {
5757
this.repository = repository;
58-
this.snapshot = snapshot;
58+
this.snapshots = snapshots;
5959
}
6060

6161
/**
@@ -70,14 +70,26 @@ public DeleteSnapshotRequest(String repository) {
7070
public DeleteSnapshotRequest(StreamInput in) throws IOException {
7171
super(in);
7272
repository = in.readString();
73-
snapshot = in.readString();
73+
if (in.getVersion().onOrAfter(SnapshotsService.MULTI_DELETE_VERSION)) {
74+
snapshots = in.readStringArray();
75+
} else {
76+
snapshots = new String[] {in.readString()};
77+
}
7478
}
7579

7680
@Override
7781
public void writeTo(StreamOutput out) throws IOException {
7882
super.writeTo(out);
7983
out.writeString(repository);
80-
out.writeString(snapshot);
84+
if (out.getVersion().onOrAfter(SnapshotsService.MULTI_DELETE_VERSION)) {
85+
out.writeStringArray(snapshots);
86+
} else {
87+
if (snapshots.length != 1) {
88+
throw new IllegalArgumentException(
89+
"Can't write snapshot delete with more than one snapshot to version [" + out.getVersion() + "]");
90+
}
91+
out.writeString(snapshots[0]);
92+
}
8193
}
8294

8395
@Override
@@ -86,8 +98,8 @@ public ActionRequestValidationException validate() {
8698
if (repository == null) {
8799
validationException = addValidationError("repository is missing", validationException);
88100
}
89-
if (snapshot == null) {
90-
validationException = addValidationError("snapshot is missing", validationException);
101+
if (snapshots == null || snapshots.length == 0) {
102+
validationException = addValidationError("snapshots are missing", validationException);
91103
}
92104
return validationException;
93105
}
@@ -108,21 +120,21 @@ public String repository() {
108120
}
109121

110122
/**
111-
* Returns repository name
123+
* Returns snapshot names
112124
*
113-
* @return repository name
125+
* @return snapshot names
114126
*/
115-
public String snapshot() {
116-
return this.snapshot;
127+
public String[] snapshots() {
128+
return this.snapshots;
117129
}
118130

119131
/**
120-
* Sets snapshot name
132+
* Sets snapshot names
121133
*
122134
* @return this request
123135
*/
124-
public DeleteSnapshotRequest snapshot(String snapshot) {
125-
this.snapshot = snapshot;
136+
public DeleteSnapshotRequest snapshots(String... snapshots) {
137+
this.snapshots = snapshots;
126138
return this;
127139
}
128140
}

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequestBuilder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAc
3939
/**
4040
* Constructs delete snapshot request builder with specified repository and snapshot names
4141
*/
42-
public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAction action, String repository, String snapshot) {
43-
super(client, action, new DeleteSnapshotRequest(repository, snapshot));
42+
public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAction action, String repository, String... snapshots) {
43+
super(client, action, new DeleteSnapshotRequest(repository, snapshots));
4444
}
4545

4646
/**
@@ -57,11 +57,11 @@ public DeleteSnapshotRequestBuilder setRepository(String repository) {
5757
/**
5858
* Sets the snapshot name
5959
*
60-
* @param snapshot snapshot name
60+
* @param snapshots snapshot names
6161
* @return this builder
6262
*/
63-
public DeleteSnapshotRequestBuilder setSnapshot(String snapshot) {
64-
request.snapshot(snapshot);
63+
public DeleteSnapshotRequestBuilder setSnapshots(String... snapshots) {
64+
request.snapshots(snapshots);
6565
return this;
6666
}
6767
}

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/TransportDeleteSnapshotAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.transport.TransportService;
3636

3737
import java.io.IOException;
38+
import java.util.Arrays;
3839

3940
/**
4041
* Transport action for delete snapshot operation
@@ -70,7 +71,7 @@ protected ClusterBlockException checkBlock(DeleteSnapshotRequest request, Cluste
7071
@Override
7172
protected void masterOperation(final DeleteSnapshotRequest request, ClusterState state,
7273
final ActionListener<AcknowledgedResponse> listener) {
73-
snapshotsService.deleteSnapshot(request.repository(), request.snapshot(),
74+
snapshotsService.deleteSnapshots(request.repository(), Arrays.asList(request.snapshots()),
7475
ActionListener.map(listener, v -> new AcknowledgedResponse(true)));
7576
}
7677
}

server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ public interface ClusterAdminClient extends ElasticsearchClient {
529529
/**
530530
* Delete snapshot.
531531
*/
532-
DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String snapshot);
532+
DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String... snapshot);
533533

534534
/**
535535
* Restores a snapshot.

server/src/main/java/org/elasticsearch/client/Requests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,14 +526,14 @@ public static RestoreSnapshotRequest restoreSnapshotRequest(String repository, S
526526
}
527527

528528
/**
529-
* Deletes a snapshot
529+
* Deletes snapshots
530530
*
531-
* @param snapshot snapshot name
531+
* @param snapshots snapshot names
532532
* @param repository repository name
533533
* @return delete snapshot request
534534
*/
535-
public static DeleteSnapshotRequest deleteSnapshotRequest(String repository, String snapshot) {
536-
return new DeleteSnapshotRequest(repository, snapshot);
535+
public static DeleteSnapshotRequest deleteSnapshotRequest(String repository, String... snapshots) {
536+
return new DeleteSnapshotRequest(repository, snapshots);
537537
}
538538

539539
/**

server/src/main/java/org/elasticsearch/client/support/AbstractClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,8 +976,8 @@ public void deleteSnapshot(DeleteSnapshotRequest request, ActionListener<Acknowl
976976
}
977977

978978
@Override
979-
public DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String name) {
980-
return new DeleteSnapshotRequestBuilder(this, DeleteSnapshotAction.INSTANCE, repository, name);
979+
public DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String... names) {
980+
return new DeleteSnapshotRequestBuilder(this, DeleteSnapshotAction.INSTANCE, repository, names);
981981
}
982982

983983

server/src/main/java/org/elasticsearch/cluster/SnapshotDeletionsInProgress.java

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@
2929
import org.elasticsearch.repositories.RepositoryData;
3030
import org.elasticsearch.repositories.RepositoryOperation;
3131
import org.elasticsearch.snapshots.Snapshot;
32+
import org.elasticsearch.snapshots.SnapshotId;
33+
import org.elasticsearch.snapshots.SnapshotsService;
3234

3335
import java.io.IOException;
3436
import java.util.ArrayList;
3537
import java.util.Collections;
38+
import java.util.HashSet;
3639
import java.util.List;
3740
import java.util.Objects;
3841

@@ -140,8 +143,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
140143
for (Entry entry : entries) {
141144
builder.startObject();
142145
{
143-
builder.field("repository", entry.snapshot.getRepository());
144-
builder.field("snapshot", entry.snapshot.getSnapshotId().getName());
146+
builder.field("repository", entry.repository());
147+
builder.startArray("snapshots");
148+
for (SnapshotId snapshot : entry.snapshots) {
149+
builder.value(snapshot.getName());
150+
}
151+
builder.endArray();
145152
builder.humanReadableField("start_time_millis", "start_time", new TimeValue(entry.startTime));
146153
builder.field("repository_state_id", entry.repositoryStateId);
147154
}
@@ -155,7 +162,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
155162
public String toString() {
156163
StringBuilder builder = new StringBuilder("SnapshotDeletionsInProgress[");
157164
for (int i = 0; i < entries.size(); i++) {
158-
builder.append(entries.get(i).getSnapshot().getSnapshotId().getName());
165+
builder.append(entries.get(i).getSnapshots());
159166
if (i + 1 < entries.size()) {
160167
builder.append(",");
161168
}
@@ -167,29 +174,36 @@ public String toString() {
167174
* A class representing a snapshot deletion request entry in the cluster state.
168175
*/
169176
public static final class Entry implements Writeable, RepositoryOperation {
170-
private final Snapshot snapshot;
177+
private final List<SnapshotId> snapshots;
178+
private final String repoName;
171179
private final long startTime;
172180
private final long repositoryStateId;
173181

174-
public Entry(Snapshot snapshot, long startTime, long repositoryStateId) {
175-
this.snapshot = snapshot;
182+
public Entry(List<SnapshotId> snapshots, String repoName, long startTime, long repositoryStateId) {
183+
this.snapshots = snapshots;
184+
assert snapshots.size() == new HashSet<>(snapshots).size() : "Duplicate snapshot ids in " + snapshots;
185+
this.repoName = repoName;
176186
this.startTime = startTime;
177187
this.repositoryStateId = repositoryStateId;
178188
assert repositoryStateId > RepositoryData.EMPTY_REPO_GEN :
179189
"Can't delete based on an empty or unknown repository generation but saw [" + repositoryStateId + "]";
180190
}
181191

182192
public Entry(StreamInput in) throws IOException {
183-
this.snapshot = new Snapshot(in);
193+
if (in.getVersion().onOrAfter(SnapshotsService.MULTI_DELETE_VERSION)) {
194+
this.repoName = in.readString();
195+
this.snapshots = in.readList(SnapshotId::new);
196+
} else {
197+
final Snapshot snapshot = new Snapshot(in);
198+
this.snapshots = Collections.singletonList(snapshot.getSnapshotId());
199+
this.repoName = snapshot.getRepository();
200+
}
184201
this.startTime = in.readVLong();
185202
this.repositoryStateId = in.readLong();
186203
}
187204

188-
/**
189-
* The snapshot to delete.
190-
*/
191-
public Snapshot getSnapshot() {
192-
return snapshot;
205+
public List<SnapshotId> getSnapshots() {
206+
return snapshots;
193207
}
194208

195209
/**
@@ -208,26 +222,34 @@ public boolean equals(Object o) {
208222
return false;
209223
}
210224
Entry that = (Entry) o;
211-
return snapshot.equals(that.snapshot)
225+
return repoName.equals(that.repoName)
226+
&& snapshots.equals(that.snapshots)
212227
&& startTime == that.startTime
213228
&& repositoryStateId == that.repositoryStateId;
214229
}
215230

216231
@Override
217232
public int hashCode() {
218-
return Objects.hash(snapshot, startTime, repositoryStateId);
233+
return Objects.hash(snapshots, repoName, startTime, repositoryStateId);
219234
}
220235

221236
@Override
222237
public void writeTo(StreamOutput out) throws IOException {
223-
snapshot.writeTo(out);
238+
if (out.getVersion().onOrAfter(SnapshotsService.MULTI_DELETE_VERSION)) {
239+
out.writeString(repoName);
240+
out.writeCollection(snapshots);
241+
} else {
242+
assert snapshots.size() == 1 : "Only single deletion allowed in mixed version cluster containing [" + out.getVersion() +
243+
"] but saw " + snapshots;
244+
new Snapshot(repoName, snapshots.get(0)).writeTo(out);
245+
}
224246
out.writeVLong(startTime);
225247
out.writeLong(repositoryStateId);
226248
}
227249

228250
@Override
229251
public String repository() {
230-
return snapshot.getRepository();
252+
return repoName;
231253
}
232254

233255
@Override

0 commit comments

Comments
 (0)