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 134dc921c450d..51f3940774860 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.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 Cancellable createAsync(CreateSnapshotRequest createSnapshotRequest, Requ
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 239fa06d14add..57fc15a212bce 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.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 @@ static Request createSnapshot(CreateSnapshotRequest createSnapshotRequest) throw
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());
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 b2b2919047fff..f8941077b2e80 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.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 void testDeleteSnapshot() throws IOException {
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 0000000000000..18122bc209b0e
--- /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 0000000000000..fb289355e08fb
--- /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 4d1eb0952e384..c2f4028ffb5a9 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.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 String target() {
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);
+ }
}