From ba0618db80138bbf181e8e9244e996afeaa116a8 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Wed, 18 Jul 2018 07:31:35 +0200 Subject: [PATCH 1/4] 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 --- .../client/RequestConverters.java | 15 ++ .../elasticsearch/client/SnapshotClient.java | 32 ++++ .../client/RequestConvertersTests.java | 26 ++++ .../client/RestHighLevelClientTests.java | 1 - .../org/elasticsearch/client/SnapshotIT.java | 37 +++++ .../SnapshotClientDocumentationIT.java | 106 +++++++++++++ .../snapshot/restore_snapshot.asciidoc | 144 ++++++++++++++++++ .../restore/RestoreSnapshotRequest.java | 96 +++++++++++- .../restore/RestoreSnapshotResponse.java | 54 +++++++ .../elasticsearch/snapshots/RestoreInfo.java | 101 +++++++++++- .../restore/RestoreSnapshotRequestTests.java | 124 +++++++++++++++ .../restore/RestoreSnapshotResponseTests.java | 56 +++++++ 12 files changed, 787 insertions(+), 5 deletions(-) create mode 100644 docs/java-rest/high-level/snapshot/restore_snapshot.asciidoc create mode 100644 server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java create mode 100644 server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index 9dbd4916c774b..fd10e515ac9ac 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -40,6 +40,7 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.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.GetStoredScriptRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; @@ -979,6 +980,20 @@ static Request snapshotsStatus(SnapshotsStatusRequest snapshotsStatusRequest) { 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) { String endpoint = new EndpointBuilder().addPathPartAsIs("_snapshot") .addPathPart(deleteSnapshotRequest.repository()) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java index ae115839baeaf..319eb96a9f899 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java @@ -30,6 +30,8 @@ import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.elasticsearch.action.admin.cluster.snapshots.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.SnapshotsStatusResponse; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; @@ -252,6 +254,36 @@ public void statusAsync(SnapshotsStatusRequest snapshotsStatusRequest, RequestOp SnapshotsStatusResponse::fromXContent, listener, emptySet()); } + /** + * Restores a snapshot. + * See Snapshot and Restore + * API on elastic.co + * + * @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 Snapshot and Restore + * API on elastic.co + * + * @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 listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(restoreSnapshotRequest, RequestConverters::restoreSnapshot, options, + RestoreSnapshotResponse::fromXContent, listener, emptySet()); + } + /** * Deletes a snapshot. * See Snapshot and Restore diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index fb4e3b22712f5..abb84defa2fe9 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -41,6 +41,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; +import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; @@ -2196,6 +2197,31 @@ public void testSnapshotsStatus() { assertThat(request.getEntity(), is(nullValue())); } + public void testRestoreSnapshot() throws IOException { + Map 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); + Boolean waitForCompletion = randomBoolean(); + restoreSnapshotRequest.waitForCompletion(waitForCompletion); + if (waitForCompletion) { + expectedParams.put("wait_for_completion", waitForCompletion.toString()); + } + if (randomBoolean()) { + restoreSnapshotRequest.masterNodeTimeout("120s"); + expectedParams.put("master_timeout", "120s"); + } + + 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() { Map expectedParams = new HashMap<>(); String repository = randomIndicesNames(1, 1)[0]; diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index 47870125aa299..0144d262b3b2d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -661,7 +661,6 @@ public void testApiNamingConventions() throws Exception { "reindex_rethrottle", "render_search_template", "scripts_painless_execute", - "snapshot.restore", "tasks.get", "termvectors", "update_by_query" diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java index 5483f055c2c12..74e6309b94910 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java @@ -28,6 +28,8 @@ import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse; +import org.elasticsearch.action.admin.cluster.snapshots.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.SnapshotsStatusResponse; import org.elasticsearch.common.settings.Settings; @@ -40,12 +42,15 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.snapshots.RestoreInfo; import java.io.IOException; +import java.util.Collections; import java.util.stream.Collectors; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; public class SnapshotIT extends ESRestHighLevelClientTestCase { @@ -205,6 +210,38 @@ public void testSnapshotsStatus() throws IOException { 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); + + CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(testRepository, testSnapshot); + createSnapshotRequest.indices(testIndex); + createSnapshotRequest.waitForCompletion(true); + CreateSnapshotResponse createSnapshotResponse = createTestSnapshot(createSnapshotRequest); + assertEquals(RestStatus.OK, createSnapshotResponse.status()); + + 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 { String repository = "test_repository"; String snapshot = "test_snapshot"; diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java index fff3e7ece7066..922fcb984d94a 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SnapshotClientDocumentationIT.java @@ -33,6 +33,8 @@ import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; +import org.elasticsearch.action.admin.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.support.IndicesOptions; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; @@ -53,12 +55,15 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.snapshots.RestoreInfo; import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotShardFailure; import org.elasticsearch.snapshots.SnapshotState; import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -263,6 +268,107 @@ public void onFailure(Exception e) { } } + 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 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 listener = + new ActionListener() { + @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 { RestHighLevelClient client = highLevelClient(); diff --git a/docs/java-rest/high-level/snapshot/restore_snapshot.asciidoc b/docs/java-rest/high-level/snapshot/restore_snapshot.asciidoc new file mode 100644 index 0000000000000..a4b83ca419a41 --- /dev/null +++ b/docs/java-rest/high-level/snapshot/restore_snapshot.asciidoc @@ -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 don’t 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 don’t 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. diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java index c1b8c73c9ef0f..604159d3b8c29 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java @@ -27,14 +27,17 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import static org.elasticsearch.action.ValidateActions.addValidationError; import static org.elasticsearch.common.settings.Settings.readSettingsFromStream; @@ -45,7 +48,7 @@ /** * Restore snapshot request */ -public class RestoreSnapshotRequest extends MasterNodeRequest { +public class RestoreSnapshotRequest extends MasterNodeRequest implements ToXContentObject { private String snapshot; private String repository; @@ -531,13 +534,13 @@ public RestoreSnapshotRequest source(Map source) { } else if (name.equals("rename_pattern")) { if (entry.getValue() instanceof String) { renamePattern((String) entry.getValue()); - } else { + } else if (entry.getValue() != null) { throw new IllegalArgumentException("malformed rename_pattern"); } } else if (name.equals("rename_replacement")) { if (entry.getValue() instanceof String) { renameReplacement((String) entry.getValue()); - } else { + } else if (entry.getValue() != null) { throw new IllegalArgumentException("malformed rename_replacement"); } } else if (name.equals("index_settings")) { @@ -563,6 +566,45 @@ public RestoreSnapshotRequest source(Map source) { 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); + } + builder.field("rename_pattern", renamePattern); + 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 public void readFrom(StreamInput in) throws IOException { throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable"); @@ -573,4 +615,52 @@ 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; + 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 "RestoreSnapshotRequest{" + + "snapshot='" + snapshot + '\'' + + ", repository='" + repository + '\'' + + ", indices=" + (indices == null ? null : Arrays.asList(indices)) + + ", indicesOptions=" + indicesOptions + + ", renamePattern='" + renamePattern + '\'' + + ", renameReplacement='" + renameReplacement + '\'' + + ", waitForCompletion=" + waitForCompletion + + ", includeGlobalState=" + includeGlobalState + + ", partial=" + partial + + ", includeAliases=" + includeAliases + + ", settings=" + settings + + ", indexSettings=" + indexSettings + + ", ignoreIndexSettings=" + (ignoreIndexSettings == null ? null : Arrays.asList(ignoreIndexSettings)) + + ", masterNodeTimeout=" + masterNodeTimeout + + '}'; + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java index 5a02e4bcb1387..f78370c441cbc 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java @@ -26,10 +26,12 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.snapshots.RestoreInfo; import java.io.IOException; +import java.util.Objects; /** * Contains information about restores snapshot @@ -86,4 +88,56 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par builder.endObject(); return builder; } + + public static RestoreSnapshotResponse fromXContent(XContentParser parser) throws IOException { + RestoreSnapshotResponse response = new RestoreSnapshotResponse(); + + parser.nextToken(); // move to '{' + + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['{']"); + } + + parser.nextToken(); // move to 'snapshot' || 'accepted' + + if ("snapshot".equals(parser.currentName())) { + response.restoreInfo = RestoreInfo.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() != XContentParser.Token.END_OBJECT) { + throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['}']"); + } + + parser.nextToken(); // move past '}' + + return response; + } + + @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 + '}'; + } } diff --git a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java index 36e80501fc1b1..1fa4b34a75c41 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java +++ b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java @@ -18,18 +18,22 @@ */ package org.elasticsearch.snapshots; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; -import org.elasticsearch.common.xcontent.ToXContent.Params; +import org.elasticsearch.common.xcontent.ContextParser; +import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Information about successfully completed restore operation. @@ -66,6 +70,10 @@ public String name() { return name; } + private void setName(String name) { + this.name = name; + } + /** * List of restored indices * @@ -75,6 +83,10 @@ public List indices() { return indices; } + private void setIndices(List indices) { + this.indices = indices; + } + /** * Number of shards being restored * @@ -102,6 +114,11 @@ public int successfulShards() { return successfulShards; } + private void setShards(Shards shards) { + this.successfulShards = shards.getSuccessfulShards(); + this.totalShards = shards.getTotalShards(); + } + /** * REST status of the operation * @@ -141,6 +158,63 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } + /** + * Internal helper class solely for XContent parsing. + */ + private static final class Shards { + private static final ContextParser PARSER; + + static { + ObjectParser internalParser = new ObjectParser<>("shards"); + internalParser.declareInt(Shards::setTotalShards, new ParseField(Fields.TOTAL)); + internalParser.declareInt(Shards::setFailedShards, new ParseField(Fields.FAILED)); + internalParser.declareInt(Shards::setSuccessfulShards, new ParseField(Fields.SUCCESSFUL)); + PARSER = (p, v) -> internalParser.parse(p, new Shards(), null); + } + + private int totalShards; + + private int failedShards; + + private int successfulShards; + + public int getTotalShards() { + return totalShards; + } + + public void setTotalShards(int totalShards) { + this.totalShards = totalShards; + } + + public int getFailedShards() { + return failedShards; + } + + public void setFailedShards(int failedShards) { + this.failedShards = failedShards; + } + + public int getSuccessfulShards() { + return successfulShards; + } + + public void setSuccessfulShards(int successfulShards) { + this.successfulShards = successfulShards; + } + } + + private static final ObjectParser PARSER = new ObjectParser<>(RestoreInfo.class.getName(), RestoreInfo::new); + + static { + PARSER.declareString(RestoreInfo::setName, new ParseField(Fields.SNAPSHOT)); + PARSER.declareStringArray(RestoreInfo::setIndices, new ParseField(Fields.INDICES)); + PARSER.declareObject(RestoreInfo::setShards, Shards.PARSER, new ParseField(Fields.SHARDS)); + } + + public static RestoreInfo fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + /** * {@inheritDoc} */ @@ -193,4 +267,29 @@ public static RestoreInfo readOptionalRestoreInfo(StreamInput in) throws IOExcep 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 "RestoreInfo{" + + "name='" + name + '\'' + + ", indices=" + indices + + ", totalShards=" + totalShards + + ", successfulShards=" + successfulShards + + '}'; + } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java new file mode 100644 index 0000000000000..ea77872bc85a6 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java @@ -0,0 +1,124 @@ +/* + * 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.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.ESTestCase; + +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 ESTestCase { + // tests creating XContent and parsing with source(Map) equivalency + public void testToXContent() throws IOException { + String repo = randomAlphaOfLength(5); + String snapshot = randomAlphaOfLength(10); + + RestoreSnapshotRequest original = new RestoreSnapshotRequest(repo, snapshot); + + if (randomBoolean()) { + List indices = new ArrayList<>(); + int count = randomInt(3) + 1; + + for (int i = 0; i < count; ++i) { + indices.add(randomAlphaOfLength(randomInt(3) + 2)); + } + + original.indices(indices); + } + if (randomBoolean()) { + original.renamePattern(randomUnicodeOfLengthBetween(1, 100)); + } + if (randomBoolean()) { + original.renameReplacement(randomUnicodeOfLengthBetween(1, 100)); + } + original.partial(randomBoolean()); + original.includeAliases(randomBoolean()); + + if (randomBoolean()) { + Map settings = new HashMap<>(); + int count = randomInt(3) + 1; + + for (int i = 0; i < count; ++i) { + settings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5)); + } + + original.settings(settings); + } + if (randomBoolean()) { + Map indexSettings = new HashMap<>(); + int count = randomInt(3) + 1; + + for (int i = 0; i < count; ++i) { + indexSettings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5));; + } + original.indexSettings(indexSettings); + } + + original.includeGlobalState(randomBoolean()); + + if (randomBoolean()) { + Collection wildcardStates = randomSubsetOf( + Arrays.asList(IndicesOptions.WildcardStates.values())); + Collection options = randomSubsetOf( + Arrays.asList(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE)); + + original.indicesOptions(new IndicesOptions( + options.isEmpty() ? IndicesOptions.Option.NONE : EnumSet.copyOf(options), + wildcardStates.isEmpty() ? IndicesOptions.WildcardStates.NONE : EnumSet.copyOf(wildcardStates))); + } + + original.waitForCompletion(randomBoolean()); + + if (randomBoolean()) { + original.masterNodeTimeout("60s"); + } + + 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 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(repo, snapshot); + processed.masterNodeTimeout(original.masterNodeTimeout()); + processed.waitForCompletion(original.waitForCompletion()); + + processed.source(map); + + assertEquals(original, processed); + } +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java new file mode 100644 index 0000000000000..9ae7ce544b408 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java @@ -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 { + + @Override + protected RestoreSnapshotResponse createTestInstance() { + if (randomBoolean()) { + String name = randomRealisticUnicodeOfCodepointLengthBetween(1, 30); + List 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 false; + } +} From a79f8c24164556515a05e921bc62baf7aa81439a Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Thu, 19 Jul 2018 09:16:00 +0200 Subject: [PATCH 2/4] Address review comments --- .../client/RequestConvertersTests.java | 12 ++-- .../org/elasticsearch/client/SnapshotIT.java | 4 ++ .../restore/RestoreSnapshotRequest.java | 29 +++------ .../elasticsearch/snapshots/RestoreInfo.java | 8 +-- .../restore/RestoreSnapshotRequestTests.java | 59 ++++++++++++------- .../test/rest/ESRestTestCase.java | 5 ++ 6 files changed, 64 insertions(+), 53 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index abb84defa2fe9..59f302bfda177 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -2205,14 +2205,14 @@ public void testRestoreSnapshot() throws IOException { RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(repository, snapshot); setRandomMasterTimeout(restoreSnapshotRequest, expectedParams); - Boolean waitForCompletion = randomBoolean(); - restoreSnapshotRequest.waitForCompletion(waitForCompletion); - if (waitForCompletion) { - expectedParams.put("wait_for_completion", waitForCompletion.toString()); + if (randomBoolean()) { + restoreSnapshotRequest.waitForCompletion(true); + expectedParams.put("wait_for_completion", "true"); } if (randomBoolean()) { - restoreSnapshotRequest.masterNodeTimeout("120s"); - expectedParams.put("master_timeout", "120s"); + String timeout = randomTimeValue(); + restoreSnapshotRequest.masterNodeTimeout(timeout); + expectedParams.put("master_timeout", timeout); } Request request = RequestConverters.restoreSnapshot(restoreSnapshotRequest); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java index 74e6309b94910..06aec70a01884 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java @@ -220,6 +220,7 @@ public void testRestoreSnapshot() throws IOException { 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); @@ -227,6 +228,9 @@ public void testRestoreSnapshot() throws IOException { 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); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java index 604159d3b8c29..53aa522772aac 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java @@ -534,13 +534,13 @@ public RestoreSnapshotRequest source(Map source) { } else if (name.equals("rename_pattern")) { if (entry.getValue() instanceof String) { renamePattern((String) entry.getValue()); - } else if (entry.getValue() != null) { + } else { throw new IllegalArgumentException("malformed rename_pattern"); } } else if (name.equals("rename_replacement")) { if (entry.getValue() instanceof String) { renameReplacement((String) entry.getValue()); - } else if (entry.getValue() != null) { + } else { throw new IllegalArgumentException("malformed rename_replacement"); } } else if (name.equals("index_settings")) { @@ -577,8 +577,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (indicesOptions != null) { indicesOptions.toXContent(builder, params); } - builder.field("rename_pattern", renamePattern); - builder.field("rename_replacement", renameReplacement); + 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); @@ -646,21 +650,6 @@ public int hashCode() { @Override public String toString() { - return "RestoreSnapshotRequest{" + - "snapshot='" + snapshot + '\'' + - ", repository='" + repository + '\'' + - ", indices=" + (indices == null ? null : Arrays.asList(indices)) + - ", indicesOptions=" + indicesOptions + - ", renamePattern='" + renamePattern + '\'' + - ", renameReplacement='" + renameReplacement + '\'' + - ", waitForCompletion=" + waitForCompletion + - ", includeGlobalState=" + includeGlobalState + - ", partial=" + partial + - ", includeAliases=" + includeAliases + - ", settings=" + settings + - ", indexSettings=" + indexSettings + - ", ignoreIndexSettings=" + (ignoreIndexSettings == null ? null : Arrays.asList(ignoreIndexSettings)) + - ", masterNodeTimeout=" + masterNodeTimeout + - '}'; + return Strings.toString(this); } } diff --git a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java index 1fa4b34a75c41..fc482a1882d7f 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java +++ b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java @@ -19,6 +19,7 @@ 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.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; @@ -285,11 +286,6 @@ public int hashCode() { @Override public String toString() { - return "RestoreInfo{" + - "name='" + name + '\'' + - ", indices=" + indices + - ", totalShards=" + totalShards + - ", successfulShards=" + successfulShards + - '}'; + return Strings.toString(this); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java index ea77872bc85a6..fbe8761a07d12 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequestTests.java @@ -21,13 +21,14 @@ 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.ESTestCase; +import org.elasticsearch.test.AbstractWireSerializingTestCase; import java.io.IOException; import java.util.ArrayList; @@ -39,14 +40,8 @@ import java.util.List; import java.util.Map; -public class RestoreSnapshotRequestTests extends ESTestCase { - // tests creating XContent and parsing with source(Map) equivalency - public void testToXContent() throws IOException { - String repo = randomAlphaOfLength(5); - String snapshot = randomAlphaOfLength(10); - - RestoreSnapshotRequest original = new RestoreSnapshotRequest(repo, snapshot); - +public class RestoreSnapshotRequestTests extends AbstractWireSerializingTestCase { + private RestoreSnapshotRequest randomState(RestoreSnapshotRequest instance) { if (randomBoolean()) { List indices = new ArrayList<>(); int count = randomInt(3) + 1; @@ -55,16 +50,16 @@ public void testToXContent() throws IOException { indices.add(randomAlphaOfLength(randomInt(3) + 2)); } - original.indices(indices); + instance.indices(indices); } if (randomBoolean()) { - original.renamePattern(randomUnicodeOfLengthBetween(1, 100)); + instance.renamePattern(randomUnicodeOfLengthBetween(1, 100)); } if (randomBoolean()) { - original.renameReplacement(randomUnicodeOfLengthBetween(1, 100)); + instance.renameReplacement(randomUnicodeOfLengthBetween(1, 100)); } - original.partial(randomBoolean()); - original.includeAliases(randomBoolean()); + instance.partial(randomBoolean()); + instance.includeAliases(randomBoolean()); if (randomBoolean()) { Map settings = new HashMap<>(); @@ -74,7 +69,7 @@ public void testToXContent() throws IOException { settings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5)); } - original.settings(settings); + instance.settings(settings); } if (randomBoolean()) { Map indexSettings = new HashMap<>(); @@ -83,10 +78,10 @@ public void testToXContent() throws IOException { for (int i = 0; i < count; ++i) { indexSettings.put(randomAlphaOfLengthBetween(2, 5), randomAlphaOfLengthBetween(2, 5));; } - original.indexSettings(indexSettings); + instance.indexSettings(indexSettings); } - original.includeGlobalState(randomBoolean()); + instance.includeGlobalState(randomBoolean()); if (randomBoolean()) { Collection wildcardStates = randomSubsetOf( @@ -94,17 +89,39 @@ public void testToXContent() throws IOException { Collection options = randomSubsetOf( Arrays.asList(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE)); - original.indicesOptions(new IndicesOptions( + instance.indicesOptions(new IndicesOptions( options.isEmpty() ? IndicesOptions.Option.NONE : EnumSet.copyOf(options), wildcardStates.isEmpty() ? IndicesOptions.WildcardStates.NONE : EnumSet.copyOf(wildcardStates))); } - original.waitForCompletion(randomBoolean()); + instance.waitForCompletion(randomBoolean()); if (randomBoolean()) { - original.masterNodeTimeout("60s"); + instance.masterNodeTimeout(randomTimeValue()); } + return instance; + } + + @Override + protected RestoreSnapshotRequest createTestInstance() { + return randomState(new RestoreSnapshotRequest(randomAlphaOfLength(5), randomAlphaOfLength(10))); + } + + @Override + protected Writeable.Reader 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()); @@ -113,7 +130,7 @@ public void testToXContent() throws IOException { // 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(repo, snapshot); + RestoreSnapshotRequest processed = new RestoreSnapshotRequest(original.repository(), original.snapshot()); processed.masterNodeTimeout(original.masterNodeTimeout()); processed.waitForCompletion(original.waitForCompletion()); diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java index 937adddf3a43d..e25e8b43d3a72 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java @@ -536,6 +536,11 @@ protected static void createIndex(String name, Settings settings, String mapping 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 { updateIndexSettings(index, settings.build()); } From 04d85426472ebda41a97376712a9ad38a65115c6 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 24 Jul 2018 05:26:02 +0200 Subject: [PATCH 3/4] Support unknown fields in RestoreSnapshotResponse --- .../restore/RestoreSnapshotResponse.java | 49 +++++------ .../elasticsearch/snapshots/RestoreInfo.java | 81 +++---------------- .../restore/RestoreSnapshotResponseTests.java | 2 +- 3 files changed, 29 insertions(+), 103 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java index f78370c441cbc..2c0ebef2e61b4 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java @@ -21,8 +21,10 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -33,6 +35,8 @@ import java.io.IOException; import java.util.Objects; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + /** * Contains information about restores snapshot */ @@ -89,38 +93,23 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par return builder; } - public static RestoreSnapshotResponse fromXContent(XContentParser parser) throws IOException { - RestoreSnapshotResponse response = new RestoreSnapshotResponse(); - - parser.nextToken(); // move to '{' - - if (parser.currentToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['{']"); - } - - parser.nextToken(); // move to 'snapshot' || 'accepted' - - if ("snapshot".equals(parser.currentName())) { - response.restoreInfo = RestoreInfo.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() != XContentParser.Token.END_OBJECT) { - throw new IllegalArgumentException("unexpected token [" + parser.currentToken() + "], expected ['}']"); - } + public static final ConstructingObjectParser 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")); + } - parser.nextToken(); // move past '}' - return response; + public static RestoreSnapshotResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); } @Override diff --git a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java index fc482a1882d7f..6a58b52f72a81 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java +++ b/server/src/main/java/org/elasticsearch/snapshots/RestoreInfo.java @@ -23,7 +23,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; -import org.elasticsearch.common.xcontent.ContextParser; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -71,10 +70,6 @@ public String name() { return name; } - private void setName(String name) { - this.name = name; - } - /** * List of restored indices * @@ -84,10 +79,6 @@ public List indices() { return indices; } - private void setIndices(List indices) { - this.indices = indices; - } - /** * Number of shards being restored * @@ -115,11 +106,6 @@ public int successfulShards() { return successfulShards; } - private void setShards(Shards shards) { - this.successfulShards = shards.getSuccessfulShards(); - this.totalShards = shards.getTotalShards(); - } - /** * REST status of the operation * @@ -138,9 +124,6 @@ static final class Fields { static final String SUCCESSFUL = "successful"; } - /** - * {@inheritDoc} - */ @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); @@ -159,66 +142,23 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } - /** - * Internal helper class solely for XContent parsing. - */ - private static final class Shards { - private static final ContextParser PARSER; - - static { - ObjectParser internalParser = new ObjectParser<>("shards"); - internalParser.declareInt(Shards::setTotalShards, new ParseField(Fields.TOTAL)); - internalParser.declareInt(Shards::setFailedShards, new ParseField(Fields.FAILED)); - internalParser.declareInt(Shards::setSuccessfulShards, new ParseField(Fields.SUCCESSFUL)); - PARSER = (p, v) -> internalParser.parse(p, new Shards(), null); - } - - private int totalShards; - - private int failedShards; - - private int successfulShards; - - public int getTotalShards() { - return totalShards; - } - - public void setTotalShards(int totalShards) { - this.totalShards = totalShards; - } - - public int getFailedShards() { - return failedShards; - } - - public void setFailedShards(int failedShards) { - this.failedShards = failedShards; - } - - public int getSuccessfulShards() { - return successfulShards; - } - - public void setSuccessfulShards(int successfulShards) { - this.successfulShards = successfulShards; - } - } - - private static final ObjectParser PARSER = new ObjectParser<>(RestoreInfo.class.getName(), RestoreInfo::new); + private static final ObjectParser PARSER = new ObjectParser<>(RestoreInfo.class.getName(), true, RestoreInfo::new); static { - PARSER.declareString(RestoreInfo::setName, new ParseField(Fields.SNAPSHOT)); - PARSER.declareStringArray(RestoreInfo::setIndices, new ParseField(Fields.INDICES)); - PARSER.declareObject(RestoreInfo::setShards, Shards.PARSER, new ParseField(Fields.SHARDS)); + ObjectParser 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); } - /** - * {@inheritDoc} - */ @Override public void readFrom(StreamInput in) throws IOException { name = in.readString(); @@ -232,9 +172,6 @@ public void readFrom(StreamInput in) throws IOException { successfulShards = in.readVInt(); } - /** - * {@inheritDoc} - */ @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(name); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java index 9ae7ce544b408..17d1ecafabdae 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponseTests.java @@ -51,6 +51,6 @@ protected RestoreSnapshotResponse doParseInstance(XContentParser parser) throws @Override protected boolean supportsUnknownFields() { - return false; + return true; } } From f48ca63995411ca2e143bcafe998ca9633f12d8f Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 24 Jul 2018 15:47:12 +0200 Subject: [PATCH 4/4] Improve formatting --- .../snapshots/restore/RestoreSnapshotResponse.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java index 2c0ebef2e61b4..171509c018228 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotResponse.java @@ -95,11 +95,12 @@ public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params par public static final ConstructingObjectParser 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); + 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 {