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 978de02bea3c0..d0140d5e2346d 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
@@ -37,6 +37,7 @@
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
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;
@@ -880,6 +881,19 @@ static Request verifyRepository(VerifyRepositoryRequest verifyRepositoryRequest)
return request;
}
+ static Request createSnapshot(CreateSnapshotRequest createSnapshotRequest) throws IOException {
+ String endpoint = new EndpointBuilder().addPathPart("_snapshot")
+ .addPathPart(createSnapshotRequest.repository())
+ .addPathPart(createSnapshotRequest.snapshot())
+ .build();
+ Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+ Params params = new Params(request);
+ params.withMasterTimeout(createSnapshotRequest.masterNodeTimeout());
+ params.withWaitForCompletion(createSnapshotRequest.waitForCompletion());
+ request.setEntity(createEntity(createSnapshotRequest, REQUEST_BODY_CONTENT_TYPE));
+ 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 36b4f473ce82f..4482fce2edf94 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
@@ -28,6 +28,8 @@
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse;
@@ -164,6 +166,30 @@ public void verifyRepositoryAsync(VerifyRepositoryRequest verifyRepositoryReques
VerifyRepositoryResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Creates a snapshot.
+ *
+ * See Snapshot and Restore
+ * API on elastic.co
+ */
+ public CreateSnapshotResponse createSnapshot(CreateSnapshotRequest createSnapshotRequest, RequestOptions options)
+ throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(createSnapshotRequest, RequestConverters::createSnapshot, options,
+ CreateSnapshotResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously creates a snapshot.
+ *
+ * See Snapshot and Restore
+ * API on elastic.co
+ */
+ public void createSnapshotAsync(CreateSnapshotRequest createSnapshotRequest, RequestOptions options,
+ ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(createSnapshotRequest, RequestConverters::createSnapshot, options,
+ CreateSnapshotResponse::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 f2c4580e6e3a4..954dd77b07c01 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
@@ -37,6 +37,7 @@
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.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.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
@@ -1988,6 +1989,28 @@ public void testVerifyRepository() {
assertThat(expectedParams, equalTo(request.getParameters()));
}
+ public void testCreateSnapshot() throws IOException {
+ Map expectedParams = new HashMap<>();
+ String repository = randomIndicesNames(1, 1)[0];
+ String snapshot = "snapshot-" + generateRandomStringArray(1, randomInt(10), false, false)[0];
+ String endpoint = "/_snapshot/" + repository + "/" + snapshot;
+
+ CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repository, snapshot);
+ setRandomMasterTimeout(createSnapshotRequest, expectedParams);
+ Boolean waitForCompletion = randomBoolean();
+ createSnapshotRequest.waitForCompletion(waitForCompletion);
+
+ if (waitForCompletion) {
+ expectedParams.put("wait_for_completion", waitForCompletion.toString());
+ }
+
+ Request request = RequestConverters.createSnapshot(createSnapshotRequest);
+ assertThat(endpoint, equalTo(request.getEndpoint()));
+ assertThat(HttpPut.METHOD_NAME, equalTo(request.getMethod()));
+ assertThat(expectedParams, equalTo(request.getParameters()));
+ assertToXContentBody(createSnapshotRequest, 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 f4d325e158bc5..aacb2f5025ee4 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
@@ -28,6 +28,8 @@
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse;
import org.elasticsearch.common.xcontent.XContentType;
@@ -35,7 +37,6 @@
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
-import java.util.Locale;
import static org.hamcrest.Matchers.equalTo;
@@ -49,12 +50,12 @@ private PutRepositoryResponse createTestRepository(String repository, String typ
highLevelClient().snapshot()::createRepositoryAsync);
}
- private Response createTestSnapshot(String repository, String snapshot) throws IOException {
- Request createSnapshot = new Request("put", String.format(Locale.ROOT, "_snapshot/%s/%s", repository, snapshot));
- createSnapshot.addParameter("wait_for_completion", "true");
- return highLevelClient().getLowLevelClient().performRequest(createSnapshot);
- }
+ private CreateSnapshotResponse createTestSnapshot(CreateSnapshotRequest createSnapshotRequest) throws IOException {
+ // assumes the repository already exists
+ return execute(createSnapshotRequest, highLevelClient().snapshot()::createSnapshot,
+ highLevelClient().snapshot()::createSnapshotAsync);
+ }
public void testCreateRepository() throws IOException {
PutRepositoryResponse response = createTestRepository("test", FsRepository.TYPE, "{\"location\": \".\"}");
@@ -119,6 +120,21 @@ public void testVerifyRepository() throws IOException {
assertThat(response.getNodes().size(), equalTo(1));
}
+ public void testCreateSnapshot() throws IOException {
+ String repository = "test_repository";
+ assertTrue(createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}").isAcknowledged());
+
+ String snapshot = "test_snapshot";
+ CreateSnapshotRequest request = new CreateSnapshotRequest(repository, snapshot);
+ boolean waitForCompletion = randomBoolean();
+ request.waitForCompletion(waitForCompletion);
+ request.partial(randomBoolean());
+ request.includeGlobalState(randomBoolean());
+
+ CreateSnapshotResponse response = createTestSnapshot(request);
+ assertEquals(waitForCompletion ? RestStatus.OK : RestStatus.ACCEPTED, response.status());
+ }
+
public void testDeleteSnapshot() throws IOException {
String repository = "test_repository";
String snapshot = "test_snapshot";
@@ -126,9 +142,11 @@ public void testDeleteSnapshot() throws IOException {
PutRepositoryResponse putRepositoryResponse = createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}");
assertTrue(putRepositoryResponse.isAcknowledged());
- Response putSnapshotResponse = createTestSnapshot(repository, snapshot);
+ CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repository, snapshot);
+ createSnapshotRequest.waitForCompletion(true);
+ CreateSnapshotResponse createSnapshotResponse = createTestSnapshot(createSnapshotRequest);
// check that the request went ok without parsing JSON here. When using the high level client, check acknowledgement instead.
- assertEquals(200, putSnapshotResponse.getStatusLine().getStatusCode());
+ assertEquals(RestStatus.OK, createSnapshotResponse.status());
DeleteSnapshotRequest request = new DeleteSnapshotRequest(repository, snapshot);
DeleteSnapshotResponse response = execute(request, highLevelClient().snapshot()::delete, highLevelClient().snapshot()::deleteAsync);
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 965f9641e48ad..9c0e31bdcfb70 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java
@@ -29,6 +29,10 @@
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
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.indices.create.CreateIndexRequest;
+import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
@@ -41,6 +45,7 @@
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.repositories.fs.FsRepository;
+import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.util.HashMap;
@@ -367,6 +372,90 @@ public void onFailure(Exception e) {
}
}
+ public void testSnapshotCreate() throws IOException {
+ RestHighLevelClient client = highLevelClient();
+
+ CreateIndexRequest createIndexRequest = new CreateIndexRequest("test-index0");
+ client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
+ createIndexRequest = new CreateIndexRequest("test-index1");
+ client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
+
+ createTestRepositories();
+
+ // tag::create-snapshot-request
+ CreateSnapshotRequest request = new CreateSnapshotRequest();
+ // end::create-snapshot-request
+
+ // tag::create-snapshot-request-repositoryName
+ request.repository(repositoryName); // <1>
+ // end::create-snapshot-request-repositoryName
+ // tag::create-snapshot-request-snapshotName
+ request.snapshot(snapshotName); // <1>
+ // end::create-snapshot-request-snapshotName
+ // tag::create-snapshot-request-indices
+ request.indices("test-index0", "test-index1"); // <1>
+ // end::create-snapshot-request-indices
+ // tag::create-snapshot-request-indicesOptions
+ request.indicesOptions(IndicesOptions.fromOptions(false, false, true, true)); // <1>
+ // end::create-snapshot-request-indicesOptions
+ // tag::create-snapshot-request-partial
+ request.partial(false); // <1>
+ // end::create-snapshot-request-partial
+ // tag::create-snapshot-request-includeGlobalState
+ request.includeGlobalState(true); // <1>
+ // end::create-snapshot-request-includeGlobalState
+
+ // tag::create-snapshot-request-masterTimeout
+ request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+ request.masterNodeTimeout("1m"); // <2>
+ // end::create-snapshot-request-masterTimeout
+ // tag::create-snapshot-request-waitForCompletion
+ request.waitForCompletion(true); // <1>
+ // end::create-snapshot-request-waitForCompletion
+
+ // tag::create-snapshot-execute
+ CreateSnapshotResponse response = client.snapshot().createSnapshot(request, RequestOptions.DEFAULT);
+ // end::create-snapshot-execute
+
+ // tag::create-snapshot-response
+ RestStatus status = response.status(); // <1>
+ // end::create-snapshot-response
+
+ assertEquals(RestStatus.OK, status);
+ }
+
+ public void testSnapshotCreateAsync() throws InterruptedException {
+ RestHighLevelClient client = highLevelClient();
+ {
+ CreateSnapshotRequest request = new CreateSnapshotRequest(repositoryName, snapshotName);
+
+ // tag::create-snapshot-execute-listener
+ ActionListener listener =
+ new ActionListener() {
+ @Override
+ public void onResponse(CreateSnapshotResponse createSnapshotResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception exception) {
+ // <2>
+ }
+ };
+ // end::create-snapshot-execute-listener
+
+ // Replace the empty listener by a blocking listener in test
+ final CountDownLatch latch = new CountDownLatch(1);
+ listener = new LatchedActionListener<>(listener, latch);
+
+ // tag::create-snapshot-execute-async
+ client.snapshot().createSnapshotAsync(request, RequestOptions.DEFAULT, listener); // <1>
+ // end::create-snapshot-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/create_snapshot.asciidoc b/docs/java-rest/high-level/snapshot/create_snapshot.asciidoc
new file mode 100644
index 0000000000000..dbd31380a9b4b
--- /dev/null
+++ b/docs/java-rest/high-level/snapshot/create_snapshot.asciidoc
@@ -0,0 +1,121 @@
+[[java-rest-high-snapshot-create-snapshot]]
+=== Create Snapshot API
+
+Use the Create Snapshot API to create a new snapshot.
+
+[[java-rest-high-snapshot-create-snapshot-request]]
+==== Create Snapshot Request
+
+A `CreateSnapshotRequest`:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request]
+--------------------------------------------------
+
+==== Required Arguments
+The following arguments are mandatory:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-repositoryName]
+--------------------------------------------------
+<1> The name of the repository.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-snapshotName]
+--------------------------------------------------
+<1> The name of the snapshot.
+
+==== Optional Arguments
+The following arguments are optional:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-indices]
+--------------------------------------------------
+<1> A list of indices the snapshot is applied to.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-indicesOptions]
+--------------------------------------------------
+<1> Options applied to the indices.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-partial]
+--------------------------------------------------
+<1> Set `partial` to `true` to allow a successful snapshot without the
+availability of all the indices primary shards. Defaults to `false`.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-request-includeGlobalState]
+--------------------------------------------------
+<1> Set `includeGlobalState` to `false` to prevent writing the cluster's global
+state as part of the snapshot. Defaults to `true`.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-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[create-snapshot-request-waitForCompletion]
+--------------------------------------------------
+<1> Waits for the snapshot to be completed before a response is returned.
+
+[[java-rest-high-snapshot-create-snapshot-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-execute]
+--------------------------------------------------
+
+[[java-rest-high-snapshot-create-snapshot-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a create snapshot request requires both the
+`CreateSnapshotRequest` 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[create-snapshot-execute-async]
+--------------------------------------------------
+<1> The `CreateSnapshotRequest` 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 `CreateSnapshotResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-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-create-snapshot-response]]
+==== Snapshot Create Response
+
+Use the `CreateSnapshotResponse` to retrieve information about the evaluated
+request:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[create-snapshot-response]
+--------------------------------------------------
+<1> Indicates the node has started the request.
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index fa904b81cc41d..de5cd1ebd3dc1 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -142,12 +142,14 @@ The Java High Level REST Client supports the following Snapshot APIs:
* <>
* <>
* <>
+* <>
* <>
include::snapshot/get_repository.asciidoc[]
include::snapshot/create_repository.asciidoc[]
include::snapshot/delete_repository.asciidoc[]
include::snapshot/verify_repository.asciidoc[]
+include::snapshot/create_snapshot.asciidoc[]
include::snapshot/delete_snapshot.asciidoc[]
== Tasks APIs
diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
index 5d5f4685f03d2..2ff01ab01ed1f 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java
@@ -28,14 +28,17 @@
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.common.Strings.EMPTY_ARRAY;
@@ -58,7 +61,8 @@
* must not contain invalid file name characters {@link org.elasticsearch.common.Strings#INVALID_FILENAME_CHARS}
*
*/
-public class CreateSnapshotRequest extends MasterNodeRequest implements IndicesRequest.Replaceable {
+public class CreateSnapshotRequest extends MasterNodeRequest
+ implements IndicesRequest.Replaceable, ToXContentObject {
private String snapshot;
@@ -407,6 +411,34 @@ public CreateSnapshotRequest source(Map source) {
return this;
}
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field("repository", repository);
+ builder.field("snapshot", snapshot);
+ builder.startArray("indices");
+ for (String index : indices) {
+ builder.value(index);
+ }
+ builder.endArray();
+ builder.field("partial", partial);
+ if (settings != null) {
+ builder.startObject("settings");
+ if (settings.isEmpty() == false) {
+ settings.toXContent(builder, params);
+ }
+ builder.endObject();
+ }
+ builder.field("include_global_state", includeGlobalState);
+ if (indicesOptions != null) {
+ indicesOptions.toXContent(builder, params);
+ }
+ builder.field("wait_for_completion", waitForCompletion);
+ builder.field("master_node_timeout", masterNodeTimeout.toString());
+ builder.endObject();
+ return builder;
+ }
+
@Override
public void readFrom(StreamInput in) throws IOException {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
@@ -416,4 +448,42 @@ public void readFrom(StreamInput in) throws IOException {
public String getDescription() {
return "snapshot [" + repository + ":" + snapshot + "]";
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CreateSnapshotRequest that = (CreateSnapshotRequest) o;
+ return partial == that.partial &&
+ includeGlobalState == that.includeGlobalState &&
+ waitForCompletion == that.waitForCompletion &&
+ Objects.equals(snapshot, that.snapshot) &&
+ Objects.equals(repository, that.repository) &&
+ Arrays.equals(indices, that.indices) &&
+ Objects.equals(indicesOptions, that.indicesOptions) &&
+ Objects.equals(settings, that.settings) &&
+ Objects.equals(masterNodeTimeout, that.masterNodeTimeout);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(snapshot, repository, indicesOptions, partial, settings, includeGlobalState, waitForCompletion);
+ result = 31 * result + Arrays.hashCode(indices);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "CreateSnapshotRequest{" +
+ "snapshot='" + snapshot + '\'' +
+ ", repository='" + repository + '\'' +
+ ", indices=" + (indices == null ? null : Arrays.asList(indices)) +
+ ", indicesOptions=" + indicesOptions +
+ ", partial=" + partial +
+ ", settings=" + settings +
+ ", includeGlobalState=" + includeGlobalState +
+ ", waitForCompletion=" + waitForCompletion +
+ ", masterNodeTimeout=" + masterNodeTimeout +
+ '}';
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponse.java
index 1f9f77f9ed3df..a2dc02c5c8299 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponse.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponse.java
@@ -25,10 +25,13 @@
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
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 org.elasticsearch.snapshots.SnapshotInfo;
import java.io.IOException;
+import java.util.Objects;
/**
* Create snapshot response
@@ -45,6 +48,10 @@ public class CreateSnapshotResponse extends ActionResponse implements ToXContent
CreateSnapshotResponse() {
}
+ void setSnapshotInfo(SnapshotInfo snapshotInfo) {
+ this.snapshotInfo = snapshotInfo;
+ }
+
/**
* Returns snapshot information if snapshot was completed by the time this method returned or null otherwise.
*
@@ -93,4 +100,58 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.endObject();
return builder;
}
+
+ public static CreateSnapshotResponse fromXContent(XContentParser parser) throws IOException {
+ CreateSnapshotResponse createSnapshotResponse = new CreateSnapshotResponse();
+
+ parser.nextToken(); // move to '{'
+
+ if (parser.currentToken() != Token.START_OBJECT) {
+ throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['{']");
+ }
+
+ parser.nextToken(); // move to 'snapshot' || 'accepted'
+
+ if ("snapshot".equals(parser.currentName())) {
+ createSnapshotResponse.snapshotInfo = SnapshotInfo.fromXContent(parser);
+ } else if ("accepted".equals(parser.currentName())) {
+ parser.nextToken(); // move to 'accepted' field value
+
+ if (parser.booleanValue()) {
+ // ensure accepted is a boolean value
+ }
+
+ parser.nextToken(); // move past 'true'/'false'
+ } else {
+ throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "] expected ['snapshot', 'accepted']");
+ }
+
+ if (parser.currentToken() != Token.END_OBJECT) {
+ throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['}']");
+ }
+
+ parser.nextToken(); // move past '}'
+
+ return createSnapshotResponse;
+ }
+
+ @Override
+ public String toString() {
+ return "CreateSnapshotResponse{" +
+ "snapshotInfo=" + snapshotInfo +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CreateSnapshotResponse that = (CreateSnapshotResponse) o;
+ return Objects.equals(snapshotInfo, that.snapshotInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(snapshotInfo);
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java b/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java
index b284ec87dd42c..93641574bde12 100644
--- a/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java
+++ b/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java
@@ -22,12 +22,15 @@
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContentFragment;
+import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.RestRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -38,7 +41,7 @@
* Controls how to deal with unavailable concrete indices (closed or missing), how wildcard expressions are expanded
* to actual indices (all, closed or open indices) and how to deal with wildcard expressions that resolve to no indices.
*/
-public class IndicesOptions {
+public class IndicesOptions implements ToXContentFragment {
public enum WildcardStates {
OPEN,
@@ -313,6 +316,18 @@ public static IndicesOptions fromMap(Map map, IndicesOptions def
defaultSettings);
}
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startArray("expand_wildcards");
+ for (WildcardStates expandWildcard : expandWildcards) {
+ builder.value(expandWildcard.toString().toLowerCase(Locale.ROOT));
+ }
+ builder.endArray();
+ builder.field("ignore_unavailable", ignoreUnavailable());
+ builder.field("allow_no_indices", allowNoIndices());
+ return builder;
+ }
+
/**
* Returns true if the name represents a valid name for one of the indices option
* false otherwise
diff --git a/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java b/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java
index 618dd3b8bc3b9..893fd79f2d3ca 100644
--- a/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java
+++ b/server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java
@@ -257,7 +257,7 @@ protected void doStart() {
indexMetaDataFormat = new ChecksumBlobStoreFormat<>(INDEX_METADATA_CODEC, METADATA_NAME_FORMAT,
IndexMetaData::fromXContent, namedXContentRegistry, isCompress());
snapshotFormat = new ChecksumBlobStoreFormat<>(SNAPSHOT_CODEC, SNAPSHOT_NAME_FORMAT,
- SnapshotInfo::fromXContent, namedXContentRegistry, isCompress());
+ SnapshotInfo::fromXContentInternal, namedXContentRegistry, isCompress());
}
@Override
diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
index 073007f4225df..ddd7385056d55 100644
--- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
+++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java
@@ -23,18 +23,23 @@
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
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.io.stream.Writeable;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
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;
@@ -79,6 +84,170 @@ public final class SnapshotInfo implements Comparable, ToXContent,
private static final Comparator COMPARATOR =
Comparator.comparing(SnapshotInfo::startTime).thenComparing(SnapshotInfo::snapshotId);
+ private static final class SnapshotInfoBuilder {
+ private String snapshotName = null;
+ private String snapshotUUID = null;
+ private String state = null;
+ private String reason = null;
+ private List indices = null;
+ private long startTime = 0L;
+ private long endTime = 0L;
+ private ShardStatsBuilder shardStatsBuilder = null;
+ private Boolean includeGlobalState = null;
+ private int version = -1;
+ private List shardFailures = null;
+
+ private void setSnapshotName(String snapshotName) {
+ this.snapshotName = snapshotName;
+ }
+
+ private void setSnapshotUUID(String snapshotUUID) {
+ this.snapshotUUID = snapshotUUID;
+ }
+
+ private void setState(String state) {
+ this.state = state;
+ }
+
+ private void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ private void setIndices(List indices) {
+ this.indices = indices;
+ }
+
+ private void setStartTime(long startTime) {
+ this.startTime = startTime;
+ }
+
+ private void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+
+ private void setShardStatsBuilder(ShardStatsBuilder shardStatsBuilder) {
+ this.shardStatsBuilder = shardStatsBuilder;
+ }
+
+ private void setIncludeGlobalState(Boolean includeGlobalState) {
+ this.includeGlobalState = includeGlobalState;
+ }
+
+ 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 ignoreVersion(String version) {
+ // ignore extra field
+ }
+
+ private void ignoreStartTime(String startTime) {
+ // ignore extra field
+ }
+
+ private void ignoreEndTime(String endTime) {
+ // ignore extra field
+ }
+
+ private void ignoreDurationInMillis(long durationInMillis) {
+ // ignore extra field
+ }
+
+ private SnapshotInfo build() {
+ SnapshotId snapshotId = new SnapshotId(snapshotName, snapshotUUID);
+
+ if (indices == null) {
+ indices = Collections.emptyList();
+ }
+
+ SnapshotState snapshotState = state == null ? null : SnapshotState.valueOf(state);
+ Version version = this.version == -1 ? Version.CURRENT : Version.fromId(this.version);
+
+ int totalShards = shardStatsBuilder == null ? 0 : shardStatsBuilder.getTotalShards();
+ int successfulShards = shardStatsBuilder == null ? 0 : shardStatsBuilder.getSuccessfulShards();
+
+ if (shardFailures == null) {
+ shardFailures = new ArrayList<>();
+ }
+
+ return new SnapshotInfo(snapshotId, indices, snapshotState, reason, version, startTime, endTime,
+ totalShards, successfulShards, shardFailures, includeGlobalState);
+ }
+ }
+
+ private static final class ShardStatsBuilder {
+ private int totalShards;
+ private int successfulShards;
+
+ private void setTotalShards(int totalShards) {
+ this.totalShards = totalShards;
+ }
+
+ int getTotalShards() {
+ return totalShards;
+ }
+
+ private void setSuccessfulShards(int successfulShards) {
+ this.successfulShards = successfulShards;
+ }
+
+ int getSuccessfulShards() {
+ return successfulShards;
+ }
+
+ private void ignoreFailedShards(int failedShards) {
+ // ignore extra field
+ }
+ }
+
+ private static final ObjectParser SNAPSHOT_INFO_PARSER =
+ new ObjectParser<>(SnapshotInfoBuilder.class.getName(), SnapshotInfoBuilder::new);
+
+ private static final ObjectParser SHARD_STATS_PARSER =
+ new ObjectParser<>(ShardStatsBuilder.class.getName(), ShardStatsBuilder::new);
+
+ static {
+ SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setSnapshotName, new ParseField(SNAPSHOT));
+ SNAPSHOT_INFO_PARSER.declareString(SnapshotInfoBuilder::setSnapshotUUID, new ParseField(UUID));
+ 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));
+ SNAPSHOT_INFO_PARSER.declareLong(SnapshotInfoBuilder::setStartTime, new ParseField(START_TIME_IN_MILLIS));
+ SNAPSHOT_INFO_PARSER.declareLong(SnapshotInfoBuilder::setEndTime, new ParseField(END_TIME_IN_MILLIS));
+ 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.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));
+ SNAPSHOT_INFO_PARSER.declareLong(SnapshotInfoBuilder::ignoreDurationInMillis, new ParseField(DURATION_IN_MILLIS));
+
+ SHARD_STATS_PARSER.declareInt(ShardStatsBuilder::setTotalShards, new ParseField(TOTAL));
+ SHARD_STATS_PARSER.declareInt(ShardStatsBuilder::setSuccessfulShards, new ParseField(SUCCESSFUL));
+ SHARD_STATS_PARSER.declareInt(ShardStatsBuilder::ignoreFailedShards, new ParseField(FAILED));
+ }
+
private final SnapshotId snapshotId;
@Nullable
@@ -317,29 +486,21 @@ public int compareTo(final SnapshotInfo o) {
return COMPARATOR.compare(this, o);
}
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- final SnapshotInfo that = (SnapshotInfo) o;
- return startTime == that.startTime && snapshotId.equals(that.snapshotId);
- }
-
- @Override
- public int hashCode() {
- int result = snapshotId.hashCode();
- result = 31 * result + Long.hashCode(startTime);
- return result;
- }
-
@Override
public String toString() {
- return "SnapshotInfo[snapshotId=" + snapshotId + ", state=" + state + ", indices=" + indices + "]";
+ return "SnapshotInfo{" +
+ "snapshotId=" + snapshotId +
+ ", state=" + state +
+ ", reason='" + reason + '\'' +
+ ", indices=" + indices +
+ ", startTime=" + startTime +
+ ", endTime=" + endTime +
+ ", totalShards=" + totalShards +
+ ", successfulShards=" + successfulShards +
+ ", includeGlobalState=" + includeGlobalState +
+ ", version=" + version +
+ ", shardFailures=" + shardFailures +
+ '}';
}
/**
@@ -448,12 +609,30 @@ private XContentBuilder toXContentSnapshot(final XContentBuilder builder, final
return builder;
}
+ 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;
+ }
+
/**
* This method creates a SnapshotInfo from internal x-content. It does not
* handle x-content written with the external version as external x-content
* is only for display purposes and does not need to be parsed.
*/
- public static SnapshotInfo fromXContent(final XContentParser parser) throws IOException {
+ public static SnapshotInfo fromXContentInternal(final XContentParser parser) throws IOException {
String name = null;
String uuid = null;
Version version = Version.CURRENT;
@@ -607,4 +786,28 @@ private static SnapshotState snapshotState(final String reason, final List indices = new ArrayList<>();
+ int count = randomInt(3) + 1;
+
+ for (int i = 0; i < count; ++i) {
+ indices.add(randomAlphaOfLength(randomInt(3) + 2));
+ }
+
+ original.indices(indices);
+ }
+
+ if (randomBoolean()) { // replace
+ original.partial(randomBoolean());
+ }
+
+ if (randomBoolean()) { // replace
+ Map settings = new HashMap<>();
+ int count = randomInt(3) + 1;
+
+ for (int i = 0; i < count; ++i) {
+ settings.put(randomAlphaOfLength(randomInt(3) + 2), randomAlphaOfLength(randomInt(3) + 2));
+ }
+
+ }
+
+ if (randomBoolean()) { // replace
+ original.includeGlobalState(randomBoolean());
+ }
+
+ if (randomBoolean()) { // replace
+ IndicesOptions[] indicesOptions = new IndicesOptions[] {
+ IndicesOptions.STRICT_EXPAND_OPEN,
+ IndicesOptions.STRICT_EXPAND_OPEN_CLOSED,
+ IndicesOptions.LENIENT_EXPAND_OPEN,
+ IndicesOptions.STRICT_EXPAND_OPEN_FORBID_CLOSED,
+ IndicesOptions.STRICT_SINGLE_INDEX_NO_EXPAND_FORBID_CLOSED};
+
+ original.indicesOptions(randomFrom(indicesOptions));
+ }
+
+ if (randomBoolean()) { // replace
+ original.waitForCompletion(randomBoolean());
+ }
+
+ if (randomBoolean()) { // replace
+ original.masterNodeTimeout("60s");
+ }
+
+ XContentBuilder builder = original.toXContent(XContentFactory.jsonBuilder(), null);
+ XContentParser parser = XContentType.JSON.xContent().createParser(
+ NamedXContentRegistry.EMPTY, null, BytesReference.bytes(builder).streamInput());
+ Map map = parser.mapOrdered();
+ CreateSnapshotRequest processed = new CreateSnapshotRequest((String)map.get("repository"), (String)map.get("snapshot"));
+ processed.waitForCompletion((boolean)map.getOrDefault("wait_for_completion", false));
+ processed.masterNodeTimeout((String)map.getOrDefault("master_node_timeout", "30s"));
+ processed.source(map);
+
+ assertEquals(original, processed);
+ }
+}
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
new file mode 100644
index 0000000000000..bbfc9755bf215
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotResponseTests.java
@@ -0,0 +1,71 @@
+/*
+ * 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.create;
+
+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.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class CreateSnapshotResponseTests extends AbstractXContentTestCase {
+
+ @Override
+ protected CreateSnapshotResponse doParseInstance(XContentParser parser) throws IOException {
+ return CreateSnapshotResponse.fromXContent(parser);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return false;
+ }
+
+ @Override
+ protected CreateSnapshotResponse createTestInstance() {
+ SnapshotId snapshotId = new SnapshotId("test", UUID.randomUUID().toString());
+ List indices = new ArrayList<>();
+ indices.add("test0");
+ indices.add("test1");
+ String reason = "reason";
+ long startTime = System.currentTimeMillis();
+ long endTime = startTime + 10000;
+ int totalShards = randomIntBetween(1, 3);
+ int successfulShards = randomIntBetween(0, totalShards);
+ List shardFailures = new ArrayList<>();
+
+ for (int count = successfulShards; count < totalShards; ++count) {
+ shardFailures.add(new SnapshotShardFailure(
+ "node-id", new ShardId("index-" + count, UUID.randomUUID().toString(), randomInt()), "reason"));
+ }
+
+ boolean globalState = randomBoolean();
+
+ CreateSnapshotResponse response = new CreateSnapshotResponse();
+ response.setSnapshotInfo(
+ new SnapshotInfo(snapshotId, indices, startTime, reason, endTime, totalShards, shardFailures, globalState));
+ return response;
+ }
+}