From 7fbe2e3f78ec63e784f2f939df164515149026bd Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Tue, 29 Sep 2020 21:46:45 +0200 Subject: [PATCH 1/6] Add Clone Snapshot Request Handling Scaffolding 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. --- .../elasticsearch/action/ActionModule.java | 5 + .../snapshots/clone/CloneSnapshotAction.java | 33 +++++ .../snapshots/clone/CloneSnapshotRequest.java | 114 ++++++++++++++++++ .../clone/CloneSnapshotRequestBuilder.java | 45 +++++++ .../clone/TransportCloneSnapshotAction.java | 77 ++++++++++++ .../client/ClusterAdminClient.java | 17 +++ .../client/support/AbstractClient.java | 18 +++ .../cluster/RestCloneSnapshotAction.java | 58 +++++++++ .../snapshots/SnapshotUtils.java | 47 ++++++++ .../snapshots/SnapshotUtilsTests.java | 32 +++++ 10 files changed, 446 insertions(+) create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotAction.java create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequestBuilder.java create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/TransportCloneSnapshotAction.java create mode 100644 server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 97a2f6f05cbb0..5260d40bd536f 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -63,6 +63,8 @@ import org.elasticsearch.action.admin.cluster.settings.TransportClusterUpdateSettingsAction; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsAction; import org.elasticsearch.action.admin.cluster.shards.TransportClusterSearchShardsAction; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotAction; +import org.elasticsearch.action.admin.cluster.snapshots.clone.TransportCloneSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotAction; @@ -265,6 +267,7 @@ import org.elasticsearch.rest.action.admin.cluster.RestCancelTasksAction; import org.elasticsearch.rest.action.admin.cluster.RestCleanupRepositoryAction; import org.elasticsearch.rest.action.admin.cluster.RestClearVotingConfigExclusionsAction; +import org.elasticsearch.rest.action.admin.cluster.RestCloneSnapshotAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterAllocationExplainAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterGetSettingsAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterHealthAction; @@ -509,6 +512,7 @@ public void reg actions.register(GetSnapshotsAction.INSTANCE, TransportGetSnapshotsAction.class); actions.register(DeleteSnapshotAction.INSTANCE, TransportDeleteSnapshotAction.class); actions.register(CreateSnapshotAction.INSTANCE, TransportCreateSnapshotAction.class); + actions.register(CloneSnapshotAction.INSTANCE, TransportCloneSnapshotAction.class); actions.register(RestoreSnapshotAction.INSTANCE, TransportRestoreSnapshotAction.class); actions.register(SnapshotsStatusAction.INSTANCE, TransportSnapshotsStatusAction.class); @@ -659,6 +663,7 @@ public void initRestHandlers(Supplier nodesInCluster) { registerHandler.accept(new RestCleanupRepositoryAction()); registerHandler.accept(new RestGetSnapshotsAction()); registerHandler.accept(new RestCreateSnapshotAction()); + registerHandler.accept(new RestCloneSnapshotAction()); registerHandler.accept(new RestRestoreSnapshotAction()); registerHandler.accept(new RestDeleteSnapshotAction()); registerHandler.accept(new RestSnapshotsStatusAction()); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotAction.java new file mode 100644 index 0000000000000..e995469759bf0 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotAction.java @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.cluster.snapshots.clone; + +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; + +public final class CloneSnapshotAction extends ActionType { + + public static final CloneSnapshotAction INSTANCE = new CloneSnapshotAction(); + public static final String NAME = "cluster:admin/snapshot/clone"; + + private CloneSnapshotAction() { + super(NAME, AcknowledgedResponse::new); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000000..dd03818c30a0a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequest.java @@ -0,0 +1,114 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.cluster.snapshots.clone; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +public class CloneSnapshotRequest extends MasterNodeRequest { + + private final String repository; + + private final String source; + + private final String target; + + // TODO: the current logic does not allow for specifying index resolution parameters like hidden and such. Do we care about cloning + // system or hidden indices? + private String[] indices; + + public CloneSnapshotRequest(StreamInput in) throws IOException { + super(in); + repository = in.readString(); + source = in.readString(); + target = in.readString(); + indices = in.readStringArray(); + } + + public CloneSnapshotRequest(String repository, String source, String target, String[] indices) { + this.repository = repository; + this.source = source; + this.target = target; + this.indices = indices; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(repository); + out.writeString(source); + out.writeString(target); + out.writeStringArray(indices); + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (source == null) { + validationException = addValidationError("source snapshot name is missing", null); + } + if (target == null) { + validationException = addValidationError("target snapshot name is missing", null); + } + if (repository == null) { + validationException = addValidationError("repository is missing", validationException); + } + if (indices == null) { + validationException = addValidationError("indices is null", validationException); + } else if (indices.length == 0) { + validationException = addValidationError("indices patterns are empty", validationException); + } else { + for (String index : indices) { + if (index == null) { + validationException = addValidationError("index is null", validationException); + break; + } + } + } + return validationException; + } + + public String[] indices() { + return this.indices; + } + + public CloneSnapshotRequest indices(String... indices) { + this.indices = indices; + return this; + } + + public String repository() { + return this.repository; + } + + public String target() { + return this.target; + } + + public String source() { + return this.source; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequestBuilder.java new file mode 100644 index 0000000000000..7d62184d92c48 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/CloneSnapshotRequestBuilder.java @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.cluster.snapshots.clone; + +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder; +import org.elasticsearch.client.ElasticsearchClient; +import org.elasticsearch.common.Strings; + +public class CloneSnapshotRequestBuilder extends MasterNodeOperationRequestBuilder { + + protected CloneSnapshotRequestBuilder(ElasticsearchClient client, ActionType action, + CloneSnapshotRequest request) { + super(client, action, request); + } + + public CloneSnapshotRequestBuilder(ElasticsearchClient client, ActionType action, + String repository, String source, String target) { + this(client, action, new CloneSnapshotRequest(repository, source, target, Strings.EMPTY_ARRAY)); + } + + public CloneSnapshotRequestBuilder setIndices(String... indices) { + request.indices(indices); + return this; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/TransportCloneSnapshotAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/TransportCloneSnapshotAction.java new file mode 100644 index 0000000000000..a8a7f18d0ecb2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/clone/TransportCloneSnapshotAction.java @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.cluster.snapshots.clone; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.snapshots.SnapshotsService; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; + +/** + * Transport action for the clone snapshot operation. + */ +public final class TransportCloneSnapshotAction extends TransportMasterNodeAction { + + private final SnapshotsService snapshotsService; + + @Inject + public TransportCloneSnapshotAction(TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, SnapshotsService snapshotsService, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver) { + super(CloneSnapshotAction.NAME, transportService, clusterService, threadPool, actionFilters, CloneSnapshotRequest::new, + indexNameExpressionResolver); + this.snapshotsService = snapshotsService; + } + + @Override + protected String executor() { + return ThreadPool.Names.SAME; + } + + @Override + protected AcknowledgedResponse read(StreamInput in) throws IOException { + return new AcknowledgedResponse(in); + } + + @Override + protected ClusterBlockException checkBlock(CloneSnapshotRequest request, ClusterState state) { + // Cluster is not affected but we look up repositories in metadata + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); + } + + @Override + protected void masterOperation(Task task, final CloneSnapshotRequest request, ClusterState state, + final ActionListener listener) { + throw new UnsupportedOperationException("not implemented yet"); + } +} diff --git a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java index 0093eee3204fb..69b7ef08831f4 100644 --- a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java +++ b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java @@ -71,6 +71,8 @@ import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequestBuilder; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsResponse; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; @@ -505,6 +507,21 @@ public interface ClusterAdminClient extends ElasticsearchClient { */ CreateSnapshotRequestBuilder prepareCreateSnapshot(String repository, String name); + /** + * Clones a snapshot. + */ + CloneSnapshotRequestBuilder prepareCloneSnapshot(String repository, String source, String target); + + /** + * Clones a snapshot. + */ + ActionFuture cloneSnapshot(CloneSnapshotRequest request); + + /** + * Clones a snapshot. + */ + void cloneSnapshot(CloneSnapshotRequest request, ActionListener listener); + /** * Get snapshots. */ diff --git a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java index 2601a9af69690..763e5ec88bbc7 100644 --- a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java +++ b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java @@ -94,6 +94,9 @@ import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequestBuilder; import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsResponse; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotAction; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; @@ -935,6 +938,21 @@ public CreateSnapshotRequestBuilder prepareCreateSnapshot(String repository, Str return new CreateSnapshotRequestBuilder(this, CreateSnapshotAction.INSTANCE, repository, name); } + @Override + public CloneSnapshotRequestBuilder prepareCloneSnapshot(String repository, String source, String target) { + return new CloneSnapshotRequestBuilder(this, CloneSnapshotAction.INSTANCE, repository, source, target); + } + + @Override + public ActionFuture cloneSnapshot(CloneSnapshotRequest request) { + return execute(CloneSnapshotAction.INSTANCE, request); + } + + @Override + public void cloneSnapshot(CloneSnapshotRequest request, ActionListener listener) { + execute(CloneSnapshotAction.INSTANCE, request, listener); + } + @Override public ActionFuture getSnapshots(GetSnapshotsRequest request) { return execute(GetSnapshotsAction.INSTANCE, request); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java new file mode 100644 index 0000000000000..f2be2858f79c6 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.rest.action.admin.cluster; + +import org.elasticsearch.action.admin.cluster.snapshots.clone.CloneSnapshotRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.PUT; + +/** + * Clones indices from one snapshot into another snapshot in the same repository + */ +public class RestCloneSnapshotAction extends BaseRestHandler { + + @Override + public List routes() { + return List.of(new Route(PUT, "/_snapshot/{repository}/{source_snapshot}/_clone/{target_snapshot}")); + } + + @Override + public String getName() { + return "clone_snapshot_action"; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + final CloneSnapshotRequest cloneSnapshotRequest = new CloneSnapshotRequest( + request.param("repository"), request.param("source_snapshot"), request.param("target_snapshot"), + XContentMapValues.nodeStringArrayValue(request.contentParser().map().getOrDefault("indices", Collections.emptyList()))); + cloneSnapshotRequest.masterNodeTimeout(request.paramAsTime("master_timeout", cloneSnapshotRequest.masterNodeTimeout())); + return channel -> client.admin().cluster().cloneSnapshot(cloneSnapshotRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotUtils.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotUtils.java index 0c5e92d782113..801a6e38be39c 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotUtils.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotUtils.java @@ -20,13 +20,20 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.repositories.IndexId; +import org.elasticsearch.repositories.RepositoryData; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Snapshot utilities @@ -118,4 +125,44 @@ public static List filterIndices(List availableIndices, String[] } return List.copyOf(result); } + + /** + * Finds the {@link IndexId}s in the given source snapshot that match the given index patterns for a snapshot clone operation. + * + * @param sourceSnapshotId source snapshot id + * @param targetSnapshot target snapshot that the clone operation would create + * @param repositoryData repository data of the repository that the clone + * @param indexPatterns index patterns to clone from source- to target snapshot + * @return list of index ids to clone + * @throws SnapshotException on failure to find concrete request index ids or any index ids + */ + static List findIndexIdsToClone(SnapshotId sourceSnapshotId, Snapshot targetSnapshot, RepositoryData repositoryData, + String... indexPatterns) { + final Map indicesInSource = repositoryData.getIndices().values() + .stream() + .filter(indexId -> repositoryData.getSnapshots(indexId).contains(sourceSnapshotId)) + .collect(Collectors.toMap(IndexId::getName, Function.identity())); + final List matchingIndices = new ArrayList<>(); + for (String indexNameOrPattern : indexPatterns) { + if (Regex.isSimpleMatchPattern(indexNameOrPattern)) { + for (IndexId indexId : indicesInSource.values()) { + if (Regex.simpleMatch(indexNameOrPattern, indexId.getName())) { + matchingIndices.add(indexId); + } + } + } else { + final IndexId foundIndexId = indicesInSource.get(indexNameOrPattern); + if (foundIndexId == null) { + throw new SnapshotException(targetSnapshot, "No index [" + indexNameOrPattern + "] found in the source snapshot [" + + sourceSnapshotId + "]"); + } + matchingIndices.add(foundIndexId); + } + } + if (matchingIndices.isEmpty()) { + throw new SnapshotException(targetSnapshot, "No indices in the source snapshot [" + sourceSnapshotId + + "] matched requested pattern [" + Strings.arrayToCommaDelimitedString(indexPatterns) + "]"); + } + return matchingIndices; + } } diff --git a/server/src/test/java/org/elasticsearch/snapshots/SnapshotUtilsTests.java b/server/src/test/java/org/elasticsearch/snapshots/SnapshotUtilsTests.java index 64a7dead59f55..a6dbb2e3f914d 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SnapshotUtilsTests.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SnapshotUtilsTests.java @@ -18,13 +18,21 @@ */ package org.elasticsearch.snapshots; +import org.elasticsearch.Version; import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.repositories.IndexId; +import org.elasticsearch.repositories.RepositoryData; +import org.elasticsearch.repositories.ShardGenerations; import org.elasticsearch.test.ESTestCase; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; public class SnapshotUtilsTests extends ESTestCase { public void testIndexNameFiltering() { @@ -55,4 +63,28 @@ private void assertIndexNameFiltering(String[] indices, String[] filter, Indices List actual = SnapshotUtils.filterIndices(indicesList, filter, indicesOptions); assertThat(actual, containsInAnyOrder(expected)); } + + public void testFilterIndexIdsForClone() { + final SnapshotId sourceSnapshotId = new SnapshotId("source", UUIDs.randomBase64UUID(random())); + final Snapshot targetSnapshot = new Snapshot("test-repo", new SnapshotId("target", UUIDs.randomBase64UUID(random()))); + final IndexId existingIndexId = new IndexId("test-idx", UUIDs.randomBase64UUID(random())); + final RepositoryData repositoryData = RepositoryData.EMPTY.addSnapshot(sourceSnapshotId, SnapshotState.SUCCESS, Version.CURRENT, + ShardGenerations.builder().put(existingIndexId, 0, UUIDs.randomBase64UUID()).build(), + Collections.emptyMap(), Collections.emptyMap()); + { + final SnapshotException sne = expectThrows(SnapshotException.class, () -> SnapshotUtils.findIndexIdsToClone( + sourceSnapshotId, targetSnapshot, repositoryData, "does-not-exist")); + assertThat(sne.getMessage(), containsString("No index [does-not-exist] found in the source snapshot ")); + } + { + final SnapshotException sne = expectThrows(SnapshotException.class, () -> SnapshotUtils.findIndexIdsToClone( + sourceSnapshotId, targetSnapshot, repositoryData, "does-not-exist-*")); + assertThat(sne.getMessage(), containsString("No indices in the source snapshot [" + sourceSnapshotId + + "] matched requested pattern [")); + } + assertThat(SnapshotUtils.findIndexIdsToClone(sourceSnapshotId, targetSnapshot, repositoryData, existingIndexId.getName()), + contains(existingIndexId)); + assertThat(SnapshotUtils.findIndexIdsToClone(sourceSnapshotId, targetSnapshot, repositoryData, "test-*"), + contains(existingIndexId)); + } } From 756c0515ff25b31e6f1d2aa26da77c052fbf0f89 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Wed, 30 Sep 2020 07:15:29 +0200 Subject: [PATCH 2/6] fix var name --- .../rest/action/admin/cluster/RestCloneSnapshotAction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java index f2be2858f79c6..6ab246be91b63 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestCloneSnapshotAction.java @@ -39,7 +39,7 @@ public class RestCloneSnapshotAction extends BaseRestHandler { @Override public List routes() { - return List.of(new Route(PUT, "/_snapshot/{repository}/{source_snapshot}/_clone/{target_snapshot}")); + return List.of(new Route(PUT, "/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}")); } @Override @@ -50,7 +50,7 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { final CloneSnapshotRequest cloneSnapshotRequest = new CloneSnapshotRequest( - request.param("repository"), request.param("source_snapshot"), request.param("target_snapshot"), + request.param("repository"), request.param("snapshot"), request.param("target_snapshot"), XContentMapValues.nodeStringArrayValue(request.contentParser().map().getOrDefault("indices", Collections.emptyList()))); cloneSnapshotRequest.masterNodeTimeout(request.paramAsTime("master_timeout", cloneSnapshotRequest.masterNodeTimeout())); return channel -> client.admin().cluster().cloneSnapshot(cloneSnapshotRequest, new RestToXContentListener<>(channel)); From 77aca29c31c3d8b031ee4f19d9230811a4eadf91 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Wed, 30 Sep 2020 11:37:56 +0200 Subject: [PATCH 3/6] CR comments --- .../snapshots/clone/CloneSnapshotRequest.java | 32 +++++++++++-- .../clone/CloneSnapshotRequestBuilder.java | 20 ++++++++ .../snapshots/SnapshotUtils.java | 47 ------------------- .../snapshots/SnapshotUtilsTests.java | 32 ------------- 4 files changed, 49 insertions(+), 82 deletions(-) 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 dd03818c30a0a..9acb8558c0667 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 @@ -20,6 +20,8 @@ package org.elasticsearch.action.admin.cluster.snapshots.clone; 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.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -28,7 +30,7 @@ import static org.elasticsearch.action.ValidateActions.addValidationError; -public class CloneSnapshotRequest extends MasterNodeRequest { +public class CloneSnapshotRequest extends MasterNodeRequest implements IndicesRequest.Replaceable{ private final String repository; @@ -36,10 +38,10 @@ public class CloneSnapshotRequest extends MasterNodeRequest