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 93fb10bd56136..4b8974699b35d 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 @@ -104,7 +104,7 @@ static Request createSnapshot(CreateSnapshotRequest createSnapshotRequest) throw static Request getSnapshots(GetSnapshotsRequest getSnapshotsRequest) { RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder().addPathPartAsIs("_snapshot") - .addPathPart(getSnapshotsRequest.repository()); + .addCommaSeparatedPathParts(getSnapshotsRequest.repositories()); String endpoint; if (getSnapshotsRequest.snapshots().length == 0) { endpoint = endpointBuilder.addPathPart("_all").build(); 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 616850c513af7..d72f8fd26b30a 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 @@ -44,9 +44,7 @@ import java.io.IOException; import java.util.Collections; -import java.util.stream.Collectors; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; @@ -155,37 +153,44 @@ public void testCreateSnapshot() throws IOException { } public void testGetSnapshots() throws IOException { - String repository = "test_repository"; + String repository1 = "test_repository1"; + String repository2 = "test_repository2"; String snapshot1 = "test_snapshot1"; String snapshot2 = "test_snapshot2"; - AcknowledgedResponse putRepositoryResponse = createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}"); + AcknowledgedResponse putRepositoryResponse = + createTestRepository(repository1, FsRepository.TYPE, "{\"location\": \"loc1\"}"); assertTrue(putRepositoryResponse.isAcknowledged()); - CreateSnapshotRequest createSnapshotRequest1 = new CreateSnapshotRequest(repository, snapshot1); + AcknowledgedResponse putRepositoryResponse2 = + createTestRepository(repository2, FsRepository.TYPE, "{\"location\": \"loc2\"}"); + assertTrue(putRepositoryResponse2.isAcknowledged()); + + CreateSnapshotRequest createSnapshotRequest1 = new CreateSnapshotRequest(repository1, snapshot1); createSnapshotRequest1.waitForCompletion(true); CreateSnapshotResponse putSnapshotResponse1 = createTestSnapshot(createSnapshotRequest1); - CreateSnapshotRequest createSnapshotRequest2 = new CreateSnapshotRequest(repository, snapshot2); + CreateSnapshotRequest createSnapshotRequest2 = new CreateSnapshotRequest(repository2, snapshot2); createSnapshotRequest2.waitForCompletion(true); CreateSnapshotResponse putSnapshotResponse2 = createTestSnapshot(createSnapshotRequest2); // check that the request went ok without parsing JSON here. When using the high level client, check acknowledgement instead. assertEquals(RestStatus.OK, putSnapshotResponse1.status()); assertEquals(RestStatus.OK, putSnapshotResponse2.status()); - GetSnapshotsRequest request; - if (randomBoolean()) { - request = new GetSnapshotsRequest(repository); - } else if (randomBoolean()) { - request = new GetSnapshotsRequest(repository, new String[] {"_all"}); + GetSnapshotsRequest request = new GetSnapshotsRequest( + randomFrom(new String[]{"_all"}, new String[]{"*"}, new String[]{repository1, repository2}), + randomFrom(new String[]{"_all"}, new String[]{"*"}, new String[]{snapshot1, snapshot2}) + ); + request.ignoreUnavailable(true); - } else { - request = new GetSnapshotsRequest(repository, new String[] {snapshot1, snapshot2}); - } GetSnapshotsResponse response = execute(request, highLevelClient().snapshot()::get, highLevelClient().snapshot()::getAsync); + logger.info(response.getSnapshots()); assertEquals(2, response.getSnapshots().size()); - assertThat(response.getSnapshots().stream().map((s) -> s.snapshotId().getName()).collect(Collectors.toList()), - contains("test_snapshot1", "test_snapshot2")); + assertEquals(repository1, response.getSnapshots().get(0).repository()); + assertEquals(snapshot1, response.getSnapshots().get(0).snapshotId().getName()); + + assertEquals(repository2, response.getSnapshots().get(1).repository()); + assertEquals(snapshot2, response.getSnapshots().get(1).snapshotId().getName()); } public void testSnapshotsStatus() throws IOException { 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 66720b70ee3a6..bea8835e093b8 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 @@ -41,7 +41,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -148,15 +147,16 @@ public void testCreateSnapshot() throws IOException { public void testGetSnapshots() { Map expectedParams = new HashMap<>(); - String repository = RequestConvertersTests.randomIndicesNames(1, 1)[0]; + String repository1 = randomAlphaOfLength(10); + String repository2 = randomAlphaOfLength(10); String snapshot1 = "snapshot1-" + randomAlphaOfLengthBetween(2, 5).toLowerCase(Locale.ROOT); String snapshot2 = "snapshot2-" + randomAlphaOfLengthBetween(2, 5).toLowerCase(Locale.ROOT); - String endpoint = String.format(Locale.ROOT, "/_snapshot/%s/%s,%s", repository, snapshot1, snapshot2); + String endpoint = String.format(Locale.ROOT, "/_snapshot/%s,%s/%s,%s", repository1, repository2, snapshot1, snapshot2); GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(); - getSnapshotsRequest.repository(repository); - getSnapshotsRequest.snapshots(Arrays.asList(snapshot1, snapshot2).toArray(new String[0])); + getSnapshotsRequest.repositories(repository1, repository2); + getSnapshotsRequest.snapshots(new String[]{snapshot1, snapshot2}); RequestConvertersTests.setRandomMasterTimeout(getSnapshotsRequest, expectedParams); if (randomBoolean()) { 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 d80c24be6618a..7ad8d9b703f1c 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 @@ -590,7 +590,7 @@ public void testSnapshotGetSnapshots() throws IOException { // end::get-snapshots-request // tag::get-snapshots-request-repositoryName - request.repository(repositoryName); // <1> + request.repositories(repositoryName); // <1> // end::get-snapshots-request-repositoryName // tag::get-snapshots-request-snapshots diff --git a/docs/reference/cat/snapshots.asciidoc b/docs/reference/cat/snapshots.asciidoc index 5677a0f2a7cd4..9aec416de680a 100644 --- a/docs/reference/cat/snapshots.asciidoc +++ b/docs/reference/cat/snapshots.asciidoc @@ -18,9 +18,9 @@ Which looks like: [source,txt] -------------------------------------------------- -id status start_epoch start_time end_epoch end_time duration indices successful_shards failed_shards total_shards -snap1 FAILED 1445616705 18:11:45 1445616978 18:16:18 4.6m 1 4 1 5 -snap2 SUCCESS 1445634298 23:04:58 1445634672 23:11:12 6.2m 2 10 0 10 +id repository status start_epoch start_time end_epoch end_time duration indices successful_shards failed_shards total_shards +snap1 repo1 FAILED 1445616705 18:11:45 1445616978 18:16:18 4.6m 1 4 1 5 +snap2 repo1 SUCCESS 1445634298 23:04:58 1445634672 23:11:12 6.2m 2 10 0 10 -------------------------------------------------- // TESTRESPONSE[s/FAILED/SUCCESS/ s/14456\d+/\\d+/ s/\d+(\.\d+)?(m|s|ms)/\\d+(\\.\\d+)?(m|s|ms)/] // TESTRESPONSE[s/\d+:\d+:\d+/\\d+:\\d+:\\d+/] diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.snapshots/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.snapshots/10_basic.yml index 6e03ceb98c716..16940d427e639 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.snapshots/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.snapshots/10_basic.yml @@ -1,5 +1,9 @@ --- "Help": + - skip: + version: " - 7.2.99" + reason: PR https://github.com/elastic/elasticsearch/pull/41799 not yet backported + - do: cat.snapshots: help: true @@ -7,6 +11,7 @@ - match: $body: | /^ id .+ \n + repository .+ \n status .+ \n start_epoch .+ \n start_time .+ \n @@ -21,6 +26,9 @@ $/ --- "Test cat snapshots output": + - skip: + version: " - 7.2.99" + reason: PR https://github.com/elastic/elasticsearch/pull/41799 not yet backported - do: snapshot.create_repository: @@ -74,6 +82,6 @@ - match: $body: | - /^ snap1\s+ SUCCESS\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \S+\s+ 2\s+ 2\s+ 0\s+ 2\s*\n - snap2\s+ SUCCESS\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \S+\s+ 2\s+ 2\s+ 0\s+ 2\s*\n + /^ snap1\s+ test_cat_snapshots_1\s+ SUCCESS\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \S+\s+ 2\s+ 2\s+ 0\s+ 2\s*\n + snap2\s+ test_cat_snapshots_1\s+ SUCCESS\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \d+\s+ \d\d\:\d\d\:\d\d\s+ \S+\s+ 2\s+ 2\s+ 0\s+ 2\s*\n $/ diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/get/GetRepositoriesResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/get/GetRepositoriesResponse.java index 24228aa565871..03030604702f3 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/get/GetRepositoriesResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/get/GetRepositoriesResponse.java @@ -58,6 +58,9 @@ public List repositories() { return repositories.repositories(); } + public GetRepositoriesResponse(StreamInput in) throws IOException { + readFrom(in); + } @Override public void readFrom(StreamInput in) throws IOException { diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotResponseFailureItem.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotResponseFailureItem.java new file mode 100644 index 0000000000000..677b9b485c993 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotResponseFailureItem.java @@ -0,0 +1,86 @@ +package org.elasticsearch.action.admin.cluster.snapshots.get; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +public class GetSnapshotResponseFailureItem implements Streamable, ToXContentObject, Comparable { + static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>(GetSnapshotResponseFailureItem.class.getName(), true, + (args) -> new GetSnapshotResponseFailureItem((String) args[0], (ElasticsearchException) args[1])); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("repository")); + PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), + new ParseField("error")); + } + + private String repository; + private Exception error; + + + public String getRepository() { + return repository; + } + + public Exception getError() { + return error; + } + + + GetSnapshotResponseFailureItem(String repository, ElasticsearchException error) { + this.repository = repository; + this.error = error; + } + + public GetSnapshotResponseFailureItem(StreamInput in) throws IOException { + readFrom(in); + } + + @Override + public void readFrom(StreamInput in) throws IOException { + repository = in.readString(); + error = in.readException(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(repository); + out.writeException(error); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("repository", repository); + ElasticsearchException.generateFailureXContent(builder, params, error, true); + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetSnapshotResponseFailureItem that = (GetSnapshotResponseFailureItem) o; + return Objects.equals(repository, that.repository) && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + return Objects.hash(repository, error); + } + + @Override + public int compareTo(GetSnapshotResponseFailureItem o) { + return repository.compareTo(o.repository); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java index 41ae57031d320..dc322e0f6168c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequest.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.admin.cluster.snapshots.get; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.MasterNodeRequest; import org.elasticsearch.common.Strings; @@ -37,8 +38,9 @@ public class GetSnapshotsRequest extends MasterNodeRequest public static final String ALL_SNAPSHOTS = "_all"; public static final String CURRENT_SNAPSHOT = "_current"; public static final boolean DEFAULT_VERBOSE_MODE = true; + public static final Version MULTIPLE_REPOSITORIES_SUPPORT_ADDED = Version.V_8_0_0; - private String repository; + private String[] repositories; private String[] snapshots = Strings.EMPTY_ARRAY; @@ -50,28 +52,32 @@ public GetSnapshotsRequest() { } /** - * Constructs a new get snapshots request with given repository name and list of snapshots + * Constructs a new get snapshots request with given repository names and list of snapshots * - * @param repository repository name + * @param repositories repository names * @param snapshots list of snapshots */ - public GetSnapshotsRequest(String repository, String[] snapshots) { - this.repository = repository; + public GetSnapshotsRequest(String[] repositories, String[] snapshots) { + this.repositories = repositories; this.snapshots = snapshots; } /** - * Constructs a new get snapshots request with given repository name + * Constructs a new get snapshots request with given repository names * - * @param repository repository name + * @param repositories repository names */ - public GetSnapshotsRequest(String repository) { - this.repository = repository; + public GetSnapshotsRequest(String... repositories) { + this.repositories = repositories; } public GetSnapshotsRequest(StreamInput in) throws IOException { super(in); - repository = in.readString(); + if (in.getVersion().onOrAfter(MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + repositories = in.readStringArray(); + } else { + repositories = new String[]{in.readString()}; + } snapshots = in.readStringArray(); ignoreUnavailable = in.readBoolean(); verbose = in.readBoolean(); @@ -80,7 +86,11 @@ public GetSnapshotsRequest(StreamInput in) throws IOException { @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeString(repository); + if (out.getVersion().onOrAfter(MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + out.writeStringArray(repositories); + } else { + out.writeString(repositories[0]); + } out.writeStringArray(snapshots); out.writeBoolean(ignoreUnavailable); out.writeBoolean(verbose); @@ -89,30 +99,30 @@ public void writeTo(StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; - if (repository == null) { - validationException = addValidationError("repository is missing", validationException); + if (repositories == null) { + validationException = addValidationError("repositories are missing", validationException); } return validationException; } /** - * Sets repository name + * Sets repository names * - * @param repository repository name + * @param repositories repository names * @return this request */ - public GetSnapshotsRequest repository(String repository) { - this.repository = repository; + public GetSnapshotsRequest repositories(String... repositories) { + this.repositories = repositories; return this; } /** - * Returns repository name + * Returns repository names * - * @return repository name + * @return repository names */ - public String repository() { - return this.repository; + public String[] repositories() { + return this.repositories; } /** diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java index 052f8da0c7508..406cc2805ca29 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsRequestBuilder.java @@ -29,28 +29,21 @@ public class GetSnapshotsRequestBuilder extends MasterNodeOperationRequestBuilder { - /** - * Constructs the new get snapshot request - */ - public GetSnapshotsRequestBuilder(ElasticsearchClient client, GetSnapshotsAction action) { - super(client, action, new GetSnapshotsRequest()); - } - /** * Constructs the new get snapshot request with specified repository */ - public GetSnapshotsRequestBuilder(ElasticsearchClient client, GetSnapshotsAction action, String repository) { - super(client, action, new GetSnapshotsRequest(repository)); + public GetSnapshotsRequestBuilder(ElasticsearchClient client, GetSnapshotsAction action, String[] repositories) { + super(client, action, new GetSnapshotsRequest(repositories)); } /** - * Sets the repository name + * Sets the repository names * - * @param repository repository name + * @param repositories repository names * @return this builder */ - public GetSnapshotsRequestBuilder setRepository(String repository) { - request.repository(repository); + public GetSnapshotsRequestBuilder setRepositories(String... repositories) { + request.repositories(repositories); return this; } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java index 6f757cb60ca86..f9fe1a6223e70 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java @@ -44,20 +44,24 @@ public class GetSnapshotsResponse extends ActionResponse implements ToXContentOb @SuppressWarnings("unchecked") private static final ConstructingObjectParser GET_SNAPSHOT_PARSER = new ConstructingObjectParser<>(GetSnapshotsResponse.class.getName(), true, - (args) -> new GetSnapshotsResponse((List) args[0])); + (args) -> new GetSnapshotsResponse((List) args[0], (List)args[1])); static { GET_SNAPSHOT_PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), (p, c) -> SnapshotInfo.SNAPSHOT_INFO_PARSER.apply(p, c).build(), new ParseField("snapshots")); + GET_SNAPSHOT_PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), + (p, c) -> GetSnapshotResponseFailureItem.PARSER.apply(p, c), new ParseField("failures")); } private List snapshots = Collections.emptyList(); + private List failures = Collections.emptyList(); GetSnapshotsResponse() { } - GetSnapshotsResponse(List snapshots) { + GetSnapshotsResponse(List snapshots, List failures) { this.snapshots = Collections.unmodifiableList(snapshots); + this.failures = failures; } /** @@ -69,15 +73,33 @@ public List getSnapshots() { return snapshots; } + /** + * Returns the list of failures + * + * @return the list of failed repositories + */ + public List getFailures() { + return failures; + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - int size = in.readVInt(); - List builder = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - builder.add(new SnapshotInfo(in)); + int snapshotsSize = in.readVInt(); + List snapshotsBuilder = new ArrayList<>(snapshotsSize); + for (int i = 0; i < snapshotsSize; i++) { + snapshotsBuilder.add(new SnapshotInfo(in)); + } + snapshots = Collections.unmodifiableList(snapshotsBuilder); + + if (in.getVersion().onOrAfter(GetSnapshotsRequest.MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + int failuresSize = in.readVInt(); + List failuresBuilder = new ArrayList<>(); + for (int i = 0; i < failuresSize; i++) { + failuresBuilder.add(new GetSnapshotResponseFailureItem(in)); + } + failures = Collections.unmodifiableList(failuresBuilder); } - snapshots = Collections.unmodifiableList(builder); } @Override @@ -87,6 +109,12 @@ public void writeTo(StreamOutput out) throws IOException { for (SnapshotInfo snapshotInfo : snapshots) { snapshotInfo.writeTo(out); } + if (out.getVersion().onOrAfter(GetSnapshotsRequest.MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + out.writeVInt(failures.size()); + for (GetSnapshotResponseFailureItem failureItem : failures) { + failureItem.writeTo(out); + } + } } @Override @@ -97,6 +125,11 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par snapshotInfo.toXContent(builder, params); } builder.endArray(); + builder.startArray("failures"); + for (GetSnapshotResponseFailureItem failure : failures) { + failure.toXContent(builder, params); + } + builder.endArray(); builder.endObject(); return builder; } @@ -110,11 +143,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GetSnapshotsResponse that = (GetSnapshotsResponse) o; - return Objects.equals(snapshots, that.snapshots); + return Objects.equals(snapshots, that.snapshots) && Objects.equals(failures, that.failures); } @Override public int hashCode() { - return Objects.hash(snapshots); + return Objects.hash(snapshots, failures); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java index 23ffbd0dd1e3c..216a57a570602 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java @@ -19,14 +19,21 @@ package org.elasticsearch.action.admin.cluster.snapshots.get; +import org.apache.logging.log4j.core.util.Throwables; import org.apache.lucene.util.CollectionUtil; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionListenerResponseHandler; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRequest; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesResponse; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeAction; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.RepositoryMetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.regex.Regex; @@ -46,6 +53,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.stream.Collectors; /** @@ -81,73 +90,119 @@ protected ClusterBlockException checkBlock(GetSnapshotsRequest request, ClusterS @Override protected void masterOperation(final GetSnapshotsRequest request, final ClusterState state, final ActionListener listener) { - try { - final String repository = request.repository(); - final Map allSnapshotIds = new HashMap<>(); - final List currentSnapshots = new ArrayList<>(); - for (SnapshotInfo snapshotInfo : snapshotsService.currentSnapshots(repository)) { - SnapshotId snapshotId = snapshotInfo.snapshotId(); - allSnapshotIds.put(snapshotId.getName(), snapshotId); - currentSnapshots.add(snapshotInfo); - } + final String[] repositories = request.repositories(); + transportService.sendRequest(transportService.getLocalNode(), GetRepositoriesAction.NAME, + new GetRepositoriesRequest(repositories), + new ActionListenerResponseHandler<>( + ActionListener.wrap( + response -> + // switch to GENERIC thread pool because it might be long running operation + threadPool.executor(ThreadPool.Names.GENERIC).execute( + () -> getMultipleReposSnapshotInfo(response.repositories(), request.snapshots(), + request.ignoreUnavailable(), request.verbose(), listener)), + listener::onFailure), + GetRepositoriesResponse::new)); + } + + private void getMultipleReposSnapshotInfo(List repos, String[] snapshots, boolean ignoreUnavailable, + boolean verbose, ActionListener listener) { + List>> futures = new ArrayList<>(repos.size()); - final RepositoryData repositoryData; - if (isCurrentSnapshotsOnly(request.snapshots()) == false) { - repositoryData = snapshotsService.getRepositoryData(repository); - for (SnapshotId snapshotId : repositoryData.getAllSnapshotIds()) { - allSnapshotIds.put(snapshotId.getName(), snapshotId); + // run concurrently for all repos on SNAPSHOT thread pool + for (final RepositoryMetaData repo : repos) { + futures.add(threadPool.executor(ThreadPool.Names.SNAPSHOT).submit( + () -> getSingleRepoSnapshotInfo(repo.name(), snapshots, ignoreUnavailable, verbose))); + } + List snapshotInfos = new ArrayList<>(); + List failures = new ArrayList<>(); + assert repos.size() == futures.size(); + + for (int i = 0; i < repos.size(); i++) { + try { + snapshotInfos.addAll(futures.get(i).get()); + } catch (InterruptedException e) { + Throwables.rethrow(e); + } catch (ExecutionException e) { + if (e.getCause() instanceof ElasticsearchException) { + failures.add(new GetSnapshotResponseFailureItem(repos.get(i).name(), (ElasticsearchException) e.getCause())); + } else { + Throwables.rethrow(e); } - } else { - repositoryData = null; } + } + CollectionUtil.timSort(snapshotInfos); + CollectionUtil.timSort(failures); - final Set toResolve = new HashSet<>(); - if (isAllSnapshots(request.snapshots())) { - toResolve.addAll(allSnapshotIds.values()); - } else { - for (String snapshotOrPattern : request.snapshots()) { - if (GetSnapshotsRequest.CURRENT_SNAPSHOT.equalsIgnoreCase(snapshotOrPattern)) { - toResolve.addAll(currentSnapshots.stream().map(SnapshotInfo::snapshotId).collect(Collectors.toList())); - } else if (Regex.isSimpleMatchPattern(snapshotOrPattern) == false) { - if (allSnapshotIds.containsKey(snapshotOrPattern)) { - toResolve.add(allSnapshotIds.get(snapshotOrPattern)); - } else if (request.ignoreUnavailable() == false) { - throw new SnapshotMissingException(repository, snapshotOrPattern); - } - } else { - for (Map.Entry entry : allSnapshotIds.entrySet()) { - if (Regex.simpleMatch(snapshotOrPattern, entry.getKey())) { - toResolve.add(entry.getValue()); - } + // We need to preserve pre 7.2 BwC - if there is a single repo and it has failed, fail the whole request + if (snapshotInfos.size() == 0 && failures.size() == 1) { + listener.onFailure(failures.get(0).getError()); + } else { + listener.onResponse(new GetSnapshotsResponse(snapshotInfos, failures)); + } + } + + private List getSingleRepoSnapshotInfo(String repo, String[] snapshots, boolean ignoreUnavailable, boolean verbose) { + final Map allSnapshotIds = new HashMap<>(); + final List currentSnapshots = new ArrayList<>(); + for (SnapshotInfo snapshotInfo : snapshotsService.currentSnapshots(repo)) { + SnapshotId snapshotId = snapshotInfo.snapshotId(); + allSnapshotIds.put(snapshotId.getName(), snapshotId); + currentSnapshots.add(snapshotInfo); + } + + final RepositoryData repositoryData; + if (isCurrentSnapshotsOnly(snapshots) == false) { + repositoryData = snapshotsService.getRepositoryData(repo); + for (SnapshotId snapshotId : repositoryData.getAllSnapshotIds()) { + allSnapshotIds.put(snapshotId.getName(), snapshotId); + } + } else { + repositoryData = null; + } + + final Set toResolve = new HashSet<>(); + if (isAllSnapshots(snapshots)) { + toResolve.addAll(allSnapshotIds.values()); + } else { + for (String snapshotOrPattern : snapshots) { + if (GetSnapshotsRequest.CURRENT_SNAPSHOT.equalsIgnoreCase(snapshotOrPattern)) { + toResolve.addAll(currentSnapshots.stream().map(SnapshotInfo::snapshotId).collect(Collectors.toList())); + } else if (Regex.isSimpleMatchPattern(snapshotOrPattern) == false) { + if (allSnapshotIds.containsKey(snapshotOrPattern)) { + toResolve.add(allSnapshotIds.get(snapshotOrPattern)); + } else if (ignoreUnavailable == false) { + throw new SnapshotMissingException(repo, snapshotOrPattern); + } + } else { + for (Map.Entry entry : allSnapshotIds.entrySet()) { + if (Regex.simpleMatch(snapshotOrPattern, entry.getKey())) { + toResolve.add(entry.getValue()); } } } + } - if (toResolve.isEmpty() && request.ignoreUnavailable() == false && isCurrentSnapshotsOnly(request.snapshots()) == false) { - throw new SnapshotMissingException(repository, request.snapshots()[0]); - } + if (toResolve.isEmpty() && ignoreUnavailable == false && isCurrentSnapshotsOnly(snapshots) == false) { + throw new SnapshotMissingException(repo, snapshots[0]); } + } - final List snapshotInfos; - if (request.verbose()) { - final Set incompatibleSnapshots = repositoryData != null ? + final List snapshotInfos; + if (verbose) { + final Set incompatibleSnapshots = repositoryData != null ? new HashSet<>(repositoryData.getIncompatibleSnapshotIds()) : Collections.emptySet(); - snapshotInfos = snapshotsService.snapshots(repository, new ArrayList<>(toResolve), - incompatibleSnapshots, request.ignoreUnavailable()); + snapshotInfos = snapshotsService.snapshots(repo, new ArrayList<>(toResolve), + incompatibleSnapshots, ignoreUnavailable); + } else { + if (repositoryData != null) { + // want non-current snapshots as well, which are found in the repository data + snapshotInfos = buildSimpleSnapshotInfos(repo, toResolve, repositoryData, currentSnapshots); } else { - if (repositoryData != null) { - // want non-current snapshots as well, which are found in the repository data - snapshotInfos = buildSimpleSnapshotInfos(toResolve, repositoryData, currentSnapshots); - } else { - // only want current snapshots - snapshotInfos = currentSnapshots.stream().map(SnapshotInfo::basic).collect(Collectors.toList()); - CollectionUtil.timSort(snapshotInfos); - } + // only want current snapshots + snapshotInfos = currentSnapshots.stream().map(SnapshotInfo::basic).collect(Collectors.toList()); } - listener.onResponse(new GetSnapshotsResponse(snapshotInfos)); - } catch (Exception e) { - listener.onFailure(e); } + return snapshotInfos; } private boolean isAllSnapshots(String[] snapshots) { @@ -158,7 +213,7 @@ private boolean isCurrentSnapshotsOnly(String[] snapshots) { return (snapshots.length == 1 && GetSnapshotsRequest.CURRENT_SNAPSHOT.equalsIgnoreCase(snapshots[0])); } - private List buildSimpleSnapshotInfos(final Set toResolve, + private List buildSimpleSnapshotInfos(final String repository, final Set toResolve, final RepositoryData repositoryData, final List currentSnapshots) { List snapshotInfos = new ArrayList<>(); @@ -180,9 +235,8 @@ private List buildSimpleSnapshotInfos(final Set toReso final List indices = entry.getValue(); CollectionUtil.timSort(indices); final SnapshotId snapshotId = entry.getKey(); - snapshotInfos.add(new SnapshotInfo(snapshotId, indices, repositoryData.getSnapshotState(snapshotId))); + snapshotInfos.add(new SnapshotInfo(snapshotId, repository, indices, repositoryData.getSnapshotState(snapshotId))); } - CollectionUtil.timSort(snapshotInfos); return Collections.unmodifiableList(snapshotInfos); } } diff --git a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java index 42aaed10d6172..596d43009ba59 100644 --- a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java +++ b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java @@ -484,19 +484,19 @@ public interface ClusterAdminClient extends ElasticsearchClient { CreateSnapshotRequestBuilder prepareCreateSnapshot(String repository, String name); /** - * Get snapshot. + * Get snapshots. */ ActionFuture getSnapshots(GetSnapshotsRequest request); /** - * Get snapshot. + * Get snapshots. */ void getSnapshots(GetSnapshotsRequest request, ActionListener listener); /** - * Get snapshot. + * Get snapshots. */ - GetSnapshotsRequestBuilder prepareGetSnapshots(String repository); + GetSnapshotsRequestBuilder prepareGetSnapshots(String... repositories); /** * Delete snapshot. diff --git a/server/src/main/java/org/elasticsearch/client/Requests.java b/server/src/main/java/org/elasticsearch/client/Requests.java index 19ad2fb397edc..3e4ac2eafed59 100644 --- a/server/src/main/java/org/elasticsearch/client/Requests.java +++ b/server/src/main/java/org/elasticsearch/client/Requests.java @@ -494,13 +494,13 @@ public static CreateSnapshotRequest createSnapshotRequest(String repository, Str } /** - * Gets snapshots from repository + * Gets snapshots from repositories * - * @param repository repository name + * @param repositories repository names * @return get snapshot request */ - public static GetSnapshotsRequest getSnapshotsRequest(String repository) { - return new GetSnapshotsRequest(repository); + public static GetSnapshotsRequest getSnapshotsRequest(String... repositories) { + return new GetSnapshotsRequest(repositories); } /** 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 e79f0567babe6..f72bae2731457 100644 --- a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java +++ b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java @@ -955,8 +955,8 @@ public void getSnapshots(GetSnapshotsRequest request, ActionListener shardFailures, final long repositoryStateId, final boolean includeGlobalState) { - SnapshotInfo blobStoreSnapshot = new SnapshotInfo(snapshotId, - indices.stream().map(IndexId::getName).collect(Collectors.toList()), + SnapshotInfo blobStoreSnapshot = new SnapshotInfo(snapshotId, metadata.name(), + indices.stream().map(IndexId::getName).collect(Collectors.toList()), startTime, failure, System.currentTimeMillis(), totalShards, shardFailures, includeGlobalState); try { diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java index f42180b5029b8..b43e6dcda2e47 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetSnapshotsAction.java @@ -49,10 +49,10 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - String repository = request.param("repository"); + final String[] repositories = request.paramAsStringArray("repository", Strings.EMPTY_ARRAY); String[] snapshots = request.paramAsStringArray("snapshot", Strings.EMPTY_ARRAY); - GetSnapshotsRequest getSnapshotsRequest = getSnapshotsRequest(repository).snapshots(snapshots); + GetSnapshotsRequest getSnapshotsRequest = getSnapshotsRequest(repositories).snapshots(snapshots); getSnapshotsRequest.ignoreUnavailable(request.paramAsBoolean("ignore_unavailable", getSnapshotsRequest.ignoreUnavailable())); getSnapshotsRequest.verbose(request.paramAsBoolean("verbose", getSnapshotsRequest.verbose())); getSnapshotsRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getSnapshotsRequest.masterNodeTimeout())); diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java index 22258ce2d8878..dd5099b83394c 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java @@ -23,6 +23,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.Table; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.time.DateFormatter; @@ -58,7 +59,7 @@ public String getName() { @Override protected RestChannelConsumer doCatRequest(final RestRequest request, NodeClient client) { GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest() - .repository(request.param("repository")) + .repositories(request.paramAsStringArray("repository", Strings.EMPTY_ARRAY)) .snapshots(new String[]{GetSnapshotsRequest.ALL_SNAPSHOTS}); getSnapshotsRequest.ignoreUnavailable(request.paramAsBoolean("ignore_unavailable", getSnapshotsRequest.ignoreUnavailable())); @@ -66,7 +67,7 @@ protected RestChannelConsumer doCatRequest(final RestRequest request, NodeClient getSnapshotsRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getSnapshotsRequest.masterNodeTimeout())); return channel -> - client.admin().cluster().getSnapshots(getSnapshotsRequest, new RestResponseListener(channel) { + client.admin().cluster().getSnapshots(getSnapshotsRequest, new RestResponseListener<>(channel) { @Override public RestResponse buildResponse(GetSnapshotsResponse getSnapshotsResponse) throws Exception { return RestTable.buildResponse(buildTable(request, getSnapshotsResponse), channel); @@ -84,6 +85,7 @@ protected Table getTableWithHeader(RestRequest request) { return new Table() .startHeaders() .addCell("id", "alias:id,snapshot;desc:unique snapshot") + .addCell("repository", "alias:re,repo;desc:repository name") .addCell("status", "alias:s,status;text-align:right;desc:snapshot name") .addCell("start_epoch", "alias:ste,startEpoch;desc:start time in seconds since 1970-01-01 00:00:00") .addCell("start_time", "alias:sti,startTime;desc:start time in HH:MM:SS") @@ -106,6 +108,7 @@ private Table buildTable(RestRequest req, GetSnapshotsResponse getSnapshotsRespo table.startRow(); table.addCell(snapshotStatus.snapshotId().getName()); + table.addCell(snapshotStatus.repository()); table.addCell(snapshotStatus.state()); table.addCell(TimeUnit.SECONDS.convert(snapshotStatus.startTime(), TimeUnit.MILLISECONDS)); table.addCell(FORMATTER.format(Instant.ofEpochMilli(snapshotStatus.startTime()))); diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java index 38aa945bcca47..527ed676e9157 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java @@ -54,6 +54,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, private static final DateFormatter DATE_TIME_FORMATTER = DateFormatter.forPattern("strictDateOptionalTime"); private static final String SNAPSHOT = "snapshot"; private static final String UUID = "uuid"; + private static final String REPOSITORY = "repository"; private static final String INDICES = "indices"; private static final String STATE = "state"; private static final String REASON = "reason"; @@ -76,11 +77,14 @@ public final class SnapshotInfo implements Comparable, ToXContent, private static final String INCLUDE_GLOBAL_STATE = "include_global_state"; private static final Comparator COMPARATOR = - Comparator.comparing(SnapshotInfo::startTime).thenComparing(SnapshotInfo::snapshotId); + Comparator.comparing(SnapshotInfo::repository). + thenComparing(SnapshotInfo::startTime). + thenComparing(SnapshotInfo::snapshotId); public static final class SnapshotInfoBuilder { private String snapshotName = null; private String snapshotUUID = null; + private String repository = null; private String state = null; private String reason = null; private List indices = null; @@ -99,6 +103,8 @@ private void setSnapshotUUID(String snapshotUUID) { this.snapshotUUID = snapshotUUID; } + private void setRepository(String repository) {this.repository = repository; } + private void setState(String state) { this.state = state; } @@ -152,7 +158,7 @@ public SnapshotInfo build() { shardFailures = new ArrayList<>(); } - return new SnapshotInfo(snapshotId, indices, snapshotState, reason, version, startTime, endTime, + return new SnapshotInfo(snapshotId, repository, indices, snapshotState, reason, version, startTime, endTime, totalShards, successfulShards, shardFailures, includeGlobalState); } } @@ -187,6 +193,7 @@ int getSuccessfulShards() { static { SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setSnapshotName, new ParseField(SNAPSHOT)); SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setSnapshotUUID, new ParseField(UUID)); + SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setRepository, new ParseField(REPOSITORY)); SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setState, new ParseField(STATE)); SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setReason, new ParseField(REASON)); SNAPSHOT_INFO_PARSER.declareStringArray(SnapshotInfoBuilder::setIndices, new ParseField(INDICES)); @@ -204,6 +211,9 @@ int getSuccessfulShards() { private final SnapshotId snapshotId; + @Nullable + private final String repository; + @Nullable private final SnapshotState state; @@ -228,31 +238,33 @@ int getSuccessfulShards() { private final List shardFailures; - public SnapshotInfo(SnapshotId snapshotId, List indices, SnapshotState state) { - this(snapshotId, indices, state, null, null, 0L, 0L, 0, 0, + public SnapshotInfo(SnapshotId snapshotId, String repository, List indices, SnapshotState state) { + this(snapshotId, repository, indices, state, null, null, 0L, 0L, 0, 0, Collections.emptyList(), null); } - public SnapshotInfo(SnapshotId snapshotId, List indices, SnapshotState state, Version version) { - this(snapshotId, indices, state, null, version, 0L, 0L, 0, 0, + public SnapshotInfo(SnapshotId snapshotId, String repository, List indices, SnapshotState state, Version version) { + this(snapshotId, repository, indices, state, null, version, 0L, 0L, 0, 0, Collections.emptyList(), null); } - public SnapshotInfo(SnapshotId snapshotId, List indices, long startTime, Boolean includeGlobalState) { - this(snapshotId, indices, SnapshotState.IN_PROGRESS, null, Version.CURRENT, startTime, 0L, + public SnapshotInfo(SnapshotId snapshotId, String repository, List indices, long startTime, Boolean includeGlobalState) { + this(snapshotId, repository, indices, SnapshotState.IN_PROGRESS, null, Version.CURRENT, startTime, 0L, 0, 0, Collections.emptyList(), includeGlobalState); } - public SnapshotInfo(SnapshotId snapshotId, List indices, long startTime, String reason, long endTime, + public SnapshotInfo(SnapshotId snapshotId, String repository, List indices, long startTime, String reason, long endTime, int totalShards, List shardFailures, Boolean includeGlobalState) { - this(snapshotId, indices, snapshotState(reason, shardFailures), reason, Version.CURRENT, + this(snapshotId, repository, indices, snapshotState(reason, shardFailures), reason, Version.CURRENT, startTime, endTime, totalShards, totalShards - shardFailures.size(), shardFailures, includeGlobalState); } - private SnapshotInfo(SnapshotId snapshotId, List indices, SnapshotState state, String reason, Version version, + private SnapshotInfo(SnapshotId snapshotId, String repository, List indices, SnapshotState state, String reason, + Version version, long startTime, long endTime, int totalShards, int successfulShards, List shardFailures, Boolean includeGlobalState) { this.snapshotId = Objects.requireNonNull(snapshotId); + this.repository = repository; this.indices = Collections.unmodifiableList(Objects.requireNonNull(indices)); this.state = state; this.reason = reason; @@ -270,6 +282,15 @@ private SnapshotInfo(SnapshotId snapshotId, List indices, SnapshotState */ public SnapshotInfo(final StreamInput in) throws IOException { snapshotId = new SnapshotId(in); + if (in.getVersion().onOrAfter(GetSnapshotsRequest.MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + if (in.readBoolean()) { + repository = in.readString(); + } else { + repository = null; + } + } else { + repository = null; + } int size = in.readVInt(); List indicesListBuilder = new ArrayList<>(); for (int i = 0; i < size; i++) { @@ -300,8 +321,8 @@ public SnapshotInfo(final StreamInput in) throws IOException { * Gets a new {@link SnapshotInfo} instance for a snapshot that is incompatible with the * current version of the cluster. */ - public static SnapshotInfo incompatible(SnapshotId snapshotId) { - return new SnapshotInfo(snapshotId, Collections.emptyList(), SnapshotState.INCOMPATIBLE, + public static SnapshotInfo incompatible(String repository, SnapshotId snapshotId) { + return new SnapshotInfo(snapshotId, repository, Collections.emptyList(), SnapshotState.INCOMPATIBLE, "the snapshot is incompatible with the current version of Elasticsearch and its exact version is unknown", null, 0L, 0L, 0, 0, Collections.emptyList(), null); @@ -309,10 +330,10 @@ public static SnapshotInfo incompatible(SnapshotId snapshotId) { /** * Gets a new {@link SnapshotInfo} instance from the given {@link SnapshotInfo} with - * all information stripped out except the snapshot id, state, and indices. + * all information stripped out except the repository, snapshot id, state, and indices. */ public SnapshotInfo basic() { - return new SnapshotInfo(snapshotId, indices, state); + return new SnapshotInfo(snapshotId, repository, indices, state); } /** @@ -324,6 +345,16 @@ public SnapshotId snapshotId() { return snapshotId; } + + /** + * Returns repository name the snapshot belongs to. + * + * @return repository name. + */ + public String repository() { + return repository; + } + /** * Returns snapshot state; {@code null} if the state is unknown. * @@ -429,8 +460,7 @@ public Version version() { } /** - * Compares two snapshots by their start time; if the start times are the same, then - * compares the two snapshots by their snapshot ids. + * Compares two snapshots by their repo name, then start time, then snapshot id. */ @Override public int compareTo(final SnapshotInfo o) { @@ -441,6 +471,7 @@ public int compareTo(final SnapshotInfo o) { public String toString() { return "SnapshotInfo{" + "snapshotId=" + snapshotId + + ", repository=" + repository + ", state=" + state + ", reason='" + reason + '\'' + ", indices=" + indices + @@ -480,6 +511,9 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa builder.startObject(); builder.field(SNAPSHOT, snapshotId.getName()); builder.field(UUID, snapshotId.getUUID()); + if (repository != null) { + builder.field(REPOSITORY, repository); + } if (version != null) { builder.field(VERSION_ID, version.id); builder.field(VERSION, version.toString()); @@ -529,6 +563,9 @@ private XContentBuilder toXContentInternal(final XContentBuilder builder, final builder.startObject(SNAPSHOT); builder.field(NAME, snapshotId.getName()); builder.field(UUID, snapshotId.getUUID()); + if (repository != null) { + builder.field(REPOSITORY, repository); + } assert version != null : "version must always be known when writing a snapshot metadata blob"; builder.field(VERSION_ID, version.id); builder.startArray(INDICES); @@ -564,6 +601,7 @@ private XContentBuilder toXContentInternal(final XContentBuilder builder, final public static SnapshotInfo fromXContentInternal(final XContentParser parser) throws IOException { String name = null; String uuid = null; + String repository = null; Version version = Version.CURRENT; SnapshotState state = SnapshotState.IN_PROGRESS; String reason = null; @@ -593,6 +631,8 @@ public static SnapshotInfo fromXContentInternal(final XContentParser parser) thr name = parser.text(); } else if (UUID.equals(currentFieldName)) { uuid = parser.text(); + } else if (REPOSITORY.equals(currentFieldName)) { + repository = parser.text(); } else if (STATE.equals(currentFieldName)) { state = SnapshotState.valueOf(parser.text()); } else if (REASON.equals(currentFieldName)) { @@ -641,7 +681,7 @@ public static SnapshotInfo fromXContentInternal(final XContentParser parser) thr // the old format where there wasn't a UUID uuid = name; } - return new SnapshotInfo(new SnapshotId(name, uuid), + return new SnapshotInfo(new SnapshotId(name, uuid), repository, indices, state, reason, @@ -657,6 +697,14 @@ public static SnapshotInfo fromXContentInternal(final XContentParser parser) thr @Override public void writeTo(final StreamOutput out) throws IOException { snapshotId.writeTo(out); + if (out.getVersion().onOrAfter(GetSnapshotsRequest.MULTIPLE_REPOSITORIES_SUPPORT_ADDED)) { + if (repository != null) { + out.writeBoolean(true); + out.writeString(repository); + } else { + out.writeBoolean(false); + } + } out.writeVInt(indices.size()); for (String index : indices) { out.writeString(index); @@ -707,6 +755,7 @@ public boolean equals(Object o) { totalShards == that.totalShards && successfulShards == that.successfulShards && Objects.equals(snapshotId, that.snapshotId) && + Objects.equals(repository, that.repository) && state == that.state && Objects.equals(reason, that.reason) && Objects.equals(indices, that.indices) && @@ -717,8 +766,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - - return Objects.hash(snapshotId, state, reason, indices, startTime, endTime, + return Objects.hash(snapshotId, repository, state, reason, indices, startTime, endTime, totalShards, successfulShards, includeGlobalState, version, shardFailures); } } diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java index 1559bae8259b0..3c6843e0780c3 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java @@ -167,7 +167,7 @@ public RepositoryData getRepositoryData(final String repositoryName) { public SnapshotInfo snapshot(final String repositoryName, final SnapshotId snapshotId) { List entries = currentSnapshots(repositoryName, Collections.singletonList(snapshotId.getName())); if (!entries.isEmpty()) { - return inProgressSnapshot(entries.iterator().next()); + return inProgressSnapshot(repositoryName, entries.iterator().next()); } return repositoriesService.repository(repositoryName).getSnapshotInfo(snapshotId); } @@ -192,7 +192,7 @@ public List snapshots(final String repositoryName, final List entries = currentSnapshots(repositoryName, snapshotIdsToIterate.stream().map(SnapshotId::getName).collect(Collectors.toList())); for (SnapshotsInProgress.Entry entry : entries) { - snapshotSet.add(inProgressSnapshot(entry)); + snapshotSet.add(inProgressSnapshot(repositoryName, entry)); snapshotIdsToIterate.remove(entry.snapshot().getSnapshotId()); } // then, look in the repository @@ -202,7 +202,7 @@ public List snapshots(final String repositoryName, if (incompatibleSnapshotIds.contains(snapshotId)) { // an incompatible snapshot - cannot read its snapshot metadata file, just return // a SnapshotInfo indicating its incompatible - snapshotSet.add(SnapshotInfo.incompatible(snapshotId)); + snapshotSet.add(SnapshotInfo.incompatible(repositoryName, snapshotId)); } else { snapshotSet.add(repository.getSnapshotInfo(snapshotId)); } @@ -229,7 +229,7 @@ public List currentSnapshots(final String repositoryName) { List snapshotList = new ArrayList<>(); List entries = currentSnapshots(repositoryName, Collections.emptyList()); for (SnapshotsInProgress.Entry entry : entries) { - snapshotList.add(inProgressSnapshot(entry)); + snapshotList.add(inProgressSnapshot(repositoryName, entry)); } CollectionUtil.timSort(snapshotList); return unmodifiableList(snapshotList); @@ -552,13 +552,12 @@ private void cleanupAfterError(Exception exception) { try { repositoriesService.repository(snapshot.snapshot().getRepository()) .finalizeSnapshot(snapshot.snapshot().getSnapshotId(), - snapshot.indices(), - snapshot.startTime(), - ExceptionsHelper.detailedMessage(exception), - 0, - Collections.emptyList(), - snapshot.getRepositoryStateId(), - snapshot.includeGlobalState()); + snapshot.indices(), + snapshot.startTime(), + ExceptionsHelper.detailedMessage(exception), + 0, + Collections.emptyList(), + snapshot.getRepositoryStateId(), snapshot.includeGlobalState()); } catch (Exception inner) { inner.addSuppressed(exception); logger.warn(() -> new ParameterizedMessage("[{}] failed to close snapshot in repository", @@ -570,9 +569,9 @@ private void cleanupAfterError(Exception exception) { } - private static SnapshotInfo inProgressSnapshot(SnapshotsInProgress.Entry entry) { - return new SnapshotInfo(entry.snapshot().getSnapshotId(), - entry.indices().stream().map(IndexId::getName).collect(Collectors.toList()), + private static SnapshotInfo inProgressSnapshot(String repository, SnapshotsInProgress.Entry entry) { + return new SnapshotInfo(entry.snapshot().getSnapshotId(), repository, + entry.indices().stream().map(IndexId::getName).collect(Collectors.toList()), entry.startTime(), entry.includeGlobalState()); } @@ -983,13 +982,12 @@ protected void doRun() { } SnapshotInfo snapshotInfo = repository.finalizeSnapshot( snapshot.getSnapshotId(), - entry.indices(), - entry.startTime(), - failure, - entry.shards().size(), - unmodifiableList(shardFailures), - entry.getRepositoryStateId(), - entry.includeGlobalState()); + entry.indices(), + entry.startTime(), + failure, + entry.shards().size(), + unmodifiableList(shardFailures), + entry.getRepositoryStateId(), entry.includeGlobalState()); removeSnapshotFromClusterState(snapshot, snapshotInfo, null); logger.info("snapshot [{}] completed with state [{}]", snapshot, snapshotInfo.state()); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponseTests.java index bbb11fc6feef0..32d29ed5a9187 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponseTests.java @@ -46,6 +46,7 @@ protected boolean supportsUnknownFields() { @Override protected CreateSnapshotResponse createTestInstance() { SnapshotId snapshotId = new SnapshotId("test", UUID.randomUUID().toString()); + String repo = randomAlphaOfLength(10); List indices = new ArrayList<>(); indices.add("test0"); indices.add("test1"); @@ -64,6 +65,7 @@ protected CreateSnapshotResponse createTestInstance() { boolean globalState = randomBoolean(); return new CreateSnapshotResponse( - new SnapshotInfo(snapshotId, indices, startTime, reason, endTime, totalShards, shardFailures, globalState)); + new SnapshotInfo(snapshotId, repo, indices, startTime, reason, endTime, totalShards, shardFailures, + globalState)); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java index c5bd7d9f38ac1..34ad822603dc2 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java @@ -50,13 +50,15 @@ protected GetSnapshotsResponse createTestInstance() { ArrayList snapshots = new ArrayList<>(); for (int i = 0; i < randomIntBetween(5, 10); ++i) { SnapshotId snapshotId = new SnapshotId("snapshot " + i, UUIDs.base64UUID()); + String repo = randomAlphaOfLength(10); String reason = randomBoolean() ? null : "reason"; ShardId shardId = new ShardId("index", UUIDs.base64UUID(), 2); List shardFailures = Collections.singletonList(new SnapshotShardFailure("node-id", shardId, "reason")); - snapshots.add(new SnapshotInfo(snapshotId, Arrays.asList("indice1", "indice2"), System.currentTimeMillis(), reason, + snapshots.add(new SnapshotInfo(snapshotId, repo, Arrays.asList("indice1", "indice2"), + System.currentTimeMillis(), reason, System.currentTimeMillis(), randomIntBetween(2, 3), shardFailures, randomBoolean())); } - return new GetSnapshotsResponse(snapshots); + return new GetSnapshotsResponse(snapshots, Collections.emptyList()); } } diff --git a/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java b/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java index 3a78b4786fc5c..bca8775dc6858 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java @@ -69,6 +69,7 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.TimeValue; @@ -116,6 +117,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -3126,6 +3128,93 @@ public void testCannotCreateSnapshotsWithSameName() throws Exception { assertThat(createSnapshotResponse.getSnapshotInfo().snapshotId().getName(), equalTo(snapshotName)); } + public void testGetSnapshotsMultipleRepos() { + final Client client = client(); + + List snaphostList = new ArrayList<>(); + List repoList = new ArrayList<>(); + List> repoSnapshotList = new ArrayList<>(); + + logger.info("--> create an index and index some documents"); + final String indexName = "test-idx"; + assertAcked(prepareCreate(indexName)); + ensureGreen(); + for (int i = 0; i < 10; i++) { + index(indexName, "_doc", Integer.toString(i), "foo", "bar" + i); + } + refresh(); + + for (int repoIndex = 0; repoIndex < randomIntBetween(2, 5); repoIndex++) { + final String repoName = "repo" + repoIndex; + repoList.add(repoName); + final Path repoPath = randomRepoPath(); + logger.info("--> create repository with name " + repoName); + assertAcked(client.admin().cluster().preparePutRepository(repoName) + .setType("mock").setSettings(Settings.builder() + .put("location", repoPath) + .put("compress", false) + .put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES) + .put("wait_after_unblock", 200))); + + for (int snapshotIndex = 0; snapshotIndex < randomIntBetween(2, 5); snapshotIndex++) { + final String snapshotName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); + snaphostList.add(snapshotName); + logger.info("--> create snapshot with index {} and name {} in repository {}", snapshotIndex, snapshotName, repoName); + CreateSnapshotResponse createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repoName, snapshotName) + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); + repoSnapshotList.add(Tuple.tuple(repoName, snapshotName)); + } + } + + Supplier repoNames = () -> randomFrom(new String[]{"_all"}, + new String[]{"repo*"}, repoList.toArray(new String[0])); + + + logger.info("--> get and verify snapshots"); + GetSnapshotsResponse getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(repoNames.get()) + .setSnapshots(randomFrom("_all", "*")) + .get(); + + + List snapshots = getSnapshotsResponse.getSnapshots(); + assertThat(snapshots, hasSize(repoSnapshotList.size())); + + for (int i = 0; i < snapshots.size(); i++) { + assertEquals(repoSnapshotList.get(i).v1(), snapshots.get(i).repository()); + assertEquals(repoSnapshotList.get(i).v2(), snapshots.get(i).snapshotId().getName()); + } + + + getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(randomFrom("_all", "repo*")) + .setIgnoreUnavailable(false) + .setSnapshots(snaphostList.toArray(new String[0])) + .get(); + /*logger.info("--> specify all snapshot names with ignoreUnavailable=false"); + expectThrows(SnapshotMissingException.class, () -> ); + + logger.info("--> specify all snapshot names with ignoreUnavailable=true"); + getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(randomFrom("_all", "repo*")) + .setIgnoreUnavailable(true) + .setSnapshots(snaphostList.toArray(new String[0])) + .get(); + + snapshots = getSnapshotsResponse.getSnapshots(); + assertThat(snapshots, hasSize(repoSnapshotList.size())); + + for (int i = 0; i < snapshots.size(); i++) { + assertEquals(repoSnapshotList.get(i).v1(), snapshots.get(i).repository()); + assertEquals(repoSnapshotList.get(i).v2(), snapshots.get(i).snapshotId().getName()); + }*/ + } + public void testGetSnapshotsRequest() throws Exception { final String repositoryName = "test-repo"; final String indexName = "test-idx"; 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 5a0472339c192..ca38370d4634a 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 @@ -169,7 +169,8 @@ public SnapshotInfo getSnapshotInfo(SnapshotId snapshotId) { ArrayList indices = new ArrayList<>(indicesMap.size()); indicesMap.keysIt().forEachRemaining(indices::add); - return new SnapshotInfo(snapshotId, indices, SnapshotState.SUCCESS, response.getState().getNodes().getMaxNodeVersion()); + return new SnapshotInfo(snapshotId, metadata.name(), + indices, SnapshotState.SUCCESS, response.getState().getNodes().getMaxNodeVersion()); } @Override