Browse Source

Add REST Test for Snapshot Clone API (#63863)

Adds snapshot clone REST tests and HLRC support for the API.
Armin Braun 5 years ago
parent
commit
6128d357fc

+ 27 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotClient.java

@@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe
 import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest;
 import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
 import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
 import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@@ -236,6 +237,32 @@ public final class SnapshotClient {
             CreateSnapshotResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Clones a snapshot.
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
+     * API on elastic.co</a>
+     */
+    public AcknowledgedResponse clone(CloneSnapshotRequest cloneSnapshotRequest, RequestOptions options)
+            throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(cloneSnapshotRequest, SnapshotRequestConverters::cloneSnapshot, options,
+                AcknowledgedResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously clones a snapshot.
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore
+     * API on elastic.co</a>
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable cloneAsync(CloneSnapshotRequest cloneSnapshotRequest, RequestOptions options,
+                                   ActionListener<AcknowledgedResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(cloneSnapshotRequest,
+                SnapshotRequestConverters::cloneSnapshot, options,
+                AcknowledgedResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Get snapshots.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html"> Snapshot and Restore

+ 16 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java

@@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteReposito
 import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRequest;
 import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
+import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest;
 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;
@@ -123,6 +124,21 @@ final class SnapshotRequestConverters {
         return request;
     }
 
+    static Request cloneSnapshot(CloneSnapshotRequest cloneSnapshotRequest) throws IOException {
+        String endpoint = new RequestConverters.EndpointBuilder().addPathPart("_snapshot")
+                .addPathPart(cloneSnapshotRequest.repository())
+                .addPathPart(cloneSnapshotRequest.source())
+                .addPathPart("_clone")
+                .addPathPart(cloneSnapshotRequest.target())
+                .build();
+        Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+        RequestConverters.Params params = new RequestConverters.Params();
+        params.withMasterTimeout(cloneSnapshotRequest.masterNodeTimeout());
+        request.addParameters(params.asMap());
+        request.setEntity(RequestConverters.createEntity(cloneSnapshotRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
+        return request;
+    }
+
     static Request getSnapshots(GetSnapshotsRequest getSnapshotsRequest) {
         RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder().addPathPartAsIs("_snapshot")
             .addCommaSeparatedPathParts(getSnapshotsRequest.repositories());

+ 25 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/SnapshotIT.java

@@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe
 import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
+import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest;
 import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
 import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
 import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@@ -350,6 +351,30 @@ public class SnapshotIT extends ESRestHighLevelClientTestCase {
         assertTrue(response.isAcknowledged());
     }
 
+    public void testCloneSnapshot() throws IOException {
+        String repository = "test_repository";
+        String snapshot = "source_snapshot";
+        String targetSnapshot = "target_snapshot";
+        final String testIndex =  "test_idx";
+
+        createIndex(testIndex, Settings.EMPTY);
+        assertTrue("index [" + testIndex + "] should have been created", indexExists(testIndex));
+
+        AcknowledgedResponse putRepositoryResponse = createTestRepository(repository, FsRepository.TYPE, "{\"location\": \".\"}");
+        assertTrue(putRepositoryResponse.isAcknowledged());
+
+        CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest(repository, snapshot);
+        createSnapshotRequest.waitForCompletion(true);
+
+        CreateSnapshotResponse createSnapshotResponse = createTestSnapshot(createSnapshotRequest);
+        assertEquals(RestStatus.OK, createSnapshotResponse.status());
+
+        CloneSnapshotRequest request = new CloneSnapshotRequest(repository, snapshot, targetSnapshot, new String[]{testIndex});
+        AcknowledgedResponse response = execute(request, highLevelClient().snapshot()::clone, highLevelClient().snapshot()::cloneAsync);
+
+        assertTrue(response.isAcknowledged());
+    }
+
     private static Map<String, Object> randomUserMetadata() {
         if (randomBoolean()) {
             return null;

+ 43 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/snapshot.clone.json

@@ -0,0 +1,43 @@
+{
+  "snapshot.clone":{
+    "documentation":{
+      "url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-snapshots.html",
+      "description":"Clones indices from one snapshot into another snapshot in the same repository."
+    },
+    "stability":"stable",
+    "url":{
+      "paths":[
+        {
+          "path":"/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}",
+          "methods":[
+            "PUT"
+          ],
+          "parts":{
+            "repository":{
+              "type":"string",
+              "description":"A repository name"
+            },
+            "snapshot":{
+              "type":"string",
+              "description":"The name of the snapshot to clone from"
+            },
+            "target_snapshot":{
+              "type":"string",
+              "description":"The name of the cloned snapshot to create"
+            }
+          }
+        }
+      ]
+    },
+    "params":{
+      "master_timeout":{
+        "type":"time",
+        "description":"Explicit operation timeout for connection to master node"
+      }
+    },
+    "body":{
+      "description":"The snapshot clone definition",
+      "required":true
+    }
+  }
+}

+ 54 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.clone/10_basic.yml

@@ -0,0 +1,54 @@
+---
+setup:
+
+  - do:
+      snapshot.create_repository:
+        repository: test_repo_create_1
+        body:
+          type: fs
+          settings:
+            location: "test_repo_create_1_loc"
+
+  - do:
+      indices.create:
+        index: test_index_1
+        body:
+          settings:
+            number_of_shards:   1
+            number_of_replicas: 1
+
+  - do:
+      indices.create:
+        index: test_index_2
+        body:
+          settings:
+            number_of_shards:   1
+            number_of_replicas: 1
+
+  - do:
+      snapshot.create:
+        repository: test_repo_create_1
+        snapshot: test_snapshot
+        wait_for_completion: true
+
+---
+"Clone a snapshot":
+  - skip:
+      version: " - 7.9.99"
+      reason: "Clone snapshot functionality was introduced in 7.10"
+  - do:
+      snapshot.clone:
+        repository: test_repo_create_1
+        snapshot: test_snapshot
+        target_snapshot: target_snapshot_1
+        body:
+          "indices": test_index_2
+
+  - match: { acknowledged: true }
+
+  - do:
+      snapshot.delete:
+        repository: test_repo_create_1
+        snapshot: target_snapshot_1
+
+  - match: { acknowledged: true }

+ 29 - 1
server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java

@@ -23,14 +23,17 @@ import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.IndicesRequest;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 
-public class CloneSnapshotRequest extends MasterNodeRequest<CloneSnapshotRequest> implements IndicesRequest.Replaceable{
+public class CloneSnapshotRequest extends MasterNodeRequest<CloneSnapshotRequest> implements IndicesRequest.Replaceable, ToXContentObject {
 
     private final String repository;
 
@@ -139,4 +142,29 @@ public class CloneSnapshotRequest extends MasterNodeRequest<CloneSnapshotRequest
     public String source() {
         return this.source;
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        builder.field("repository", repository);
+        builder.field("source", source);
+        builder.field("target", target);
+        if (indices != null) {
+            builder.startArray("indices");
+            for (String index : indices) {
+                builder.value(index);
+            }
+            builder.endArray();
+        }
+        if (indicesOptions != null) {
+            indicesOptions.toXContent(builder, params);
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    @Override
+    public String toString() {
+        return Strings.toString(this);
+    }
 }