From 775dcfb9cadde37ef0a4337943794c80bb97da83 Mon Sep 17 00:00:00 2001 From: John Durham Date: Sat, 11 Mar 2023 11:30:56 -0600 Subject: [PATCH 01/10] implement CreateCollectionSnapshotAPI and DeleteCollectionSnapshotAPI --- .../api/CreateCollectionSnapshotAPI.java | 165 ++++++++++++++++++ .../api/DeleteCollectionSnapshotAPI.java | 145 +++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java new file mode 100644 index 000000000000..ea067a2b0faf --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.solr.handler.admin.api; + +import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2; +import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; +import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; +import static org.apache.solr.common.params.CommonAdminParams.ASYNC; +import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import org.apache.solr.client.solrj.SolrResponse; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterState; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.snapshots.SolrSnapshotManager; +import org.apache.solr.handler.admin.CollectionsHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.jersey.SolrJerseyResponse; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +@Path("/collections/{collName}/snapshots") +public class CreateCollectionSnapshotAPI extends AdminAPIBase { + + @Inject + public CreateCollectionSnapshotAPI( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @POST + @Path("/{snapshotName}") + @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) + @PermissionName(COLL_EDIT_PERM) + public CreateSnapshotResponse createSnapshot( + @Parameter(description = "The name of the collection.", required = true) + @PathParam("collName") + String collName, + @Parameter(description = "The name of the snapshot to be created.", required = true) + @PathParam("snapshotName") + String snapshotName, + @Parameter(description = "TODO follow aliases") + @DefaultValue("false") + @QueryParam("followAliases") + boolean followAliases, + @QueryParam("asyncId") String asyncId) + throws Exception { + + final CreateSnapshotResponse response = instantiateJerseyResponse(CreateSnapshotResponse.class); + final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); + recordCollectionForLogAndTracing(collName, solrQueryRequest); + + final String collectionName = + followAliases + ? coreContainer + .getZkController() + .getZkStateReader() + .getAliases() + .resolveSimpleAlias(collName) + : collName; + + final ClusterState clusterState = coreContainer.getZkController().getClusterState(); + if (!clusterState.hasCollection(collectionName)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Collection '" + collectionName + "' does not exist, no action taken."); + } + + final SolrZkClient client = coreContainer.getZkController().getZkClient(); + if (SolrSnapshotManager.snapshotExists(client, collectionName, snapshotName)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Snapshot with name '" + + snapshotName + + "' already exists for collection '" + + collectionName + + "', no action taken."); + } + + final ZkNodeProps remoteMessage = + createRemoteMessage(collName, followAliases, snapshotName, asyncId); + final SolrResponse remoteResponse = + CollectionsHandler.submitCollectionApiCommand( + coreContainer, + coreContainer.getDistributedCollectionCommandRunner(), + remoteMessage, + CollectionParams.CollectionAction.CREATESNAPSHOT, + DEFAULT_COLLECTION_OP_TIMEOUT); + + if (remoteResponse.getException() != null) { + throw remoteResponse.getException(); + } + + response.collection = collName; + response.followAliases = followAliases; + response.snapshotName = snapshotName; + response.requestId = asyncId; + + return response; + } + + public static class CreateSnapshotResponse extends SolrJerseyResponse { + @Schema(description = "The name of the collection.") + @JsonProperty(COLLECTION_PROP) + String collection; + + @Schema(description = "The name of the snapshot to be created.") + @JsonProperty("snapshot") + String snapshotName; + + @Schema(description = "todo") + @JsonProperty("followAliases") + boolean followAliases; + + @JsonProperty("requestId") + String requestId; + } + + private ZkNodeProps createRemoteMessage( + String collectionName, boolean followAliases, String snapshotName, String asyncId) { + final Map remoteMessage = new HashMap<>(); + + remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.CREATESNAPSHOT.toLower()); + remoteMessage.put(COLLECTION_PROP, collectionName); + remoteMessage.put(CoreAdminParams.COMMIT_NAME, snapshotName); + remoteMessage.put(FOLLOW_ALIASES, followAliases); + if (asyncId != null) remoteMessage.put(ASYNC, asyncId); + + return new ZkNodeProps(remoteMessage); + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java new file mode 100644 index 000000000000..0640e9917d4b --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.solr.handler.admin.api; + +import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2; +import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; +import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; +import static org.apache.solr.common.params.CommonAdminParams.ASYNC; +import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import org.apache.solr.client.solrj.SolrResponse; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterState; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.CollectionsHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.jersey.SolrJerseyResponse; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +@Path("/collections/{collName}/snapshots") +public class DeleteCollectionSnapshotAPI extends AdminAPIBase { + + @Inject + public DeleteCollectionSnapshotAPI( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @DELETE + @Path("/{snapshotName}") + @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) + @PermissionName(COLL_EDIT_PERM) + public DeleteSnapshotResponse deleteSnapshot( + @Parameter(description = "The name of the collection.", required = true) + @PathParam("collName") + String collName, + @Parameter(description = "The name of the snapshot to be deleted.", required = true) + @PathParam("snapshotName") + String snapshotName, + @Parameter(description = "TODO follow aliases") + @DefaultValue("false") + @QueryParam("followAliases") + boolean followAliases, + @QueryParam("asyncId") String asyncId) + throws Exception { + final DeleteSnapshotResponse response = instantiateJerseyResponse(DeleteSnapshotResponse.class); + final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); + recordCollectionForLogAndTracing(collName, solrQueryRequest); + + String collectionName = + coreContainer + .getZkController() + .getZkStateReader() + .getAliases() + .resolveSimpleAlias(collName); + + ClusterState clusterState = coreContainer.getZkController().getClusterState(); + if (!clusterState.hasCollection(collectionName)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Collection '" + collectionName + "' does not exist, no action taken."); + } + + final ZkNodeProps remoteMessage = + createRemoteMessage(collName, followAliases, snapshotName, asyncId); + final SolrResponse remoteResponse = + CollectionsHandler.submitCollectionApiCommand( + coreContainer, + coreContainer.getDistributedCollectionCommandRunner(), + remoteMessage, + CollectionParams.CollectionAction.DELETESNAPSHOT, + DEFAULT_COLLECTION_OP_TIMEOUT); + + if (remoteResponse.getException() != null) { + throw remoteResponse.getException(); + } + + response.collection = collName; + response.snapshotName = snapshotName; + response.requestId = asyncId; + + return response; + } + + public static class DeleteSnapshotResponse extends SolrJerseyResponse { + @Schema(description = "The name of the collection.") + @JsonProperty(COLLECTION_PROP) + String collection; + + @Schema(description = "The name of the snapshot to be deleted.") + @JsonProperty("snapshot") + String snapshotName; + + @JsonProperty("requestId") + String requestId; + } + + private ZkNodeProps createRemoteMessage( + String collectionName, boolean followAliases, String snapshotName, String asyncId) { + final Map remoteMessage = new HashMap<>(); + + remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.DELETESNAPSHOT.toLower()); + remoteMessage.put(COLLECTION_PROP, collectionName); + remoteMessage.put(CoreAdminParams.COMMIT_NAME, snapshotName); + remoteMessage.put(FOLLOW_ALIASES, followAliases); + + if (asyncId != null) remoteMessage.put(ASYNC, asyncId); + + return new ZkNodeProps(remoteMessage); + } +} From b08747ab91f59e144eab7948c1fcb21dcdaff5eb Mon Sep 17 00:00:00 2001 From: John Durham Date: Sat, 11 Mar 2023 17:08:48 -0600 Subject: [PATCH 02/10] implement ListCollectSnapshotsAPI --- .../handler/admin/CollectionsHandler.java | 115 ++++++------------ .../admin/api/ListCollectionSnapshotsAPI.java | 100 +++++++++++++++ 2 files changed, 140 insertions(+), 75 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 67ae42ca38a7..9a98c2c8feac 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -212,9 +212,11 @@ import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI; import org.apache.solr.handler.admin.api.CollectionPropertyAPI; import org.apache.solr.handler.admin.api.CollectionStatusAPI; +import org.apache.solr.handler.admin.api.CreateCollectionSnapshotAPI; import org.apache.solr.handler.admin.api.CreateShardAPI; import org.apache.solr.handler.admin.api.DeleteAliasAPI; import org.apache.solr.handler.admin.api.DeleteCollectionAPI; +import org.apache.solr.handler.admin.api.DeleteCollectionSnapshotAPI; import org.apache.solr.handler.admin.api.DeleteNodeAPI; import org.apache.solr.handler.admin.api.DeleteReplicaAPI; import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI; @@ -222,6 +224,7 @@ import org.apache.solr.handler.admin.api.ForceLeaderAPI; import org.apache.solr.handler.admin.api.InstallShardDataAPI; import org.apache.solr.handler.admin.api.ListAliasesAPI; +import org.apache.solr.handler.admin.api.ListCollectionSnapshotsAPI; import org.apache.solr.handler.admin.api.ListCollectionsAPI; import org.apache.solr.handler.admin.api.MigrateDocsAPI; import org.apache.solr.handler.admin.api.ModifyCollectionAPI; @@ -1725,96 +1728,55 @@ public Map execute( (req, rsp, h) -> { req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); - String extCollectionName = req.getParams().get(COLLECTION_PROP); - boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); - String collectionName = - followAliases - ? h.coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(extCollectionName) - : extCollectionName; - String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME); - ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final String extCollectionName = req.getParams().get(COLLECTION_PROP); + final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); + final String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME); + final String asyncId = req.getParams().get(ASYNC); - SolrZkClient client = h.coreContainer.getZkController().getZkClient(); - if (SolrSnapshotManager.snapshotExists(client, collectionName, commitName)) { - throw new SolrException( - ErrorCode.BAD_REQUEST, - "Snapshot with name '" - + commitName - + "' already exists for collection '" - + collectionName - + "', no action taken."); - } + final CreateCollectionSnapshotAPI createCollectionSnapshotAPI = + new CreateCollectionSnapshotAPI(h.coreContainer, req, rsp); - Map params = - copy( - req.getParams(), - null, - COLLECTION_PROP, - FOLLOW_ALIASES, - CoreAdminParams.COMMIT_NAME); - return params; + final CreateCollectionSnapshotAPI.CreateSnapshotResponse createSnapshotResponse = + createCollectionSnapshotAPI.createSnapshot( + extCollectionName, commitName, followAliases, asyncId); + + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createSnapshotResponse); + + return null; }), DELETESNAPSHOT_OP( DELETESNAPSHOT, (req, rsp, h) -> { req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); - String extCollectionName = req.getParams().get(COLLECTION_PROP); - String collectionName = - h.coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(extCollectionName); - ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final String extCollectionName = req.getParams().get(COLLECTION_PROP); + final String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME); + final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); + final String asyncId = req.getParams().get(ASYNC); - Map params = - copy( - req.getParams(), - null, - COLLECTION_PROP, - FOLLOW_ALIASES, - CoreAdminParams.COMMIT_NAME); - return params; + final DeleteCollectionSnapshotAPI deleteCollectionSnapshotAPI = + new DeleteCollectionSnapshotAPI(h.coreContainer, req, rsp); + + final DeleteCollectionSnapshotAPI.DeleteSnapshotResponse deleteSnapshotResponse = + deleteCollectionSnapshotAPI.deleteSnapshot( + extCollectionName, commitName, followAliases, asyncId); + + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteSnapshotResponse); + return null; }), LISTSNAPSHOTS_OP( LISTSNAPSHOTS, (req, rsp, h) -> { req.getParams().required().check(COLLECTION_PROP); - String extCollectionName = req.getParams().get(COLLECTION_PROP); - String collectionName = - h.coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(extCollectionName); - ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final ListCollectionSnapshotsAPI listCollectionSnapshotsAPI = + new ListCollectionSnapshotsAPI(h.coreContainer, req, rsp); + + final ListCollectionSnapshotsAPI.ListSnapshotsResponse response = + listCollectionSnapshotsAPI.listSnapshots(req.getParams().get(COLLECTION_PROP)); - NamedList snapshots = new NamedList(); - SolrZkClient client = h.coreContainer.getZkController().getZkClient(); - Collection m = - SolrSnapshotManager.listSnapshots(client, collectionName); - for (CollectionSnapshotMetaData meta : m) { + NamedList snapshots = new NamedList<>(); + for (CollectionSnapshotMetaData meta : response.snapshots.values()) { snapshots.add(meta.getName(), meta.toNamedList()); } @@ -2119,7 +2081,10 @@ public Collection> getJerseyResources() { CollectionPropertyAPI.class, DeleteNodeAPI.class, ListAliasesAPI.class, - AliasPropertyAPI.class); + AliasPropertyAPI.class, + ListCollectionSnapshotsAPI.class, + CreateCollectionSnapshotAPI.class, + DeleteCollectionSnapshotAPI.class); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java new file mode 100644 index 000000000000..a08867220bb5 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.solr.handler.admin.api; + +import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterState; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.snapshots.CollectionSnapshotMetaData; +import org.apache.solr.core.snapshots.SolrSnapshotManager; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.jersey.SolrJerseyResponse; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +@Path("/collections/{collName}/snapshots") +public class ListCollectionSnapshotsAPI extends AdminAPIBase { + + @Inject + public ListCollectionSnapshotsAPI( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @GET + @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) + @PermissionName(COLL_EDIT_PERM) + public ListSnapshotsResponse listSnapshots( + @Parameter(description = "The name of the collection.", required = true) + @PathParam("collName") + String collName) + throws Exception { + + final ListSnapshotsResponse response = instantiateJerseyResponse(ListSnapshotsResponse.class); + final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); + recordCollectionForLogAndTracing(collName, solrQueryRequest); + + String collectionName = + coreContainer + .getZkController() + .getZkStateReader() + .getAliases() + .resolveSimpleAlias(collName); + ClusterState clusterState = coreContainer.getZkController().getClusterState(); + if (!clusterState.hasCollection(collectionName)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Collection '" + collectionName + "' does not exist, no action taken."); + } + + SolrZkClient client = coreContainer.getZkController().getZkClient(); + Collection m = + SolrSnapshotManager.listSnapshots(client, collectionName); + + Map snapshots = new HashMap<>(m.size()); + for (CollectionSnapshotMetaData metaData : m) { + snapshots.put(metaData.getName(), metaData); + } + + response.snapshots = snapshots; + + return response; + } + + public static class ListSnapshotsResponse extends SolrJerseyResponse { + @Schema(description = "The snapshots for the collection.") + @JsonProperty(SolrSnapshotManager.SNAPSHOTS_INFO) + public Map snapshots; + } +} From 8b0690ae6d59081e1f1d3f40a98066b8a9b0c410 Mon Sep 17 00:00:00 2001 From: John Durham Date: Sat, 18 Mar 2023 13:29:12 -0500 Subject: [PATCH 03/10] tidy up and add followAlias description --- .../solr/handler/admin/api/CreateCollectionSnapshotAPI.java | 4 ++-- .../solr/handler/admin/api/DeleteCollectionSnapshotAPI.java | 2 +- .../solr/handler/admin/api/ListCollectionSnapshotsAPI.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java index ea067a2b0faf..1c7c35927996 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java @@ -73,7 +73,7 @@ public CreateSnapshotResponse createSnapshot( @Parameter(description = "The name of the snapshot to be created.", required = true) @PathParam("snapshotName") String snapshotName, - @Parameter(description = "TODO follow aliases") + @Parameter(description = "A flag that treats the collName parameter as a collection alias.") @DefaultValue("false") @QueryParam("followAliases") boolean followAliases, @@ -142,7 +142,7 @@ public static class CreateSnapshotResponse extends SolrJerseyResponse { @JsonProperty("snapshot") String snapshotName; - @Schema(description = "todo") + @Schema(description = "A flag that treats the collName parameter as a collection alias.") @JsonProperty("followAliases") boolean followAliases; diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java index 0640e9917d4b..3235aba36b96 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java @@ -71,7 +71,7 @@ public DeleteSnapshotResponse deleteSnapshot( @Parameter(description = "The name of the snapshot to be deleted.", required = true) @PathParam("snapshotName") String snapshotName, - @Parameter(description = "TODO follow aliases") + @Parameter(description = "A flag that treats the collName parameter as a collection alias.") @DefaultValue("false") @QueryParam("followAliases") boolean followAliases, diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java index a08867220bb5..ec31c28ca9a2 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java @@ -17,7 +17,7 @@ package org.apache.solr.handler.admin.api; import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.Parameter; @@ -54,7 +54,7 @@ public ListCollectionSnapshotsAPI( @GET @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) - @PermissionName(COLL_EDIT_PERM) + @PermissionName(COLL_READ_PERM) public ListSnapshotsResponse listSnapshots( @Parameter(description = "The name of the collection.", required = true) @PathParam("collName") From 875d7a7d7831cd16b8437880abec17648769e3d2 Mon Sep 17 00:00:00 2001 From: John Durham Date: Sun, 19 Mar 2023 15:01:12 -0500 Subject: [PATCH 04/10] add some documentation to the API resources --- .../solr/handler/admin/api/CreateCollectionSnapshotAPI.java | 6 ++++++ .../solr/handler/admin/api/DeleteCollectionSnapshotAPI.java | 6 ++++++ .../solr/handler/admin/api/ListCollectionSnapshotsAPI.java | 3 +++ 3 files changed, 15 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java index 1c7c35927996..bd8dbc91d746 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java @@ -51,6 +51,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +/** V2 API for Creating Collection Snapshots. */ @Path("/collections/{collName}/snapshots") public class CreateCollectionSnapshotAPI extends AdminAPIBase { @@ -62,6 +63,7 @@ public CreateCollectionSnapshotAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } + /** This API is analogous to V1's (POST /solr/admin/collections?action=CREATESNAPSHOT */ @POST @Path("/{snapshotName}") @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @@ -133,6 +135,10 @@ public CreateSnapshotResponse createSnapshot( return response; } + /** + * The Response for {@link CreateCollectionSnapshotAPI}'s {@link #createSnapshot(String, String, + * boolean, String)} + */ public static class CreateSnapshotResponse extends SolrJerseyResponse { @Schema(description = "The name of the collection.") @JsonProperty(COLLECTION_PROP) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java index 3235aba36b96..da8040d81d3a 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java @@ -49,6 +49,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +/** V2 API for Deleting Collection Snapshots. */ @Path("/collections/{collName}/snapshots") public class DeleteCollectionSnapshotAPI extends AdminAPIBase { @@ -60,6 +61,7 @@ public DeleteCollectionSnapshotAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } + /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT */ @DELETE @Path("/{snapshotName}") @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @@ -116,6 +118,10 @@ public DeleteSnapshotResponse deleteSnapshot( return response; } + /** + * The Response for {@link DeleteCollectionSnapshotAPI}'s {@link #deleteSnapshot(String, String, + * boolean, String)} + */ public static class DeleteSnapshotResponse extends SolrJerseyResponse { @Schema(description = "The name of the collection.") @JsonProperty(COLLECTION_PROP) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java index ec31c28ca9a2..776bafe6c0aa 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java @@ -41,6 +41,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +/** V2 API for Listing Collection Snapshots. */ @Path("/collections/{collName}/snapshots") public class ListCollectionSnapshotsAPI extends AdminAPIBase { @@ -52,6 +53,7 @@ public ListCollectionSnapshotsAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } + /** This API is analogous to V1's (POST /solr/admin/collections?action=LISTSNAPSHOTS */ @GET @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @PermissionName(COLL_READ_PERM) @@ -92,6 +94,7 @@ public ListSnapshotsResponse listSnapshots( return response; } + /** The Response for {@link ListCollectionSnapshotsAPI}'s {@link #listSnapshots(String)} */ public static class ListSnapshotsResponse extends SolrJerseyResponse { @Schema(description = "The snapshots for the collection.") @JsonProperty(SolrSnapshotManager.SNAPSHOTS_INFO) From 3dcf9bfd46dcc302b527b5d56ed89973b4975ea4 Mon Sep 17 00:00:00 2001 From: John Durham Date: Wed, 5 Apr 2023 22:14:34 -0500 Subject: [PATCH 05/10] added method to AdminAPIBase to resolve collection names. used a json request body on CreateCollectionSnapshotAPI instead of query params --- .../handler/admin/CollectionsHandler.java | 22 ++++--- .../solr/handler/admin/api/AdminAPIBase.java | 21 +++++++ .../api/CreateCollectionSnapshotAPI.java | 59 ++++++++----------- .../api/DeleteCollectionSnapshotAPI.java | 34 ++++------- .../admin/api/ListCollectionSnapshotsAPI.java | 25 ++------ 5 files changed, 76 insertions(+), 85 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 9a98c2c8feac..5b9c9969ec26 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -202,8 +202,6 @@ import org.apache.solr.core.backup.BackupManager; import org.apache.solr.core.backup.BackupProperties; import org.apache.solr.core.backup.repository.BackupRepository; -import org.apache.solr.core.snapshots.CollectionSnapshotMetaData; -import org.apache.solr.core.snapshots.SolrSnapshotManager; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.admin.api.AddReplicaAPI; import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI; @@ -1736,9 +1734,14 @@ public Map execute( final CreateCollectionSnapshotAPI createCollectionSnapshotAPI = new CreateCollectionSnapshotAPI(h.coreContainer, req, rsp); + final CreateCollectionSnapshotAPI.CreateSnapshotRequestBody requestBody = + new CreateCollectionSnapshotAPI.CreateSnapshotRequestBody(); + requestBody.followAliases = followAliases; + requestBody.asyncId = asyncId; + final CreateCollectionSnapshotAPI.CreateSnapshotResponse createSnapshotResponse = createCollectionSnapshotAPI.createSnapshot( - extCollectionName, commitName, followAliases, asyncId); + extCollectionName, commitName, requestBody); V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createSnapshotResponse); @@ -1775,12 +1778,13 @@ public Map execute( final ListCollectionSnapshotsAPI.ListSnapshotsResponse response = listCollectionSnapshotsAPI.listSnapshots(req.getParams().get(COLLECTION_PROP)); - NamedList snapshots = new NamedList<>(); - for (CollectionSnapshotMetaData meta : response.snapshots.values()) { - snapshots.add(meta.getName(), meta.toNamedList()); - } - - rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots); + // NamedList snapshots = new NamedList<>(); + // for (CollectionSnapshotMetaData meta : response.snapshots.values()) { + // snapshots.add(meta.getName(), meta.toNamedList()); + // } + // + // rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots); + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); return null; }), REPLACENODE_OP( diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java index 16d4b189d022..e2ccc23ce65c 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java @@ -19,6 +19,7 @@ import org.apache.solr.api.JerseyResource; import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.core.CoreContainer; import org.apache.solr.logging.MDCLoggingContext; import org.apache.solr.request.SolrQueryRequest; @@ -59,6 +60,26 @@ public static void validateZooKeeperAwareCoreContainer(CoreContainer coreContain } } + protected String resolveCollectionName(String collName, boolean followAliases) { + final String collectionName = + followAliases + ? coreContainer + .getZkController() + .getZkStateReader() + .getAliases() + .resolveSimpleAlias(collName) + : collName; + + final ClusterState clusterState = coreContainer.getZkController().getClusterState(); + if (!clusterState.hasCollection(collectionName)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Collection '" + collectionName + "' does not exist, no action taken."); + } + + return collectionName; + } + /** * TODO Taken from CollectionsHandler.handleRequestBody, but its unclear where (if ever) this gets * cleared. diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java index bd8dbc91d746..b331af1aa7c2 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java @@ -27,18 +27,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; -import javax.ws.rs.DefaultValue; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.common.SolrException; -import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.cloud.ZkNodeProps; import org.apache.solr.common.params.CollectionParams; @@ -46,8 +44,9 @@ import org.apache.solr.core.CoreContainer; import org.apache.solr.core.snapshots.SolrSnapshotManager; import org.apache.solr.handler.admin.CollectionsHandler; +import org.apache.solr.jersey.AsyncJerseyResponse; +import org.apache.solr.jersey.JacksonReflectMapWriter; import org.apache.solr.jersey.PermissionName; -import org.apache.solr.jersey.SolrJerseyResponse; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -63,7 +62,7 @@ public CreateCollectionSnapshotAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } - /** This API is analogous to V1's (POST /solr/admin/collections?action=CREATESNAPSHOT */ + /** This API is analogous to V1's (POST /solr/admin/collections?action=CREATESNAPSHOT) */ @POST @Path("/{snapshotName}") @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @@ -75,32 +74,15 @@ public CreateSnapshotResponse createSnapshot( @Parameter(description = "The name of the snapshot to be created.", required = true) @PathParam("snapshotName") String snapshotName, - @Parameter(description = "A flag that treats the collName parameter as a collection alias.") - @DefaultValue("false") - @QueryParam("followAliases") - boolean followAliases, - @QueryParam("asyncId") String asyncId) + @RequestBody(description = "Contains user provided parameters", required = true) + CreateSnapshotRequestBody requestBody) throws Exception { final CreateSnapshotResponse response = instantiateJerseyResponse(CreateSnapshotResponse.class); final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); recordCollectionForLogAndTracing(collName, solrQueryRequest); - final String collectionName = - followAliases - ? coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(collName) - : collName; - - final ClusterState clusterState = coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - SolrException.ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final String collectionName = resolveCollectionName(collName, requestBody.followAliases); final SolrZkClient client = coreContainer.getZkController().getZkClient(); if (SolrSnapshotManager.snapshotExists(client, collectionName, snapshotName)) { @@ -114,7 +96,7 @@ public CreateSnapshotResponse createSnapshot( } final ZkNodeProps remoteMessage = - createRemoteMessage(collName, followAliases, snapshotName, asyncId); + createRemoteMessage(collName, requestBody.followAliases, snapshotName, requestBody.asyncId); final SolrResponse remoteResponse = CollectionsHandler.submitCollectionApiCommand( coreContainer, @@ -128,18 +110,30 @@ public CreateSnapshotResponse createSnapshot( } response.collection = collName; - response.followAliases = followAliases; + response.followAliases = requestBody.followAliases; response.snapshotName = snapshotName; - response.requestId = asyncId; + response.requestId = requestBody.asyncId; return response; } + /** + * The RequestBody for {@link CreateCollectionSnapshotAPI}'s {@link #createSnapshot(String, + * String, CreateSnapshotRequestBody)} + */ + public static class CreateSnapshotRequestBody implements JacksonReflectMapWriter { + @JsonProperty(value = "followAliases", defaultValue = "false") + public boolean followAliases; + + @JsonProperty("async") + public String asyncId; + } + /** * The Response for {@link CreateCollectionSnapshotAPI}'s {@link #createSnapshot(String, String, - * boolean, String)} + * CreateSnapshotRequestBody)} */ - public static class CreateSnapshotResponse extends SolrJerseyResponse { + public static class CreateSnapshotResponse extends AsyncJerseyResponse { @Schema(description = "The name of the collection.") @JsonProperty(COLLECTION_PROP) String collection; @@ -151,12 +145,9 @@ public static class CreateSnapshotResponse extends SolrJerseyResponse { @Schema(description = "A flag that treats the collName parameter as a collection alias.") @JsonProperty("followAliases") boolean followAliases; - - @JsonProperty("requestId") - String requestId; } - private ZkNodeProps createRemoteMessage( + public static ZkNodeProps createRemoteMessage( String collectionName, boolean followAliases, String snapshotName, String asyncId) { final Map remoteMessage = new HashMap<>(); diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java index da8040d81d3a..e591e7aed7ea 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java @@ -37,15 +37,13 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import org.apache.solr.client.solrj.SolrResponse; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.ZkNodeProps; import org.apache.solr.common.params.CollectionParams; import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.CollectionsHandler; +import org.apache.solr.jersey.AsyncJerseyResponse; import org.apache.solr.jersey.PermissionName; -import org.apache.solr.jersey.SolrJerseyResponse; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -61,7 +59,7 @@ public DeleteCollectionSnapshotAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } - /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT */ + /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */ @DELETE @Path("/{snapshotName}") @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @@ -77,28 +75,16 @@ public DeleteSnapshotResponse deleteSnapshot( @DefaultValue("false") @QueryParam("followAliases") boolean followAliases, - @QueryParam("asyncId") String asyncId) + @QueryParam("async") String asyncId) throws Exception { final DeleteSnapshotResponse response = instantiateJerseyResponse(DeleteSnapshotResponse.class); final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); recordCollectionForLogAndTracing(collName, solrQueryRequest); - String collectionName = - coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(collName); - - ClusterState clusterState = coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - SolrException.ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final String collectionName = resolveCollectionName(collName, followAliases); final ZkNodeProps remoteMessage = - createRemoteMessage(collName, followAliases, snapshotName, asyncId); + createRemoteMessage(collectionName, followAliases, snapshotName, asyncId); final SolrResponse remoteResponse = CollectionsHandler.submitCollectionApiCommand( coreContainer, @@ -113,6 +99,7 @@ public DeleteSnapshotResponse deleteSnapshot( response.collection = collName; response.snapshotName = snapshotName; + response.followAliases = followAliases; response.requestId = asyncId; return response; @@ -122,7 +109,7 @@ public DeleteSnapshotResponse deleteSnapshot( * The Response for {@link DeleteCollectionSnapshotAPI}'s {@link #deleteSnapshot(String, String, * boolean, String)} */ - public static class DeleteSnapshotResponse extends SolrJerseyResponse { + public static class DeleteSnapshotResponse extends AsyncJerseyResponse { @Schema(description = "The name of the collection.") @JsonProperty(COLLECTION_PROP) String collection; @@ -131,11 +118,12 @@ public static class DeleteSnapshotResponse extends SolrJerseyResponse { @JsonProperty("snapshot") String snapshotName; - @JsonProperty("requestId") - String requestId; + @Schema(description = "A flag that treats the collName parameter as a collection alias.") + @JsonProperty("followAliases") + boolean followAliases; } - private ZkNodeProps createRemoteMessage( + public static ZkNodeProps createRemoteMessage( String collectionName, boolean followAliases, String snapshotName, String asyncId) { final Map remoteMessage = new HashMap<>(); diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java index 776bafe6c0aa..b3fa81fb2c0b 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java @@ -23,21 +23,19 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.util.CollectionUtil; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.snapshots.CollectionSnapshotMetaData; import org.apache.solr.core.snapshots.SolrSnapshotManager; +import org.apache.solr.jersey.AsyncJerseyResponse; import org.apache.solr.jersey.PermissionName; -import org.apache.solr.jersey.SolrJerseyResponse; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -53,7 +51,7 @@ public ListCollectionSnapshotsAPI( super(coreContainer, solrQueryRequest, solrQueryResponse); } - /** This API is analogous to V1's (POST /solr/admin/collections?action=LISTSNAPSHOTS */ + /** This API is analogous to V1's (POST /solr/admin/collections?action=LISTSNAPSHOTS) */ @GET @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) @PermissionName(COLL_READ_PERM) @@ -67,24 +65,13 @@ public ListSnapshotsResponse listSnapshots( final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer(); recordCollectionForLogAndTracing(collName, solrQueryRequest); - String collectionName = - coreContainer - .getZkController() - .getZkStateReader() - .getAliases() - .resolveSimpleAlias(collName); - ClusterState clusterState = coreContainer.getZkController().getClusterState(); - if (!clusterState.hasCollection(collectionName)) { - throw new SolrException( - SolrException.ErrorCode.BAD_REQUEST, - "Collection '" + collectionName + "' does not exist, no action taken."); - } + final String collectionName = resolveCollectionName(collName, true); SolrZkClient client = coreContainer.getZkController().getZkClient(); Collection m = SolrSnapshotManager.listSnapshots(client, collectionName); - Map snapshots = new HashMap<>(m.size()); + Map snapshots = CollectionUtil.newHashMap(m.size()); for (CollectionSnapshotMetaData metaData : m) { snapshots.put(metaData.getName(), metaData); } @@ -95,7 +82,7 @@ public ListSnapshotsResponse listSnapshots( } /** The Response for {@link ListCollectionSnapshotsAPI}'s {@link #listSnapshots(String)} */ - public static class ListSnapshotsResponse extends SolrJerseyResponse { + public static class ListSnapshotsResponse extends AsyncJerseyResponse { @Schema(description = "The snapshots for the collection.") @JsonProperty(SolrSnapshotManager.SNAPSHOTS_INFO) public Map snapshots; From f4a2423b14af57870586feea863add0a7457a783 Mon Sep 17 00:00:00 2001 From: John Durham Date: Wed, 5 Apr 2023 22:14:51 -0500 Subject: [PATCH 06/10] added Create and Delete collection snapshot unit tests --- .../api/CreateCollectionSnapshotAPITest.java | 65 +++++++++++++++++++ .../api/DeleteCollectionSnapshotAPITest.java | 65 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java new file mode 100644 index 000000000000..c5f31fbdf45a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.solr.handler.admin.api; + +import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; +import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; +import static org.apache.solr.common.params.CommonAdminParams.ASYNC; +import static org.hamcrest.Matchers.containsInAnyOrder; + +import java.util.Map; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.params.CoreAdminParams; +import org.hamcrest.MatcherAssert; +import org.junit.Test; + +public class CreateCollectionSnapshotAPITest extends SolrTestCaseJ4 { + + @Test + public void testConstructsValidOverseerMessage() { + final ZkNodeProps messageOne = + CreateCollectionSnapshotAPI.createRemoteMessage( + "myCollName", false, "mySnapshotName", null); + final Map rawMessageOne = messageOne.getProperties(); + assertEquals(4, rawMessageOne.size()); + MatcherAssert.assertThat( + rawMessageOne.keySet(), + containsInAnyOrder( + QUEUE_OPERATION, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME, FOLLOW_ALIASES)); + assertEquals("createsnapshot", rawMessageOne.get(QUEUE_OPERATION)); + assertEquals("myCollName", rawMessageOne.get(COLLECTION_PROP)); + assertEquals("mySnapshotName", rawMessageOne.get(CoreAdminParams.COMMIT_NAME)); + assertEquals(false, rawMessageOne.get(FOLLOW_ALIASES)); + + final ZkNodeProps messageTwo = + CreateCollectionSnapshotAPI.createRemoteMessage( + "myCollName", true, "mySnapshotName", "myAsyncId"); + final Map rawMessageTwo = messageTwo.getProperties(); + assertEquals(5, rawMessageTwo.size()); + MatcherAssert.assertThat( + rawMessageTwo.keySet(), + containsInAnyOrder( + QUEUE_OPERATION, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME, FOLLOW_ALIASES, ASYNC)); + assertEquals("createsnapshot", rawMessageTwo.get(QUEUE_OPERATION)); + assertEquals("myCollName", rawMessageTwo.get(COLLECTION_PROP)); + assertEquals("mySnapshotName", rawMessageTwo.get(CoreAdminParams.COMMIT_NAME)); + assertEquals(true, rawMessageTwo.get(FOLLOW_ALIASES)); + assertEquals("myAsyncId", rawMessageTwo.get(ASYNC)); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java new file mode 100644 index 000000000000..ac363a02bae8 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.solr.handler.admin.api; + +import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; +import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; +import static org.apache.solr.common.params.CommonAdminParams.ASYNC; +import static org.hamcrest.Matchers.containsInAnyOrder; + +import java.util.Map; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.params.CoreAdminParams; +import org.hamcrest.MatcherAssert; +import org.junit.Test; + +public class DeleteCollectionSnapshotAPITest extends SolrTestCaseJ4 { + + @Test + public void testConstructsValidOverseerMessage() { + final ZkNodeProps messageOne = + DeleteCollectionSnapshotAPI.createRemoteMessage( + "myCollName", false, "mySnapshotName", null); + final Map rawMessageOne = messageOne.getProperties(); + assertEquals(4, rawMessageOne.size()); + MatcherAssert.assertThat( + rawMessageOne.keySet(), + containsInAnyOrder( + QUEUE_OPERATION, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME, FOLLOW_ALIASES)); + assertEquals("deletesnapshot", rawMessageOne.get(QUEUE_OPERATION)); + assertEquals("myCollName", rawMessageOne.get(COLLECTION_PROP)); + assertEquals("mySnapshotName", rawMessageOne.get(CoreAdminParams.COMMIT_NAME)); + assertEquals(false, rawMessageOne.get(FOLLOW_ALIASES)); + + final ZkNodeProps messageTwo = + DeleteCollectionSnapshotAPI.createRemoteMessage( + "myCollName", true, "mySnapshotName", "myAsyncId"); + final Map rawMessageTwo = messageTwo.getProperties(); + assertEquals(5, rawMessageTwo.size()); + MatcherAssert.assertThat( + rawMessageTwo.keySet(), + containsInAnyOrder( + QUEUE_OPERATION, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME, FOLLOW_ALIASES, ASYNC)); + assertEquals("deletesnapshot", rawMessageTwo.get(QUEUE_OPERATION)); + assertEquals("myCollName", rawMessageTwo.get(COLLECTION_PROP)); + assertEquals("mySnapshotName", rawMessageTwo.get(CoreAdminParams.COMMIT_NAME)); + assertEquals(true, rawMessageTwo.get(FOLLOW_ALIASES)); + assertEquals("myAsyncId", rawMessageTwo.get(ASYNC)); + } +} From cc5745b988e7e6bf37be73cc37c80a0ff353f1ab Mon Sep 17 00:00:00 2001 From: John Durham Date: Thu, 13 Apr 2023 23:07:33 -0500 Subject: [PATCH 07/10] add snapshot docs to collection-management.adoc --- .../handler/admin/CollectionsHandler.java | 15 +- .../pages/collection-management.adoc | 314 ++++++++++++++++++ 2 files changed, 322 insertions(+), 7 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 5b9c9969ec26..0b9f94ee712a 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -202,6 +202,8 @@ import org.apache.solr.core.backup.BackupManager; import org.apache.solr.core.backup.BackupProperties; import org.apache.solr.core.backup.repository.BackupRepository; +import org.apache.solr.core.snapshots.CollectionSnapshotMetaData; +import org.apache.solr.core.snapshots.SolrSnapshotManager; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.admin.api.AddReplicaAPI; import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI; @@ -1778,13 +1780,12 @@ public Map execute( final ListCollectionSnapshotsAPI.ListSnapshotsResponse response = listCollectionSnapshotsAPI.listSnapshots(req.getParams().get(COLLECTION_PROP)); - // NamedList snapshots = new NamedList<>(); - // for (CollectionSnapshotMetaData meta : response.snapshots.values()) { - // snapshots.add(meta.getName(), meta.toNamedList()); - // } - // - // rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots); - V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); + NamedList snapshots = new NamedList<>(); + for (CollectionSnapshotMetaData meta : response.snapshots.values()) { + snapshots.add(meta.getName(), meta.toNamedList()); + } + + rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots); return null; }), REPLACENODE_OP( diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc index aeb9c249b9d1..c3e10948d9be 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc @@ -2242,3 +2242,317 @@ The primary use-case is to redistribute the leader role if there are a large num Rebalancing will likely not improve performance unless the imbalance of leadership roles is measured in multiples of 10. NOTE: The BALANCESHARDUNIQUE command that distributes the preferredLeader property does not guarantee perfect distribution and in some collection topologies it is impossible to make that guarantee. + +[[createsnapshot]] +== CREATESNAPSHOT: Create a snapshot of a collection + +Creates a snapshot of a specified collection. + +=== CREATESNAPSHOT Example + +*Input* + +The following API command creates a snapshot of a specified collection. + +[.dynamic-tabs] +-- +[example.tab-pane#v1createsnapshot] +==== +[.tab-label]*V1 API* + +[source,bash] +---- +http://localhost:8983/solr/admin/collections?action=CREATESNAPSHOT&collection=techproducts&commitName=snapshot0&followAliases=true&async=someAsyncId +---- +==== + +[example.tab-pane#v2createsnapshot] +==== +[.tab-label]*V2 API* + +[source,bash] +---- +curl -X POST http://localhost:8983/api/collections/techproducts/snapshots/snapshot0 -H 'Content-Type: application/json' -d ' + { + "followAliases": true, + "asyncId": "someAsyncId" + } +' +---- +==== +-- + +*Output* + +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 214 + }, + "requestid": "someAsyncId" + "collection": "techproducts", + "snapshot": "snapshot0", + "followAliases": true +} +---- + +=== CREATESNAPSHOT Parameters + +`collection`:: ++ +[%autowidth,frame=none] +|=== +s|Required |Default: none +|=== ++ +The name of the collection to create a snapshot for. + +`snapshot`:: ++ +[%autowidth,frame=none] +|=== +s|Required |Default: none +|=== ++ +The name of the snapshot to create for the collection. + +`followAliases`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: false +|=== ++ +A flag that treats the collection parameter as an alias for the actual collection name to be resolved. + +`async`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: none +|=== ++ +Request ID to track this action which will be xref:configuration-guide:collections-api.adoc#asynchronous-calls[processed asynchronously]. + +[[listsnapshots]] +== LISTSNAPSHOTS: List all snapshots for a collection + +Lists all the snapshots taken of a collection. + +=== LISTSNAPSHOTS Example + +*Input* + +The following API command lists all the snapshots taken of a collection. + +[.dynamic-tabs] +-- +[example.tab-pane#v1listsnapshots] +==== +[.tab-label]*V1 API* + +[source,bash] +---- +http://localhost:8983/solr/admin/collections?action=LISTSNAPSHOTS&collection=techproducts +---- +==== + +[example.tab-pane#v2listsnapshots] +==== +[.tab-label]*V2 API* + +[source,bash] +---- +curl -X GET http://localhost:8983/api/collections/techproducts/snapshots +---- +==== +-- + +*Output* + +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 2 + }, + "snapshots": { + "snapshot0": { + "name": "snapshot0", + "status": "Successful", + "creationDate": 1677985318116, + "replicaSnapshots": [ + { + "coreName": "techproducts_shard1_replica_n6", + "indexDirPath": "/path/to/solr/dir/node1/solr/techproducts_shard1_replica_n6/data/index/", + "generationNumber": 2, + "leader": true, + "shardId": "shard1", + "files": [ + "_0.si", + "_0.fdm", + "_0_Lucene90_0.dvd", + "segments_2", + "_0_Lucene90_0.doc", + "_0_Lucene90_0.tim", + "_0.fdx", + "_0.fdt", + "_0_Lucene90_0.dvm", + "_0_Lucene90_0.tip", + "_0_Lucene90_0.tmd", + "_0.fnm" + ] + }, + { + "coreName": "techproducts_shard1_replica_n2", + "indexDirPath": "/path/to/solr/dir/node2/solr/techproducts_shard1_replica_n2/data/index/", + "generationNumber": 2, + "leader": false, + "shardId": "shard1", + "files": [ + "_0.si", + "_0.fdm", + "_0_Lucene90_0.dvd", + "segments_2", + "_0_Lucene90_0.doc", + "_0_Lucene90_0.tim", + "_0.fdx", + "_0.fdt", + "_0_Lucene90_0.dvm", + "_0_Lucene90_0.tip", + "_0_Lucene90_0.tmd", + "_0.fnm" + ] + }, + { + "coreName": "techproducts_shard2_replica_n4", + "indexDirPath": "/path/to/solr/dir/node1/solr/techproducts_shard2_replica_n4/data/index/", + "generationNumber": 6, + "leader": true, + "shardId": "shard2", + "files": [ + "segments_6" + ] + }, + { + "coreName": "techproducts_shard2_replica_n1", + "indexDirPath": "/path/to/solr/dir/node2/solr/techproducts_shard2_replica_n1/data/index/", + "generationNumber": 6, + "leader": false, + "shardId": "shard2", + "files": [ + "segments_6" + ] + } + ], + "shards": [ + "shard2", + "shard1" + ] + } + } +} + +---- + +=== LISTSNAPSHOTS Parameters + +`collection`:: ++ +[%autowidth,frame=none] +|=== +s|Required |Default: none +|=== ++ +The name of the collection to create a snapshot for. + +[[deletesnapshot]] +== DELETESNAPSHOT: Delete a snapshot taken of a collection + +Deletes a snapshot taken of a specified collection. + +=== DELETESNAPSHOT Example + +*Input* + +The following API command deletes a snapshot taken of a collection. + +[.dynamic-tabs] +-- +[example.tab-pane#v1deletesnapshot] +==== +[.tab-label]*V1 API* + +[source,bash] +---- +http://localhost:8983/solr/admin/collections?action=DELETESNAPSHOT&collection=techproducts&commitName=snapshot0&followAliases=true&async=someAsyncId +---- +==== + +[example.tab-pane#v2deletesnapshot] +==== +[.tab-label]*V2 API* + +[source,bash] +---- +curl -X DELETE http://localhost:8983/api/collections/techproducts/snapshots/snapshot0?followAliases=true&async=someAsyncId +---- +==== +-- + +*Output* + +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 20 + }, + "requestid": "someAsyncId", + "collection": "techproducts", + "snapshot": "snapshot0", + "followAliases": true +} +---- + +=== DELETESNAPSHOT Parameters + +`collection`:: ++ +[%autowidth,frame=none] +|=== +s|Required |Default: none +|=== ++ +The name of the collection to delete a snapshot from. + +`snapshot`:: ++ +[%autowidth,frame=none] +|=== +s|Required |Default: none +|=== ++ +The name of the snapshot to delete. + +`followAliases`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: false +|=== ++ +A flag that treats the collectionName parameter as an alias for the actual collection name to be resolved. + +`async`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: none +|=== ++ +Request ID to track this action which will be xref:configuration-guide:collections-api.adoc#asynchronous-calls[processed asynchronously]. \ No newline at end of file From 205d4f91c7f4d5c931bfa92e6316919ebdf7cdcd Mon Sep 17 00:00:00 2001 From: Jason Gerlowski Date: Mon, 17 Apr 2023 13:37:38 -0400 Subject: [PATCH 08/10] Small tweak to ref-guide docs --- .../pages/collection-management.adoc | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc index c3e10948d9be..2cbe1d4c419f 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc @@ -1592,8 +1592,8 @@ Multiple collections cannot be backed up to the same location. [NOTE] ==== -Previous versions of Solr supported a different snapshot-based backup method without the incremental support described above. -Solr can still restore from backups that use this old format, but creating new backups of this format is not recommended and snapshot-based backups are officially deprecated. +Previous versions of Solr supported a different backup file format that lacked the incremental support described above. +Solr can still restore from backups that use this old format, but creating new backups of this format is not recommended and is officially deprecated. See the `incremental` parameter below for more information. ==== @@ -1673,9 +1673,9 @@ This parameter has no effect if `incremental=false` is specified. |Optional |Default: `true` |=== + -A boolean parameter allowing users to choose whether to create an incremental (`incremental=true`) or a "snapshot" (`incremental=false`) backup. +A boolean parameter allowing users to choose whether to create an incremental (`incremental=true`) or a "full" (`incremental=false`) backup. If unspecified, backups are done incrementally by default. -Incremental backups are preferred in all known circumstances and snapshot backups are deprecated, so this parameter should only be used after much consideration. +Incremental backups are preferred in all known circumstances and "full" (i.e. non-incremental) backups are deprecated, so this parameter should only be used after much consideration. [example.tab-pane#backup-response-incremental] @@ -1713,7 +1713,7 @@ Basic metadata is returned about each backup including: the timestamp the backup [NOTE] ==== -Previous versions of Solr supported a different snapshot-based backup file structure that did not support the storage of multiple backups at the same location. +Previous versions of Solr supported a different backup file structure that did not support the storage of multiple backups at the same location. Solr can still restore backups stored in this old format, but it is deprecated and will be removed in subsequent versions of Solr. The LISTBACKUP API does not support the deprecated format and attempts to use this API on a location holding an older backup will result in an error message. ==== @@ -1968,7 +1968,7 @@ Deletes backup files stored at the specified repository location. [NOTE] ==== -Previous versions of Solr supported a different snapshot-based backup file structure that did not support the storage of multiple backups at the same location. +Previous versions of Solr supported a different backup file structure that did not support the storage of multiple backups at the same location. Solr can still restore backups stored in this old format, but it is deprecated and will be removed in subsequent versions of Solr. The DELETEBACKUP API does not support the deprecated format and attempts to use this API on a location holding an older backup will result in an error message. ==== @@ -2246,7 +2246,11 @@ NOTE: The BALANCESHARDUNIQUE command that distributes the preferredLeader proper [[createsnapshot]] == CREATESNAPSHOT: Create a snapshot of a collection -Creates a snapshot of a specified collection. +Solr has support for creating collection "snapshots", which "checkpoint" the collection state in a way that allows users to revert to that point if needed later on. +This is particularly useful prior to reindexing or making config changes to a collection. + +Unlike backups, which copy collection data off-disk, snapshots themselves don't provide disaster recovery in case of disk or hardware failure. +They provide less protection than backups, at a much cheaper cost. === CREATESNAPSHOT Example @@ -2555,4 +2559,4 @@ A flag that treats the collectionName parameter as an alias for the actual colle |Optional |Default: none |=== + -Request ID to track this action which will be xref:configuration-guide:collections-api.adoc#asynchronous-calls[processed asynchronously]. \ No newline at end of file +Request ID to track this action which will be xref:configuration-guide:collections-api.adoc#asynchronous-calls[processed asynchronously]. From 8fb34e050f1fcd5c466d946e5017e09c19cf330f Mon Sep 17 00:00:00 2001 From: Jason Gerlowski Date: Mon, 17 Apr 2023 14:11:50 -0400 Subject: [PATCH 09/10] Correct 'asyncId' ref-guide type --- .../modules/deployment-guide/pages/collection-management.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc index c454cd617879..60ba5fc2e59f 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc @@ -2294,7 +2294,7 @@ http://localhost:8983/solr/admin/collections?action=CREATESNAPSHOT&collection=te curl -X POST http://localhost:8983/api/collections/techproducts/snapshots/snapshot0 -H 'Content-Type: application/json' -d ' { "followAliases": true, - "asyncId": "someAsyncId" + "async": "someAsyncId" } ' ---- From 9d378b53856f636413e487a8ffbb5899e589a868 Mon Sep 17 00:00:00 2001 From: Jason Gerlowski Date: Mon, 17 Apr 2023 19:41:31 -0400 Subject: [PATCH 10/10] Add CHANGES.txt entry --- solr/CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index e72728ca6c9d..2ad0c92fafb5 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -87,6 +87,10 @@ Improvements * SOLR-16504: Convert CLI tools to use Jetty HTTP 2 client. (Bence Szabo via Eric Pugh) +* SOLR-15737: Solr's collection-level "snapshot" APIs now have v2 equivalents. Snapshots can be created at `POST + /api/collections/collName/snapshots/snapshotName`, listed at `GET /api/collections/collName/snapshots`, and deleted at + `DELETE /api/collections/collName/snapshots/snapshotName`. (John Durham via Jason Gerlowski) + Optimizations ---------------------