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 134dc921c45..51f39407748 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
@@ -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.
+ *
+ * See Snapshot and Restore
+ * API on elastic.co
+ */
+ public AcknowledgedResponse clone(CloneSnapshotRequest cloneSnapshotRequest, RequestOptions options)
+ throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(cloneSnapshotRequest, SnapshotRequestConverters::cloneSnapshot, options,
+ AcknowledgedResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously clones a snapshot.
+ *
+ * See Snapshot and Restore
+ * API on elastic.co
+ * @return cancellable that may be used to cancel the request
+ */
+ public Cancellable cloneAsync(CloneSnapshotRequest cloneSnapshotRequest, RequestOptions options,
+ ActionListener listener) {
+ return restHighLevelClient.performRequestAsyncAndParseEntity(cloneSnapshotRequest,
+ SnapshotRequestConverters::cloneSnapshot, options,
+ AcknowledgedResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Get snapshots.
* See Snapshot and Restore
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java
index 796845d3525..e3dec27f97a 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SnapshotRequestConverters.java
+++ b/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")
.addPathPart(getSnapshotsRequest.repository());
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 3715011f186..7701d55687e 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,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;
@@ -351,6 +352,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 randomUserMetadata() {
if (randomBoolean()) {
return null;
diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/snapshot.clone.json b/rest-api-spec/src/main/resources/rest-api-spec/api/snapshot.clone.json
new file mode 100644
index 00000000000..18122bc209b
--- /dev/null
+++ b/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
+ }
+ }
+}
diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.clone/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/snapshot.clone/10_basic.yml
new file mode 100644
index 00000000000..fb289355e08
--- /dev/null
+++ b/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 }
diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java
index 4d1eb0952e3..c2f4028ffb5 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java
+++ b/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 implements IndicesRequest.Replaceable{
+public class CloneSnapshotRequest extends MasterNodeRequest implements IndicesRequest.Replaceable, ToXContentObject {
private final String repository;
@@ -139,4 +142,29 @@ public class CloneSnapshotRequest extends MasterNodeRequest