Skip to content

Commit

Permalink
Get snapshots support for multiple repositories (elastic#42090)
Browse files Browse the repository at this point in the history
This commit adds multiple repositories support to get snapshots
request.
If some repository throws an exception this method does not fail fast
instead, it returns results for all repositories.
This PR is opened in favour of elastic#41799, because we decided to change
the response format in a non-BwC manner. It makes sense to read a
discussion of the aforementioned PR.
This is the continuation of work done here elastic#15151.
  • Loading branch information
andrershov authored Jun 19, 2019
1 parent 43ec1d8 commit 680d6ed
Show file tree
Hide file tree
Showing 51 changed files with 972 additions and 446 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ static Request createSnapshot(CreateSnapshotRequest createSnapshotRequest) throw

static Request getSnapshots(GetSnapshotsRequest getSnapshotsRequest) {
RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder().addPathPartAsIs("_snapshot")
.addPathPart(getSnapshotsRequest.repository());
.addCommaSeparatedPathParts(getSnapshotsRequest.repositories());
String endpoint;
if (getSnapshotsRequest.snapshots().length == 0) {
endpoint = endpointBuilder.addPathPart("_all").build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,16 @@
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.snapshots.RestoreInfo;
import org.elasticsearch.snapshots.SnapshotInfo;
import org.mockito.internal.util.collections.Sets;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.hasSize;
import static org.hamcrest.Matchers.is;

public class SnapshotIT extends ESRestHighLevelClientTestCase {
Expand All @@ -61,14 +60,14 @@ private AcknowledgedResponse createTestRepository(String repository, String type
request.settings(settings, XContentType.JSON);
request.type(type);
return execute(request, highLevelClient().snapshot()::createRepository,
highLevelClient().snapshot()::createRepositoryAsync);
highLevelClient().snapshot()::createRepositoryAsync);
}

private CreateSnapshotResponse createTestSnapshot(CreateSnapshotRequest createSnapshotRequest) throws IOException {
// assumes the repository already exists

return execute(createSnapshotRequest, highLevelClient().snapshot()::create,
highLevelClient().snapshot()::createAsync);
highLevelClient().snapshot()::createAsync);
}

public void testCreateRepository() throws IOException {
Expand All @@ -84,7 +83,7 @@ public void testSnapshotGetRepositoriesUsingParams() throws IOException {
GetRepositoriesRequest request = new GetRepositoriesRequest();
request.repositories(new String[]{testRepository});
GetRepositoriesResponse response = execute(request, highLevelClient().snapshot()::getRepository,
highLevelClient().snapshot()::getRepositoryAsync);
highLevelClient().snapshot()::getRepositoryAsync);
assertThat(1, equalTo(response.repositories().size()));
}

Expand All @@ -93,19 +92,19 @@ public void testSnapshotGetDefaultRepositories() throws IOException {
assertTrue(createTestRepository("test", FsRepository.TYPE, "{\"location\": \".\"}").isAcknowledged());

GetRepositoriesResponse response = execute(new GetRepositoriesRequest(), highLevelClient().snapshot()::getRepository,
highLevelClient().snapshot()::getRepositoryAsync);
highLevelClient().snapshot()::getRepositoryAsync);
assertThat(2, equalTo(response.repositories().size()));
}

public void testSnapshotGetRepositoriesNonExistent() {
String repository = "doesnotexist";
GetRepositoriesRequest request = new GetRepositoriesRequest(new String[]{repository});
ElasticsearchException exception = expectThrows(ElasticsearchException.class, () -> execute(request,
highLevelClient().snapshot()::getRepository, highLevelClient().snapshot()::getRepositoryAsync));
highLevelClient().snapshot()::getRepository, highLevelClient().snapshot()::getRepositoryAsync));

assertThat(exception.status(), equalTo(RestStatus.NOT_FOUND));
assertThat(exception.getMessage(), equalTo(
"Elasticsearch exception [type=repository_missing_exception, reason=[" + repository + "] missing]"));
"Elasticsearch exception [type=repository_missing_exception, reason=[" + repository + "] missing]"));
}

public void testSnapshotDeleteRepository() throws IOException {
Expand All @@ -114,12 +113,12 @@ public void testSnapshotDeleteRepository() throws IOException {

GetRepositoriesRequest request = new GetRepositoriesRequest();
GetRepositoriesResponse response = execute(request, highLevelClient().snapshot()::getRepository,
highLevelClient().snapshot()::getRepositoryAsync);
highLevelClient().snapshot()::getRepositoryAsync);
assertThat(1, equalTo(response.repositories().size()));

DeleteRepositoryRequest deleteRequest = new DeleteRepositoryRequest(repository);
AcknowledgedResponse deleteResponse = execute(deleteRequest, highLevelClient().snapshot()::deleteRepository,
highLevelClient().snapshot()::deleteRepositoryAsync);
highLevelClient().snapshot()::deleteRepositoryAsync);

assertTrue(deleteResponse.isAcknowledged());
}
Expand All @@ -130,7 +129,7 @@ public void testVerifyRepository() throws IOException {

VerifyRepositoryRequest request = new VerifyRepositoryRequest("test");
VerifyRepositoryResponse response = execute(request, highLevelClient().snapshot()::verifyRepository,
highLevelClient().snapshot()::verifyRepositoryAsync);
highLevelClient().snapshot()::verifyRepositoryAsync);
assertThat(response.getNodes().size(), equalTo(1));
}

Expand All @@ -153,25 +152,31 @@ public void testCreateSnapshot() throws IOException {
if (waitForCompletion == false) {
// If we don't wait for the snapshot to complete we have to cancel it to not leak the snapshot task
AcknowledgedResponse deleteResponse = execute(
new DeleteSnapshotRequest(repository, snapshot),
highLevelClient().snapshot()::delete, highLevelClient().snapshot()::deleteAsync
new DeleteSnapshotRequest(repository, snapshot),
highLevelClient().snapshot()::delete, highLevelClient().snapshot()::deleteAsync
);
assertTrue(deleteResponse.isAcknowledged());
}
}

public void testGetSnapshots() throws IOException {
String repository = "test_repository";
String repository1 = "test_repository1";
String repository2 = "test_repository2";
String snapshot1 = "test_snapshot1";
String snapshot2 = "test_snapshot2";

AcknowledgedResponse putRepositoryResponse = createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}");
AcknowledgedResponse putRepositoryResponse =
createTestRepository(repository1, FsRepository.TYPE, "{\"location\": \"loc1\"}");
assertTrue(putRepositoryResponse.isAcknowledged());

CreateSnapshotRequest createSnapshotRequest1 = new CreateSnapshotRequest(repository, snapshot1);
AcknowledgedResponse putRepositoryResponse2 =
createTestRepository(repository2, FsRepository.TYPE, "{\"location\": \"loc2\"}");
assertTrue(putRepositoryResponse2.isAcknowledged());

CreateSnapshotRequest createSnapshotRequest1 = new CreateSnapshotRequest(repository1, snapshot1);
createSnapshotRequest1.waitForCompletion(true);
CreateSnapshotResponse putSnapshotResponse1 = createTestSnapshot(createSnapshotRequest1);
CreateSnapshotRequest createSnapshotRequest2 = new CreateSnapshotRequest(repository, snapshot2);
CreateSnapshotRequest createSnapshotRequest2 = new CreateSnapshotRequest(repository2, snapshot2);
createSnapshotRequest2.waitForCompletion(true);
Map<String, Object> originalMetadata = randomUserMetadata();
createSnapshotRequest2.userMetadata(originalMetadata);
Expand All @@ -180,28 +185,26 @@ public void testGetSnapshots() throws IOException {
assertEquals(RestStatus.OK, putSnapshotResponse1.status());
assertEquals(RestStatus.OK, putSnapshotResponse2.status());

GetSnapshotsRequest request;
if (randomBoolean()) {
request = new GetSnapshotsRequest(repository);
} else if (randomBoolean()) {
request = new GetSnapshotsRequest(repository, new String[] {"_all"});
GetSnapshotsRequest request = new GetSnapshotsRequest(
randomFrom(new String[]{"_all"}, new String[]{"*"}, new String[]{repository1, repository2}),
randomFrom(new String[]{"_all"}, new String[]{"*"}, new String[]{snapshot1, snapshot2})
);
request.ignoreUnavailable(true);

} else {
request = new GetSnapshotsRequest(repository, new String[] {snapshot1, snapshot2});
}
GetSnapshotsResponse response = execute(request, highLevelClient().snapshot()::get, highLevelClient().snapshot()::getAsync);

assertEquals(2, response.getSnapshots().size());
assertThat(response.getSnapshots().stream().map((s) -> s.snapshotId().getName()).collect(Collectors.toList()),
contains("test_snapshot1", "test_snapshot2"));
response.getSnapshots().stream()
.filter(s -> s.snapshotId().getName().equals("test_snapshot2"))
.findFirst()
.map(SnapshotInfo::userMetadata)
.ifPresentOrElse(metadata -> assertEquals(originalMetadata, metadata),
() -> assertNull("retrieved metadata is null, expected non-null metadata", originalMetadata));
assertThat(response.isFailed(), is(false));
assertThat(response.getRepositories(), equalTo(Sets.newSet(repository1, repository2)));

assertThat(response.getSnapshots(repository1), hasSize(1));
assertThat(response.getSnapshots(repository1).get(0).snapshotId().getName(), equalTo(snapshot1));

assertThat(response.getSnapshots(repository2), hasSize(1));
assertThat(response.getSnapshots(repository2).get(0).snapshotId().getName(), equalTo(snapshot2));
assertThat(response.getSnapshots(repository2).get(0).userMetadata(), equalTo(originalMetadata));
}


public void testSnapshotsStatus() throws IOException {
String testRepository = "test";
String testSnapshot = "snapshot";
Expand All @@ -223,7 +226,7 @@ public void testSnapshotsStatus() throws IOException {
request.repository(testRepository);
request.snapshots(new String[]{testSnapshot});
SnapshotsStatusResponse response = execute(request, highLevelClient().snapshot()::status,
highLevelClient().snapshot()::statusAsync);
highLevelClient().snapshot()::statusAsync);
assertThat(response.getSnapshots().size(), equalTo(1));
assertThat(response.getSnapshots().get(0).getSnapshot().getRepository(), equalTo(testRepository));
assertThat(response.getSnapshots().get(0).getSnapshot().getSnapshotId().getName(), equalTo(testSnapshot));
Expand Down Expand Up @@ -260,7 +263,7 @@ public void testRestoreSnapshot() throws IOException {
request.renameReplacement(restoredIndex);

RestoreSnapshotResponse response = execute(request, highLevelClient().snapshot()::restore,
highLevelClient().snapshot()::restoreAsync);
highLevelClient().snapshot()::restoreAsync);

RestoreInfo restoreInfo = response.getRestoreInfo();
assertThat(restoreInfo.name(), equalTo(testSnapshot));
Expand Down Expand Up @@ -301,17 +304,17 @@ private static Map<String, Object> randomUserMetadata() {
for (int i = 0; i < fields; i++) {
if (randomBoolean()) {
metadata.put(randomValueOtherThanMany(metadata::containsKey, () -> randomAlphaOfLengthBetween(2,10)),
randomAlphaOfLengthBetween(5, 5));
randomAlphaOfLengthBetween(5, 5));
} else {
Map<String, Object> nested = new HashMap<>();
long nestedFields = randomLongBetween(0, 4);
for (int j = 0; j < nestedFields; j++) {
nested.put(randomValueOtherThanMany(nested::containsKey, () -> randomAlphaOfLengthBetween(2,10)),
randomAlphaOfLengthBetween(5, 5));
randomAlphaOfLengthBetween(5, 5));
}
metadata.put(randomValueOtherThanMany(metadata::containsKey, () -> randomAlphaOfLengthBetween(2,10)), nested);
}
}
return metadata;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@

import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -148,15 +147,16 @@ public void testCreateSnapshot() throws IOException {

public void testGetSnapshots() {
Map<String, String> expectedParams = new HashMap<>();
String repository = RequestConvertersTests.randomIndicesNames(1, 1)[0];
String repository1 = randomAlphaOfLength(10);
String repository2 = randomAlphaOfLength(10);
String snapshot1 = "snapshot1-" + randomAlphaOfLengthBetween(2, 5).toLowerCase(Locale.ROOT);
String snapshot2 = "snapshot2-" + randomAlphaOfLengthBetween(2, 5).toLowerCase(Locale.ROOT);

String endpoint = String.format(Locale.ROOT, "/_snapshot/%s/%s,%s", repository, snapshot1, snapshot2);
String endpoint = String.format(Locale.ROOT, "/_snapshot/%s,%s/%s,%s", repository1, repository2, snapshot1, snapshot2);

GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest();
getSnapshotsRequest.repository(repository);
getSnapshotsRequest.snapshots(Arrays.asList(snapshot1, snapshot2).toArray(new String[0]));
getSnapshotsRequest.repositories(repository1, repository2);
getSnapshotsRequest.snapshots(new String[]{snapshot1, snapshot2});
RequestConvertersTests.setRandomMasterTimeout(getSnapshotsRequest, expectedParams);

if (randomBoolean()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ public void testSnapshotGetSnapshots() throws IOException {
// end::get-snapshots-request

// tag::get-snapshots-request-repositoryName
request.repository(repositoryName); // <1>
request.repositories(repositoryName); // <1>
// end::get-snapshots-request-repositoryName

// tag::get-snapshots-request-snapshots
Expand All @@ -616,7 +616,7 @@ public void testSnapshotGetSnapshots() throws IOException {
// end::get-snapshots-execute

// tag::get-snapshots-response
List<SnapshotInfo> snapshotsInfos = response.getSnapshots();
List<SnapshotInfo> snapshotsInfos = response.getSnapshots(repositoryName);
SnapshotInfo snapshotInfo = snapshotsInfos.get(0);
RestStatus restStatus = snapshotInfo.status(); // <1>
SnapshotId snapshotId = snapshotInfo.snapshotId(); // <2>
Expand Down
23 changes: 19 additions & 4 deletions docs/reference/cat/snapshots.asciidoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[[cat-snapshots]]
== cat snapshots

The `snapshots` command shows all snapshots that belong to a specific repository.
The `snapshots` command shows all snapshots that belong to a specific repository
or multiple repositories.
To find a list of available repositories to query, the command `/_cat/repositories` can be used.
Querying the snapshots of a repository named `repo1` then looks as follows.

Expand All @@ -18,9 +19,9 @@ Which looks like:

[source,txt]
--------------------------------------------------
id status start_epoch start_time end_epoch end_time duration indices successful_shards failed_shards total_shards
snap1 FAILED 1445616705 18:11:45 1445616978 18:16:18 4.6m 1 4 1 5
snap2 SUCCESS 1445634298 23:04:58 1445634672 23:11:12 6.2m 2 10 0 10
id repository status start_epoch start_time end_epoch end_time duration indices successful_shards failed_shards total_shards
snap1 repo1 FAILED 1445616705 18:11:45 1445616978 18:16:18 4.6m 1 4 1 5
snap2 repo1 SUCCESS 1445634298 23:04:58 1445634672 23:11:12 6.2m 2 10 0 10
--------------------------------------------------
// TESTRESPONSE[s/FAILED/SUCCESS/ s/14456\d+/\\d+/ s/\d+(\.\d+)?(m|s|ms)/\\d+(\\.\\d+)?(m|s|ms)/]
// TESTRESPONSE[s/\d+:\d+:\d+/\\d+:\\d+:\\d+/]
Expand All @@ -32,3 +33,17 @@ Each snapshot contains information about when it was started and stopped.
Start and stop timestamps are available in two formats.
The `HH:MM:SS` output is simply for quick human consumption.
The epoch time retains more information, including date, and is machine sortable if the snapshot process spans days.

It is also possible to get the list of snapshots from multiple repositories.
Here are some examples:

[source,js]
--------------------------------------------------
GET /_cat/snapshots/_all
GET /_cat/snapshots/repo1,repo2
GET /_cat/snapshots/repo*
--------------------------------------------------
// CONSOLE
// TEST[skip:no repo2]

Please note that if one of the repositories fails during the request you will get an exception instead of the table.
5 changes: 5 additions & 0 deletions docs/reference/migration/migrate_8_0/snapshots.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

// end::notable-breaking-changes[]

[float]
=== Get snapshots response format is changed
It's possible to get snapshots from multiple repositories in one go. The response format has changed
and now contains separate response for each repository. See <<modules-snapshots>> for more information.

[float]
==== Deprecated node level compress setting removed

Expand Down
10 changes: 10 additions & 0 deletions docs/reference/modules/snapshots.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,16 @@ that setting `verbose` to `false` will omit all other information about the snap
such as status information, the number of snapshotted shards, etc. The default
value of the `verbose` parameter is `true`.

It is also possible to retrieve snapshots from multiple repositories in one go, for example:
[source,sh]
-----------------------------------
GET /_snapshot/_all
GET /_snapshot/my_backup,my_fs_backup
GET /_snapshot/my*/snap*
-----------------------------------
// CONSOLE
// TEST[skip:no my_fs_backup]

A currently running snapshot can be retrieved using the following command:

[source,sh]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void testUrlRepository() throws Exception {
.prepareGetSnapshots("test-repo")
.setSnapshots("test-snap")
.get()
.getSnapshots()
.getSnapshots("test-repo")
.get(0)
.state();
assertThat(state, equalTo(SnapshotState.SUCCESS));
Expand Down Expand Up @@ -116,16 +116,16 @@ public void testUrlRepository() throws Exception {

logger.info("--> list available shapshots");
GetSnapshotsResponse getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(1));
assertThat(getSnapshotsResponse.getSnapshots("url-repo"), notNullValue());
assertThat(getSnapshotsResponse.getSnapshots("url-repo").size(), equalTo(1));

logger.info("--> delete snapshot");
AcknowledgedResponse deleteSnapshotResponse = client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get();
assertAcked(deleteSnapshotResponse);

logger.info("--> list available shapshot again, no snapshots should be returned");
getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("url-repo").get();
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(0));
assertThat(getSnapshotsResponse.getSnapshots("url-repo"), notNullValue());
assertThat(getSnapshotsResponse.getSnapshots("url-repo").size(), equalTo(0));
}
}
Loading

0 comments on commit 680d6ed

Please sign in to comment.