Add Create Snapshot to High-Level Rest Client (#31215)

Added support to the high-level rest client for the create snapshot API call. This required 
several changes to toXContent which may need to be cleaned up in a later PR. Also 
added several parsers for fromXContent to be able to retrieve appropriate responses 
along with tests.
This commit is contained in:
Jack Conradson 2018-06-27 09:30:10 -07:00 committed by GitHub
parent 01623f66de
commit 61eefc84f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 877 additions and 34 deletions

View File

@ -37,6 +37,7 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe
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 @@ final class RequestConverters {
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())

View File

@ -28,6 +28,8 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequ
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 final class SnapshotClient {
VerifyRepositoryResponse::fromXContent, listener, emptySet());
}
/**
* Creates a snapshot.
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
* API on elastic.co</a>
*/
public CreateSnapshotResponse createSnapshot(CreateSnapshotRequest createSnapshotRequest, RequestOptions options)
throws IOException {
return restHighLevelClient.performRequestAndParseEntity(createSnapshotRequest, RequestConverters::createSnapshot, options,
CreateSnapshotResponse::fromXContent, emptySet());
}
/**
* Asynchronously creates a snapshot.
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
* API on elastic.co</a>
*/
public void createSnapshotAsync(CreateSnapshotRequest createSnapshotRequest, RequestOptions options,
ActionListener<CreateSnapshotResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(createSnapshotRequest, RequestConverters::createSnapshot, options,
CreateSnapshotResponse::fromXContent, listener, emptySet());
}
/**
* Deletes a snapshot.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore

View File

@ -37,6 +37,7 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe
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 class RequestConvertersTests extends ESTestCase {
assertThat(expectedParams, equalTo(request.getParameters()));
}
public void testCreateSnapshot() throws IOException {
Map<String, String> 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<String, String> expectedParams = new HashMap<>();
String repository = randomIndicesNames(1, 1)[0];

View File

@ -28,6 +28,8 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequ
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.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.util.Locale;
import static org.hamcrest.Matchers.equalTo;
@ -49,12 +50,12 @@ public class SnapshotIT extends ESRestHighLevelClientTestCase {
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 class SnapshotIT extends ESRestHighLevelClientTestCase {
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 class SnapshotIT extends ESRestHighLevelClientTestCase {
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);

View File

@ -29,6 +29,10 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequ
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.settings.Settings;
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 class SnapshotClientDocumentationIT extends ESRestHighLevelClientTestCase
}
}
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<CreateSnapshotResponse> listener =
new ActionListener<CreateSnapshotResponse>() {
@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();

View File

@ -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.

View File

@ -142,12 +142,14 @@ The Java High Level REST Client supports the following Snapshot APIs:
* <<java-rest-high-snapshot-create-repository>>
* <<java-rest-high-snapshot-delete-repository>>
* <<java-rest-high-snapshot-verify-repository>>
* <<java-rest-high-snapshot-create-snapshot>>
* <<java-rest-high-snapshot-delete-snapshot>>
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

View File

@ -28,14 +28,17 @@ import org.elasticsearch.common.Strings;
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 @@ import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBo
* <li>must not contain invalid file name characters {@link org.elasticsearch.common.Strings#INVALID_FILENAME_CHARS} </li>
* </ul>
*/
public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotRequest> implements IndicesRequest.Replaceable {
public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotRequest>
implements IndicesRequest.Replaceable, ToXContentObject {
private String snapshot;
@ -407,6 +411,34 @@ public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotReque
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 class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotReque
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 +
'}';
}
}

View File

@ -25,10 +25,13 @@ import org.elasticsearch.common.io.stream.StreamInput;
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 class CreateSnapshotResponse extends ActionResponse implements ToXContent
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);
}
}

View File

@ -22,12 +22,15 @@ package org.elasticsearch.action.support;
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 @@ import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeSt
* 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 class IndicesOptions {
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

View File

@ -257,7 +257,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
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

View File

@ -23,18 +23,23 @@ import org.elasticsearch.Version;
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<SnapshotInfo>, ToXContent,
private static final Comparator<SnapshotInfo> 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<String> indices = null;
private long startTime = 0L;
private long endTime = 0L;
private ShardStatsBuilder shardStatsBuilder = null;
private Boolean includeGlobalState = null;
private int version = -1;
private List<SnapshotShardFailure> 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<String> 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<SnapshotInfoBuilder, Void> SNAPSHOT_INFO_PARSER =
new ObjectParser<>(SnapshotInfoBuilder.class.getName(), SnapshotInfoBuilder::new);
private static final ObjectParser<ShardStatsBuilder, Void> 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 final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContent,
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 @@ public final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContent,
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 @@ public final class SnapshotInfo implements Comparable<SnapshotInfo>, ToXContent,
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SnapshotInfo that = (SnapshotInfo) o;
return startTime == that.startTime &&
endTime == that.endTime &&
totalShards == that.totalShards &&
successfulShards == that.successfulShards &&
Objects.equals(snapshotId, that.snapshotId) &&
state == that.state &&
Objects.equals(reason, that.reason) &&
Objects.equals(indices, that.indices) &&
Objects.equals(includeGlobalState, that.includeGlobalState) &&
Objects.equals(version, that.version) &&
Objects.equals(shardFailures, that.shardFailures);
}
@Override
public int hashCode() {
return Objects.hash(snapshotId, state, reason, indices, startTime, endTime,
totalShards, successfulShards, includeGlobalState, version, shardFailures);
}
}

View File

@ -33,6 +33,7 @@ import org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.util.Objects;
/**
* Stores information about failures that occurred during shard snapshotting process
@ -151,7 +152,12 @@ public class SnapshotShardFailure implements ShardOperationFailedException {
@Override
public String toString() {
return shardId + " failed, reason [" + reason + "]";
return "SnapshotShardFailure{" +
"shardId=" + shardId +
", reason='" + reason + '\'' +
", nodeId='" + nodeId + '\'' +
", status=" + status +
'}';
}
/**
@ -238,4 +244,23 @@ public class SnapshotShardFailure implements ShardOperationFailedException {
builder.field("status", status.name());
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SnapshotShardFailure that = (SnapshotShardFailure) o;
// customized to account for discrepancies in shardId/Index toXContent/fromXContent related to uuid
return shardId.id() == that.shardId.id() &&
shardId.getIndexName().equals(shardId.getIndexName()) &&
Objects.equals(reason, that.reason) &&
Objects.equals(nodeId, that.nodeId) &&
status.getStatus() == that.status.getStatus();
}
@Override
public int hashCode() {
// customized to account for discrepancies in shardId/Index toXContent/fromXContent related to uuid
return Objects.hash(shardId.id(), shardId.getIndexName(), reason, nodeId, status.getStatus());
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.action.support.IndicesOptions;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CreateSnapshotRequestTests extends ESTestCase {
// tests creating XContent and parsing with source(Map) equivalency
public void testToXContent() throws IOException {
String repo = randomAlphaOfLength(5);
String snap = randomAlphaOfLength(10);
CreateSnapshotRequest original = new CreateSnapshotRequest(repo, snap);
if (randomBoolean()) { // replace
List<String> 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<String, Object> 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<String, Object> 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);
}
}

View File

@ -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<CreateSnapshotResponse> {
@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<String> 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<SnapshotShardFailure> 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;
}
}