diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java index 134dc921c450d..e4221bb2737f2 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java @@ -30,7 +30,7 @@ import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; @@ -335,13 +335,13 @@ public Cancellable restoreAsync(RestoreSnapshotRequest restoreSnapshotRequest, R * See Snapshot and Restore * API on elastic.co * - * @param deleteSnapshotRequest the request + * @param deleteSnapshotsRequest the request * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @return the response * @throws IOException in case there is a problem sending the request or parsing back the response */ - public AcknowledgedResponse delete(DeleteSnapshotRequest deleteSnapshotRequest, RequestOptions options) throws IOException { - return restHighLevelClient.performRequestAndParseEntity(deleteSnapshotRequest, + public AcknowledgedResponse delete(DeleteSnapshotsRequest deleteSnapshotsRequest, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(deleteSnapshotsRequest, SnapshotRequestConverters::deleteSnapshot, options, AcknowledgedResponse::fromXContent, emptySet()); } @@ -351,14 +351,14 @@ public AcknowledgedResponse delete(DeleteSnapshotRequest deleteSnapshotRequest, * See Snapshot and Restore * API on elastic.co * - * @param deleteSnapshotRequest the request + * @param deleteSnapshotsRequest the request * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @param listener the listener to be notified upon request completion * @return cancellable that may be used to cancel the request */ - public Cancellable deleteAsync(DeleteSnapshotRequest deleteSnapshotRequest, RequestOptions options, + public Cancellable deleteAsync(DeleteSnapshotsRequest deleteSnapshotsRequest, RequestOptions options, ActionListener listener) { - return restHighLevelClient.performRequestAsyncAndParseEntity(deleteSnapshotRequest, + return restHighLevelClient.performRequestAsyncAndParseEntity(deleteSnapshotsRequest, SnapshotRequestConverters::deleteSnapshot, options, AcknowledgedResponse::fromXContent, listener, emptySet()); } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java index 703aa0d672555..e5ffe037eba96 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java @@ -29,7 +29,7 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; @@ -173,15 +173,15 @@ static Request restoreSnapshot(RestoreSnapshotRequest restoreSnapshotRequest) th return request; } - static Request deleteSnapshot(DeleteSnapshotRequest deleteSnapshotRequest) { + static Request deleteSnapshot(DeleteSnapshotsRequest deleteSnapshotsRequest) { String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_snapshot") - .addPathPart(deleteSnapshotRequest.repository()) - .addPathPart(deleteSnapshotRequest.snapshot()) + .addPathPart(deleteSnapshotsRequest.repository()) + .addCommaSeparatedPathParts(deleteSnapshotsRequest.snapshots()) .build(); Request request = new Request(HttpDelete.METHOD_NAME, endpoint); RequestConverters.Params parameters = new RequestConverters.Params(); - parameters.withMasterTimeout(deleteSnapshotRequest.masterNodeTimeout()); + parameters.withMasterTimeout(deleteSnapshotsRequest.masterNodeTimeout()); request.addParameters(parameters.asMap()); return request; } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java index f9679cf5eb61c..200cfad9a46a2 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java @@ -30,7 +30,7 @@ import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; @@ -165,7 +165,7 @@ public void testCreateSnapshot() throws IOException { if (waitForCompletion == false) { // If we don't wait for the snapshot to complete we have to cancel it to not leak the snapshot task AcknowledgedResponse deleteResponse = execute( - new DeleteSnapshotRequest(repository, snapshot), + new DeleteSnapshotsRequest(repository, snapshot), highLevelClient().snapshot()::delete, highLevelClient().snapshot()::deleteAsync ); assertTrue(deleteResponse.isAcknowledged()); @@ -301,7 +301,7 @@ public void testDeleteSnapshot() throws IOException { // check that the request went ok without parsing JSON here. When using the high level client, check acknowledgement instead. assertEquals(RestStatus.OK, createSnapshotResponse.status()); - DeleteSnapshotRequest request = new DeleteSnapshotRequest(repository, snapshot); + DeleteSnapshotsRequest request = new DeleteSnapshotsRequest(repository, snapshot); AcknowledgedResponse response = execute(request, highLevelClient().snapshot()::delete, highLevelClient().snapshot()::deleteAsync); assertTrue(response.isAcknowledged()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotRequestConvertersTests.java index 23789d390357b..6bb5bfec903b5 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotRequestConvertersTests.java @@ -28,7 +28,7 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; @@ -267,12 +267,12 @@ public void testDeleteSnapshot() { String endpoint = String.format(Locale.ROOT, "/_snapshot/%s/%s", repository, snapshot); - DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest(); - deleteSnapshotRequest.repository(repository); - deleteSnapshotRequest.snapshot(snapshot); - RequestConvertersTests.setRandomMasterTimeout(deleteSnapshotRequest, expectedParams); + DeleteSnapshotsRequest deleteSnapshotsRequest = new DeleteSnapshotsRequest(); + deleteSnapshotsRequest.repository(repository); + deleteSnapshotsRequest.snapshots(new String[]{snapshot}); + RequestConvertersTests.setRandomMasterTimeout(deleteSnapshotsRequest, expectedParams); - Request request = SnapshotRequestConverters.deleteSnapshot(deleteSnapshotRequest); + Request request = SnapshotRequestConverters.deleteSnapshot(deleteSnapshotsRequest); assertThat(request.getEndpoint(), equalTo(endpoint)); assertThat(request.getMethod(), equalTo(HttpDelete.METHOD_NAME)); assertThat(request.getParameters(), equalTo(expectedParams)); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java index 1bc3e3f040af6..97f1caba62bcf 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java @@ -29,7 +29,7 @@ import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; @@ -752,8 +752,7 @@ public void testSnapshotDeleteSnapshot() throws IOException { createTestSnapshots(); // tag::delete-snapshot-request - DeleteSnapshotRequest request = new DeleteSnapshotRequest(repositoryName); - request.snapshot(snapshotName); + DeleteSnapshotsRequest request = new DeleteSnapshotsRequest(repositoryName, snapshotName); // end::delete-snapshot-request // tag::delete-snapshot-request-masterTimeout @@ -774,7 +773,7 @@ public void testSnapshotDeleteSnapshot() throws IOException { public void testSnapshotDeleteSnapshotAsync() throws InterruptedException { RestHighLevelClient client = highLevelClient(); { - DeleteSnapshotRequest request = new DeleteSnapshotRequest(); + DeleteSnapshotsRequest request = new DeleteSnapshotsRequest(); // tag::delete-snapshot-execute-listener ActionListener listener = diff --git a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLSnapshotRestoreTests.java b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLSnapshotRestoreTests.java index 2cc1d0992fafc..45f77b3ed3663 100644 --- a/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLSnapshotRestoreTests.java +++ b/modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLSnapshotRestoreTests.java @@ -120,7 +120,8 @@ public void testUrlRepository() throws Exception { assertThat(getSnapshotsResponse.getSnapshots("url-repo").size(), equalTo(1)); logger.info("--> delete snapshot"); - AcknowledgedResponse deleteSnapshotResponse = client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get(); + AcknowledgedResponse deleteSnapshotResponse = client.admin().cluster().prepareDeleteSnapshots("test-repo", + new String[]{"test-snap"}).get(); assertAcked(deleteSnapshotResponse); logger.info("--> list available shapshot again, no snapshots should be returned"); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequestBuilder.java index 1e47160903c85..2efbb52049aea 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequestBuilder.java @@ -26,21 +26,21 @@ /** * Delete snapshot request builder */ -public class DeleteSnapshotRequestBuilder extends MasterNodeOperationRequestBuilder { /** * Constructs delete snapshot request builder */ public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAction action) { - super(client, action, new DeleteSnapshotRequest()); + super(client, action, new DeleteSnapshotsRequest()); } /** * Constructs delete snapshot request builder with specified repository and snapshot names */ - public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAction action, String repository, String snapshot) { - super(client, action, new DeleteSnapshotRequest(repository, snapshot)); + public DeleteSnapshotRequestBuilder(ElasticsearchClient client, DeleteSnapshotAction action, String repository, String[] snapshots) { + super(client, action, new DeleteSnapshotsRequest(repository, snapshots)); } /** @@ -57,11 +57,11 @@ public DeleteSnapshotRequestBuilder setRepository(String repository) { /** * Sets the snapshot name * - * @param snapshot snapshot name + * @param snapshots snapshot names * @return this builder */ - public DeleteSnapshotRequestBuilder setSnapshot(String snapshot) { - request.snapshot(snapshot); + public DeleteSnapshotRequestBuilder setSnapshots(String... snapshots) { + request.snapshots(snapshots); return this; } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotsRequest.java similarity index 64% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequest.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotsRequest.java index 93581c937c50e..9e79ed3807f0c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/DeleteSnapshotsRequest.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.admin.cluster.snapshots.delete; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.MasterNodeRequest; import org.elasticsearch.common.io.stream.StreamInput; @@ -35,16 +36,18 @@ * files that are associated with this particular snapshot. All files that are shared with * at least one other existing snapshot are left intact. */ -public class DeleteSnapshotRequest extends MasterNodeRequest { +public class DeleteSnapshotsRequest extends MasterNodeRequest { + + public static final Version MULTI_DELETE_VERSION = Version.V_8_0_0; private String repository; - private String snapshot; + private String[] snapshots; /** * Constructs a new delete snapshots request */ - public DeleteSnapshotRequest() { + public DeleteSnapshotsRequest() { } /** @@ -53,9 +56,19 @@ public DeleteSnapshotRequest() { * @param repository repository name * @param snapshot snapshot name */ - public DeleteSnapshotRequest(String repository, String snapshot) { + public DeleteSnapshotsRequest(String repository, String snapshot) { + this(repository, new String[]{snapshot}); + } + + /** + * Constructs a new delete snapshots request with repository and snapshot name + * + * @param repository repository name + * @param snapshots snapshot names + */ + public DeleteSnapshotsRequest(String repository, String[] snapshots) { this.repository = repository; - this.snapshot = snapshot; + this.snapshots = snapshots; } /** @@ -63,21 +76,33 @@ public DeleteSnapshotRequest(String repository, String snapshot) { * * @param repository repository name */ - public DeleteSnapshotRequest(String repository) { + public DeleteSnapshotsRequest(String repository) { this.repository = repository; } - public DeleteSnapshotRequest(StreamInput in) throws IOException { + public DeleteSnapshotsRequest(StreamInput in) throws IOException { super(in); repository = in.readString(); - snapshot = in.readString(); + if (in.getVersion().onOrAfter(MULTI_DELETE_VERSION)) { + snapshots = in.readStringArray(); + } else { + snapshots = new String[] {in.readString()}; + } } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeString(repository); - out.writeString(snapshot); + if (out.getVersion().onOrAfter(MULTI_DELETE_VERSION)) { + out.writeStringArray(snapshots); + } else { + if (snapshots.length != 1) { + throw new IllegalArgumentException( + "Can't write snapshot delete with more than one snapshot to version [" + out.getVersion() + "]"); + } + out.writeString(snapshots[0]); + } } @Override @@ -86,14 +111,14 @@ public ActionRequestValidationException validate() { if (repository == null) { validationException = addValidationError("repository is missing", validationException); } - if (snapshot == null) { + if (snapshots == null || snapshots.length == 0) { validationException = addValidationError("snapshot is missing", validationException); } return validationException; } - public DeleteSnapshotRequest repository(String repository) { + public DeleteSnapshotsRequest repository(String repository) { this.repository = repository; return this; } @@ -112,8 +137,8 @@ public String repository() { * * @return repository name */ - public String snapshot() { - return this.snapshot; + public String[] snapshots() { + return this.snapshots; } /** @@ -121,8 +146,8 @@ public String snapshot() { * * @return this request */ - public DeleteSnapshotRequest snapshot(String snapshot) { - this.snapshot = snapshot; + public DeleteSnapshotsRequest snapshots(String[] snapshots) { + this.snapshots = snapshots; return this; } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/TransportDeleteSnapshotAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/TransportDeleteSnapshotAction.java index 5c3395012d30d..8b0746312fa49 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/TransportDeleteSnapshotAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/delete/TransportDeleteSnapshotAction.java @@ -40,7 +40,7 @@ /** * Transport action for delete snapshot operation */ -public class TransportDeleteSnapshotAction extends TransportMasterNodeAction { +public class TransportDeleteSnapshotAction extends TransportMasterNodeAction { private final SnapshotsService snapshotsService; @Inject @@ -48,7 +48,7 @@ public TransportDeleteSnapshotAction(TransportService transportService, ClusterS ThreadPool threadPool, SnapshotsService snapshotsService, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { super(DeleteSnapshotAction.NAME, transportService, clusterService, threadPool, actionFilters, - DeleteSnapshotRequest::new,indexNameExpressionResolver); + DeleteSnapshotsRequest::new,indexNameExpressionResolver); this.snapshotsService = snapshotsService; } @@ -63,15 +63,15 @@ protected AcknowledgedResponse read(StreamInput in) throws IOException { } @Override - protected ClusterBlockException checkBlock(DeleteSnapshotRequest request, ClusterState state) { + protected ClusterBlockException checkBlock(DeleteSnapshotsRequest request, ClusterState state) { // Cluster is not affected but we look up repositories in metadata return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); } @Override - protected void masterOperation(Task task, final DeleteSnapshotRequest request, ClusterState state, + protected void masterOperation(Task task, final DeleteSnapshotsRequest request, ClusterState state, final ActionListener listener) { - snapshotsService.deleteSnapshot(request.repository(), request.snapshot(), + snapshotsService.deleteSnapshots(request.repository(), request.snapshots(), ActionListener.map(listener, v -> new AcknowledgedResponse(true)), false); } } diff --git a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java index fdee39fdb1f93..16c66cfc829c4 100644 --- a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java +++ b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java @@ -74,7 +74,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequestBuilder; @@ -519,17 +519,17 @@ public interface ClusterAdminClient extends ElasticsearchClient { /** * Delete snapshot. */ - ActionFuture deleteSnapshot(DeleteSnapshotRequest request); + ActionFuture deleteSnapshots(DeleteSnapshotsRequest request); /** * Delete snapshot. */ - void deleteSnapshot(DeleteSnapshotRequest request, ActionListener listener); + void deleteSnapshots(DeleteSnapshotsRequest request, ActionListener listener); /** * Delete snapshot. */ - DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String snapshot); + DeleteSnapshotRequestBuilder prepareDeleteSnapshots(String repository, String[] snapshot); /** * Restores a snapshot. diff --git a/server/src/main/java/org/elasticsearch/client/Requests.java b/server/src/main/java/org/elasticsearch/client/Requests.java index 01d04c64ae1b1..dbfd71ae1c4e0 100644 --- a/server/src/main/java/org/elasticsearch/client/Requests.java +++ b/server/src/main/java/org/elasticsearch/client/Requests.java @@ -35,7 +35,7 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; @@ -520,8 +520,8 @@ public static RestoreSnapshotRequest restoreSnapshotRequest(String repository, S * @param repository repository name * @return delete snapshot request */ - public static DeleteSnapshotRequest deleteSnapshotRequest(String repository, String snapshot) { - return new DeleteSnapshotRequest(repository, snapshot); + public static DeleteSnapshotsRequest deleteSnapshotRequest(String repository, String snapshot) { + return new DeleteSnapshotsRequest(repository, new String[]{snapshot}); } /** diff --git a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java index 5bb480d8c23c3..a41577308a61d 100644 --- a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java +++ b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java @@ -99,7 +99,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotAction; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsAction; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; @@ -943,18 +943,18 @@ public GetSnapshotsRequestBuilder prepareGetSnapshots(String... repositories) { @Override - public ActionFuture deleteSnapshot(DeleteSnapshotRequest request) { + public ActionFuture deleteSnapshots(DeleteSnapshotsRequest request) { return execute(DeleteSnapshotAction.INSTANCE, request); } @Override - public void deleteSnapshot(DeleteSnapshotRequest request, ActionListener listener) { + public void deleteSnapshots(DeleteSnapshotsRequest request, ActionListener listener) { execute(DeleteSnapshotAction.INSTANCE, request, listener); } @Override - public DeleteSnapshotRequestBuilder prepareDeleteSnapshot(String repository, String name) { - return new DeleteSnapshotRequestBuilder(this, DeleteSnapshotAction.INSTANCE, repository, name); + public DeleteSnapshotRequestBuilder prepareDeleteSnapshots(String repository, String[] names) { + return new DeleteSnapshotRequestBuilder(this, DeleteSnapshotAction.INSTANCE, repository, names); } diff --git a/server/src/main/java/org/elasticsearch/repositories/FilterRepository.java b/server/src/main/java/org/elasticsearch/repositories/FilterRepository.java index 76645834fb112..821e2496de678 100644 --- a/server/src/main/java/org/elasticsearch/repositories/FilterRepository.java +++ b/server/src/main/java/org/elasticsearch/repositories/FilterRepository.java @@ -37,6 +37,7 @@ import org.elasticsearch.snapshots.SnapshotShardFailure; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -83,7 +84,8 @@ public void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations shardGenera } @Override - public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener) { + public void deleteSnapshot(Collection snapshotId, long repositoryStateId, boolean writeShardGens, + ActionListener listener) { in.deleteSnapshot(snapshotId, repositoryStateId, writeShardGens, listener); } diff --git a/server/src/main/java/org/elasticsearch/repositories/Repository.java b/server/src/main/java/org/elasticsearch/repositories/Repository.java index fab0a448007e8..e4b935ef1338c 100644 --- a/server/src/main/java/org/elasticsearch/repositories/Repository.java +++ b/server/src/main/java/org/elasticsearch/repositories/Repository.java @@ -36,6 +36,7 @@ import org.elasticsearch.snapshots.SnapshotShardFailure; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -132,14 +133,14 @@ void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations shardGenerations, boolean writeShardGens, ActionListener listener); /** - * Deletes snapshot + * Deletes snapshots * - * @param snapshotId snapshot id + * @param snapshotIds snapshot ids to delete * @param repositoryStateId the unique id identifying the state of the repository when the snapshot deletion began * @param writeShardGens if shard generations should be written to the repository * @param listener completion listener */ - void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener); + void deleteSnapshot(Collection snapshotIds, long repositoryStateId, boolean writeShardGens, ActionListener listener); /** * Returns snapshot throttle time in nanoseconds diff --git a/server/src/main/java/org/elasticsearch/repositories/RepositoryData.java b/server/src/main/java/org/elasticsearch/repositories/RepositoryData.java index 357268fa051e0..90073b94c6314 100644 --- a/server/src/main/java/org/elasticsearch/repositories/RepositoryData.java +++ b/server/src/main/java/org/elasticsearch/repositories/RepositoryData.java @@ -34,12 +34,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -145,13 +147,27 @@ public Map getIndices() { * Returns the list of {@link IndexId} that have their snapshots updated but not removed (because they are still referenced by other * snapshots) after removing the given snapshot from the repository. * - * @param snapshotId SnapshotId to remove + * @param snapshotIds SnapshotId to remove * @return List of indices that are changed but not removed */ - public List indicesToUpdateAfterRemovingSnapshot(SnapshotId snapshotId) { + public List indicesToUpdateAfterRemovingSnapshot(Collection snapshotIds) { return indexSnapshots.entrySet().stream() - .filter(entry -> entry.getValue().size() > 1 && entry.getValue().contains(snapshotId)) - .map(Map.Entry::getKey) + .filter(entry -> { + final Collection existingIds = entry.getValue(); + final boolean containsAll = existingIds.containsAll(snapshotIds); + if (containsAll && existingIds.size() > snapshotIds.size()) { + return true; + } + if (snapshotIds.containsAll(existingIds)) { + return false; + } + for (SnapshotId snapshotId : snapshotIds) { + if (entry.getValue().contains(snapshotId)) { + return true; + } + } + return false; + }).map(Map.Entry::getKey) .collect(Collectors.toList()); } @@ -201,37 +217,33 @@ public RepositoryData withGenId(long newGeneration) { /** * Remove a snapshot and remove any indices that no longer exist in the repository due to the deletion of the snapshot. * - * @param snapshotId Snapshot Id + * @param snapshots Snapshot ids to remove * @param updatedShardGenerations Shard generations that changed as a result of removing the snapshot. * The {@code String[]} passed for each {@link IndexId} contains the new shard generation id for each * changed shard indexed by its shardId */ - public RepositoryData removeSnapshot(final SnapshotId snapshotId, final ShardGenerations updatedShardGenerations) { - Map newSnapshotIds = snapshotIds.values().stream() - .filter(id -> !snapshotId.equals(id)) + public RepositoryData removeSnapshot(final Collection snapshots, final ShardGenerations updatedShardGenerations) { + Map newSnapshotIds = snapshotIds.values().stream().filter(Predicate.not(snapshots::contains)) .collect(Collectors.toMap(SnapshotId::getUUID, Function.identity())); - if (newSnapshotIds.size() == snapshotIds.size()) { - throw new ResourceNotFoundException("Attempting to remove non-existent snapshot [{}] from repository data", snapshotId); + if (newSnapshotIds.size() != snapshotIds.size() - snapshots.size()) { + final Collection notFound = new HashSet<>(snapshots); + notFound.removeAll(snapshotIds.values()); + throw new ResourceNotFoundException("Attempting to remove non-existent snapshots {} from repository data", notFound); } Map newSnapshotStates = new HashMap<>(snapshotStates); - newSnapshotStates.remove(snapshotId.getUUID()); + for (SnapshotId snapshotId : snapshots) { + newSnapshotStates.remove(snapshotId.getUUID()); + } Map> indexSnapshots = new HashMap<>(); for (final IndexId indexId : indices.values()) { Set set; Set snapshotIds = this.indexSnapshots.get(indexId); assert snapshotIds != null; - if (snapshotIds.contains(snapshotId)) { - if (snapshotIds.size() == 1) { - // removing the snapshot will mean no more snapshots - // have this index, so just skip over it - continue; - } - set = new LinkedHashSet<>(snapshotIds); - set.remove(snapshotId); - } else { - set = snapshotIds; + set = new LinkedHashSet<>(snapshotIds); + set.removeAll(snapshots); + if (set.isEmpty() == false) { + indexSnapshots.put(indexId, set); } - indexSnapshots.put(indexId, set); } return new RepositoryData(genId, newSnapshotIds, newSnapshotStates, indexSnapshots, diff --git a/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java b/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java index b56a22e99845b..479d7b6572d37 100644 --- a/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java +++ b/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java @@ -76,11 +76,9 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.Index; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.snapshots.IndexShardRestoreFailedException; -import org.elasticsearch.index.snapshots.IndexShardSnapshotException; import org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException; import org.elasticsearch.index.snapshots.IndexShardSnapshotStatus; import org.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSnapshot; @@ -100,7 +98,6 @@ import org.elasticsearch.repositories.RepositoryVerificationException; import org.elasticsearch.repositories.ShardGenerations; import org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException; -import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.snapshots.SnapshotException; import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; @@ -413,14 +410,16 @@ public RepositoryMetaData getMetadata() { } @Override - public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener) { + public void deleteSnapshot(Collection snapshotIds, long repositoryStateId, boolean writeShardGens, + ActionListener listener) { if (isReadOnly()) { listener.onFailure(new RepositoryException(metadata.name(), "cannot delete snapshot from a readonly repository")); } else { final long latestKnownGen = latestKnownRepoGen.get(); if (latestKnownGen > repositoryStateId) { + // TODO: Nicer exception listener.onFailure(new ConcurrentSnapshotExecutionException( - new Snapshot(metadata.name(), snapshotId), "Another concurrent operation moved repo generation to [ " + latestKnownGen + metadata.name(), snapshotIds.toString(), "Another concurrent operation moved repo generation to [ " + latestKnownGen + "] but this delete assumed generation [" + repositoryStateId + "]")); return; } @@ -430,9 +429,9 @@ public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolea // Cache the indices that were found before writing out the new index-N blob so that a stuck master will never // delete an index that was created by another master node after writing this index-N blob. final Map foundIndices = blobStore().blobContainer(indicesPath()).children(); - doDeleteShardSnapshots(snapshotId, repositoryStateId, foundIndices, rootBlobs, repositoryData, writeShardGens, listener); + doDeleteShardSnapshots(snapshotIds, repositoryStateId, foundIndices, rootBlobs, repositoryData, writeShardGens, listener); } catch (Exception ex) { - listener.onFailure(new RepositoryException(metadata.name(), "failed to delete snapshot [" + snapshotId + "]", ex)); + listener.onFailure(new RepositoryException(metadata.name(), "failed to delete snapshots " + snapshotIds, ex)); } } } @@ -465,7 +464,7 @@ private RepositoryData safeRepositoryData(long repositoryStateId, Map foundIndices, + private void doDeleteShardSnapshots(Collection snapshotIds, long repositoryStateId, Map foundIndices, Map rootBlobs, RepositoryData repositoryData, boolean writeShardGens, ActionListener listener) { if (writeShardGens) { // First write the new shard state metadata (with the removed snapshot) and compute deletion targets final StepListener> writeShardMetaDataAndComputeDeletesStep = new StepListener<>(); - writeUpdatedShardMetaDataAndComputeDeletes(snapshotId, repositoryData, true, writeShardMetaDataAndComputeDeletesStep); + writeUpdatedShardMetaDataAndComputeDeletes(snapshotIds, repositoryData, true, writeShardMetaDataAndComputeDeletesStep); // Once we have put the new shard-level metadata into place, we can update the repository metadata as follows: // 1. Remove the snapshot from the list of existing snapshots // 2. Update the index shard generations of all updated shard folders @@ -495,7 +494,7 @@ private void doDeleteShardSnapshots(SnapshotId snapshotId, long repositoryStateI for (ShardSnapshotMetaDeleteResult newGen : deleteResults) { builder.put(newGen.indexId, newGen.shardId, newGen.newGeneration); } - final RepositoryData updatedRepoData = repositoryData.removeSnapshot(snapshotId, builder.build()); + final RepositoryData updatedRepoData = repositoryData.removeSnapshot(snapshotIds, builder.build()); writeIndexGen(updatedRepoData, repositoryStateId, true, ActionListener.wrap(v -> writeUpdatedRepoDataStep.onResponse(updatedRepoData), listener::onFailure)); }, listener::onFailure); @@ -505,20 +504,20 @@ private void doDeleteShardSnapshots(SnapshotId snapshotId, long repositoryStateI final ActionListener afterCleanupsListener = new GroupedActionListener<>(ActionListener.wrap(() -> listener.onResponse(null)), 2); asyncCleanupUnlinkedRootAndIndicesBlobs(foundIndices, rootBlobs, updatedRepoData, afterCleanupsListener); - asyncCleanupUnlinkedShardLevelBlobs(snapshotId, writeShardMetaDataAndComputeDeletesStep.result(), afterCleanupsListener); + asyncCleanupUnlinkedShardLevelBlobs(snapshotIds, writeShardMetaDataAndComputeDeletesStep.result(), afterCleanupsListener); }, listener::onFailure); } else { // Write the new repository data first (with the removed snapshot), using no shard generations - final RepositoryData updatedRepoData = repositoryData.removeSnapshot(snapshotId, ShardGenerations.EMPTY); + final RepositoryData updatedRepoData = repositoryData.removeSnapshot(snapshotIds, ShardGenerations.EMPTY); writeIndexGen(updatedRepoData, repositoryStateId, false, ActionListener.wrap(v -> { // Run unreferenced blobs cleanup in parallel to shard-level snapshot deletion final ActionListener afterCleanupsListener = new GroupedActionListener<>(ActionListener.wrap(() -> listener.onResponse(null)), 2); asyncCleanupUnlinkedRootAndIndicesBlobs(foundIndices, rootBlobs, updatedRepoData, afterCleanupsListener); final StepListener> writeMetaAndComputeDeletesStep = new StepListener<>(); - writeUpdatedShardMetaDataAndComputeDeletes(snapshotId, repositoryData, false, writeMetaAndComputeDeletesStep); + writeUpdatedShardMetaDataAndComputeDeletes(snapshotIds, repositoryData, false, writeMetaAndComputeDeletesStep); writeMetaAndComputeDeletesStep.whenComplete(deleteResults -> - asyncCleanupUnlinkedShardLevelBlobs(snapshotId, deleteResults, afterCleanupsListener), + asyncCleanupUnlinkedShardLevelBlobs(snapshotIds, deleteResults, afterCleanupsListener), afterCleanupsListener::onFailure); }, listener::onFailure)); } @@ -531,17 +530,18 @@ private void asyncCleanupUnlinkedRootAndIndicesBlobs(Map l -> cleanupStaleBlobs(foundIndices, rootBlobs, updatedRepoData, ActionListener.map(l, ignored -> null)))); } - private void asyncCleanupUnlinkedShardLevelBlobs(SnapshotId snapshotId, Collection deleteResults, + private void asyncCleanupUnlinkedShardLevelBlobs(Collection snapshotIds, + Collection deleteResults, ActionListener listener) { threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(ActionRunnable.wrap( listener, l -> { try { - blobContainer().deleteBlobsIgnoringIfNotExists(resolveFilesToDelete(snapshotId, deleteResults)); + blobContainer().deleteBlobsIgnoringIfNotExists(resolveFilesToDelete(snapshotIds, deleteResults)); l.onResponse(null); } catch (Exception e) { logger.warn( - () -> new ParameterizedMessage("[{}] Failed to delete some blobs during snapshot delete", snapshotId), + () -> new ParameterizedMessage("{} Failed to delete some blobs during snapshot delete", snapshotIds), e); throw e; } @@ -549,11 +549,11 @@ private void asyncCleanupUnlinkedShardLevelBlobs(SnapshotId snapshotId, Collecti } // updates the shard state metadata for shards of a snapshot that is to be deleted. Also computes the files to be cleaned up. - private void writeUpdatedShardMetaDataAndComputeDeletes(SnapshotId snapshotId, RepositoryData oldRepositoryData, + private void writeUpdatedShardMetaDataAndComputeDeletes(Collection snapshotIds, RepositoryData oldRepositoryData, boolean useUUIDs, ActionListener> onAllShardsCompleted) { final Executor executor = threadPool.executor(ThreadPool.Names.SNAPSHOT); - final List indices = oldRepositoryData.indicesToUpdateAfterRemovingSnapshot(snapshotId); + final List indices = oldRepositoryData.indicesToUpdateAfterRemovingSnapshot(snapshotIds); if (indices.isEmpty()) { onAllShardsCompleted.onResponse(Collections.emptyList()); @@ -567,55 +567,44 @@ private void writeUpdatedShardMetaDataAndComputeDeletes(SnapshotId snapshotId, R for (IndexId indexId : indices) { final Set survivingSnapshots = oldRepositoryData.getSnapshots(indexId).stream() - .filter(id -> id.equals(snapshotId) == false).collect(Collectors.toSet()); + .filter(id -> snapshotIds.contains(id) == false).collect(Collectors.toSet()); executor.execute(ActionRunnable.wrap(deleteIndexMetaDataListener, deleteIdxMetaListener -> { - final IndexMetaData indexMetaData; - try { - indexMetaData = getSnapshotIndexMetaData(snapshotId, indexId); - } catch (Exception ex) { - logger.warn(() -> - new ParameterizedMessage("[{}] [{}] failed to read metadata for index", snapshotId, indexId.getName()), ex); - // Just invoke the listener without any shard generations to count it down, this index will be cleaned up - // by the stale data cleanup in the end. - // TODO: Getting here means repository corruption. We should find a way of dealing with this instead of just ignoring - // it and letting the cleanup deal with it. + final int shardCount = findMaxShardCount(snapshotIds, indexId); + if (shardCount == 0) { deleteIdxMetaListener.onResponse(null); return; } - final int shardCount = indexMetaData.getNumberOfShards(); - assert shardCount > 0 : "index did not have positive shard count, get [" + shardCount + "]"; // Listener for collecting the results of removing the snapshot from each shard's metadata in the current index final ActionListener allShardsListener = new GroupedActionListener<>(deleteIdxMetaListener, shardCount); - final Index index = indexMetaData.getIndex(); - for (int shardId = 0; shardId < indexMetaData.getNumberOfShards(); shardId++) { - final ShardId shard = new ShardId(index, shardId); + for (int shardId = 0; shardId < shardCount; shardId++) { + final int finalShardId = shardId; executor.execute(new AbstractRunnable() { @Override protected void doRun() throws Exception { - final BlobContainer shardContainer = shardContainer(indexId, shard); - final Set blobs = getShardBlobs(shard, shardContainer); + final BlobContainer shardContainer = shardContainer(indexId, finalShardId); + final Set blobs = getShardBlobs(shardContainer); final BlobStoreIndexShardSnapshots blobStoreIndexShardSnapshots; final String newGen; if (useUUIDs) { newGen = UUIDs.randomBase64UUID(); blobStoreIndexShardSnapshots = buildBlobStoreIndexShardSnapshots(blobs, shardContainer, - oldRepositoryData.shardGenerations().getShardGen(indexId, shard.getId())).v1(); + oldRepositoryData.shardGenerations().getShardGen(indexId, finalShardId)).v1(); } else { Tuple tuple = buildBlobStoreIndexShardSnapshots(blobs, shardContainer); newGen = Long.toString(tuple.v2() + 1); blobStoreIndexShardSnapshots = tuple.v1(); } - allShardsListener.onResponse(deleteFromShardSnapshotMeta(survivingSnapshots, indexId, shard, snapshotId, - shardContainer, blobs, blobStoreIndexShardSnapshots, newGen)); + allShardsListener.onResponse(deleteFromShardSnapshotMeta(survivingSnapshots, indexId, finalShardId, + snapshotIds, shardContainer, blobs, blobStoreIndexShardSnapshots, newGen)); } @Override public void onFailure(Exception ex) { logger.warn( () -> new ParameterizedMessage("[{}] failed to delete shard data for shard [{}][{}]", - snapshotId, indexId.getName(), shard.id()), ex); + snapshotIds, indexId.getName(), finalShardId), ex); // Just passing null here to count down the listener instead of failing it, the stale data left behind // here will be retried in the next delete or repository cleanup allShardsListener.onResponse(null); @@ -626,7 +615,22 @@ public void onFailure(Exception ex) { } } - private List resolveFilesToDelete(SnapshotId snapshotId, Collection deleteResults) { + private int findMaxShardCount(Collection snapshotIds, IndexId indexId) { + int maxShardCount = 0; + // TODO: parallelize this + for (SnapshotId snapshotId : snapshotIds) { + try { + maxShardCount = Math.max(maxShardCount, getSnapshotIndexMetaData(snapshotId, indexId).getNumberOfShards()); + } catch (Exception e) { + // TODO: Getting here means repository corruption. We should find a way of dealing with this instead of just ignoring + // it and letting the cleanup deal with it. + } + } + return maxShardCount; + } + + private List resolveFilesToDelete(Collection snapshotIds, + Collection deleteResults) { final String basePath = basePath().buildAsString(); final int basePathLen = basePath.length(); return Stream.concat( @@ -635,8 +639,9 @@ private List resolveFilesToDelete(SnapshotId snapshotId, Collection shardPath + blob); }), - deleteResults.stream().map(shardResult -> shardResult.indexId).distinct().map(indexId -> - indexContainer(indexId).path().buildAsString() + globalMetaDataFormat.blobName(snapshotId.getUUID())) + deleteResults.stream().map(shardResult -> shardResult.indexId).distinct().flatMap(indexId -> + snapshotIds.stream().map(snapshotId -> // TODO: Add filter via RepositoryData + indexContainer(indexId).path().buildAsString() + globalMetaDataFormat.blobName(snapshotId.getUUID()))) ).map(absolutePath -> { assert absolutePath.startsWith(basePath); return absolutePath.substring(basePathLen); @@ -1600,9 +1605,10 @@ public String toString() { * Delete snapshot from shard level metadata. */ private ShardSnapshotMetaDeleteResult deleteFromShardSnapshotMeta(Set survivingSnapshots, IndexId indexId, - ShardId snapshotShardId, SnapshotId snapshotId, + int snapshotShardId, Collection snapshotIds, BlobContainer shardContainer, Set blobs, - BlobStoreIndexShardSnapshots snapshots, String indexGeneration) { + BlobStoreIndexShardSnapshots snapshots, + String indexGeneration) throws IOException { // Build a list of snapshots that should be preserved List newSnapshotsList = new ArrayList<>(); final Set survivingSnapshotNames = survivingSnapshots.stream().map(SnapshotId::getName).collect(Collectors.toSet()); @@ -1613,17 +1619,17 @@ private ShardSnapshotMetaDeleteResult deleteFromShardSnapshotMeta(Set survivingSnapshotUUIDs = survivingSnapshots.stream().map(SnapshotId::getUUID).collect(Collectors.toSet()); - return new ShardSnapshotMetaDeleteResult(indexId, snapshotShardId.id(), indexGeneration, + return new ShardSnapshotMetaDeleteResult(indexId, snapshotShardId, indexGeneration, unusedBlobs(blobs, survivingSnapshotUUIDs, updatedSnapshots)); } } catch (IOException e) { - throw new IndexShardSnapshotFailedException(snapshotShardId, - "Failed to finalize snapshot deletion [" + snapshotId + "] with shard index [" + throw new IOException( + "Failed to finalize snapshot deletion " + snapshotIds + " with shard index [" + indexShardSnapshotsFormat.blobName(indexGeneration) + "]", e); } } @@ -1635,12 +1641,12 @@ private void writeShardIndexBlob(BlobContainer shardContainer, String indexGener indexShardSnapshotsFormat.writeAtomic(updatedSnapshots, shardContainer, indexGeneration); } - private static Set getShardBlobs(final ShardId snapshotShardId, final BlobContainer shardContainer) { + private static Set getShardBlobs(BlobContainer shardContainer) throws IOException { final Set blobs; try { blobs = shardContainer.listBlobs().keySet(); } catch (IOException e) { - throw new IndexShardSnapshotException(snapshotShardId, "Failed to list content of shard directory", e); + throw new IOException("Failed to list content of shard directory", e); } return blobs; } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestDeleteSnapshotAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestDeleteSnapshotAction.java index 81a3ddd31c59a..f2dd972c5cb58 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestDeleteSnapshotAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestDeleteSnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.RestController; @@ -47,8 +47,8 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - DeleteSnapshotRequest deleteSnapshotRequest = deleteSnapshotRequest(request.param("repository"), request.param("snapshot")); - deleteSnapshotRequest.masterNodeTimeout(request.paramAsTime("master_timeout", deleteSnapshotRequest.masterNodeTimeout())); - return channel -> client.admin().cluster().deleteSnapshot(deleteSnapshotRequest, new RestToXContentListener<>(channel)); + DeleteSnapshotsRequest deleteSnapshotsRequest = deleteSnapshotRequest(request.param("repository"), request.param("snapshot")); + deleteSnapshotsRequest.masterNodeTimeout(request.paramAsTime("master_timeout", deleteSnapshotsRequest.masterNodeTimeout())); + return channel -> client.admin().cluster().deleteSnapshots(deleteSnapshotsRequest, new RestToXContentListener<>(channel)); } } diff --git a/server/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java b/server/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java index 91a40fea09ffb..b90a0319646d1 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java +++ b/server/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java @@ -23,12 +23,17 @@ import org.elasticsearch.rest.RestStatus; import java.io.IOException; +import java.util.Collection; /** * Thrown when a user tries to multiple conflicting snapshot/restore operations at the same time. */ public class ConcurrentSnapshotExecutionException extends SnapshotException { + public ConcurrentSnapshotExecutionException(String repositoryName, Collection snapshots, String msg) { + super(repositoryName, snapshots.size() == 1 ? snapshots.iterator().next().getName() : snapshots.toString(), msg); + } + public ConcurrentSnapshotExecutionException(final String repositoryName, final String snapshotName, final String msg) { super(repositoryName, snapshotName, msg); } diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java index a48f893dfd408..72c9ff1b48e56 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java @@ -80,6 +80,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -784,8 +785,8 @@ private void finalizeSnapshotDeletionFromPreviousMaster(ClusterState state) { if (deletionsInProgress != null && deletionsInProgress.hasDeletionsInProgress()) { assert deletionsInProgress.getEntries().size() == 1 : "only one in-progress deletion allowed per cluster"; SnapshotDeletionsInProgress.Entry entry = deletionsInProgress.getEntries().get(0); - deleteSnapshotFromRepository(entry.getSnapshot(), null, entry.repositoryStateId(), - state.nodes().getMinNodeVersion()); + deleteSnapshotsFromRepository(Collections.singletonList(entry.getSnapshot().getSnapshotId()), null, entry.repositoryStateId(), + entry.repository(), state.nodes().getMinNodeVersion()); } } @@ -844,7 +845,7 @@ public ClusterState execute(ClusterState currentState) { entries.add(updatedSnapshot); // Clean up the snapshot that failed to start from the old master - deleteSnapshot(snapshot.snapshot(), new ActionListener<>() { + deleteSnapshots(Collections.singletonList(snapshot.snapshot().getSnapshotId()), new ActionListener<>() { @Override public void onResponse(Void aVoid) { logger.debug("cleaned up abandoned snapshot {} in INIT state", snapshot.snapshot()); @@ -854,7 +855,7 @@ public void onResponse(Void aVoid) { public void onFailure(Exception e) { logger.warn("failed to clean up abandoned snapshot {} in INIT state", snapshot.snapshot()); } - }, updatedSnapshot.repositoryStateId(), false); + }, snapshot.repository(), updatedSnapshot.repositoryStateId(), false); } assert updatedSnapshot.shards().size() == snapshot.shards().size() : "Shard count changed during snapshot status update from [" + snapshot + "] to [" + updatedSnapshot + "]"; @@ -1153,33 +1154,34 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS * If the snapshot is still running cancels the snapshot first and then deletes it from the repository. * * @param repositoryName repositoryName - * @param snapshotName snapshotName + * @param snapshotNames snapshot names * @param listener listener */ - public void deleteSnapshot(final String repositoryName, final String snapshotName, final ActionListener listener, - final boolean immediatePriority) { + public void deleteSnapshots(final String repositoryName, final String[] snapshotNames, final ActionListener listener, + final boolean immediatePriority) { // First, look for the snapshot in the repository final Repository repository = repositoriesService.repository(repositoryName); + final Set snNames = Set.of(snapshotNames); repository.getRepositoryData(ActionListener.wrap(repositoryData -> { - Optional matchedEntry = repositoryData.getSnapshotIds() + Collection matchedEntry = new ArrayList<>(repositoryData.getSnapshotIds() .stream() - .filter(s -> s.getName().equals(snapshotName)) - .findFirst(); + .filter(s -> snNames.contains(s.getName())) + .collect(Collectors.toList())); // if nothing found by the same name, then look in the cluster state for current in progress snapshots long repoGenId = repositoryData.getGenId(); - if (matchedEntry.isPresent() == false) { + Optional matchedInProgress = currentSnapshots(repositoryName, Collections.emptyList()).stream() - .filter(s -> s.snapshot().getSnapshotId().getName().equals(snapshotName)).findFirst(); + .filter(s -> snNames.contains(s.snapshot().getSnapshotId().getName())).findAny(); if (matchedInProgress.isPresent()) { - matchedEntry = matchedInProgress.map(s -> s.snapshot().getSnapshotId()); + matchedEntry.add(matchedInProgress.map(s -> s.snapshot().getSnapshotId()).get()); // Derive repository generation if a snapshot is in progress because it will increment the generation when it finishes repoGenId = matchedInProgress.get().repositoryStateId() + 1L; } + + if (matchedEntry.isEmpty()) { + throw new SnapshotMissingException(repositoryName, snNames.toString()); } - if (matchedEntry.isPresent() == false) { - throw new SnapshotMissingException(repositoryName, snapshotName); - } - deleteSnapshot(new Snapshot(repositoryName, matchedEntry.get()), listener, repoGenId, immediatePriority); + deleteSnapshots(matchedEntry, listener, repositoryName, repoGenId, immediatePriority); }, listener::onFailure)); } @@ -1188,28 +1190,28 @@ public void deleteSnapshot(final String repositoryName, final String snapshotNam *

* If the snapshot is still running cancels the snapshot first and then deletes it from the repository. * - * @param snapshot snapshot + * @param snapshots snapshots * @param listener listener * @param repositoryStateId the unique id for the state of the repository */ - private void deleteSnapshot(final Snapshot snapshot, final ActionListener listener, final long repositoryStateId, - final boolean immediatePriority) { - logger.info("deleting snapshot [{}]", snapshot); + private void deleteSnapshots(Collection snapshots, ActionListener listener, String repository, + long repositoryStateId, boolean immediatePriority) { + logger.info("deleting snapshots {} from [{}]", snapshots, repository); Priority priority = immediatePriority ? Priority.IMMEDIATE : Priority.NORMAL; clusterService.submitStateUpdateTask("delete snapshot", new ClusterStateUpdateTask(priority) { - boolean waitForSnapshot = false; + Snapshot waitForSnapshot; @Override public ClusterState execute(ClusterState currentState) { SnapshotDeletionsInProgress deletionsInProgress = currentState.custom(SnapshotDeletionsInProgress.TYPE); if (deletionsInProgress != null && deletionsInProgress.hasDeletionsInProgress()) { - throw new ConcurrentSnapshotExecutionException(snapshot, + throw new ConcurrentSnapshotExecutionException(repository, snapshots, "cannot delete - another snapshot is currently being deleted in [" + deletionsInProgress + "]"); } final RepositoryCleanupInProgress repositoryCleanupInProgress = currentState.custom(RepositoryCleanupInProgress.TYPE); if (repositoryCleanupInProgress != null && repositoryCleanupInProgress.hasCleanupInProgress()) { - throw new ConcurrentSnapshotExecutionException(snapshot.getRepository(), snapshot.getSnapshotId().getName(), + throw new ConcurrentSnapshotExecutionException(repository, snapshots, "cannot delete snapshot while a repository cleanup is in-progress in [" + repositoryCleanupInProgress + "]"); } RestoreInProgress restoreInProgress = currentState.custom(RestoreInProgress.TYPE); @@ -1218,34 +1220,50 @@ public ClusterState execute(ClusterState currentState) { // otherwise we could end up deleting a snapshot that is being restored // and the files the restore depends on would all be gone if (restoreInProgress.isEmpty() == false) { - throw new ConcurrentSnapshotExecutionException(snapshot, + throw new ConcurrentSnapshotExecutionException(repository, snapshots, "cannot delete snapshot during a restore in progress in [" + restoreInProgress + "]"); } } ClusterState.Builder clusterStateBuilder = ClusterState.builder(currentState); - SnapshotsInProgress snapshots = currentState.custom(SnapshotsInProgress.TYPE); - SnapshotsInProgress.Entry snapshotEntry = snapshots != null ? snapshots.snapshot(snapshot) : null; + SnapshotsInProgress snapshotsInProgress = currentState.custom(SnapshotsInProgress.TYPE); + final SnapshotsInProgress.Entry snapshotEntry; + if (snapshotsInProgress != null) { + assert snapshotsInProgress.entries().size() <= 1 : + "Assumed one or no snapshot ongoing but saw [" + snapshotsInProgress + "]"; + snapshotEntry = snapshots.stream() + .filter(sn -> snapshotsInProgress.snapshot(new Snapshot(repository, sn)) != null) + .findFirst().map(sn -> snapshotsInProgress.snapshot(new Snapshot(repository, sn))) + .orElse(null); + } else { + snapshotEntry = null; + } if (snapshotEntry == null) { // This snapshot is not running - delete - if (snapshots != null && !snapshots.entries().isEmpty()) { + if (snapshotsInProgress != null && !snapshotsInProgress.entries().isEmpty()) { // However other snapshots are running - cannot continue - throw new ConcurrentSnapshotExecutionException(snapshot, "another snapshot is currently running cannot delete"); + throw new ConcurrentSnapshotExecutionException(repository, snapshots, + "another snapshot is currently running cannot delete"); } - // add the snapshot deletion to the cluster state - SnapshotDeletionsInProgress.Entry entry = new SnapshotDeletionsInProgress.Entry( - snapshot, - threadPool.absoluteTimeInMillis(), - repositoryStateId - ); - if (deletionsInProgress != null) { - deletionsInProgress = deletionsInProgress.withAddedEntry(entry); - } else { - deletionsInProgress = SnapshotDeletionsInProgress.newInstance(entry); + for (SnapshotId snapshot : snapshots) { + // add the snapshot deletion to the cluster state + SnapshotDeletionsInProgress.Entry entry = new SnapshotDeletionsInProgress.Entry( + new Snapshot(repository, snapshot), + threadPool.absoluteTimeInMillis(), + repositoryStateId + ); + if (deletionsInProgress != null) { + if (deletionsInProgress.getEntries().stream().noneMatch( + e -> e.getSnapshot().getSnapshotId().equals(snapshot))) { + deletionsInProgress = deletionsInProgress.withAddedEntry(entry); + } + } else { + deletionsInProgress = SnapshotDeletionsInProgress.newInstance(entry); + } } clusterStateBuilder.putCustom(SnapshotDeletionsInProgress.TYPE, deletionsInProgress); } else { // This snapshot is currently running - stopping shards first - waitForSnapshot = true; + waitForSnapshot = snapshotEntry.snapshot(); final ImmutableOpenMap shards; @@ -1305,16 +1323,17 @@ public void onFailure(String source, Exception e) { @Override public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { - if (waitForSnapshot) { + if (waitForSnapshot != null) { logger.trace("adding snapshot completion listener to wait for deleted snapshot to finish"); - addListener(snapshot, ActionListener.wrap( + addListener(waitForSnapshot, ActionListener.wrap( snapshotInfo -> { logger.debug("deleted snapshot completed - deleting files"); threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(() -> { try { - deleteSnapshot(snapshot.getRepository(), snapshot.getSnapshotId().getName(), listener, true); + deleteSnapshots(waitForSnapshot.getRepository(), + snapshots.stream().map(SnapshotId::getName).toArray(String[]::new), listener, true); } catch (Exception ex) { - logger.warn(() -> new ParameterizedMessage("[{}] failed to delete snapshot", snapshot), ex); + logger.warn(() -> new ParameterizedMessage("[{}] failed to delete snapshot", waitForSnapshot), ex); } } ); @@ -1323,12 +1342,14 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS logger.warn("deleted snapshot failed - deleting files", e); threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(() -> { try { - deleteSnapshot(snapshot.getRepository(), snapshot.getSnapshotId().getName(), listener, true); + deleteSnapshots( + waitForSnapshot.getRepository(), + snapshots.stream().map(SnapshotId::getName).toArray(String[]::new), listener, true); } catch (SnapshotMissingException smex) { logger.info(() -> new ParameterizedMessage( "Tried deleting in-progress snapshot [{}], but it could not be found after failing to abort.", smex.getSnapshotName()), e); - listener.onFailure(new SnapshotException(snapshot, + listener.onFailure(new SnapshotException(waitForSnapshot, "Tried deleting in-progress snapshot [" + smex.getSnapshotName() + "], but it " + "could not be found after failing to abort.", smex)); } @@ -1337,7 +1358,8 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS )); } else { logger.debug("deleted snapshot is not running - deleting files"); - deleteSnapshotFromRepository(snapshot, listener, repositoryStateId, newState.nodes().getMinNodeVersion()); + deleteSnapshotsFromRepository( + snapshots, listener, repositoryStateId, repository, newState.nodes().getMinNodeVersion()); } } }); @@ -1381,20 +1403,21 @@ public static boolean isRepositoryInUse(ClusterState clusterState, String reposi /** * Deletes snapshot from repository * - * @param snapshot snapshot - * @param listener listener + * @param snapshots snapshot + * @param listener listener * @param repositoryStateId the unique id representing the state of the repository at the time the deletion began * @param version minimum ES version the repository should be readable by */ - private void deleteSnapshotFromRepository(Snapshot snapshot, @Nullable ActionListener listener, long repositoryStateId, - Version version) { + private void deleteSnapshotsFromRepository(Collection snapshots, @Nullable ActionListener listener, + long repositoryStateId, String repositoryName, Version version) { threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(ActionRunnable.wrap(listener, l -> { - Repository repository = repositoriesService.repository(snapshot.getRepository()); - repository.deleteSnapshot(snapshot.getSnapshotId(), repositoryStateId, version.onOrAfter(SHARD_GEN_IN_REPO_DATA_VERSION), + Repository repository = repositoriesService.repository(repositoryName); + repository.deleteSnapshot(snapshots, repositoryStateId, + version.onOrAfter(SHARD_GEN_IN_REPO_DATA_VERSION), ActionListener.wrap(v -> { - logger.info("snapshot [{}] deleted", snapshot); - removeSnapshotDeletionFromClusterState(snapshot, null, l); - }, ex -> removeSnapshotDeletionFromClusterState(snapshot, ex, l) + logger.info("snapshots {} deleted", snapshots); + removeSnapshotDeletionFromClusterState(snapshots, null, l); + }, ex -> removeSnapshotDeletionFromClusterState(snapshots, ex, l) )); })); } @@ -1402,8 +1425,8 @@ private void deleteSnapshotFromRepository(Snapshot snapshot, @Nullable ActionLis /** * Removes the snapshot deletion from {@link SnapshotDeletionsInProgress} in the cluster state. */ - private void removeSnapshotDeletionFromClusterState(final Snapshot snapshot, @Nullable final Exception failure, - @Nullable final ActionListener listener) { + private void removeSnapshotDeletionFromClusterState(Collection snapshots, @Nullable Exception failure, + @Nullable ActionListener listener) { clusterService.submitStateUpdateTask("remove snapshot deletion metadata", new ClusterStateUpdateTask() { @Override public ClusterState execute(ClusterState currentState) { @@ -1411,10 +1434,11 @@ public ClusterState execute(ClusterState currentState) { if (deletions != null) { boolean changed = false; if (deletions.hasDeletionsInProgress()) { - assert deletions.getEntries().size() == 1 : "should have exactly one deletion in progress"; - SnapshotDeletionsInProgress.Entry entry = deletions.getEntries().get(0); - deletions = deletions.withRemovedEntry(entry); - changed = true; + for (SnapshotDeletionsInProgress.Entry entry : deletions.getEntries().stream() + .filter(e -> snapshots.contains(e.getSnapshot().getSnapshotId())).collect(Collectors.toList())) { + changed = true; + deletions = deletions.withRemovedEntry(entry); + } } if (changed) { return ClusterState.builder(currentState).putCustom(SnapshotDeletionsInProgress.TYPE, deletions).build(); @@ -1425,7 +1449,7 @@ public ClusterState execute(ClusterState currentState) { @Override public void onFailure(String source, Exception e) { - logger.warn(() -> new ParameterizedMessage("[{}] failed to remove snapshot deletion metadata", snapshot), e); + logger.warn(() -> new ParameterizedMessage("{} failed to remove snapshot deletion metadata", snapshots), e); if (listener != null) { listener.onFailure(e); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/SnapshotBlocksIT.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/SnapshotBlocksIT.java index 05ff6567aa58d..797826203057e 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/SnapshotBlocksIT.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/SnapshotBlocksIT.java @@ -124,7 +124,8 @@ public void testDeleteSnapshotWithBlocks() { logger.info("--> deleting a snapshot is allowed when the cluster is read only"); try { setClusterReadOnly(true); - assertTrue(client().admin().cluster().prepareDeleteSnapshot(REPOSITORY_NAME, SNAPSHOT_NAME).get().isAcknowledged()); + assertTrue( + client().admin().cluster().prepareDeleteSnapshots(REPOSITORY_NAME, new String[]{SNAPSHOT_NAME}).get().isAcknowledged()); } finally { setClusterReadOnly(false); } diff --git a/server/src/test/java/org/elasticsearch/repositories/RepositoriesServiceTests.java b/server/src/test/java/org/elasticsearch/repositories/RepositoriesServiceTests.java index 2daa4afe3e149..1402e5ec19207 100644 --- a/server/src/test/java/org/elasticsearch/repositories/RepositoriesServiceTests.java +++ b/server/src/test/java/org/elasticsearch/repositories/RepositoriesServiceTests.java @@ -47,6 +47,7 @@ import org.elasticsearch.transport.TransportService; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -163,7 +164,8 @@ public void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations indices, lo } @Override - public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener) { + public void deleteSnapshot(Collection snapshotId, long repositoryStateId, boolean writeShardGens, + ActionListener listener) { listener.onResponse(null); } diff --git a/server/src/test/java/org/elasticsearch/repositories/RepositoryDataTests.java b/server/src/test/java/org/elasticsearch/repositories/RepositoryDataTests.java index 12adfb49120a7..9a63aa610a02b 100644 --- a/server/src/test/java/org/elasticsearch/repositories/RepositoryDataTests.java +++ b/server/src/test/java/org/elasticsearch/repositories/RepositoryDataTests.java @@ -66,7 +66,8 @@ public void testIndicesToUpdateAfterRemovingSnapshot() { final Set snapshotIds = repositoryData.getSnapshots(index); return snapshotIds.contains(randomSnapshot) && snapshotIds.size() > 1; }).toArray(IndexId[]::new); - assertThat(repositoryData.indicesToUpdateAfterRemovingSnapshot(randomSnapshot), containsInAnyOrder(indicesToUpdate)); + assertThat(repositoryData.indicesToUpdateAfterRemovingSnapshot( + Collections.singleton(randomSnapshot)), containsInAnyOrder(indicesToUpdate)); } public void testXContent() throws IOException { @@ -148,7 +149,7 @@ public void testRemoveSnapshot() { List snapshotIds = new ArrayList<>(repositoryData.getSnapshotIds()); assertThat(snapshotIds.size(), greaterThan(0)); SnapshotId removedSnapshotId = snapshotIds.remove(randomIntBetween(0, snapshotIds.size() - 1)); - RepositoryData newRepositoryData = repositoryData.removeSnapshot(removedSnapshotId, ShardGenerations.EMPTY); + RepositoryData newRepositoryData = repositoryData.removeSnapshot(Collections.singleton(removedSnapshotId), ShardGenerations.EMPTY); // make sure the repository data's indices no longer contain the removed snapshot for (final IndexId indexId : newRepositoryData.getIndices().values()) { assertFalse(newRepositoryData.getSnapshots(indexId).contains(removedSnapshotId)); diff --git a/server/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java b/server/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java index 13102182cd7b0..9c2ee15932b8b 100644 --- a/server/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java +++ b/server/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java @@ -182,7 +182,7 @@ public void testIndexGenerationalFiles() throws Exception { // removing a snapshot and writing to a new index generational file repositoryData = ESBlobStoreRepositoryIntegTestCase.getRepositoryData(repository).removeSnapshot( - repositoryData.getSnapshotIds().iterator().next(), ShardGenerations.EMPTY); + Collections.singleton(repositoryData.getSnapshotIds().iterator().next()), ShardGenerations.EMPTY); writeIndexGen(repository, repositoryData, repositoryData.getGenId()); assertEquals(ESBlobStoreRepositoryIntegTestCase.getRepositoryData(repository), repositoryData); assertThat(repository.latestIndexBlobId(), equalTo(expectedGeneration + 2L)); diff --git a/server/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java b/server/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java index ab5aebe853b9e..86f4bb737d7ea 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java +++ b/server/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java @@ -474,7 +474,7 @@ public void testSnapshotWithStuckNode() throws Exception { logger.info("--> execution was blocked on node [{}], aborting snapshot", blockedNode); ActionFuture deleteSnapshotResponseFuture = internalCluster().client(nodes.get(0)) - .admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").execute(); + .admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap"}).execute(); // Make sure that abort makes some progress Thread.sleep(100); unblockNode("test-repo", blockedNode); @@ -1156,7 +1156,7 @@ public void testSnapshotTotalAndIncrementalSizes() throws IOException { // drop 1st one to avoid miscalculation as snapshot reuses some files of prev snapshot assertTrue(client.admin().cluster() - .prepareDeleteSnapshot(repositoryName, snapshot0) + .prepareDeleteSnapshots(repositoryName, new String[]{snapshot0}) .get().isAcknowledged()); response = client.admin().cluster().prepareSnapshotStatus(repositoryName) diff --git a/server/src/test/java/org/elasticsearch/snapshots/MetadataLoadingDuringSnapshotRestoreIT.java b/server/src/test/java/org/elasticsearch/snapshots/MetadataLoadingDuringSnapshotRestoreIT.java index 3d65782d4824d..3bb6f24f46abb 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/MetadataLoadingDuringSnapshotRestoreIT.java +++ b/server/src/test/java/org/elasticsearch/snapshots/MetadataLoadingDuringSnapshotRestoreIT.java @@ -139,7 +139,7 @@ public void testWhenMetadataAreLoaded() throws Exception { assertIndexMetadataLoads("snap", "others", 3); // Deleting a snapshot does not load the global metadata state but loads each index metadata - assertAcked(client().admin().cluster().prepareDeleteSnapshot("repository", "snap").get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots("repository", new String[]{"snap"}).get()); assertGlobalMetadataLoads("snap", 1); assertIndexMetadataLoads("snap", "docs", 4); assertIndexMetadataLoads("snap", "others", 3); diff --git a/server/src/test/java/org/elasticsearch/snapshots/MinThreadsSnapshotRestoreIT.java b/server/src/test/java/org/elasticsearch/snapshots/MinThreadsSnapshotRestoreIT.java index e93bb00d3d09e..9ef7800303442 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/MinThreadsSnapshotRestoreIT.java +++ b/server/src/test/java/org/elasticsearch/snapshots/MinThreadsSnapshotRestoreIT.java @@ -84,13 +84,13 @@ public void testConcurrentSnapshotDeletionsNotAllowed() throws Exception { ((MockRepository)internalCluster().getInstance(RepositoriesService.class, blockedNode).repository(repo)).blockOnDataFiles(true); logger.info("--> start deletion of first snapshot"); ActionFuture future = - client().admin().cluster().prepareDeleteSnapshot(repo, snapshot2).execute(); + client().admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot2}).execute(); logger.info("--> waiting for block to kick in on node [{}]", blockedNode); waitForBlock(blockedNode, repo, TimeValue.timeValueSeconds(10)); logger.info("--> try deleting the second snapshot, should fail because the first deletion is in progress"); try { - client().admin().cluster().prepareDeleteSnapshot(repo, snapshot1).get(); + client().admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot1}).get(); fail("should not be able to delete snapshots concurrently"); } catch (ConcurrentSnapshotExecutionException e) { assertThat(e.getMessage(), containsString("cannot delete - another snapshot is currently being deleted")); @@ -103,7 +103,7 @@ public void testConcurrentSnapshotDeletionsNotAllowed() throws Exception { assertAcked(future.actionGet()); logger.info("--> delete second snapshot, which should now work"); - client().admin().cluster().prepareDeleteSnapshot(repo, snapshot1).get(); + client().admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot1}).get(); assertTrue(client().admin().cluster().prepareGetSnapshots(repo).setSnapshots("_all").get().getSnapshots(repo).isEmpty()); } @@ -129,7 +129,8 @@ public void testSnapshottingWithInProgressDeletionNotAllowed() throws Exception String blockedNode = internalCluster().getMasterName(); ((MockRepository)internalCluster().getInstance(RepositoriesService.class, blockedNode).repository(repo)).blockOnDataFiles(true); logger.info("--> start deletion of snapshot"); - ActionFuture future = client().admin().cluster().prepareDeleteSnapshot(repo, snapshot1).execute(); + ActionFuture future = + client().admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot1}).execute(); logger.info("--> waiting for block to kick in on node [{}]", blockedNode); waitForBlock(blockedNode, repo, TimeValue.timeValueSeconds(10)); @@ -184,7 +185,8 @@ public void testRestoreWithInProgressDeletionsNotAllowed() throws Exception { String blockedNode = internalCluster().getMasterName(); ((MockRepository)internalCluster().getInstance(RepositoriesService.class, blockedNode).repository(repo)).blockOnDataFiles(true); logger.info("--> start deletion of snapshot"); - ActionFuture future = client().admin().cluster().prepareDeleteSnapshot(repo, snapshot2).execute(); + ActionFuture future = + client().admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot2}).execute(); logger.info("--> waiting for block to kick in on node [{}]", blockedNode); waitForBlock(blockedNode, repo, TimeValue.timeValueSeconds(10)); diff --git a/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java b/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java index e31ad37296093..6b6567792a128 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java @@ -1300,8 +1300,14 @@ public void testDeleteSnapshot() throws Exception { int numberOfFilesBeforeDeletion = numberOfFiles(repo); logger.info("--> delete all snapshots except the first one and last one"); - for (int i = 1; i < numberOfSnapshots - 1; i++) { - client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap-" + i).get(); + + if (randomBoolean()) { + for (int i = 1; i < numberOfSnapshots - 1; i++) { + client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap-" + i}).get(); + } + } else { + client.admin().cluster().prepareDeleteSnapshots( + "test-repo", IntStream.range(1, numberOfSnapshots - 1).mapToObj(i -> "test-snap-" + i).toArray(String[]::new)).get(); } int numberOfFilesAfterDeletion = numberOfFiles(repo); @@ -1320,7 +1326,7 @@ public void testDeleteSnapshot() throws Exception { assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().getTotalHits().value, equalTo(10L * numberOfSnapshots)); logger.info("--> delete the last snapshot"); - client.admin().cluster().prepareDeleteSnapshot("test-repo", lastSnapshot).get(); + client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{lastSnapshot}).get(); logger.info("--> make sure that number of files is back to what it was when the first snapshot was made"); assertFileCount(repo, numberOfFiles[0]); } @@ -1465,7 +1471,7 @@ public void testDeleteSnapshotWithMissingIndexAndShardMetadata() throws Exceptio } logger.info("--> delete snapshot"); - client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap-1").get(); + client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap-1"}).get(); logger.info("--> make sure snapshot doesn't exist"); @@ -1506,7 +1512,7 @@ public void testDeleteSnapshotWithMissingMetadata() throws Exception { Files.delete(metadata); logger.info("--> delete snapshot"); - client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap-1").get(); + client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap-1"}).get(); logger.info("--> make sure snapshot doesn't exist"); expectThrows(SnapshotMissingException.class, () -> client.admin().cluster().prepareGetSnapshots("test-repo") @@ -1543,7 +1549,7 @@ public void testDeleteSnapshotWithCorruptedSnapshotFile() throws Exception { outChan.truncate(randomInt(10)); } logger.info("--> delete snapshot"); - client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap-1").get(); + client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap-1"}).get(); logger.info("--> make sure snapshot doesn't exist"); expectThrows(SnapshotMissingException.class, @@ -1604,7 +1610,7 @@ public void testDeleteSnapshotWithCorruptedGlobalState() throws Exception { assertThat(snapshotStatusResponse.getSnapshots(), hasSize(1)); assertThat(snapshotStatusResponse.getSnapshots().get(0).getSnapshot().getSnapshotId().getName(), equalTo("test-snap")); - assertAcked(client().admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots("test-repo", new String[]{"test-snap"}).get()); expectThrows(SnapshotMissingException.class, () -> client().admin().cluster() .prepareGetSnapshots("test-repo").addSnapshots("test-snap").get().getSnapshots("test-repo")); assertThrows(client().admin().cluster().prepareSnapshotStatus("test-repo").addSnapshots("test-snap"), @@ -2018,8 +2024,8 @@ public void testReadonlyRepository() throws Exception { assertThat(getSnapshotsResponse.getSnapshots("readonly-repo").size(), equalTo(1)); logger.info("--> try deleting snapshot"); - assertThrows(client.admin().cluster().prepareDeleteSnapshot("readonly-repo", "test-snap"), RepositoryException.class, - "cannot delete snapshot from a readonly repository"); + assertThrows(client.admin().cluster().prepareDeleteSnapshots("readonly-repo", new String[]{"test-snap"}), + RepositoryException.class, "cannot delete snapshot from a readonly repository"); logger.info("--> try making another snapshot"); assertThrows(client.admin().cluster().prepareCreateSnapshot("readonly-repo", "test-snap-2") @@ -2713,14 +2719,14 @@ public void testDeleteSnapshotWhileRestoringFails() throws Exception { logger.info("--> try deleting the snapshot while the restore is in progress (should throw an error)"); ConcurrentSnapshotExecutionException e = expectThrows(ConcurrentSnapshotExecutionException.class, () -> - client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotName).get()); + client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{snapshotName}).get()); assertEquals(repoName, e.getRepositoryName()); assertEquals(snapshotName, e.getSnapshotName()); assertThat(e.getMessage(), containsString("cannot delete snapshot during a restore")); logger.info("-- try deleting another snapshot while the restore is in progress (should throw an error)"); e = expectThrows(ConcurrentSnapshotExecutionException.class, () -> - client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotName2).get()); + client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{snapshotName2}).get()); assertEquals(repoName, e.getRepositoryName()); assertEquals(snapshotName2, e.getSnapshotName()); assertThat(e.getMessage(), containsString("cannot delete snapshot during a restore")); @@ -2759,7 +2765,7 @@ public void testSnapshotName() throws Exception { () -> client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("_foo") .get().getSnapshots("test-repo")); expectThrows(SnapshotMissingException.class, - () -> client.admin().cluster().prepareDeleteSnapshot("test-repo", "_foo").get()); + () -> client.admin().cluster().prepareDeleteSnapshots("test-repo", new String[] {"_foo"}).get()); expectThrows(SnapshotMissingException.class, () -> client.admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("_foo").get()); } @@ -2958,7 +2964,8 @@ public void testRestoreSnapshotWithCorruptedIndexMetadata() throws Exception { } } - assertAcked(client().admin().cluster().prepareDeleteSnapshot("test-repo", snapshotInfo.snapshotId().getName()).get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots("test-repo", + new String[]{snapshotInfo.snapshotId().getName()}).get()); } /** @@ -3103,7 +3110,7 @@ public void testCannotCreateSnapshotsWithSameName() throws Exception { } logger.info("--> delete the first snapshot"); - client.admin().cluster().prepareDeleteSnapshot(repositoryName, snapshotName).get(); + client.admin().cluster().prepareDeleteSnapshots(repositoryName, new String[]{snapshotName}).get(); logger.info("--> try creating a snapshot with the same name, now it should work because the first one was deleted"); createSnapshotResponse = client.admin() @@ -3172,7 +3179,7 @@ public void testGetSnapshotsRequest() throws Exception { assertEquals("snap-on-empty-repo", getSnapshotsResponse.getSnapshots("test-repo").get(0).snapshotId().getName()); unblockNode(repositoryName, initialBlockedNode); // unblock node responseListener.actionGet(TimeValue.timeValueMillis(10000L)); // timeout after 10 seconds - client.admin().cluster().prepareDeleteSnapshot(repositoryName, "snap-on-empty-repo").get(); + client.admin().cluster().prepareDeleteSnapshots(repositoryName, new String[]{"snap-on-empty-repo"}).get(); final int numSnapshots = randomIntBetween(1, 3) + 1; logger.info("--> take {} snapshot(s)", numSnapshots - 1); @@ -3839,7 +3846,7 @@ public void testSnapshotDifferentIndicesBySameName() { expectedCount = docCount; } logger.info("--> deleting snapshot [{}]", snapshotToDelete); - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotToDelete).get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{snapshotToDelete}).get()); logger.info("--> restoring snapshot [{}]", snapshotToRestore); client().admin().cluster().prepareRestoreSnapshot(repoName, snapshotToRestore).setIndices(indexName).setRenamePattern(indexName) .setRenameReplacement("restored-3").setWaitForCompletion(true).get(); diff --git a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java index 28d2beb2efcc4..3a8ed1203273f 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java @@ -40,7 +40,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotAction; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.TransportDeleteSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; @@ -408,8 +408,8 @@ public void testConcurrentSnapshotCreateAndDelete() { final StepListener deleteSnapshotStepListener = new StepListener<>(); - continueOrDie(createSnapshotResponseStepListener, createSnapshotResponse -> client().admin().cluster().deleteSnapshot( - new DeleteSnapshotRequest(repoName, snapshotName), deleteSnapshotStepListener)); + continueOrDie(createSnapshotResponseStepListener, createSnapshotResponse -> client().admin().cluster().deleteSnapshots( + new DeleteSnapshotsRequest(repoName, snapshotName), deleteSnapshotStepListener)); final StepListener createAnotherSnapshotResponseStepListener = new StepListener<>(); @@ -460,8 +460,8 @@ public void testConcurrentSnapshotCreateAndDeleteOther() { final StepListener deleteSnapshotStepListener = new StepListener<>(); continueOrDie(createOtherSnapshotResponseStepListener, - createSnapshotResponse -> client().admin().cluster().deleteSnapshot( - new DeleteSnapshotRequest(repoName, snapshotName), ActionListener.wrap( + createSnapshotResponse -> client().admin().cluster().deleteSnapshots( + new DeleteSnapshotsRequest(repoName, snapshotName), ActionListener.wrap( resp -> deleteSnapshotStepListener.onResponse(true), e -> { final Throwable unwrapped = @@ -546,8 +546,8 @@ public void run() { testClusterNodes.randomDataNodeSafe().client.admin().cluster().prepareCreateSnapshot(repoName, snapshotName) .execute(ActionListener.wrap(() -> { createdSnapshot.set(true); - testClusterNodes.randomDataNodeSafe().client.admin().cluster().deleteSnapshot( - new DeleteSnapshotRequest(repoName, snapshotName), noopListener()); + testClusterNodes.randomDataNodeSafe().client.admin().cluster().deleteSnapshots( + new DeleteSnapshotsRequest(repoName, snapshotName), noopListener()); })); scheduleNow( () -> testClusterNodes.randomMasterNodeSafe().client.admin().cluster().reroute( diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/RestoreOnlyRepository.java b/test/framework/src/main/java/org/elasticsearch/index/shard/RestoreOnlyRepository.java index f0118d3c0b699..41a5994367ba0 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/RestoreOnlyRepository.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/RestoreOnlyRepository.java @@ -38,6 +38,7 @@ import org.elasticsearch.snapshots.SnapshotShardFailure; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -101,7 +102,8 @@ public void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations shardGenera } @Override - public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener) { + public void deleteSnapshot(Collection snapshotId, long repositoryStateId, boolean writeShardGens, + ActionListener listener) { listener.onResponse(null); } diff --git a/test/framework/src/main/java/org/elasticsearch/repositories/AbstractThirdPartyRepositoryTestCase.java b/test/framework/src/main/java/org/elasticsearch/repositories/AbstractThirdPartyRepositoryTestCase.java index c6e76ac7174ed..276350adc23f4 100644 --- a/test/framework/src/main/java/org/elasticsearch/repositories/AbstractThirdPartyRepositoryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/repositories/AbstractThirdPartyRepositoryTestCase.java @@ -21,7 +21,7 @@ import org.elasticsearch.action.ActionRunnable; import org.elasticsearch.action.admin.cluster.repositories.cleanup.CleanupRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotsRequest; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.Strings; import org.elasticsearch.common.blobstore.BlobMetaData; @@ -131,7 +131,7 @@ public void testCreateSnapshot() { assertTrue(client().admin() .cluster() - .prepareDeleteSnapshot("test-repo", snapshotName) + .prepareDeleteSnapshots("test-repo", new String[]{snapshotName}) .get() .isAcknowledged()); } @@ -216,7 +216,7 @@ public void testCleanup() throws Exception { createDanglingIndex(repo, genericExec); logger.info("--> deleting a snapshot to trigger repository cleanup"); - client().admin().cluster().deleteSnapshot(new DeleteSnapshotRequest("test-repo", snapshotName)).actionGet(); + client().admin().cluster().deleteSnapshots(new DeleteSnapshotsRequest("test-repo", snapshotName)).actionGet(); assertConsistentRepository(repo, genericExec); diff --git a/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESBlobStoreRepositoryIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESBlobStoreRepositoryIntegTestCase.java index ccf3853ebe64b..68a17c913b29f 100644 --- a/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESBlobStoreRepositoryIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESBlobStoreRepositoryIntegTestCase.java @@ -151,13 +151,13 @@ public void testSnapshotAndRestore() throws Exception { } logger.info("--> delete snapshot {}:{}", repoName, snapshotName); - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotName).get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{snapshotName}).get()); expectThrows(SnapshotMissingException.class, () -> client().admin().cluster().prepareGetSnapshots(repoName).setSnapshots(snapshotName).get().getSnapshots(repoName)); expectThrows(SnapshotMissingException.class, () -> - client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotName).get()); + client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{snapshotName}).get()); expectThrows(SnapshotRestoreException.class, () -> client().admin().cluster().prepareRestoreSnapshot(repoName, snapshotName).setWaitForCompletion(randomBoolean()).get()); @@ -214,7 +214,7 @@ public void testMultipleSnapshotAndRollback() throws Exception { for (int i = 0; i < iterationCount; i++) { logger.info("--> delete snapshot {}:{}", repoName, snapshotName + "-" + i); - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repoName, snapshotName + "-" + i).get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repoName, new String[] {snapshotName + "-" + i}).get()); } } @@ -252,7 +252,7 @@ public void testIndicesDeletedFromRepository() throws Exception { assertEquals(createSnapshotResponse.getSnapshotInfo().successfulShards(), createSnapshotResponse.getSnapshotInfo().totalShards()); logger.info("--> delete a snapshot"); - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repoName, "test-snap").get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{"test-snap"}).get()); logger.info("--> verify index folder deleted from blob container"); RepositoriesService repositoriesSvc = internalCluster().getInstance(RepositoriesService.class, internalCluster().getMasterName()); @@ -272,7 +272,7 @@ public void testIndicesDeletedFromRepository() throws Exception { } } - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repoName, "test-snap2").get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repoName, new String[]{"test-snap2"}).get()); } protected void addRandomDocuments(String name, int numDocs) throws InterruptedException { diff --git a/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESMockAPIBasedRepositoryIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESMockAPIBasedRepositoryIntegTestCase.java index 03f89125ad979..d1bea24c05ea8 100644 --- a/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESMockAPIBasedRepositoryIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/repositories/blobstore/ESMockAPIBasedRepositoryIntegTestCase.java @@ -150,7 +150,7 @@ public final void testSnapshotWithLargeSegmentFiles() throws Exception { ensureGreen(index); assertHitCount(client().prepareSearch(index).setSize(0).setTrackTotalHits(true).get(), nbDocs); - assertAcked(client().admin().cluster().prepareDeleteSnapshot(repository, snapshot).get()); + assertAcked(client().admin().cluster().prepareDeleteSnapshots(repository, new String[]{snapshot}).get()); } protected static String httpServerUrl() { diff --git a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java index 20c098ee7f82a..f82c050356624 100644 --- a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java +++ b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java @@ -82,6 +82,7 @@ import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -255,7 +256,8 @@ public void finalizeSnapshot(SnapshotId snapshotId, ShardGenerations shardGenera } @Override - public void deleteSnapshot(SnapshotId snapshotId, long repositoryStateId, boolean writeShardGens, ActionListener listener) { + public void deleteSnapshot(Collection snapshotId, long repositoryStateId, boolean writeShardGens, + ActionListener listener) { throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE); } diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java index 25de192e76d04..ef58295863582 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java @@ -388,7 +388,7 @@ void deleteSnapshot(String slmPolicy, String repo, SnapshotId snapshot, Snapshot ActionListener listener) { logger.info("[{}] snapshot retention deleting snapshot [{}]", repo, snapshot); CountDownLatch latch = new CountDownLatch(1); - client.admin().cluster().prepareDeleteSnapshot(repo, snapshot.getName()) + client.admin().cluster().prepareDeleteSnapshots(repo, new String[]{snapshot.getName()}) .execute(new LatchedActionListener<>(new ActionListener<>() { @Override public void onResponse(AcknowledgedResponse acknowledgedResponse) { diff --git a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java index 7c7a555a8d5d9..20fd6c6745c4d 100644 --- a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java +++ b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/slm/SLMSnapshotBlockingIntegTests.java @@ -119,7 +119,7 @@ public void testSnapshotInProgress() throws Exception { // Cancel/delete the snapshot try { - client().admin().cluster().prepareDeleteSnapshot(REPO, snapshotName).get(); + client().admin().cluster().prepareDeleteSnapshots(REPO, new String[]{snapshotName}).get(); } catch (SnapshotMissingException e) { // ignore } @@ -223,7 +223,7 @@ public void testRetentionWhileSnapshotInProgress() throws Exception { assertBusy(() -> { try { logger.info("--> cancelling snapshot {}", secondSnapName); - client().admin().cluster().prepareDeleteSnapshot(REPO, secondSnapName).get(); + client().admin().cluster().prepareDeleteSnapshots(REPO, new String[]{secondSnapName}).get(); } catch (ConcurrentSnapshotExecutionException e) { logger.info("--> attempted to stop second snapshot", e); // just wait and retry diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java index 520d495101fdc..34a7350d029fe 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java @@ -126,7 +126,8 @@ public void testSnapshotUserRoleUnathorizedForDestructiveActions() { () -> client.admin().cluster().prepareRestoreSnapshot("repo", randomAlphaOfLength(4).toLowerCase(Locale.ROOT)).get(), "cluster:admin/snapshot/restore", "snapshot_user"); assertThrowsAuthorizationException( - () -> client.admin().cluster().prepareDeleteSnapshot("repo", randomAlphaOfLength(4).toLowerCase(Locale.ROOT)).get(), + () -> client.admin().cluster().prepareDeleteSnapshots("repo", + new String[]{randomAlphaOfLength(4).toLowerCase(Locale.ROOT)}).get(), "cluster:admin/snapshot/delete", "snapshot_user"); // try destructive/revealing actions on all indices for (final String indexToTest : Arrays.asList(INTERNAL_SECURITY_MAIN_INDEX_7, SECURITY_MAIN_ALIAS, ordinaryIndex)) { diff --git a/x-pack/snapshot-tool/src/test/java/org/elasticsearch/snapshots/AbstractCleanupTests.java b/x-pack/snapshot-tool/src/test/java/org/elasticsearch/snapshots/AbstractCleanupTests.java index f51ee07ce56ed..f065307ca1355 100644 --- a/x-pack/snapshot-tool/src/test/java/org/elasticsearch/snapshots/AbstractCleanupTests.java +++ b/x-pack/snapshot-tool/src/test/java/org/elasticsearch/snapshots/AbstractCleanupTests.java @@ -262,12 +262,12 @@ public void testCleanup() throws Throwable { logger.info("--> perform cleanup by removing snapshots"); assertTrue(client().admin() .cluster() - .prepareDeleteSnapshot("test-repo", "snap1") + .prepareDeleteSnapshots("test-repo", new String[]{"snap1"}) .get() .isAcknowledged()); assertTrue(client().admin() .cluster() - .prepareDeleteSnapshot("test-repo", "snap2") + .prepareDeleteSnapshots("test-repo", new String[]{"snap2"}) .get() .isAcknowledged()); }