diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
index d0140d5e2346d..6e96ecd9c5cb5 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
@@ -38,6 +38,7 @@
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@@ -894,6 +895,26 @@ static Request createSnapshot(CreateSnapshotRequest createSnapshotRequest) throw
return request;
}
+ static Request getSnapshots(GetSnapshotsRequest getSnapshotsRequest) {
+ EndpointBuilder endpointBuilder = new EndpointBuilder().addPathPartAsIs("_snapshot")
+ .addPathPart(getSnapshotsRequest.repository());
+ String endpoint;
+ if (getSnapshotsRequest.snapshots().length == 0) {
+ endpoint = endpointBuilder.addPathPart("_all").build();
+ } else {
+ endpoint = endpointBuilder.addCommaSeparatedPathParts(getSnapshotsRequest.snapshots()).build();
+ }
+
+ Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+
+ Params parameters = new Params(request);
+ parameters.withMasterTimeout(getSnapshotsRequest.masterNodeTimeout());
+ parameters.putParam("ignore_unavailable", Boolean.toString(getSnapshotsRequest.ignoreUnavailable()));
+ parameters.putParam("verbose", Boolean.toString(getSnapshotsRequest.verbose()));
+
+ return request;
+ }
+
static Request deleteSnapshot(DeleteSnapshotRequest deleteSnapshotRequest) {
String endpoint = new EndpointBuilder().addPathPartAsIs("_snapshot")
.addPathPart(deleteSnapshotRequest.repository())
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 4482fce2edf94..fa147a338de0a 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
@@ -32,6 +32,8 @@
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.DeleteSnapshotResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import java.io.IOException;
@@ -190,6 +192,35 @@ public void createSnapshotAsync(CreateSnapshotRequest createSnapshotRequest, Req
CreateSnapshotResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Get snapshots.
+ * See Snapshot and Restore
+ * API on elastic.co
+ *
+ * @param getSnapshotsRequest 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 GetSnapshotsResponse get(GetSnapshotsRequest getSnapshotsRequest, RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(getSnapshotsRequest, RequestConverters::getSnapshots, options,
+ GetSnapshotsResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously get snapshots.
+ * See Snapshot and Restore
+ * API on elastic.co
+ *
+ * @param getSnapshotsRequest 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
+ */
+ public void getAsync(GetSnapshotsRequest getSnapshotsRequest, RequestOptions options, ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(getSnapshotsRequest, RequestConverters::getSnapshots, options,
+ GetSnapshotsResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Deletes a snapshot.
* See Snapshot and Restore
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
index 18af52766f159..5887feefa63db 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
@@ -39,6 +39,7 @@
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
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.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -2011,6 +2012,58 @@ public void testCreateSnapshot() throws IOException {
assertToXContentBody(createSnapshotRequest, request.getEntity());
}
+ public void testGetSnapshots() {
+ Map expectedParams = new HashMap<>();
+ String repository = randomIndicesNames(1, 1)[0];
+ 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);
+
+ GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest();
+ getSnapshotsRequest.repository(repository);
+ getSnapshotsRequest.snapshots(Arrays.asList(snapshot1, snapshot2).toArray(new String[0]));
+ setRandomMasterTimeout(getSnapshotsRequest, expectedParams);
+
+ boolean ignoreUnavailable = randomBoolean();
+ getSnapshotsRequest.ignoreUnavailable(ignoreUnavailable);
+ expectedParams.put("ignore_unavailable", Boolean.toString(ignoreUnavailable));
+
+ boolean verbose = randomBoolean();
+ getSnapshotsRequest.verbose(verbose);
+ expectedParams.put("verbose", Boolean.toString(verbose));
+
+ Request request = RequestConverters.getSnapshots(getSnapshotsRequest);
+ assertThat(endpoint, equalTo(request.getEndpoint()));
+ assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod()));
+ assertThat(expectedParams, equalTo(request.getParameters()));
+ assertNull(request.getEntity());
+ }
+
+ public void testGetAllSnapshots() {
+ Map expectedParams = new HashMap<>();
+ String repository = randomIndicesNames(1, 1)[0];
+
+ String endpoint = String.format(Locale.ROOT, "/_snapshot/%s/_all", repository);
+
+ GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repository);
+ setRandomMasterTimeout(getSnapshotsRequest, expectedParams);
+
+ boolean ignoreUnavailable = randomBoolean();
+ getSnapshotsRequest.ignoreUnavailable(ignoreUnavailable);
+ expectedParams.put("ignore_unavailable", Boolean.toString(ignoreUnavailable));
+
+ boolean verbose = randomBoolean();
+ getSnapshotsRequest.verbose(verbose);
+ expectedParams.put("verbose", Boolean.toString(verbose));
+
+ Request request = RequestConverters.getSnapshots(getSnapshotsRequest);
+ assertThat(endpoint, equalTo(request.getEndpoint()));
+ assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod()));
+ assertThat(expectedParams, equalTo(request.getParameters()));
+ assertNull(request.getEntity());
+ }
+
public void testDeleteSnapshot() {
Map expectedParams = new HashMap<>();
String repository = randomIndicesNames(1, 1)[0];
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 aacb2f5025ee4..7ec2ee80f04ac 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
@@ -32,12 +32,16 @@
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.DeleteSnapshotResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
+import java.util.stream.Collectors;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
public class SnapshotIT extends ESRestHighLevelClientTestCase {
@@ -135,6 +139,40 @@ public void testCreateSnapshot() throws IOException {
assertEquals(waitForCompletion ? RestStatus.OK : RestStatus.ACCEPTED, response.status());
}
+ public void testGetSnapshots() throws IOException {
+ String repository = "test_repository";
+ String snapshot1 = "test_snapshot1";
+ String snapshot2 = "test_snapshot2";
+
+ PutRepositoryResponse putRepositoryResponse = createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}");
+ assertTrue(putRepositoryResponse.isAcknowledged());
+
+ CreateSnapshotRequest createSnapshotRequest1 = new CreateSnapshotRequest(repository, snapshot1);
+ createSnapshotRequest1.waitForCompletion(true);
+ CreateSnapshotResponse putSnapshotResponse1 = createTestSnapshot(createSnapshotRequest1);
+ CreateSnapshotRequest createSnapshotRequest2 = new CreateSnapshotRequest(repository, 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"});
+
+ } else {
+ request = new GetSnapshotsRequest(repository, new String[] {snapshot1, snapshot2});
+ }
+ GetSnapshotsResponse response = execute(request, highLevelClient().snapshot()::get, highLevelClient().snapshot()::getAsync);
+
+ assertEquals(2, response.getSnapshots().size());
+ assertThat(response.getSnapshots().stream().map((s) -> s.snapshotId().getName()).collect(Collectors.toList()),
+ contains("test_snapshot1", "test_snapshot2"));
+ }
+
public void testDeleteSnapshot() throws IOException {
String repository = "test_repository";
String snapshot = "test_snapshot";
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 9c0e31bdcfb70..e2f976a10c68f 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
@@ -31,6 +31,8 @@
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.get.GetSnapshotsRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@@ -46,6 +48,7 @@
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.snapshots.SnapshotInfo;
import java.io.IOException;
import java.util.HashMap;
@@ -456,6 +459,76 @@ public void onFailure(Exception exception) {
}
}
+ public void testSnapshotGetSnapshots() throws IOException {
+ RestHighLevelClient client = highLevelClient();
+
+ createTestRepositories();
+ createTestSnapshots();
+
+ // tag::get-snapshots-request
+ GetSnapshotsRequest request = new GetSnapshotsRequest(repositoryName);
+ // end::get-snapshots-request
+
+ // tag::get-snapshots-request-snapshots
+ String[] snapshots = { snapshotName };
+ request.snapshots(snapshots); // <1>
+ // end::get-snapshots-request-snapshots
+
+ // tag::get-snapshots-request-masterTimeout
+ request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+ request.masterNodeTimeout("1m"); // <2>
+ // end::get-snapshots-request-masterTimeout
+
+ // tag::get-snapshots-request-verbose
+ request.verbose(true); // <1>
+ // end::get-snapshots-request-verbose
+
+ // tag::get-snapshots-request-ignore-unavailable
+ request.ignoreUnavailable(false); // <1>
+ // end::get-snapshots-request-ignore-unavailable
+
+ // tag::get-snapshots-execute
+ GetSnapshotsResponse response = client.snapshot().get(request, RequestOptions.DEFAULT);
+ // end::get-snapshots-execute
+
+ // tag::get-snapshots-response
+ List snapshotsInfos = response.getSnapshots(); // <1>
+ // end::get-snapshots-response
+ assertEquals(1, snapshotsInfos.size());
+ }
+
+ public void testSnapshotGetSnapshotsAsync() throws InterruptedException {
+ RestHighLevelClient client = highLevelClient();
+ {
+ GetSnapshotsRequest request = new GetSnapshotsRequest();
+
+ // tag::get-snapshots-execute-listener
+ ActionListener listener =
+ new ActionListener() {
+ @Override
+ public void onResponse(GetSnapshotsResponse deleteSnapshotResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::get-snapshots-execute-listener
+
+ // Replace the empty listener by a blocking listener in test
+ final CountDownLatch latch = new CountDownLatch(1);
+ listener = new LatchedActionListener<>(listener, latch);
+
+ // tag::get-snapshots-execute-async
+ client.snapshot().getAsync(request, RequestOptions.DEFAULT, listener); // <1>
+ // end::get-snapshots-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
+ }
+
public void testSnapshotDeleteSnapshot() throws IOException {
RestHighLevelClient client = highLevelClient();
diff --git a/docs/java-rest/high-level/snapshot/get_snapshots.asciidoc b/docs/java-rest/high-level/snapshot/get_snapshots.asciidoc
new file mode 100644
index 0000000000000..388a6904e1391
--- /dev/null
+++ b/docs/java-rest/high-level/snapshot/get_snapshots.asciidoc
@@ -0,0 +1,103 @@
+[[java-rest-high-snapshot-get-snapshots]]
+=== Get Snapshots API
+
+Use the Get Snapshot API to get snapshots.
+
+[[java-rest-high-snapshot-get-snapshots-request]]
+==== Get Snapshots Request
+
+A `GetSnapshotsRequest`:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request]
+--------------------------------------------------
+
+==== Required Arguments
+The following arguments are mandatory:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request-repositoryName]
+--------------------------------------------------
+<1> The name of the repository.
+
+==== Optional Arguments
+The following arguments are optional:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request-snapshots]
+--------------------------------------------------
+<1> An array of snapshots to get. Otherwise it will return all snapshots for a repository.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request-masterTimeout]
+--------------------------------------------------
+<1> Timeout to connect to the master node as a `TimeValue`.
+<2> Timeout to connect to the master node as a `String`.
+
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request-verbose]
+--------------------------------------------------
+<1> `Boolean` indicating if the response should be verbose.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-request-ignore-unavailable]
+--------------------------------------------------
+<1> `Boolean` indicating if unavailable snapshots should be ignored. Otherwise the request will
+fail if any of the snapshots are unavailable.
+
+[[java-rest-high-snapshot-get-snapshots-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-execute]
+--------------------------------------------------
+
+[[java-rest-high-snapshot-get-snapshots-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a get snapshots request requires both the
+`GetSnapshotsRequest` instance and an `ActionListener` instance to be
+passed as arguments to the asynchronous method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-execute-async]
+--------------------------------------------------
+<1> The `GetSnapshotsRequest` to execute and the `ActionListener` to use when
+the execution completes.
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back with the `onResponse` method
+if the execution is successful or the `onFailure` method if the execution
+failed.
+
+A typical listener for `GetSnapshotsResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-execute-listener
+--------------------------------------------------
+<1> Called when the execution is successfully completed. The response is
+provided as an argument.
+<2> Called in case of a failure. The raised exception is provided as an
+argument.
+
+[[java-rest-high-snapshot-get-snapshots-response]]
+==== Get Snapshots Response
+
+Use the `GetSnapshotsResponse` to retrieve information about the evaluated
+request:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[get-snapshots-response]
+--------------------------------------------------
+<1> Indicates the node has started the request.
\ No newline at end of file
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index de5cd1ebd3dc1..1a126d82cbbae 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -143,6 +143,7 @@ The Java High Level REST Client supports the following Snapshot APIs:
* <>
* <>
* <>
+* <>
* <>
include::snapshot/get_repository.asciidoc[]
@@ -150,6 +151,7 @@ include::snapshot/create_repository.asciidoc[]
include::snapshot/delete_repository.asciidoc[]
include::snapshot/verify_repository.asciidoc[]
include::snapshot/create_snapshot.asciidoc[]
+include::snapshot/get_snapshots.asciidoc[]
include::snapshot/delete_snapshot.asciidoc[]
== Tasks APIs
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 0d1e5eda7f2d2..6f757cb60ca86 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
@@ -20,23 +20,37 @@
package org.elasticsearch.action.admin.cluster.snapshots.get;
import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.snapshots.SnapshotInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Get snapshots response
*/
public class GetSnapshotsResponse extends ActionResponse implements ToXContentObject {
+ @SuppressWarnings("unchecked")
+ private static final ConstructingObjectParser GET_SNAPSHOT_PARSER =
+ new ConstructingObjectParser<>(GetSnapshotsResponse.class.getName(), true,
+ (args) -> new GetSnapshotsResponse((List) args[0]));
+
+ static {
+ GET_SNAPSHOT_PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
+ (p, c) -> SnapshotInfo.SNAPSHOT_INFO_PARSER.apply(p, c).build(), new ParseField("snapshots"));
+ }
+
private List snapshots = Collections.emptyList();
GetSnapshotsResponse() {
@@ -87,4 +101,20 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par
return builder;
}
+ public static GetSnapshotsResponse fromXContent(XContentParser parser) throws IOException {
+ return GET_SNAPSHOT_PARSER.parse(parser, null);
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(snapshots);
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
index ddd7385056d55..a1f56a1e47376 100644
--- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
+++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
@@ -31,15 +31,12 @@
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ObjectParser;
-import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -84,7 +81,7 @@ public final class SnapshotInfo implements Comparable, ToXContent,
private static final Comparator COMPARATOR =
Comparator.comparing(SnapshotInfo::startTime).thenComparing(SnapshotInfo::snapshotId);
- private static final class SnapshotInfoBuilder {
+ public static final class SnapshotInfoBuilder {
private String snapshotName = null;
private String snapshotUUID = null;
private String state = null;
@@ -137,23 +134,8 @@ private void setVersion(int version) {
this.version = version;
}
- private void setShardFailures(XContentParser parser) {
- if (shardFailures == null) {
- shardFailures = new ArrayList<>();
- }
-
- try {
- if (parser.currentToken() == Token.START_ARRAY) {
- parser.nextToken();
- }
-
- while (parser.currentToken() != Token.END_ARRAY) {
- shardFailures.add(SnapshotShardFailure.fromXContent(parser));
- parser.nextToken();
- }
- } catch (IOException exception) {
- throw new UncheckedIOException(exception);
- }
+ private void setShardFailures(List shardFailures) {
+ this.shardFailures = shardFailures;
}
private void ignoreVersion(String version) {
@@ -172,7 +154,7 @@ private void ignoreDurationInMillis(long durationInMillis) {
// ignore extra field
}
- private SnapshotInfo build() {
+ public SnapshotInfo build() {
SnapshotId snapshotId = new SnapshotId(snapshotName, snapshotUUID);
if (indices == null) {
@@ -219,11 +201,11 @@ private void ignoreFailedShards(int failedShards) {
}
}
- private static final ObjectParser SNAPSHOT_INFO_PARSER =
- new ObjectParser<>(SnapshotInfoBuilder.class.getName(), SnapshotInfoBuilder::new);
+ public static final ObjectParser SNAPSHOT_INFO_PARSER =
+ new ObjectParser<>(SnapshotInfoBuilder.class.getName(), true, SnapshotInfoBuilder::new);
private static final ObjectParser SHARD_STATS_PARSER =
- new ObjectParser<>(ShardStatsBuilder.class.getName(), ShardStatsBuilder::new);
+ new ObjectParser<>(ShardStatsBuilder.class.getName(), true, ShardStatsBuilder::new);
static {
SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setSnapshotName, new ParseField(SNAPSHOT));
@@ -236,8 +218,8 @@ private void ignoreFailedShards(int failedShards) {
SNAPSHOT_INFO_PARSER.declareObject(SnapshotInfoBuilder::setShardStatsBuilder, SHARD_STATS_PARSER, new ParseField(SHARDS));
SNAPSHOT_INFO_PARSER.declareBoolean(SnapshotInfoBuilder::setIncludeGlobalState, new ParseField(INCLUDE_GLOBAL_STATE));
SNAPSHOT_INFO_PARSER.declareInt(SnapshotInfoBuilder::setVersion, new ParseField(VERSION_ID));
- SNAPSHOT_INFO_PARSER.declareField(
- SnapshotInfoBuilder::setShardFailures, parser -> parser, new ParseField(FAILURES), ValueType.OBJECT_ARRAY_OR_STRING);
+ SNAPSHOT_INFO_PARSER.declareObjectArray(SnapshotInfoBuilder::setShardFailures, SnapshotShardFailure.SNAPSHOT_SHARD_FAILURE_PARSER,
+ new ParseField(FAILURES));
SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::ignoreVersion, new ParseField(VERSION));
SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::ignoreStartTime, new ParseField(START_TIME));
SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::ignoreEndTime, new ParseField(END_TIME));
@@ -521,7 +503,7 @@ public RestStatus status() {
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
// write snapshot info to repository snapshot blob format
if (CONTEXT_MODE_SNAPSHOT.equals(params.param(CONTEXT_MODE_PARAM))) {
- return toXContentSnapshot(builder, params);
+ return toXContentInternal(builder, params);
}
final boolean verbose = params.paramAsBoolean("verbose", GetSnapshotsRequest.DEFAULT_VERBOSE_MODE);
@@ -576,7 +558,7 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa
return builder;
}
- private XContentBuilder toXContentSnapshot(final XContentBuilder builder, final ToXContent.Params params) throws IOException {
+ private XContentBuilder toXContentInternal(final XContentBuilder builder, final ToXContent.Params params) throws IOException {
builder.startObject(SNAPSHOT);
builder.field(NAME, snapshotId.getName());
builder.field(UUID, snapshotId.getUUID());
@@ -609,22 +591,12 @@ private XContentBuilder toXContentSnapshot(final XContentBuilder builder, final
return builder;
}
+ /**
+ * This method creates a SnapshotInfo from external x-content. It does not
+ * handle x-content written with the internal version.
+ */
public static SnapshotInfo fromXContent(final XContentParser parser) throws IOException {
- parser.nextToken(); // // move to '{'
-
- if (parser.currentToken() != Token.START_OBJECT) {
- throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['{']");
- }
-
- SnapshotInfo snapshotInfo = SNAPSHOT_INFO_PARSER.apply(parser, null).build();
-
- if (parser.currentToken() != Token.END_OBJECT) {
- throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['}']");
- }
-
- parser.nextToken(); // move past '}'
-
- return snapshotInfo;
+ return SNAPSHOT_INFO_PARSER.parse(parser, null).build();
}
/**
diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardFailure.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardFailure.java
index 30057a6220dcb..0df3f0b6ad580 100644
--- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardFailure.java
+++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardFailure.java
@@ -23,8 +23,10 @@
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
@@ -60,11 +62,23 @@ private SnapshotShardFailure() {
* @param reason failure reason
*/
public SnapshotShardFailure(@Nullable String nodeId, ShardId shardId, String reason) {
+ this(nodeId, shardId, reason, RestStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ /**
+ * Constructs new snapshot shard failure object
+ *
+ * @param nodeId node where failure occurred
+ * @param shardId shard id
+ * @param reason failure reason
+ * @param status rest status
+ */
+ private SnapshotShardFailure(@Nullable String nodeId, ShardId shardId, String reason, RestStatus status) {
+ assert reason != null;
this.nodeId = nodeId;
this.shardId = shardId;
this.reason = reason;
- assert reason != null;
- status = RestStatus.INTERNAL_SERVER_ERROR;
+ this.status = status;
}
/**
@@ -100,7 +114,7 @@ public String reason() {
/**
* Returns REST status corresponding to this failure
*
- * @return REST status
+ * @return REST STATUS
*/
@Override
public RestStatus status() {
@@ -173,63 +187,65 @@ public static void toXContent(SnapshotShardFailure snapshotShardFailure, XConten
builder.endObject();
}
- /**
- * Deserializes snapshot failure information from JSON
- *
- * @param parser JSON parser
- * @return snapshot failure information
- */
- public static SnapshotShardFailure fromXContent(XContentParser parser) throws IOException {
- SnapshotShardFailure snapshotShardFailure = new SnapshotShardFailure();
-
- XContentParser.Token token = parser.currentToken();
- String index = null;
- String index_uuid = IndexMetaData.INDEX_UUID_NA_VALUE;
- int shardId = -1;
- if (token == XContentParser.Token.START_OBJECT) {
- while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
- if (token == XContentParser.Token.FIELD_NAME) {
- String currentFieldName = parser.currentName();
- token = parser.nextToken();
- if (token.isValue()) {
- if ("index".equals(currentFieldName)) {
- index = parser.text();
- } else if ("index_uuid".equals(currentFieldName)) {
- index_uuid = parser.text();
- } else if ("node_id".equals(currentFieldName)) {
- snapshotShardFailure.nodeId = parser.text();
- } else if ("reason".equals(currentFieldName)) {
- // Workaround for https://github.com/elastic/elasticsearch/issues/25878
- // Some old snapshot might still have null in shard failure reasons
- snapshotShardFailure.reason = parser.textOrNull();
- } else if ("shard_id".equals(currentFieldName)) {
- shardId = parser.intValue();
- } else if ("status".equals(currentFieldName)) {
- snapshotShardFailure.status = RestStatus.valueOf(parser.text());
- } else {
- throw new ElasticsearchParseException("unknown parameter [{}]", currentFieldName);
- }
- }
- } else {
- throw new ElasticsearchParseException("unexpected token [{}]", token);
- }
- }
- } else {
- throw new ElasticsearchParseException("unexpected token [{}]", token);
- }
+ static final ConstructingObjectParser SNAPSHOT_SHARD_FAILURE_PARSER =
+ new ConstructingObjectParser<>("shard_failure", true, SnapshotShardFailure::constructSnapshotShardFailure);
+
+ static {
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("index"));
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("index_uuid"));
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("node_id"));
+ // Workaround for https://github.com/elastic/elasticsearch/issues/25878
+ // Some old snapshot might still have null in shard failure reasons
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), new ParseField("reason"));
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareInt(ConstructingObjectParser.constructorArg(), new ParseField("shard_id"));
+ SNAPSHOT_SHARD_FAILURE_PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("status"));
+ }
+
+ private static SnapshotShardFailure constructSnapshotShardFailure(Object[] args) {
+ String index = (String) args[0];
+ String indexUuid = (String) args[1];
+ String nodeId = (String) args[2];
+ String reason = (String) args[3];
+ Integer intShardId = (Integer) args[4];
+ String status = (String) args[5];
+
if (index == null) {
throw new ElasticsearchParseException("index name was not set");
}
- if (shardId == -1) {
+ if (intShardId == null) {
throw new ElasticsearchParseException("index shard was not set");
}
- snapshotShardFailure.shardId = new ShardId(index, index_uuid, shardId);
+
+ ShardId shardId = new ShardId(index, indexUuid != null ? indexUuid : IndexMetaData.INDEX_UUID_NA_VALUE, intShardId);
+
// Workaround for https://github.com/elastic/elasticsearch/issues/25878
// Some old snapshot might still have null in shard failure reasons
- if (snapshotShardFailure.reason == null) {
- snapshotShardFailure.reason = "";
+ String nonNullReason;
+ if (reason != null) {
+ nonNullReason = reason;
+ } else {
+ nonNullReason = "";
+ }
+
+
+ RestStatus restStatus;
+ if (status != null) {
+ restStatus = RestStatus.valueOf(status);
+ } else {
+ restStatus = RestStatus.INTERNAL_SERVER_ERROR;
}
- return snapshotShardFailure;
+
+ return new SnapshotShardFailure(nodeId, shardId, nonNullReason, restStatus);
+ }
+
+ /**
+ * Deserializes snapshot failure information from JSON
+ *
+ * @param parser JSON parser
+ * @return snapshot failure information
+ */
+ public static SnapshotShardFailure fromXContent(XContentParser parser) throws IOException {
+ return SNAPSHOT_SHARD_FAILURE_PARSER.parse(parser, null);
}
@Override
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
new file mode 100644
index 0000000000000..c5bd7d9f38ac1
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponseTests.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.action.admin.cluster.snapshots.get;
+
+import org.elasticsearch.common.UUIDs;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.index.shard.ShardId;
+import org.elasticsearch.snapshots.SnapshotId;
+import org.elasticsearch.snapshots.SnapshotInfo;
+import org.elasticsearch.snapshots.SnapshotShardFailure;
+import org.elasticsearch.test.AbstractStreamableXContentTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class GetSnapshotsResponseTests extends AbstractStreamableXContentTestCase {
+
+ @Override
+ protected GetSnapshotsResponse doParseInstance(XContentParser parser) throws IOException {
+ return GetSnapshotsResponse.fromXContent(parser);
+ }
+
+ @Override
+ protected GetSnapshotsResponse createBlankInstance() {
+ return new GetSnapshotsResponse();
+ }
+
+ @Override
+ 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 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,
+ System.currentTimeMillis(), randomIntBetween(2, 3), shardFailures, randomBoolean()));
+
+ }
+ return new GetSnapshotsResponse(snapshots);
+ }
+}