Add Restore Snapshot High Level REST API

With this commit we add the restore snapshot API to the Java high level
REST client.

Relates #27205
Relates #32155
This commit is contained in:
Daniel Mitterdorfer 2018-07-24 16:17:09 +02:00 committed by GitHub
parent 59cf600e03
commit 73a38895fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 732 additions and 12 deletions

View File

@ -40,6 +40,7 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@ -980,6 +981,20 @@ final class RequestConverters {
return request; return request;
} }
static Request restoreSnapshot(RestoreSnapshotRequest restoreSnapshotRequest) throws IOException {
String endpoint = new EndpointBuilder().addPathPartAsIs("_snapshot")
.addPathPart(restoreSnapshotRequest.repository())
.addPathPart(restoreSnapshotRequest.snapshot())
.addPathPartAsIs("_restore")
.build();
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
Params parameters = new Params(request);
parameters.withMasterTimeout(restoreSnapshotRequest.masterNodeTimeout());
parameters.withWaitForCompletion(restoreSnapshotRequest.waitForCompletion());
request.setEntity(createEntity(restoreSnapshotRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
static Request deleteSnapshot(DeleteSnapshotRequest deleteSnapshotRequest) { static Request deleteSnapshot(DeleteSnapshotRequest deleteSnapshotRequest) {
String endpoint = new EndpointBuilder().addPathPartAsIs("_snapshot") String endpoint = new EndpointBuilder().addPathPartAsIs("_snapshot")
.addPathPart(deleteSnapshotRequest.repository()) .addPathPart(deleteSnapshotRequest.repository())

View File

@ -30,6 +30,8 @@ import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyReposito
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; 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.CreateSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@ -252,6 +254,36 @@ public final class SnapshotClient {
SnapshotsStatusResponse::fromXContent, listener, emptySet()); SnapshotsStatusResponse::fromXContent, listener, emptySet());
} }
/**
* Restores a snapshot.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
* API on elastic.co</a>
*
* @param restoreSnapshotRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public RestoreSnapshotResponse restore(RestoreSnapshotRequest restoreSnapshotRequest, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(restoreSnapshotRequest, RequestConverters::restoreSnapshot, options,
RestoreSnapshotResponse::fromXContent, emptySet());
}
/**
* Asynchronously restores a snapshot.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
* API on elastic.co</a>
*
* @param restoreSnapshotRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
*/
public void restoreAsync(RestoreSnapshotRequest restoreSnapshotRequest, RequestOptions options,
ActionListener<RestoreSnapshotResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(restoreSnapshotRequest, RequestConverters::restoreSnapshot, options,
RestoreSnapshotResponse::fromXContent, listener, emptySet());
}
/** /**
* Deletes a snapshot. * Deletes a snapshot.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore

View File

@ -41,6 +41,7 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequ
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
@ -2198,6 +2199,31 @@ public class RequestConvertersTests extends ESTestCase {
assertThat(request.getEntity(), is(nullValue())); assertThat(request.getEntity(), is(nullValue()));
} }
public void testRestoreSnapshot() throws IOException {
Map<String, String> expectedParams = new HashMap<>();
String repository = randomIndicesNames(1, 1)[0];
String snapshot = "snapshot-" + randomAlphaOfLengthBetween(2, 5).toLowerCase(Locale.ROOT);
String endpoint = String.format(Locale.ROOT, "/_snapshot/%s/%s/_restore", repository, snapshot);
RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(repository, snapshot);
setRandomMasterTimeout(restoreSnapshotRequest, expectedParams);
if (randomBoolean()) {
restoreSnapshotRequest.waitForCompletion(true);
expectedParams.put("wait_for_completion", "true");
}
if (randomBoolean()) {
String timeout = randomTimeValue();
restoreSnapshotRequest.masterNodeTimeout(timeout);
expectedParams.put("master_timeout", timeout);
}
Request request = RequestConverters.restoreSnapshot(restoreSnapshotRequest);
assertThat(endpoint, equalTo(request.getEndpoint()));
assertThat(HttpPost.METHOD_NAME, equalTo(request.getMethod()));
assertThat(expectedParams, equalTo(request.getParameters()));
assertToXContentBody(restoreSnapshotRequest, request.getEntity());
}
public void testDeleteSnapshot() { public void testDeleteSnapshot() {
Map<String, String> expectedParams = new HashMap<>(); Map<String, String> expectedParams = new HashMap<>();
String repository = randomIndicesNames(1, 1)[0]; String repository = randomIndicesNames(1, 1)[0];

View File

@ -665,7 +665,6 @@ public class RestHighLevelClientTests extends ESTestCase {
"reindex_rethrottle", "reindex_rethrottle",
"render_search_template", "render_search_template",
"scripts_painless_execute", "scripts_painless_execute",
"snapshot.restore",
"tasks.get", "tasks.get",
"termvectors", "termvectors",
"update_by_query" "update_by_query"

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.put.PutRepositoryResponse;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest;
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -40,12 +42,15 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.snapshots.RestoreInfo;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
public class SnapshotIT extends ESRestHighLevelClientTestCase { public class SnapshotIT extends ESRestHighLevelClientTestCase {
@ -205,6 +210,42 @@ public class SnapshotIT extends ESRestHighLevelClientTestCase {
assertThat(response.getSnapshots().get(0).getIndices().containsKey(testIndex), is(true)); assertThat(response.getSnapshots().get(0).getIndices().containsKey(testIndex), is(true));
} }
public void testRestoreSnapshot() throws IOException {
String testRepository = "test";
String testSnapshot = "snapshot_1";
String testIndex = "test_index";
String restoredIndex = testIndex + "_restored";
PutRepositoryResponse putRepositoryResponse = createTestRepository(testRepository, FsRepository.TYPE, "{\"location\": \".\"}");
assertTrue(putRepositoryResponse.isAcknowledged());
createIndex(testIndex, Settings.EMPTY);
assertTrue("index [" + testIndex + "] should have been created", indexExists(testIndex));
CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(testRepository, testSnapshot);
createSnapshotRequest.indices(testIndex);
createSnapshotRequest.waitForCompletion(true);
CreateSnapshotResponse createSnapshotResponse = createTestSnapshot(createSnapshotRequest);
assertEquals(RestStatus.OK, createSnapshotResponse.status());
deleteIndex(testIndex);
assertFalse("index [" + testIndex + "] should have been deleted", indexExists(testIndex));
RestoreSnapshotRequest request = new RestoreSnapshotRequest(testRepository, testSnapshot);
request.waitForCompletion(true);
request.renamePattern(testIndex);
request.renameReplacement(restoredIndex);
RestoreSnapshotResponse response = execute(request, highLevelClient().snapshot()::restore,
highLevelClient().snapshot()::restoreAsync);
RestoreInfo restoreInfo = response.getRestoreInfo();
assertThat(restoreInfo.name(), equalTo(testSnapshot));
assertThat(restoreInfo.indices(), equalTo(Collections.singletonList(restoredIndex)));
assertThat(restoreInfo.successfulShards(), greaterThan(0));
assertThat(restoreInfo.failedShards(), equalTo(0));
}
public void testDeleteSnapshot() throws IOException { public void testDeleteSnapshot() throws IOException {
String repository = "test_repository"; String repository = "test_repository";
String snapshot = "test_snapshot"; String snapshot = "test_snapshot";

View File

@ -33,6 +33,8 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotReq
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@ -53,12 +55,15 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.snapshots.RestoreInfo;
import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotId;
import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotInfo;
import org.elasticsearch.snapshots.SnapshotShardFailure; import org.elasticsearch.snapshots.SnapshotShardFailure;
import org.elasticsearch.snapshots.SnapshotState; import org.elasticsearch.snapshots.SnapshotState;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -263,6 +268,107 @@ public class SnapshotClientDocumentationIT extends ESRestHighLevelClientTestCase
} }
} }
public void testRestoreSnapshot() throws IOException {
RestHighLevelClient client = highLevelClient();
createTestRepositories();
createTestIndex();
createTestSnapshots();
// tag::restore-snapshot-request
RestoreSnapshotRequest request = new RestoreSnapshotRequest(repositoryName, snapshotName);
// end::restore-snapshot-request
// we need to restore as a different index name
// tag::restore-snapshot-request-masterTimeout
request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
request.masterNodeTimeout("1m"); // <2>
// end::restore-snapshot-request-masterTimeout
// tag::restore-snapshot-request-waitForCompletion
request.waitForCompletion(true); // <1>
// end::restore-snapshot-request-waitForCompletion
// tag::restore-snapshot-request-partial
request.partial(false); // <1>
// end::restore-snapshot-request-partial
// tag::restore-snapshot-request-include-global-state
request.includeGlobalState(false); // <1>
// end::restore-snapshot-request-include-global-state
// tag::restore-snapshot-request-include-aliases
request.includeAliases(false); // <1>
// end::restore-snapshot-request-include-aliases
// tag::restore-snapshot-request-indices
request.indices("test_index");
// end::restore-snapshot-request-indices
String restoredIndexName = "restored_index";
// tag::restore-snapshot-request-rename
request.renamePattern("test_(.+)"); // <1>
request.renameReplacement("restored_$1"); // <2>
// end::restore-snapshot-request-rename
// tag::restore-snapshot-request-index-settings
request.indexSettings( // <1>
Settings.builder()
.put("index.number_of_replicas", 0)
.build());
request.ignoreIndexSettings("index.refresh_interval", "index.search.idle.after"); // <2>
request.indicesOptions(new IndicesOptions( // <3>
EnumSet.of(IndicesOptions.Option.IGNORE_UNAVAILABLE),
EnumSet.of(IndicesOptions.WildcardStates.OPEN)));
// end::restore-snapshot-request-index-settings
// tag::restore-snapshot-execute
RestoreSnapshotResponse response = client.snapshot().restore(request, RequestOptions.DEFAULT);
// end::restore-snapshot-execute
// tag::restore-snapshot-response
RestoreInfo restoreInfo = response.getRestoreInfo();
List<String> indices = restoreInfo.indices(); // <1>
// end::restore-snapshot-response
assertEquals(Collections.singletonList(restoredIndexName), indices);
assertEquals(0, restoreInfo.failedShards());
assertTrue(restoreInfo.successfulShards() > 0);
}
public void testRestoreSnapshotAsync() throws InterruptedException {
RestHighLevelClient client = highLevelClient();
{
RestoreSnapshotRequest request = new RestoreSnapshotRequest();
// tag::restore-snapshot-execute-listener
ActionListener<RestoreSnapshotResponse> listener =
new ActionListener<RestoreSnapshotResponse>() {
@Override
public void onResponse(RestoreSnapshotResponse restoreSnapshotResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::restore-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::restore-snapshot-execute-async
client.snapshot().restoreAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::restore-snapshot-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
public void testSnapshotDeleteRepository() throws IOException { public void testSnapshotDeleteRepository() throws IOException {
RestHighLevelClient client = highLevelClient(); RestHighLevelClient client = highLevelClient();

View File

@ -0,0 +1,144 @@
[[java-rest-high-snapshot-restore-snapshot]]
=== Restore Snapshot API
The Restore Snapshot API allows to restore a snapshot.
[[java-rest-high-snapshot-restore-snapshot-request]]
==== Restore Snapshot Request
A `RestoreSnapshotRequest`:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request]
--------------------------------------------------
==== Limiting Indices to Restore
By default all indices are restored. With the `indices` property you can
provide a list of indices that should be restored:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-indices]
--------------------------------------------------
<1> Request that Elasticsearch only restores "test_index".
==== Renaming Indices
You can rename indices using regular expressions when restoring a snapshot:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-rename]
--------------------------------------------------
<1> A regular expression matching the indices that should be renamed.
<2> A replacement pattern that references the group from the regular
expression as `$1`. "test_index" from the snapshot is restored as
"restored_index" in this example.
==== Index Settings and Options
You can also customize index settings and options when restoring:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-index-settings]
--------------------------------------------------
<1> Use `#indexSettings()` to set any specific index setting for the indices
that are restored.
<2> Use `#ignoreIndexSettings()` to provide index settings that should be
ignored from the original indices.
<3> Set `IndicesOptions.Option.IGNORE_UNAVAILABLE` in `#indicesOptions()` to
have the restore succeed even if indices are missing in the snapshot.
==== Further Arguments
The following arguments can optionally be provided:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-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[restore-snapshot-request-waitForCompletion]
--------------------------------------------------
<1> Boolean indicating whether to wait until the snapshot has been restored.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-partial]
--------------------------------------------------
<1> Boolean indicating whether the entire snapshot should succeed although one
or more indices participating in the snapshot dont have all primary
shards available.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-include-global-state]
--------------------------------------------------
<1> Boolean indicating whether restored templates that dont currently exist
in the cluster are added and existing templates with the same name are
replaced by the restored templates. The restored persistent settings are
added to the existing persistent settings.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-request-include-aliases]
--------------------------------------------------
<1> Boolean to control whether aliases should be restored. Set to `false` to
prevent aliases from being restored together with associated indices.
[[java-rest-high-snapshot-restore-snapshot-sync]]
==== Synchronous Execution
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-execute]
--------------------------------------------------
[[java-rest-high-snapshot-restore-snapshot-async]]
==== Asynchronous Execution
The asynchronous execution of a restore snapshot request requires both the
`RestoreSnapshotRequest` instance and an `ActionListener` instance to be
passed to the asynchronous method:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-execute-async]
--------------------------------------------------
<1> The `RestoreSnapshotRequest` 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 using the `onResponse` method
if the execution successfully completed or using the `onFailure` method if
it failed.
A typical listener for `RestoreSnapshotResponse` looks like:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-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-cluster-restore-snapshot-response]]
==== Restore Snapshot Response
The returned `RestoreSnapshotResponse` allows to retrieve information about the
executed operation as follows:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SnapshotClientDocumentationIT.java[restore-snapshot-response]
--------------------------------------------------
<1> The `RestoreInfo` contains details about the restored snapshot like the indices or
the number of successfully restored and failed shards.

View File

@ -27,14 +27,17 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError; import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.common.settings.Settings.readSettingsFromStream; import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
@ -45,7 +48,7 @@ import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBo
/** /**
* Restore snapshot request * Restore snapshot request
*/ */
public class RestoreSnapshotRequest extends MasterNodeRequest<RestoreSnapshotRequest> { public class RestoreSnapshotRequest extends MasterNodeRequest<RestoreSnapshotRequest> implements ToXContentObject {
private String snapshot; private String snapshot;
private String repository; private String repository;
@ -563,6 +566,49 @@ public class RestoreSnapshotRequest extends MasterNodeRequest<RestoreSnapshotReq
return this; return this;
} }
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.startArray("indices");
for (String index : indices) {
builder.value(index);
}
builder.endArray();
if (indicesOptions != null) {
indicesOptions.toXContent(builder, params);
}
if (renamePattern != null) {
builder.field("rename_pattern", renamePattern);
}
if (renameReplacement != null) {
builder.field("rename_replacement", renameReplacement);
}
builder.field("include_global_state", includeGlobalState);
builder.field("partial", partial);
builder.field("include_aliases", includeAliases);
if (settings != null) {
builder.startObject("settings");
if (settings.isEmpty() == false) {
settings.toXContent(builder, params);
}
builder.endObject();
}
if (indexSettings != null) {
builder.startObject("index_settings");
if (indexSettings.isEmpty() == false) {
indexSettings.toXContent(builder, params);
}
builder.endObject();
}
builder.startArray("ignore_index_settings");
for (String ignoreIndexSetting : ignoreIndexSettings) {
builder.value(ignoreIndexSetting);
}
builder.endArray();
builder.endObject();
return builder;
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable"); throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
@ -573,4 +619,37 @@ public class RestoreSnapshotRequest extends MasterNodeRequest<RestoreSnapshotReq
return "snapshot [" + repository + ":" + snapshot + "]"; return "snapshot [" + repository + ":" + snapshot + "]";
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RestoreSnapshotRequest that = (RestoreSnapshotRequest) o;
return waitForCompletion == that.waitForCompletion &&
includeGlobalState == that.includeGlobalState &&
partial == that.partial &&
includeAliases == that.includeAliases &&
Objects.equals(snapshot, that.snapshot) &&
Objects.equals(repository, that.repository) &&
Arrays.equals(indices, that.indices) &&
Objects.equals(indicesOptions, that.indicesOptions) &&
Objects.equals(renamePattern, that.renamePattern) &&
Objects.equals(renameReplacement, that.renameReplacement) &&
Objects.equals(settings, that.settings) &&
Objects.equals(indexSettings, that.indexSettings) &&
Arrays.equals(ignoreIndexSettings, that.ignoreIndexSettings);
}
@Override
public int hashCode() {
int result = Objects.hash(snapshot, repository, indicesOptions, renamePattern, renameReplacement, waitForCompletion,
includeGlobalState, partial, includeAliases, settings, indexSettings);
result = 31 * result + Arrays.hashCode(indices);
result = 31 * result + Arrays.hashCode(ignoreIndexSettings);
return result;
}
@Override
public String toString() {
return Strings.toString(this);
}
} }

View File

@ -21,15 +21,21 @@ package org.elasticsearch.action.admin.cluster.snapshots.restore;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.snapshots.RestoreInfo; import org.elasticsearch.snapshots.RestoreInfo;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/** /**
* Contains information about restores snapshot * Contains information about restores snapshot
@ -86,4 +92,42 @@ public class RestoreSnapshotResponse extends ActionResponse implements ToXConten
builder.endObject(); builder.endObject();
return builder; return builder;
} }
public static final ConstructingObjectParser<RestoreSnapshotResponse, Void> PARSER = new ConstructingObjectParser<>(
"restore_snapshot", true, v -> {
RestoreInfo restoreInfo = (RestoreInfo) v[0];
Boolean accepted = (Boolean) v[1];
assert (accepted == null && restoreInfo != null) ||
(accepted != null && accepted && restoreInfo == null) :
"accepted: [" + accepted + "], restoreInfo: [" + restoreInfo + "]";
return new RestoreSnapshotResponse(restoreInfo);
});
static {
PARSER.declareObject(optionalConstructorArg(), (parser, context) -> RestoreInfo.fromXContent(parser), new ParseField("snapshot"));
PARSER.declareBoolean(optionalConstructorArg(), new ParseField("accepted"));
}
public static RestoreSnapshotResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RestoreSnapshotResponse that = (RestoreSnapshotResponse) o;
return Objects.equals(restoreInfo, that.restoreInfo);
}
@Override
public int hashCode() {
return Objects.hash(restoreInfo);
}
@Override
public String toString() {
return "RestoreSnapshotResponse{" + "restoreInfo=" + restoreInfo + '}';
}
} }

View File

@ -18,18 +18,22 @@
*/ */
package org.elasticsearch.snapshots; package org.elasticsearch.snapshots;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.ToXContent.Params; import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Information about successfully completed restore operation. * Information about successfully completed restore operation.
@ -120,9 +124,6 @@ public class RestoreInfo implements ToXContentObject, Streamable {
static final String SUCCESSFUL = "successful"; static final String SUCCESSFUL = "successful";
} }
/**
* {@inheritDoc}
*/
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
@ -141,9 +142,23 @@ public class RestoreInfo implements ToXContentObject, Streamable {
return builder; return builder;
} }
/** private static final ObjectParser<RestoreInfo, Void> PARSER = new ObjectParser<>(RestoreInfo.class.getName(), true, RestoreInfo::new);
* {@inheritDoc}
*/ static {
ObjectParser<RestoreInfo, Void> shardsParser = new ObjectParser<>("shards", true, null);
shardsParser.declareInt((r, s) -> r.totalShards = s, new ParseField(Fields.TOTAL));
shardsParser.declareInt((r, s) -> { /* only consume, don't set */ }, new ParseField(Fields.FAILED));
shardsParser.declareInt((r, s) -> r.successfulShards = s, new ParseField(Fields.SUCCESSFUL));
PARSER.declareString((r, n) -> r.name = n, new ParseField(Fields.SNAPSHOT));
PARSER.declareStringArray((r, i) -> r.indices = i, new ParseField(Fields.INDICES));
PARSER.declareField(shardsParser::parse, new ParseField(Fields.SHARDS), ObjectParser.ValueType.OBJECT);
}
public static RestoreInfo fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
name = in.readString(); name = in.readString();
@ -157,9 +172,6 @@ public class RestoreInfo implements ToXContentObject, Streamable {
successfulShards = in.readVInt(); successfulShards = in.readVInt();
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(name); out.writeString(name);
@ -193,4 +205,24 @@ public class RestoreInfo implements ToXContentObject, Streamable {
return in.readOptionalStreamable(RestoreInfo::new); return in.readOptionalStreamable(RestoreInfo::new);
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RestoreInfo that = (RestoreInfo) o;
return totalShards == that.totalShards &&
successfulShards == that.successfulShards &&
Objects.equals(name, that.name) &&
Objects.equals(indices, that.indices);
}
@Override
public int hashCode() {
return Objects.hash(name, indices, totalShards, successfulShards);
}
@Override
public String toString() {
return Strings.toString(this);
}
} }

View File

@ -0,0 +1,141 @@
/*
* 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.restore;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
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.AbstractWireSerializingTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RestoreSnapshotRequestTests extends AbstractWireSerializingTestCase<RestoreSnapshotRequest> {
private RestoreSnapshotRequest randomState(RestoreSnapshotRequest instance) {
if (randomBoolean()) {
List<String> indices = new ArrayList<>();
int count = randomInt(3) + 1;
for (int i = 0; i < count; ++i) {
indices.add(randomAlphaOfLength(randomInt(3) + 2));
}
instance.indices(indices);
}
if (randomBoolean()) {
instance.renamePattern(randomUnicodeOfLengthBetween(1, 100));
}
if (randomBoolean()) {
instance.renameReplacement(randomUnicodeOfLengthBetween(1, 100));
}
instance.partial(randomBoolean());
instance.includeAliases(randomBoolean());
if (randomBoolean()) {
Map<String, Object> settings = new HashMap<>();
int count = randomInt(3) + 1;
for (int i = 0; i < count; ++i) {
settings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5));
}
instance.settings(settings);
}
if (randomBoolean()) {
Map<String, Object> indexSettings = new HashMap<>();
int count = randomInt(3) + 1;
for (int i = 0; i < count; ++i) {
indexSettings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5));;
}
instance.indexSettings(indexSettings);
}
instance.includeGlobalState(randomBoolean());
if (randomBoolean()) {
Collection<IndicesOptions.WildcardStates> wildcardStates = randomSubsetOf(
Arrays.asList(IndicesOptions.WildcardStates.values()));
Collection<IndicesOptions.Option> options = randomSubsetOf(
Arrays.asList(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE));
instance.indicesOptions(new IndicesOptions(
options.isEmpty() ? IndicesOptions.Option.NONE : EnumSet.copyOf(options),
wildcardStates.isEmpty() ? IndicesOptions.WildcardStates.NONE : EnumSet.copyOf(wildcardStates)));
}
instance.waitForCompletion(randomBoolean());
if (randomBoolean()) {
instance.masterNodeTimeout(randomTimeValue());
}
return instance;
}
@Override
protected RestoreSnapshotRequest createTestInstance() {
return randomState(new RestoreSnapshotRequest(randomAlphaOfLength(5), randomAlphaOfLength(10)));
}
@Override
protected Writeable.Reader<RestoreSnapshotRequest> instanceReader() {
return RestoreSnapshotRequest::new;
}
@Override
protected RestoreSnapshotRequest mutateInstance(RestoreSnapshotRequest instance) throws IOException {
RestoreSnapshotRequest copy = copyInstance(instance);
// ensure that at least one property is different
copy.repository("copied-" + instance.repository());
return randomState(copy);
}
public void testSource() throws IOException {
RestoreSnapshotRequest original = createTestInstance();
XContentBuilder builder = original.toXContent(XContentFactory.jsonBuilder(), new ToXContent.MapParams(Collections.emptyMap()));
XContentParser parser = XContentType.JSON.xContent().createParser(
NamedXContentRegistry.EMPTY, null, BytesReference.bytes(builder).streamInput());
Map<String, Object> map = parser.mapOrdered();
// we will only restore properties from the map that are contained in the request body. All other
// properties are restored from the original (in the actual REST action this is restored from the
// REST path and request parameters).
RestoreSnapshotRequest processed = new RestoreSnapshotRequest(original.repository(), original.snapshot());
processed.masterNodeTimeout(original.masterNodeTimeout());
processed.waitForCompletion(original.waitForCompletion());
processed.source(map);
assertEquals(original, processed);
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.restore;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.snapshots.RestoreInfo;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class RestoreSnapshotResponseTests extends AbstractXContentTestCase<RestoreSnapshotResponse> {
@Override
protected RestoreSnapshotResponse createTestInstance() {
if (randomBoolean()) {
String name = randomRealisticUnicodeOfCodepointLengthBetween(1, 30);
List<String> indices = new ArrayList<>();
indices.add("test0");
indices.add("test1");
int totalShards = randomIntBetween(1, 1000);
int successfulShards = randomIntBetween(0, totalShards);
return new RestoreSnapshotResponse(new RestoreInfo(name, indices, totalShards, successfulShards));
} else {
return new RestoreSnapshotResponse(null);
}
}
@Override
protected RestoreSnapshotResponse doParseInstance(XContentParser parser) throws IOException {
return RestoreSnapshotResponse.fromXContent(parser);
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -536,6 +536,11 @@ public abstract class ESRestTestCase extends ESTestCase {
client().performRequest(request); client().performRequest(request);
} }
protected static void deleteIndex(String name) throws IOException {
Request request = new Request("DELETE", "/" + name);
client().performRequest(request);
}
protected static void updateIndexSettings(String index, Settings.Builder settings) throws IOException { protected static void updateIndexSettings(String index, Settings.Builder settings) throws IOException {
updateIndexSettings(index, settings.build()); updateIndexSettings(index, settings.build());
} }