-
Notifications
You must be signed in to change notification settings - Fork 809
[SOLR-15737]: Create v2 equivalent of v1 'CREATESNAPSHOT', 'LISTSNAPSHOT' and 'DELETESNAPSHOT' (collection level) #1471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
775dcfb
b08747a
8b0690a
875d7a7
3dcf9bf
f4a2423
cc5745b
205d4f9
626cde4
8fb34e0
9d378b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| /* | ||
| * 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 io.swagger.v3.oas.annotations.parameters.RequestBody; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import javax.inject.Inject; | ||
| import javax.ws.rs.POST; | ||
| import javax.ws.rs.Path; | ||
| import javax.ws.rs.PathParam; | ||
| import javax.ws.rs.Produces; | ||
| import org.apache.solr.client.solrj.SolrResponse; | ||
| import org.apache.solr.common.SolrException; | ||
| 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.AsyncJerseyResponse; | ||
| import org.apache.solr.jersey.JacksonReflectMapWriter; | ||
| import org.apache.solr.jersey.PermissionName; | ||
| 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 { | ||
|
|
||
| @Inject | ||
| public CreateCollectionSnapshotAPI( | ||
| CoreContainer coreContainer, | ||
| SolrQueryRequest solrQueryRequest, | ||
| SolrQueryResponse solrQueryResponse) { | ||
| 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}) | ||
| @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, | ||
| @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 = resolveCollectionName(collName, requestBody.followAliases); | ||
|
|
||
| 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, requestBody.followAliases, snapshotName, requestBody.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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [0] Just noticed the values being added to the response here. In general, I've been trying to change the response format as little as possible in these JAX-RS conversion PRs, and this is a bit of a departure from the existing v1 format. That said, I think it's a change for the better and it's purely additive so it's not a backcompat concern. So let's go with it in this case. |
||
| response.followAliases = requestBody.followAliases; | ||
| response.snapshotName = snapshotName; | ||
| 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, | ||
| * CreateSnapshotRequestBody)} | ||
| */ | ||
| public static class CreateSnapshotResponse extends AsyncJerseyResponse { | ||
| @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 = "A flag that treats the collName parameter as a collection alias.") | ||
| @JsonProperty("followAliases") | ||
| boolean followAliases; | ||
| } | ||
|
|
||
| public static ZkNodeProps createRemoteMessage( | ||
| String collectionName, boolean followAliases, String snapshotName, String asyncId) { | ||
| final Map<String, Object> 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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q] This code is simple enough, but unless I'm missing something it seems like it could be replaced by the
V2ApiUtils.squashIntoSolrResponsepattern we use on other APIs. Any particular reason you didn't go that route here?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the approach above, because when I tried just squashing, the output I got back wouldn't fully serialize. I essentially would get this back:
At the time, I couldn't figure out where to make changes to address the serialization problem, so I just sidestepped it. I'd be happy to take another stab at it though!
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok. Actually this makes complete sense. The "squash" pattern only knows how to serialize particular types and interfaces, and I guess CollectionSnapshotMetaData isn't one of those. Forget I said anything, as long as the output looks correct 👍
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, that makes sense. I went back and uncommented the for loop.
CollectionSnapshotMetaDatadoes have atoNamedListmethod, but its not an overriden method. Would it make sense at some point to create some kind of interface that exposes that method and then have it be used by serializers/MessageWriters? I think it would fall outside the scope of this change, but it could make sense if there were other classes out there that behaved the same way.