gerlowskija commented on code in PR #1126:
URL: https://github.com/apache/solr/pull/1126#discussion_r1107644285


##########
solr/core/src/java/org/apache/solr/core/CoreContainer.java:
##########
@@ -1095,26 +1095,21 @@ protected void configure() {
                       .to(SolrNodeKeyPair.class)
                       .in(Singleton.class);
                 }
+              })
+          .register(
+              new AbstractBinder() {
+                @Override
+                protected void configure() {
+                  bindFactory(
+                          new InjectionFactories.SingletonFactory<>(
+                              coreAdminHandler.getCoreAdminAsyncTracker()))
+                      .to(CoreAdminHandler.CoreAdminAsyncTracker.class)
+                      .in(Singleton.class);
+                }
               });
       jerseyAppHandler = new 
ApplicationHandler(containerHandlers.getJerseyEndpoints());
     }
 
-    // Do Node setup logic after all handlers have been registered.

Review Comment:
   [Q] Hmm...Do you know why this is deleted on your branch?  Doesn't seem 
related to this JIRA...



##########
solr/core/src/java/org/apache/solr/handler/admin/api/CoreSnapshotAPI.java:
##########
@@ -0,0 +1,280 @@
+/*
+ * 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.CORE_EDIT_PERM;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_READ_PERM;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.inject.Inject;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+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.lucene.index.IndexCommit;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.IndexDeletionPolicyWrapper;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.snapshots.SolrSnapshotManager;
+import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+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;
+
+/** V2 API for Creating, Listing, and Deleting Core Snapshots. */
+@Path("/cores/{coreName}/snapshots")
+public class CoreSnapshotAPI extends CoreAdminAPIBase {
+
+  @Inject
+  public CoreSnapshotAPI(
+      SolrQueryRequest request,
+      SolrQueryResponse response,
+      CoreContainer coreContainer,
+      CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker) {
+    super(coreContainer, coreAdminAsyncTracker, request, response);
+  }
+
+  /** This API is analogous to V1 (POST 
/solr/admin/cores?action=CREATESNAPSHOT) */
+  @POST
+  @Path("/{snapshotName}")
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @PermissionName(CORE_EDIT_PERM)
+  public CreateSnapshotResponse createSnapshot(
+      @Parameter(description = "The name of the core to snapshot.", required = 
true)
+          @PathParam("coreName")
+          String coreName,
+      @Parameter(description = "The name to associate with the core 
snapshot.", required = true)
+          @PathParam("snapshotName")
+          String snapshotName,
+      @Parameter(description = "The id to associate with the async task.") 
@QueryParam("async")
+          String taskId)
+      throws Exception {
+    final CreateSnapshotResponse response = 
instantiateJerseyResponse(CreateSnapshotResponse.class);
+
+    return handle(
+        response,
+        coreName,
+        taskId,
+        "createSnapshot",
+        () -> {
+          try (SolrCore core = coreContainer.getCore(coreName)) {
+            if (core == null) {
+              throw new SolrException(
+                  SolrException.ErrorCode.BAD_REQUEST, "Unable to locate core 
" + coreName);
+            }
+
+            final String indexDirPath = core.getIndexDir();
+            final IndexDeletionPolicyWrapper delPol = core.getDeletionPolicy();
+            final IndexCommit ic = delPol.getAndSaveLatestCommit();
+            try {
+              if (null == ic) {
+                throw new SolrException(
+                    SolrException.ErrorCode.BAD_REQUEST,
+                    "No index commits to snapshot in core " + coreName);
+              }
+              final SolrSnapshotMetaDataManager mgr = 
core.getSnapshotMetaDataManager();
+              mgr.snapshot(snapshotName, indexDirPath, ic.getGeneration());
+
+              response.core = core.getName();
+              response.commitName = snapshotName;
+              response.indexDirPath = indexDirPath;
+              response.generation = ic.getGeneration();
+              response.files = ic.getFileNames();
+            } catch (IOException e) {
+              throw new CoreAdminAPIBaseException(e);
+            } finally {
+              delPol.releaseCommitPoint(ic);
+            }
+          }
+
+          return response;
+        });
+  }
+
+  /** The Response for {@link CoreSnapshotAPI}'s {@link 
#createSnapshot(String, String, String)} */
+  public static class CreateSnapshotResponse extends SolrJerseyResponse {
+    @Schema(description = "The name of the core.")

Review Comment:
   [+1] Awesome, thanks for the OpenAPI docs on these fields!



##########
solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java:
##########
@@ -493,4 +434,94 @@ public interface CoreAdminOp {
      */
     void execute(CallInfo it) throws Exception;
   }
+
+  public static class CoreAdminAsyncTracker {
+    private static final int MAX_TRACKED_REQUESTS = 100;
+    public static final String RUNNING = "running";
+    public static final String COMPLETED = "completed";
+    public static final String FAILED = "failed";
+    public final Map<String, Map<String, TaskObject>> requestStatusMap;
+
+    private ExecutorService parallelExecutor =
+        ExecutorUtil.newMDCAwareFixedThreadPool(
+            50, new 
SolrNamedThreadFactory("parallelCoreAdminAPIBaseExecutor"));
+
+    public CoreAdminAsyncTracker() {
+      HashMap<String, Map<String, TaskObject>> map = new HashMap<>(3, 1.0f);
+      map.put(RUNNING, Collections.synchronizedMap(new LinkedHashMap<>()));
+      map.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<>()));
+      map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<>()));
+      requestStatusMap = Collections.unmodifiableMap(map);
+    }
+
+    public void shutdown() {
+      ExecutorUtil.shutdownAndAwaitTermination(parallelExecutor);
+    }
+
+    public Map<String, TaskObject> getRequestStatusMap(String key) {
+      return requestStatusMap.get(key);
+    }
+
+    public ExecutorService getParallelExecutor() {
+      return parallelExecutor;
+    }
+
+    /** Helper method to add a task to a tracking type. */
+    public void addTask(String type, TaskObject o, boolean limit) {
+      synchronized (getRequestStatusMap(type)) {
+        if (limit && getRequestStatusMap(type).size() == MAX_TRACKED_REQUESTS) 
{
+          String key = 
getRequestStatusMap(type).entrySet().iterator().next().getKey();
+          getRequestStatusMap(type).remove(key);
+        }
+        addTask(type, o);
+      }
+    }
+
+    public void addTask(String type, TaskObject o) {
+      synchronized (getRequestStatusMap(type)) {
+        getRequestStatusMap(type).put(o.taskId, o);
+      }
+    }
+
+    /** Helper method to remove a task from a tracking map. */
+    public void removeTask(String map, String taskId) {
+      synchronized (getRequestStatusMap(map)) {

Review Comment:
   Let's ignore this 'lift' warning for now - it's coming from code that has 
only been moved around (and not changed) as a part of this PR.
   
   Might be worth adding a TODO comment somewhere in this code though that 
proposes making the status options (RUNNING, COMPLETED, FAILED) into an enum 
though, to provide a little more type-safety going forward.



##########
solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc:
##########
@@ -308,37 +330,45 @@ The name of the core to whose snapshots we want to list.
 +
 Request ID to track this action which will be processed asynchronously.
 
-=== Delete Snapshot API
+=== LIST Response
+
+The response will include the status of the request and all existing snapshots 
for the core.
+If the status is anything other than "success", an error message will explain 
why the request failed.
 
-The `DELETESNAPSHOT` command deletes a snapshot for a particular core.
+[[delete-snapshot-api]]
+== DELETE: Delete a Snapshot
 
 You can trigger a delete snapshot with an HTTP command like this (replace 
"techproducts" with the name of the core you are working with):
 
-.Delete Snapshot API Example
-[source,text]
+[.dynamic-tabs]
+--
+[example.tab-pane#v1deletesnapshot]
+====
+[.tab-label]*V1 API*
+
+[source,bash]
 ----
-http://localhost:8983/solr/admin/cores?action=DELETESNAPSHOT&core=techproducts&commitName=commit1
+curl -X DELETE 
http://localhost:8983/solr/admin/cores?action=DELETESNAPSHOT&core=techproducts&commitName=commit1

Review Comment:
   [-0] We should probably drop the `-X DELETE` in the v1 example, it's never 
been documented that way before and I'd be surprised if anything other than a 
GET or POST worked for v1.



##########
solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc:
##########
@@ -276,28 +281,45 @@ The name of the core to perform the snapshot on.
 +
 Request ID to track this action which will be processed asynchronously.
 
-=== List Snapshot API
+=== CREATE Response
+
+The response will include the status of the request, the core name, snapshot 
name, index directory path, snapshot generation, and a list of file names.
+If the status is anything other than "success", an error message will explain 
why the request failed.
 
-The `LISTSNAPSHOTS` command lists all the taken snapshots for a particular 
core.
+[[list-snapshot-api]]
+== List: List All Snapshots for a Particular Core
 
 You can trigger a list snapshot command with an HTTP command like this 
(replace "techproducts" with the name of the core you are working with):
 
-.List Snapshot API
-[source,text]
+[.dynamic-tabs]
+--
+[example.tab-pane#v1listsnapshots]
+====
+[.tab-label]*V1 API*
+
+[source,bash]
 ----
-http://localhost:8983/solr/admin/cores?action=LISTSNAPSHOTS&core=techproducts&commitName=commit1
+curl 
http://localhost:8983/solr/admin/cores?action=LISTSNAPSHOTS&core=techproducts&commitName=commit1
+
 ----
+====
 
-The list snapshot request parameters are:
+[example.tab-pane#v2listsnapshots]
+====
+[.tab-label]*V2 API*
 
-`core`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: none
-|===
-+
-The name of the core to whose snapshots we want to list.
+With the v2 API, the core and snapshot names are part of the path instead of 
query parameters.

Review Comment:
   [-0] Small nit: this looks like a copy/paste oversight - there is no 
snapshot name in the LIST API.



##########
solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java:
##########
@@ -0,0 +1,157 @@
+/*
+ * 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.handler.admin.CoreAdminHandler.CoreAdminAsyncTracker.COMPLETED;
+import static 
org.apache.solr.handler.admin.CoreAdminHandler.CoreAdminAsyncTracker.FAILED;
+import static 
org.apache.solr.handler.admin.CoreAdminHandler.CoreAdminAsyncTracker.RUNNING;
+
+import java.util.function.Supplier;
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.handler.api.V2ApiUtils;
+import org.apache.solr.jersey.SolrJerseyResponse;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.tracing.TraceUtils;
+import org.slf4j.MDC;
+
+/**
+ * A common parent for admin Core Jersey-based APIs.
+ *
+ * <p>This base class is used when creating Core APIs to allow extra 
bookkeeping tasks such as async
+ * requests handling.
+ */
+public abstract class CoreAdminAPIBase extends JerseyResource {
+
+  protected final CoreContainer coreContainer;
+  protected final CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker;
+  protected final SolrQueryRequest req;
+  protected final SolrQueryResponse rsp;
+
+  public CoreAdminAPIBase(
+      CoreContainer coreContainer,
+      CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker,
+      SolrQueryRequest req,
+      SolrQueryResponse rsp) {
+    this.coreContainer = coreContainer;
+    this.coreAdminAsyncTracker = coreAdminAsyncTracker;
+    this.req = req;
+    this.rsp = rsp;
+  }
+
+  /**
+   * Wraps the subclasses logic with extra bookkeeping logic.
+   *
+   * <p>This method currently exists to enable async handling behavior for V2 
Core APIs.
+   *
+   * <p>Since the logic for a given API lives inside the Supplier functional 
interface, checked
+   * exceptions can't be thrown directly to the calling method. To throw a 
checked exception out of
+   * the Supplier, wrap the exception using {@link 
CoreAdminAPIBase.CoreAdminAPIBaseException} and
+   * throw it instead. This handle method will retrieve the checked exception 
from {@link
+   * CoreAdminAPIBaseException} and throw it to the original calling method.
+   *
+   * @param solrJerseyResponse the response that the calling methods expects 
to return.
+   * @param coreName the name of the core that work is being done against.
+   * @param taskId an id provided for registering async work.
+   * @param actionName a name for the action being done.
+   * @param supplier the work that the calling method wants done.
+   * @return the supplied T solrJerseyResponse
+   */
+  public <T extends SolrJerseyResponse> T handle(

Review Comment:
   [+1] Overall I really like how this approach panned out; very cool!
   
   [0] Small nit: can you think of a name here that'd make the method's purpose 
clearer than `handle`?  I'm struggling a bit, but 'handle' feels generic. Maybe 
`handlePotentiallyAsynchronousTask`?
   
   [-0] The one downside of this approach is that CoreAdminHandler still has a 
lot of code that looks similar to what's in this method.  (Specifically lines 
200-250ish in CoreAdminHandler.)  Maybe this is as good as it gets, but can you 
think of any way we could refactor CoreAdminHandler, or this method, or both to 
cut down on the duplication between the two?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@solr.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@solr.apache.org
For additional commands, e-mail: issues-h...@solr.apache.org

Reply via email to