Skip to content

Commit 45ba935

Browse files
Add Clone Snapshot Request Handling Scaffolding (#63037)
Adds all the scaffolding for snapshot clone request handling and index-to-clone resolution to reduce the diff in #61839 to the bare essentials of the state machine changes for snapshot cloning and relevant tests and give us the opportunity to review the API in isolation.
1 parent 910636a commit 45ba935

File tree

8 files changed

+419
-0
lines changed

8 files changed

+419
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import org.elasticsearch.action.admin.cluster.settings.TransportClusterUpdateSettingsAction;
6464
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsAction;
6565
import org.elasticsearch.action.admin.cluster.shards.TransportClusterSearchShardsAction;
66+
import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotAction;
67+
import org.elasticsearch.action.admin.cluster.snapshots.clone.TransportCloneSnapshotAction;
6668
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction;
6769
import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction;
6870
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotAction;
@@ -265,6 +267,7 @@
265267
import org.elasticsearch.rest.action.admin.cluster.RestCancelTasksAction;
266268
import org.elasticsearch.rest.action.admin.cluster.RestCleanupRepositoryAction;
267269
import org.elasticsearch.rest.action.admin.cluster.RestClearVotingConfigExclusionsAction;
270+
import org.elasticsearch.rest.action.admin.cluster.RestCloneSnapshotAction;
268271
import org.elasticsearch.rest.action.admin.cluster.RestClusterAllocationExplainAction;
269272
import org.elasticsearch.rest.action.admin.cluster.RestClusterGetSettingsAction;
270273
import org.elasticsearch.rest.action.admin.cluster.RestClusterHealthAction;
@@ -509,6 +512,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
509512
actions.register(GetSnapshotsAction.INSTANCE, TransportGetSnapshotsAction.class);
510513
actions.register(DeleteSnapshotAction.INSTANCE, TransportDeleteSnapshotAction.class);
511514
actions.register(CreateSnapshotAction.INSTANCE, TransportCreateSnapshotAction.class);
515+
actions.register(CloneSnapshotAction.INSTANCE, TransportCloneSnapshotAction.class);
512516
actions.register(RestoreSnapshotAction.INSTANCE, TransportRestoreSnapshotAction.class);
513517
actions.register(SnapshotsStatusAction.INSTANCE, TransportSnapshotsStatusAction.class);
514518

@@ -659,6 +663,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
659663
registerHandler.accept(new RestCleanupRepositoryAction());
660664
registerHandler.accept(new RestGetSnapshotsAction());
661665
registerHandler.accept(new RestCreateSnapshotAction());
666+
registerHandler.accept(new RestCloneSnapshotAction());
662667
registerHandler.accept(new RestRestoreSnapshotAction());
663668
registerHandler.accept(new RestDeleteSnapshotAction());
664669
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+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.ActionListener;
23+
import org.elasticsearch.action.support.ActionFilters;
24+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
25+
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
26+
import org.elasticsearch.cluster.ClusterState;
27+
import org.elasticsearch.cluster.block.ClusterBlockException;
28+
import org.elasticsearch.cluster.block.ClusterBlockLevel;
29+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
30+
import org.elasticsearch.cluster.service.ClusterService;
31+
import org.elasticsearch.common.inject.Inject;
32+
import org.elasticsearch.common.io.stream.StreamInput;
33+
import org.elasticsearch.snapshots.SnapshotsService;
34+
import org.elasticsearch.tasks.Task;
35+
import org.elasticsearch.threadpool.ThreadPool;
36+
import org.elasticsearch.transport.TransportService;
37+
38+
import java.io.IOException;
39+
40+
/**
41+
* Transport action for the clone snapshot operation.
42+
*/
43+
public final class TransportCloneSnapshotAction extends TransportMasterNodeAction<CloneSnapshotRequest, AcknowledgedResponse> {
44+
45+
private final SnapshotsService snapshotsService;
46+
47+
@Inject
48+
public TransportCloneSnapshotAction(TransportService transportService, ClusterService clusterService,
49+
ThreadPool threadPool, SnapshotsService snapshotsService, ActionFilters actionFilters,
50+
IndexNameExpressionResolver indexNameExpressionResolver) {
51+
super(CloneSnapshotAction.NAME, transportService, clusterService, threadPool, actionFilters, CloneSnapshotRequest::new,
52+
indexNameExpressionResolver);
53+
this.snapshotsService = snapshotsService;
54+
}
55+
56+
@Override
57+
protected String executor() {
58+
return ThreadPool.Names.SAME;
59+
}
60+
61+
@Override
62+
protected AcknowledgedResponse read(StreamInput in) throws IOException {
63+
return new AcknowledgedResponse(in);
64+
}
65+
66+
@Override
67+
protected ClusterBlockException checkBlock(CloneSnapshotRequest request, ClusterState state) {
68+
// Cluster is not affected but we look up repositories in metadata
69+
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
70+
}
71+
72+
@Override
73+
protected void masterOperation(Task task, final CloneSnapshotRequest request, ClusterState state,
74+
final ActionListener<AcknowledgedResponse> listener) {
75+
throw new UnsupportedOperationException("not implemented yet");
76+
}
77+
}

server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest;
7272
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequestBuilder;
7373
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsResponse;
74+
import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest;
75+
import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequestBuilder;
7476
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
7577
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder;
7678
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
@@ -505,6 +507,21 @@ public interface ClusterAdminClient extends ElasticsearchClient {
505507
*/
506508
CreateSnapshotRequestBuilder prepareCreateSnapshot(String repository, String name);
507509

510+
/**
511+
* Clones a snapshot.
512+
*/
513+
CloneSnapshotRequestBuilder prepareCloneSnapshot(String repository, String source, String target);
514+
515+
/**
516+
* Clones a snapshot.
517+
*/
518+
ActionFuture<AcknowledgedResponse> cloneSnapshot(CloneSnapshotRequest request);
519+
520+
/**
521+
* Clones a snapshot.
522+
*/
523+
void cloneSnapshot(CloneSnapshotRequest request, ActionListener<AcknowledgedResponse> listener);
524+
508525
/**
509526
* Get snapshots.
510527
*/

0 commit comments

Comments
 (0)