Skip to content

Commit 5c3a4c1

Browse files
Clone Snapshot API (#61839) (#63291)
Snapshot clone API. Complete except for some TODOs around documentation (and adding HLRC support). backport of #61839, #63217, #63037
1 parent 25f8a3b commit 5c3a4c1

File tree

20 files changed

+2142
-128
lines changed

20 files changed

+2142
-128
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[[clone-snapshot-api]]
2+
=== Clone snapshot API
3+
++++
4+
<titleabbrev>Clone snapshot</titleabbrev>
5+
++++
6+
7+
Clones part or all of a snapshot into a new snapshot.
8+
9+
[source,console]
10+
----
11+
PUT /_snapshot/my_repository/source_snapshot/_clone/target_snapshot
12+
{
13+
"indices": "index_a,index_b"
14+
}
15+
----
16+
// TEST[skip:TODO]
17+
18+
[[clone-snapshot-api-request]]
19+
==== {api-request-title}
20+
21+
`PUT /_snapshot/<repository>/<source_snapshot>/_clone/<target_snapshot>`
22+
23+
[[clone-snapshot-api-desc]]
24+
==== {api-description-title}
25+
26+
The clone snapshot API allows creating a copy of all or part of an existing snapshot
27+
within the same repository.
28+
29+
[[clone-snapshot-api-params]]
30+
==== {api-path-parms-title}
31+
32+
`<repository>`::
33+
(Required, string)
34+
Name of the snapshot repository that both source and target snapshot belong to.
35+
36+
[[clone-snapshot-api-query-params]]
37+
==== {api-query-parms-title}
38+
39+
`master_timeout`::
40+
(Optional, <<time-units, time units>>) Specifies the period of time to wait for
41+
a connection to the master node. If no response is received before the timeout
42+
expires, the request fails and returns an error. Defaults to `30s`.
43+
44+
`timeout`::
45+
(Optional, <<time-units, time units>>) Specifies the period of time to wait for
46+
a response. If no response is received before the timeout expires, the request
47+
fails and returns an error. Defaults to `30s`.
48+
49+
`indices`::
50+
(Required, string)
51+
A comma-separated list of indices to include in the snapshot.
52+
<<multi-index,Multi-index syntax>> is supported.

docs/reference/snapshot-restore/index.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ understand the time requirements before proceeding.
107107
--
108108

109109
include::register-repository.asciidoc[]
110+
include::apis/clone-snapshot-api.asciidoc[]
110111
include::take-snapshot.asciidoc[]
111112
include::restore-snapshot.asciidoc[]
112113
include::monitor-snapshot-restore.asciidoc[]

docs/reference/snapshot-restore/take-snapshot.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,5 @@ PUT /_snapshot/my_backup/<snapshot-{now/d}>
124124
PUT /_snapshot/my_backup/%3Csnapshot-%7Bnow%2Fd%7D%3E
125125
-----------------------------------
126126
// TEST[continued]
127+
128+
NOTE: You can also create snapshots that are copies of part of an existing snapshot using the <<clone-snapshot-api,clone snapshot API>>.

server/src/internalClusterTest/java/org/elasticsearch/snapshots/CloneSnapshotIT.java

Lines changed: 404 additions & 6 deletions
Large diffs are not rendered by default.

server/src/main/java/org/elasticsearch/action/ActionModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
import org.elasticsearch.action.admin.cluster.settings.TransportClusterUpdateSettingsAction;
6565
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsAction;
6666
import org.elasticsearch.action.admin.cluster.shards.TransportClusterSearchShardsAction;
67+
import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotAction;
68+
import org.elasticsearch.action.admin.cluster.snapshots.clone.TransportCloneSnapshotAction;
6769
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction;
6870
import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction;
6971
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotAction;
@@ -267,6 +269,7 @@
267269
import org.elasticsearch.rest.action.admin.cluster.RestCancelTasksAction;
268270
import org.elasticsearch.rest.action.admin.cluster.RestCleanupRepositoryAction;
269271
import org.elasticsearch.rest.action.admin.cluster.RestClearVotingConfigExclusionsAction;
272+
import org.elasticsearch.rest.action.admin.cluster.RestCloneSnapshotAction;
270273
import org.elasticsearch.rest.action.admin.cluster.RestClusterAllocationExplainAction;
271274
import org.elasticsearch.rest.action.admin.cluster.RestClusterGetSettingsAction;
272275
import org.elasticsearch.rest.action.admin.cluster.RestClusterHealthAction;
@@ -522,6 +525,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
522525
actions.register(GetSnapshotsAction.INSTANCE, TransportGetSnapshotsAction.class);
523526
actions.register(DeleteSnapshotAction.INSTANCE, TransportDeleteSnapshotAction.class);
524527
actions.register(CreateSnapshotAction.INSTANCE, TransportCreateSnapshotAction.class);
528+
actions.register(CloneSnapshotAction.INSTANCE, TransportCloneSnapshotAction.class);
525529
actions.register(RestoreSnapshotAction.INSTANCE, TransportRestoreSnapshotAction.class);
526530
actions.register(SnapshotsStatusAction.INSTANCE, TransportSnapshotsStatusAction.class);
527531

@@ -665,6 +669,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
665669
registerHandler.accept(new RestCleanupRepositoryAction());
666670
registerHandler.accept(new RestGetSnapshotsAction());
667671
registerHandler.accept(new RestCreateSnapshotAction());
672+
registerHandler.accept(new RestCloneSnapshotAction());
668673
registerHandler.accept(new RestRestoreSnapshotAction());
669674
registerHandler.accept(new RestDeleteSnapshotAction());
670675
registerHandler.accept(new RestSnapshotsStatusAction());
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.snapshots.clone;
21+
22+
import org.elasticsearch.action.ActionType;
23+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
24+
25+
public final class CloneSnapshotAction extends ActionType<AcknowledgedResponse> {
26+
27+
public static final CloneSnapshotAction INSTANCE = new CloneSnapshotAction();
28+
public static final String NAME = "cluster:admin/snapshot/clone";
29+
30+
private CloneSnapshotAction() {
31+
super(NAME, AcknowledgedResponse::new);
32+
}
33+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.snapshots.clone;
21+
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
import org.elasticsearch.action.IndicesRequest;
24+
import org.elasticsearch.action.support.IndicesOptions;
25+
import org.elasticsearch.action.support.master.MasterNodeRequest;
26+
import org.elasticsearch.common.io.stream.StreamInput;
27+
import org.elasticsearch.common.io.stream.StreamOutput;
28+
29+
import java.io.IOException;
30+
31+
import static org.elasticsearch.action.ValidateActions.addValidationError;
32+
33+
public class CloneSnapshotRequest extends MasterNodeRequest<CloneSnapshotRequest> implements IndicesRequest.Replaceable{
34+
35+
private final String repository;
36+
37+
private final String source;
38+
39+
private final String target;
40+
41+
private String[] indices;
42+
43+
private IndicesOptions indicesOptions = IndicesOptions.strictExpandHidden();
44+
45+
public CloneSnapshotRequest(StreamInput in) throws IOException {
46+
super(in);
47+
repository = in.readString();
48+
source = in.readString();
49+
target = in.readString();
50+
indices = in.readStringArray();
51+
indicesOptions = IndicesOptions.readIndicesOptions(in);
52+
}
53+
54+
/**
55+
* Creates a clone snapshot request for cloning the given source snapshot's indices into the given target snapshot on the given
56+
* repository.
57+
*
58+
* @param repository repository that source snapshot belongs to and that the target snapshot will be created in
59+
* @param source source snapshot name
60+
* @param target target snapshot name
61+
* @param indices indices to clone from source to target
62+
*/
63+
public CloneSnapshotRequest(String repository, String source, String target, String[] indices) {
64+
this.repository = repository;
65+
this.source = source;
66+
this.target = target;
67+
this.indices = indices;
68+
}
69+
70+
@Override
71+
public void writeTo(StreamOutput out) throws IOException {
72+
super.writeTo(out);
73+
out.writeString(repository);
74+
out.writeString(source);
75+
out.writeString(target);
76+
out.writeStringArray(indices);
77+
indicesOptions.writeIndicesOptions(out);
78+
}
79+
80+
@Override
81+
public ActionRequestValidationException validate() {
82+
ActionRequestValidationException validationException = null;
83+
if (source == null) {
84+
validationException = addValidationError("source snapshot name is missing", null);
85+
}
86+
if (target == null) {
87+
validationException = addValidationError("target snapshot name is missing", null);
88+
}
89+
if (repository == null) {
90+
validationException = addValidationError("repository is missing", validationException);
91+
}
92+
if (indices == null) {
93+
validationException = addValidationError("indices is null", validationException);
94+
} else if (indices.length == 0) {
95+
validationException = addValidationError("indices patterns are empty", validationException);
96+
} else {
97+
for (String index : indices) {
98+
if (index == null) {
99+
validationException = addValidationError("index is null", validationException);
100+
break;
101+
}
102+
}
103+
}
104+
return validationException;
105+
}
106+
107+
@Override
108+
public String[] indices() {
109+
return this.indices;
110+
}
111+
112+
@Override
113+
public IndicesOptions indicesOptions() {
114+
return indicesOptions;
115+
}
116+
117+
@Override
118+
public CloneSnapshotRequest indices(String... indices) {
119+
this.indices = indices;
120+
return this;
121+
}
122+
123+
/**
124+
* @see CloneSnapshotRequestBuilder#setIndicesOptions
125+
*/
126+
public CloneSnapshotRequest indicesOptions(IndicesOptions indicesOptions) {
127+
this.indicesOptions = indicesOptions;
128+
return this;
129+
}
130+
131+
public String repository() {
132+
return this.repository;
133+
}
134+
135+
public String target() {
136+
return this.target;
137+
}
138+
139+
public String source() {
140+
return this.source;
141+
}
142+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.snapshots.clone;
21+
22+
import org.elasticsearch.action.ActionType;
23+
import org.elasticsearch.action.support.IndicesOptions;
24+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
25+
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
26+
import org.elasticsearch.client.ElasticsearchClient;
27+
import org.elasticsearch.common.Strings;
28+
29+
public class CloneSnapshotRequestBuilder extends MasterNodeOperationRequestBuilder<CloneSnapshotRequest, AcknowledgedResponse,
30+
CloneSnapshotRequestBuilder> {
31+
32+
protected CloneSnapshotRequestBuilder(ElasticsearchClient client, ActionType<AcknowledgedResponse> action,
33+
CloneSnapshotRequest request) {
34+
super(client, action, request);
35+
}
36+
37+
public CloneSnapshotRequestBuilder(ElasticsearchClient client, ActionType<AcknowledgedResponse> action,
38+
String repository, String source, String target) {
39+
this(client, action, new CloneSnapshotRequest(repository, source, target, Strings.EMPTY_ARRAY));
40+
}
41+
42+
/**
43+
* Sets a list of indices that should be cloned from the source to the target snapshot
44+
* <p>
45+
* The list of indices supports multi-index syntax. For example: "+test*" ,"-test42" will clone all indices with
46+
* prefix "test" except index "test42".
47+
*
48+
* @return this builder
49+
*/
50+
public CloneSnapshotRequestBuilder setIndices(String... indices) {
51+
request.indices(indices);
52+
return this;
53+
}
54+
55+
/**
56+
* Specifies the indices options. Like what type of requested indices to ignore. For example indices that don't exist.
57+
*
58+
* @param indicesOptions the desired behaviour regarding indices options
59+
* @return this request
60+
*/
61+
public CloneSnapshotRequestBuilder setIndicesOptions(IndicesOptions indicesOptions) {
62+
request.indicesOptions(indicesOptions);
63+
return this;
64+
}
65+
}

0 commit comments

Comments
 (0)