This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new fb6d76c9339 SOLR-16396: Convert v2 configset APIs to JAX-RS (#2928)
fb6d76c9339 is described below

commit fb6d76c9339c8f3b9a3388e9ffcbd3b7992b5389
Author: Jason Gerlowski <gerlowsk...@apache.org>
AuthorDate: Fri Jan 3 07:01:15 2025 -0500

    SOLR-16396: Convert v2 configset APIs to JAX-RS (#2928)
    
    Convert v2 configset APIs to JAX-RS.  Naturally this adds the APIs to
    Solr's growing v2 OAS, and ensures the APIs are included in code-gen
    artifacts.
    
    Also makes slight tweaks to line APIs up with the more REST-ful design
    chosen for other v2 APIs.
    
      - 'clone' (i.e. 'create-from-existing') is now available at `POST
        /api/configsets {...}`
      - 'delete' is now available at `DELETE /api/configsets/csName`
      - 'list' is now available at `GET /api/configsets`
---
 solr/CHANGES.txt                                   |   4 +
 .../solr/client/api/endpoint/ConfigsetsApi.java    | 100 ++++++++++++++++++++
 .../client/api/endpoint/ListConfigsetsApi.java     |  32 -------
 .../api/model/CloneConfigsetRequestBody.java}      |  15 +--
 .../solr/handler/admin/ConfigSetsHandler.java      | 101 +++++++-------------
 ...CreateConfigSetAPI.java => CloneConfigSet.java} |  62 +++++++------
 .../solr/handler/configsets/ConfigSetAPIBase.java  |  17 +++-
 ...eleteConfigSetAPI.java => DeleteConfigSet.java} |  39 ++++----
 .../solr/handler/configsets/ListConfigSets.java    |   6 +-
 ...ploadConfigSetAPI.java => UploadConfigSet.java} | 103 ++++++++++++++++-----
 .../handler/configsets/UploadConfigSetFileAPI.java |  96 -------------------
 .../org/apache/solr/cloud/TestConfigSetsAPI.java   |   8 +-
 .../handler/configsets/ListConfigSetsAPITest.java  |   2 +-
 solr/packaging/test/test_zk.bats                   |   2 +-
 .../configuration-guide/pages/configsets-api.adoc  |  40 +++-----
 15 files changed, 315 insertions(+), 312 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index ec96b6aac2e..ee8943015e2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -15,6 +15,10 @@ Improvements
   specific collections or cores.  Collection information can be fetched by a 
call to `GET /api/collections/collectionName`, and core
   information with a call to `GET /api/cores/coreName/segments`. (Jason 
Gerlowski)
 
+* SOLR-16396: All v2 configset APIs have been moved to the slightly different 
path: `/api/configsets`, to better align with the design of
+  other v2 APIs.  SolrJ now offers (experimental) SolrRequest implementations 
for all v2 configset APIs in
+  `org.apache.solr.client.solrj.request.ConfigsetsApi`. (Jason Gerlowski)
+
 Optimizations
 ---------------------
 * SOLR-17578: Remove ZkController internal core supplier, for slightly faster 
reconnection after Zookeeper session loss. (Pierre Salagnac)
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigsetsApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigsetsApi.java
new file mode 100644
index 00000000000..9961b4c9f28
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ConfigsetsApi.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.client.api.endpoint;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.QueryParam;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
+import org.apache.solr.client.api.model.ListConfigsetsResponse;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+public interface ConfigsetsApi {
+
+  /** V2 API definition for listing the configsets available to this SolrCloud 
cluster. */
+  @Path("/configsets")
+  interface List {
+    @GET
+    @Operation(
+        summary = "List the configsets available to Solr.",
+        tags = {"configsets"})
+    ListConfigsetsResponse listConfigSet() throws Exception;
+  }
+
+  /**
+   * V2 API definition for creating a (possibly slightly modified) copy of an 
existing configset
+   *
+   * <p>Equivalent to the existing v1 API /admin/configs?action=CREATE
+   */
+  @Path("/configsets")
+  interface Clone {
+    @POST
+    @Operation(
+        summary = "Create a new configset modeled on an existing one.",
+        tags = {"configsets"})
+    SolrJerseyResponse cloneExistingConfigSet(CloneConfigsetRequestBody 
requestBody)
+        throws Exception;
+  }
+
+  /**
+   * V2 API definition for deleting an existing configset.
+   *
+   * <p>Equivalent to the existing v1 API /admin/configs?action=DELETE
+   */
+  @Path("/configsets/{configSetName}")
+  interface Delete {
+    @DELETE
+    @Operation(summary = "Delete an existing configset.", tags = "configsets")
+    SolrJerseyResponse deleteConfigSet(@PathParam("configSetName") String 
configSetName)
+        throws Exception;
+  }
+
+  /**
+   * V2 API definitions for uploading a configset, in whole or part.
+   *
+   * <p>Equivalent to the existing v1 API /admin/configs?action=UPLOAD
+   */
+  @Path("/configsets/{configSetName}")
+  interface Upload {
+    @PUT
+    @Operation(summary = "Create a new configset.", tags = "configsets")
+    SolrJerseyResponse uploadConfigSet(
+        @PathParam("configSetName") String configSetName,
+        @QueryParam("overwrite") Boolean overwrite,
+        @QueryParam("cleanup") Boolean cleanup,
+        @RequestBody(required = true) InputStream requestBody)
+        throws IOException;
+
+    @PUT
+    @Path("{filePath:.+}")
+    SolrJerseyResponse uploadConfigSetFile(
+        @PathParam("configSetName") String configSetName,
+        @PathParam("filePath") String filePath,
+        @QueryParam("overwrite") Boolean overwrite,
+        @QueryParam("cleanup") Boolean cleanup,
+        @RequestBody(required = true) InputStream requestBody)
+        throws IOException;
+  }
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java
deleted file mode 100644
index 7e0cf620b7f..00000000000
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.client.api.endpoint;
-
-import io.swagger.v3.oas.annotations.Operation;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import org.apache.solr.client.api.model.ListConfigsetsResponse;
-
-/** V2 API definition for listing configsets. */
-@Path("/cluster/configs")
-public interface ListConfigsetsApi {
-  @GET
-  @Operation(
-      summary = "List the configsets available to Solr.",
-      tags = {"configsets"})
-  ListConfigsetsResponse listConfigSet() throws Exception;
-}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/CloneConfigsetRequestBody.java
similarity index 70%
rename from 
solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
rename to 
solr/api/src/java/org/apache/solr/client/api/model/CloneConfigsetRequestBody.java
index 5f7f2e6687d..14e22225986 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/CloneConfigsetRequestBody.java
@@ -14,19 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.client.solrj.request.beans;
+package org.apache.solr.client.api.model;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Map;
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
 
-public class CreateConfigPayload implements ReflectMapWriter {
-  public static final String DEFAULT_CONFIGSET =
-      "_default"; // TODO Better location for this in SolrJ?
+/** Request body for ConfigsetsApi.Clone */
+public class CloneConfigsetRequestBody {
+  public static final String DEFAULT_CONFIGSET = "_default";
 
   @JsonProperty(required = true)
   public String name;
 
-  @JsonProperty public String baseConfigSet = DEFAULT_CONFIGSET;
+  @JsonProperty(defaultValue = DEFAULT_CONFIGSET)
+  public String baseConfigSet;
+
   @JsonProperty public Map<String, Object> properties;
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
index ff69b1ee147..535deb54e44 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java
@@ -17,36 +17,30 @@
 package org.apache.solr.handler.admin;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
-import static 
org.apache.solr.handler.configsets.UploadConfigSetFileAPI.FILEPATH_PLACEHOLDER;
 
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.JerseyResource;
-import org.apache.solr.api.PayloadObj;
-import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
+import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.ConfigSetCmds;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.ConfigSetParams;
 import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
-import org.apache.solr.common.params.DefaultSolrParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.handler.configsets.CreateConfigSetAPI;
-import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
+import org.apache.solr.handler.configsets.CloneConfigSet;
+import org.apache.solr.handler.configsets.ConfigSetAPIBase;
+import org.apache.solr.handler.configsets.DeleteConfigSet;
 import org.apache.solr.handler.configsets.ListConfigSets;
-import org.apache.solr.handler.configsets.UploadConfigSetAPI;
-import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
-import org.apache.solr.request.DelegatingSolrQueryRequest;
+import org.apache.solr.handler.configsets.UploadConfigSet;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthorizationContext;
@@ -96,51 +90,30 @@ public class ConfigSetsHandler extends RequestHandlerBase 
implements PermissionN
 
     switch (action) {
       case DELETE:
-        final DeleteConfigSetAPI deleteConfigSetAPI = new 
DeleteConfigSetAPI(coreContainer);
-        final SolrQueryRequest v2DeleteReq =
-            new DelegatingSolrQueryRequest(req) {
-              @Override
-              public Map<String, String> getPathTemplateValues() {
-                return Map.of(
-                    DeleteConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
-                    req.getParams().required().get(NAME));
-              }
-            };
-        deleteConfigSetAPI.deleteConfigSet(v2DeleteReq, rsp);
+        final DeleteConfigSet deleteConfigSetAPI = new 
DeleteConfigSet(coreContainer, req, rsp);
+        final var deleteResponse =
+            
deleteConfigSetAPI.deleteConfigSet(req.getParams().required().get(NAME));
+        V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteResponse);
         break;
       case UPLOAD:
-        final SolrQueryRequest v2UploadReq =
-            new DelegatingSolrQueryRequest(req) {
-              @Override
-              public Map<String, String> getPathTemplateValues() {
-                final Map<String, String> templateValsByName = new HashMap<>();
-
-                templateValsByName.put(
-                    UploadConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
-                    req.getParams().required().get(NAME));
-                if (!req.getParams().get(ConfigSetParams.FILE_PATH, 
"").isEmpty()) {
-                  templateValsByName.put(
-                      FILEPATH_PLACEHOLDER, 
req.getParams().get(ConfigSetParams.FILE_PATH));
-                }
-                return templateValsByName;
-              }
-
-              // Set the v1 default vals where they differ from v2's
-              @Override
-              public SolrParams getParams() {
-                final ModifiableSolrParams v1Defaults = new 
ModifiableSolrParams();
-                v1Defaults.add(ConfigSetParams.OVERWRITE, "false");
-                v1Defaults.add(ConfigSetParams.CLEANUP, "false");
-                return new DefaultSolrParams(super.getParams(), v1Defaults);
-              }
-            };
+        final var uploadApi = new UploadConfigSet(coreContainer, req, rsp);
+        final var configSetName = req.getParams().required().get(NAME);
+        final var overwrite = 
req.getParams().getBool(ConfigSetParams.OVERWRITE, false);
+        final var cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, 
false);
+        final var configSetData = 
ConfigSetAPIBase.ensureNonEmptyInputStream(req);
+        SolrJerseyResponse uploadResponse;
         if (req.getParams()
             .get(ConfigSetParams.FILE_PATH, "")
             .isEmpty()) { // Uploading a whole configset
-          new UploadConfigSetAPI(coreContainer).uploadConfigSet(v2UploadReq, 
rsp);
+          uploadResponse =
+              uploadApi.uploadConfigSet(configSetName, overwrite, cleanup, 
configSetData);
         } else { // Uploading a single file
-          new 
UploadConfigSetFileAPI(coreContainer).updateConfigSetFile(v2UploadReq, rsp);
+          final var filePath = req.getParams().get(ConfigSetParams.FILE_PATH);
+          uploadResponse =
+              uploadApi.uploadConfigSetFile(
+                  configSetName, filePath, overwrite, cleanup, configSetData);
         }
+        V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, uploadResponse);
         break;
       case LIST:
         final ListConfigSets listConfigSetsAPI = new 
ListConfigSets(coreContainer);
@@ -153,12 +126,14 @@ public class ConfigSetsHandler extends RequestHandlerBase 
implements PermissionN
         }
 
         // Map v1 parameters into v2 format and process request
-        final CreateConfigPayload createPayload = new CreateConfigPayload();
-        createPayload.name = newConfigSetName;
+        final var requestBody = new CloneConfigsetRequestBody();
+        requestBody.name = newConfigSetName;
         if (req.getParams().get(ConfigSetCmds.BASE_CONFIGSET) != null) {
-          createPayload.baseConfigSet = 
req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
+          requestBody.baseConfigSet = 
req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
+        } else {
+          requestBody.baseConfigSet = "_default";
         }
-        createPayload.properties = new HashMap<>();
+        requestBody.properties = new HashMap<>();
         req.getParams().stream()
             .filter(entry -> 
entry.getKey().startsWith(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX))
             .forEach(
@@ -167,10 +142,11 @@ public class ConfigSetsHandler extends RequestHandlerBase 
implements PermissionN
                       
entry.getKey().substring(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX.length());
                   final Object value =
                       (entry.getValue().length == 1) ? entry.getValue()[0] : 
entry.getValue();
-                  createPayload.properties.put(newKey, value);
+                  requestBody.properties.put(newKey, value);
                 });
-        final CreateConfigSetAPI createConfigSetAPI = new 
CreateConfigSetAPI(coreContainer);
-        createConfigSetAPI.create(new PayloadObj<>("create", null, 
createPayload, req, rsp));
+        final CloneConfigSet createConfigSetAPI = new 
CloneConfigSet(coreContainer, req, rsp);
+        final var createResponse = 
createConfigSetAPI.cloneExistingConfigSet(requestBody);
+        V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createResponse);
         break;
       default:
         throw new IllegalStateException("Unexpected ConfigSetAction detected: 
" + action);
@@ -207,18 +183,13 @@ public class ConfigSetsHandler extends RequestHandlerBase 
implements PermissionN
 
   @Override
   public Collection<Api> getApis() {
-    final List<Api> apis = new ArrayList<>();
-    apis.addAll(AnnotatedApi.getApis(new CreateConfigSetAPI(coreContainer)));
-    apis.addAll(AnnotatedApi.getApis(new DeleteConfigSetAPI(coreContainer)));
-    apis.addAll(AnnotatedApi.getApis(new UploadConfigSetAPI(coreContainer)));
-    apis.addAll(AnnotatedApi.getApis(new 
UploadConfigSetFileAPI(coreContainer)));
-
-    return apis;
+    return new ArrayList<>();
   }
 
   @Override
   public Collection<Class<? extends JerseyResource>> getJerseyResources() {
-    return List.of(ListConfigSets.class);
+    return List.of(
+        ListConfigSets.class, CloneConfigSet.class, DeleteConfigSet.class, 
UploadConfigSet.class);
   }
 
   @Override
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/CloneConfigSet.java
similarity index 53%
rename from 
solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/configsets/CloneConfigSet.java
index 796903ff73c..e55c74e04fa 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/CloneConfigSet.java
@@ -17,54 +17,55 @@
 
 package org.apache.solr.handler.configsets;
 
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
 import static org.apache.solr.common.params.CommonParams.NAME;
 import static 
org.apache.solr.handler.admin.ConfigSetsHandler.DISABLE_CREATE_AUTH_CHECKS;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
 
+import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.solr.api.Command;
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.api.PayloadObj;
-import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
+import org.apache.solr.client.api.endpoint.ConfigsetsApi;
+import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.ConfigSetCmds;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ConfigSetParams;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
 
-/**
- * V2 API for creating a new configset as a copy of an existing one.
- *
- * <p>This API (POST /v2/cluster/configs {"create": {...}}) is analogous to 
the v1
- * /admin/configs?action=CREATE command.
- */
-@EndPoint(method = POST, path = "/cluster/configs", permission = 
CONFIG_EDIT_PERM)
-public class CreateConfigSetAPI extends ConfigSetAPIBase {
+/** V2 API implementation for ConfigsetsApi.Clone */
+public class CloneConfigSet extends ConfigSetAPIBase implements 
ConfigsetsApi.Clone {
 
-  public CreateConfigSetAPI(CoreContainer coreContainer) {
-    super(coreContainer);
+  @Inject
+  public CloneConfigSet(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
+    super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @Command(name = "create")
-  public void create(PayloadObj<CreateConfigPayload> obj) throws Exception {
-    final CreateConfigPayload createConfigPayload = obj.get();
-    if (configSetService.checkConfigExists(createConfigPayload.name)) {
+  @Override
+  @PermissionName(CONFIG_EDIT_PERM)
+  public SolrJerseyResponse cloneExistingConfigSet(CloneConfigsetRequestBody 
requestBody)
+      throws Exception {
+    final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+    if (configSetService.checkConfigExists(requestBody.name)) {
       throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "ConfigSet already exists: " + createConfigPayload.name);
+          SolrException.ErrorCode.BAD_REQUEST, "ConfigSet already exists: " + 
requestBody.name);
     }
 
     // is there a base config that already exists
-    if 
(!configSetService.checkConfigExists(createConfigPayload.baseConfigSet)) {
+    if (!configSetService.checkConfigExists(requestBody.baseConfigSet)) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
-          "Base ConfigSet does not exist: " + 
createConfigPayload.baseConfigSet);
+          "Base ConfigSet does not exist: " + requestBody.baseConfigSet);
     }
 
     if (!DISABLE_CREATE_AUTH_CHECKS
-        && !isTrusted(obj.getRequest().getUserPrincipal(), 
coreContainer.getAuthenticationPlugin())
-        && 
configSetService.isConfigSetTrusted(createConfigPayload.baseConfigSet)) {
+        && !isTrusted(solrQueryRequest.getUserPrincipal(), 
coreContainer.getAuthenticationPlugin())
+        && configSetService.isConfigSetTrusted(requestBody.baseConfigSet)) {
       throw new SolrException(
           SolrException.ErrorCode.UNAUTHORIZED,
           "Can't create a configset with an unauthenticated request from a 
trusted "
@@ -72,16 +73,17 @@ public class CreateConfigSetAPI extends ConfigSetAPIBase {
     }
 
     final Map<String, Object> configsetCommandMsg = new HashMap<>();
-    configsetCommandMsg.put(NAME, createConfigPayload.name);
-    configsetCommandMsg.put(ConfigSetCmds.BASE_CONFIGSET, 
createConfigPayload.baseConfigSet);
-    if (createConfigPayload.properties != null) {
-      for (Map.Entry<String, Object> e : 
createConfigPayload.properties.entrySet()) {
+    configsetCommandMsg.put(NAME, requestBody.name);
+    configsetCommandMsg.put(ConfigSetCmds.BASE_CONFIGSET, 
requestBody.baseConfigSet);
+    if (requestBody.properties != null) {
+      for (Map.Entry<String, Object> e : requestBody.properties.entrySet()) {
         configsetCommandMsg.put(
             ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX + e.getKey(), 
e.getValue());
       }
     }
 
     runConfigSetCommand(
-        obj.getResponse(), ConfigSetParams.ConfigSetAction.CREATE, 
configsetCommandMsg);
+        solrQueryResponse, ConfigSetParams.ConfigSetAction.CREATE, 
configsetCommandMsg);
+    return response;
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/ConfigSetAPIBase.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/ConfigSetAPIBase.java
index 3f401e31bd8..f6e99167f30 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/configsets/ConfigSetAPIBase.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/configsets/ConfigSetAPIBase.java
@@ -28,6 +28,7 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
+import org.apache.solr.api.JerseyResource;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.cloud.OverseerSolrResponseSerializer;
 import org.apache.solr.cloud.OverseerTaskQueue;
@@ -53,18 +54,26 @@ import org.slf4j.LoggerFactory;
  * <p>Contains utilities for tasks common in configset manipulation, including 
running configset
  * "commands" and checking configset "trusted-ness".
  */
-public class ConfigSetAPIBase {
+public class ConfigSetAPIBase extends JerseyResource {
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   protected final CoreContainer coreContainer;
+
+  protected final SolrQueryRequest solrQueryRequest;
+  protected final SolrQueryResponse solrQueryResponse;
   protected final Optional<DistributedCollectionConfigSetCommandRunner>
       distributedCollectionConfigSetCommandRunner;
-
   protected final ConfigSetService configSetService;
 
-  public ConfigSetAPIBase(CoreContainer coreContainer) {
+  public ConfigSetAPIBase(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
     this.coreContainer = coreContainer;
+    this.solrQueryRequest = solrQueryRequest;
+    this.solrQueryResponse = solrQueryResponse;
+
     this.distributedCollectionConfigSetCommandRunner =
         coreContainer.getDistributedCollectionCommandRunner();
     this.configSetService = coreContainer.getConfigSetService();
@@ -96,7 +105,7 @@ public class ConfigSetAPIBase {
     }
   }
 
-  protected InputStream ensureNonEmptyInputStream(SolrQueryRequest req) throws 
IOException {
+  public static InputStream ensureNonEmptyInputStream(SolrQueryRequest req) 
throws IOException {
     Iterator<ContentStream> contentStreamsIterator = 
req.getContentStreams().iterator();
 
     if (!contentStreamsIterator.hasNext()) {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSet.java
similarity index 64%
rename from 
solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSet.java
index 4867dd160fd..1a4b363a833 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSet.java
@@ -16,40 +16,37 @@
  */
 package org.apache.solr.handler.configsets;
 
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
 import static org.apache.solr.common.params.CommonParams.NAME;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
 
+import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.solr.api.EndPoint;
+import org.apache.solr.client.api.endpoint.ConfigsetsApi;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ConfigSetParams;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-/**
- * V2 API for deleting an existing configset
- *
- * <p>This API (DELETE /v2/cluster/configs/configsetName) is analogous to the 
v1
- * /admin/configs?action=DELETE command.
- */
-public class DeleteConfigSetAPI extends ConfigSetAPIBase {
-
-  public static final String CONFIGSET_NAME_PLACEHOLDER = "name";
+/** V2 API implementation for ConfigsetsApi.Delete */
+public class DeleteConfigSet extends ConfigSetAPIBase implements 
ConfigsetsApi.Delete {
 
-  public DeleteConfigSetAPI(CoreContainer coreContainer) {
-    super(coreContainer);
+  @Inject
+  public DeleteConfigSet(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
+    super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @EndPoint(
-      method = DELETE,
-      path = "/cluster/configs/{" + CONFIGSET_NAME_PLACEHOLDER + "}",
-      permission = CONFIG_EDIT_PERM)
-  public void deleteConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
-    final String configSetName = req.getPathTemplateValues().get("name");
+  @Override
+  @PermissionName(CONFIG_EDIT_PERM)
+  public SolrJerseyResponse deleteConfigSet(String configSetName) throws 
Exception {
+    final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
     if (StrUtils.isNullOrEmpty(configSetName)) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST, "No configset name provided to 
delete");
@@ -57,6 +54,8 @@ public class DeleteConfigSetAPI extends ConfigSetAPIBase {
     final Map<String, Object> configsetCommandMsg = new HashMap<>();
     configsetCommandMsg.put(NAME, configSetName);
 
-    runConfigSetCommand(rsp, ConfigSetParams.ConfigSetAction.DELETE, 
configsetCommandMsg);
+    runConfigSetCommand(
+        solrQueryResponse, ConfigSetParams.ConfigSetAction.DELETE, 
configsetCommandMsg);
+    return response;
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
index 5f5d28adcfc..5b45fa38f22 100644
--- a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
@@ -22,7 +22,7 @@ import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.HttpHeaders;
 import org.apache.solr.api.JerseyResource;
-import org.apache.solr.client.api.endpoint.ListConfigsetsApi;
+import org.apache.solr.client.api.endpoint.ConfigsetsApi;
 import org.apache.solr.client.api.model.ListConfigsetsResponse;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.PermissionName;
@@ -30,9 +30,9 @@ import org.apache.solr.jersey.PermissionName;
 /**
  * V2 API implementation for listing all available configsets.
  *
- * <p>This API (GET /v2/cluster/configs) is analogous to the v1 
/admin/configs?action=LIST command.
+ * <p>This API (GET /v2/configsets) is analogous to the v1 
/admin/configs?action=LIST command.
  */
-public class ListConfigSets extends JerseyResource implements 
ListConfigsetsApi {
+public class ListConfigSets extends JerseyResource implements 
ConfigsetsApi.List {
 
   @Context public HttpHeaders headers;
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
similarity index 53%
rename from 
solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
index 79d1b34d5ca..d42adf94999 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -16,9 +16,9 @@
  */
 package org.apache.solr.handler.configsets;
 
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.PUT;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
 
+import jakarta.inject.Inject;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
@@ -27,46 +27,47 @@ import java.util.Collections;
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
-import org.apache.solr.api.EndPoint;
+import org.apache.solr.client.api.endpoint.ConfigsetsApi;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.ConfigSetParams;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.FileTypeMagicUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/**
- * V2 API for uploading a new configset (or overwriting an existing one).
- *
- * <p>This API (PUT /v2/cluster/configs/configsetName) is analogous to the v1
- * /admin/configs?action=UPLOAD command.
- */
-public class UploadConfigSetAPI extends ConfigSetAPIBase {
-
-  public static final String CONFIGSET_NAME_PLACEHOLDER = "name";
+public class UploadConfigSet extends ConfigSetAPIBase implements 
ConfigsetsApi.Upload {
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  public UploadConfigSetAPI(CoreContainer coreContainer) {
-    super(coreContainer);
+  @Inject
+  public UploadConfigSet(
+      CoreContainer coreContainer,
+      SolrQueryRequest solrQueryRequest,
+      SolrQueryResponse solrQueryResponse) {
+    super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @EndPoint(method = PUT, path = "/cluster/configs/{name}", permission = 
CONFIG_EDIT_PERM)
-  public void uploadConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
+  @Override
+  @PermissionName(CONFIG_EDIT_PERM)
+  public SolrJerseyResponse uploadConfigSet(
+      String configSetName, Boolean overwrite, Boolean cleanup, InputStream 
requestBody)
+      throws IOException {
+    final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
     ensureConfigSetUploadEnabled();
 
-    final String configSetName = req.getPathTemplateValues().get("name");
     boolean overwritesExisting = 
configSetService.checkConfigExists(configSetName);
     boolean requestIsTrusted =
-        isTrusted(req.getUserPrincipal(), 
coreContainer.getAuthenticationPlugin());
+        isTrusted(solrQueryRequest.getUserPrincipal(), 
coreContainer.getAuthenticationPlugin());
     // Get upload parameters
-    boolean allowOverwrite = 
req.getParams().getBool(ConfigSetParams.OVERWRITE, true);
-    boolean cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
-    final InputStream inputStream = ensureNonEmptyInputStream(req);
+    if (overwrite == null) overwrite = true;
+    if (cleanup == null) cleanup = false;
 
-    if (overwritesExisting && !allowOverwrite) {
+    if (overwritesExisting && !overwrite) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
           "The configuration " + configSetName + " already exists");
@@ -84,7 +85,7 @@ public class UploadConfigSetAPI extends ConfigSetAPIBase {
     // singleFilePath is not passed.
     createBaseNode(configSetService, overwritesExisting, requestIsTrusted, 
configSetName);
 
-    try (ZipInputStream zis = new ZipInputStream(inputStream, 
StandardCharsets.UTF_8)) {
+    try (ZipInputStream zis = new ZipInputStream(requestBody, 
StandardCharsets.UTF_8)) {
       boolean hasEntry = false;
       ZipEntry zipEntry;
       while ((zipEntry = zis.getNextEntry()) != null) {
@@ -111,6 +112,60 @@ public class UploadConfigSetAPI extends ConfigSetAPIBase {
         && !configSetService.isConfigSetTrusted(configSetName)) {
       configSetService.setConfigSetTrust(configSetName, true);
     }
+    return response;
+  }
+
+  @Override
+  @PermissionName(CONFIG_EDIT_PERM)
+  public SolrJerseyResponse uploadConfigSetFile(
+      String configSetName,
+      String filePath,
+      Boolean overwrite,
+      Boolean cleanup,
+      InputStream requestBody)
+      throws IOException {
+    final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+    ensureConfigSetUploadEnabled();
+
+    boolean overwritesExisting = 
configSetService.checkConfigExists(configSetName);
+    boolean requestIsTrusted =
+        isTrusted(solrQueryRequest.getUserPrincipal(), 
coreContainer.getAuthenticationPlugin());
+
+    // Get upload parameters
+
+    String singleFilePath = filePath != null ? filePath : "";
+    if (overwrite == null) overwrite = true;
+    if (cleanup == null) cleanup = false;
+
+    String fixedSingleFilePath = singleFilePath;
+    if (fixedSingleFilePath.charAt(0) == '/') {
+      fixedSingleFilePath = fixedSingleFilePath.substring(1);
+    }
+    byte[] data = requestBody.readAllBytes();
+    if (fixedSingleFilePath.isEmpty()) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "The file path provided for upload, '" + singleFilePath + "', is not 
valid.");
+    } else if 
(ZkMaintenanceUtils.isFileForbiddenInConfigSets(fixedSingleFilePath)
+        || FileTypeMagicUtil.isFileForbiddenInConfigset(data)) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "The file type provided for upload, '"
+              + singleFilePath
+              + "', is forbidden for use in configSets.");
+    } else if (cleanup) {
+      // Cleanup is not allowed while using singleFilePath upload
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "ConfigSet uploads do not allow cleanup=true when file path is 
used.");
+    } else {
+      // Create a node for the configuration in config
+      // For creating the baseNode, the cleanup parameter is only allowed to 
be true when
+      // singleFilePath is not passed.
+      createBaseNode(configSetService, overwritesExisting, requestIsTrusted, 
configSetName);
+      configSetService.uploadFileToConfig(configSetName, fixedSingleFilePath, 
data, overwrite);
+    }
+    return response;
   }
 
   private void deleteUnusedFiles(
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetFileAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetFileAPI.java
deleted file mode 100644
index 2380a79a92b..00000000000
--- 
a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetFileAPI.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.configsets;
-
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.PUT;
-import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
-
-import java.io.InputStream;
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.ZkMaintenanceUtils;
-import org.apache.solr.common.params.ConfigSetParams;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.util.FileTypeMagicUtil;
-
-/**
- * V2 API for adding or updating a single file within a configset.
- *
- * <p>This API (PUT /v2/cluster/configs/configsetName/someFilePath) is 
analogous to the v1
- * /admin/configs?action=UPLOAD&amp;filePath=someFilePath command.
- */
-public class UploadConfigSetFileAPI extends ConfigSetAPIBase {
-
-  public static final String CONFIGSET_NAME_PLACEHOLDER =
-      UploadConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER;
-  public static final String FILEPATH_PLACEHOLDER = "*";
-
-  private static final String API_PATH =
-      "/cluster/configs/{" + CONFIGSET_NAME_PLACEHOLDER + "}/" + 
FILEPATH_PLACEHOLDER;
-
-  public UploadConfigSetFileAPI(CoreContainer coreContainer) {
-    super(coreContainer);
-  }
-
-  @EndPoint(method = PUT, path = API_PATH, permission = CONFIG_EDIT_PERM)
-  public void updateConfigSetFile(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
-    ensureConfigSetUploadEnabled();
-
-    final String configSetName = req.getPathTemplateValues().get("name");
-    boolean overwritesExisting = 
configSetService.checkConfigExists(configSetName);
-    boolean requestIsTrusted =
-        isTrusted(req.getUserPrincipal(), 
coreContainer.getAuthenticationPlugin());
-
-    // Get upload parameters
-
-    String singleFilePath = 
req.getPathTemplateValues().getOrDefault(FILEPATH_PLACEHOLDER, "");
-    boolean allowOverwrite = 
req.getParams().getBool(ConfigSetParams.OVERWRITE, true);
-    boolean cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
-    final InputStream inputStream = ensureNonEmptyInputStream(req);
-
-    String fixedSingleFilePath = singleFilePath;
-    if (fixedSingleFilePath.charAt(0) == '/') {
-      fixedSingleFilePath = fixedSingleFilePath.substring(1);
-    }
-    byte[] data = inputStream.readAllBytes();
-    if (fixedSingleFilePath.isEmpty()) {
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "The file path provided for upload, '" + singleFilePath + "', is not 
valid.");
-    } else if 
(ZkMaintenanceUtils.isFileForbiddenInConfigSets(fixedSingleFilePath)
-        || FileTypeMagicUtil.isFileForbiddenInConfigset(data)) {
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "The file type provided for upload, '"
-              + singleFilePath
-              + "', is forbidden for use in configSets.");
-    } else if (cleanup) {
-      // Cleanup is not allowed while using singleFilePath upload
-      throw new SolrException(
-          SolrException.ErrorCode.BAD_REQUEST,
-          "ConfigSet uploads do not allow cleanup=true when file path is 
used.");
-    } else {
-      // Create a node for the configuration in config
-      // For creating the baseNode, the cleanup parameter is only allowed to 
be true when
-      // singleFilePath is not passed.
-      createBaseNode(configSetService, overwritesExisting, requestIsTrusted, 
configSetName);
-      configSetService.uploadFileToConfig(configSetName, fixedSingleFilePath, 
data, allowOverwrite);
-    }
-  }
-}
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java 
b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
index eb28fdced07..54681b10e2c 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
@@ -1588,7 +1588,7 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
       final ByteBuffer fileBytes =
           TestSolrConfigHandler.getFileContent(file.getAbsolutePath(), false);
       final String uriEnding =
-          "/cluster/configs/"
+          "/configsets/"
               + configSetName
               + suffix
               + (!overwrite ? "?overwrite=false" : "")
@@ -1639,11 +1639,13 @@ public class TestConfigSetsAPI extends 
SolrCloudTestCase {
 
       final ByteBuffer sampleConfigFile =
           TestSolrConfigHandler.getFileContent(file.getAbsolutePath(), false);
+      if (uploadPath != null && !uploadPath.startsWith("/")) {
+        uploadPath = "/" + uploadPath;
+      }
       final String uriEnding =
-          "/cluster/configs/"
+          "/configsets/"
               + configSetName
               + suffix
-              + "/"
               + uploadPath
               + (!overwrite ? "?overwrite=false" : "")
               + (cleanup ? "?cleanup=true" : "");
diff --git 
a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
index 40100be48ac..776d0800e03 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
@@ -61,7 +61,7 @@ public class ListConfigSetsAPITest extends SolrTestCase {
   }
 
   /**
-   * Test the v2 to v1 response mapping for /cluster/configs
+   * Test the v2 to v1 response mapping for GET /configsets
    *
    * <p>{@link org.apache.solr.handler.admin.ConfigSetsHandler} uses {@link 
ListConfigSets} (and its
    * response class {@link ListConfigsetsResponse}) internally to serve the v1 
version of this
diff --git a/solr/packaging/test/test_zk.bats b/solr/packaging/test/test_zk.bats
index ea9a372328a..447e1338cfa 100644
--- a/solr/packaging/test/test_zk.bats
+++ b/solr/packaging/test/test_zk.bats
@@ -135,7 +135,7 @@ teardown() {
   refute_output --partial "ERROR"
 
   sleep 1
-  run curl "http://localhost:${SOLR_PORT}/api/cluster/configs?omitHeader=true";
+  run curl "http://localhost:${SOLR_PORT}/api/configsets";
   assert_output --partial '"configSets":["_default","techproducts2"]'
 }
 
diff --git 
a/solr/solr-ref-guide/modules/configuration-guide/pages/configsets-api.adoc 
b/solr/solr-ref-guide/modules/configuration-guide/pages/configsets-api.adoc
index 45987271de6..d021eb4440a 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/configsets-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/configsets-api.adoc
@@ -35,7 +35,7 @@ NOTE: This API can only be used with Solr running in 
SolrCloud mode.
 If you are not running Solr in SolrCloud mode but would still like to use 
shared configurations, please see the section xref:config-sets.adoc[].
 
 The API works by passing commands to the `configs` endpoint.
-The path to the endpoint varies depending on the API being used: the v1 API 
uses `solr/admin/configs`, while the v2 API uses `api/cluster/configs`.
+The path to the endpoint varies depending on the API being used: the v1 API 
uses `/solr/admin/configs`, while the v2 API uses `/api/configsets`.
 Examples of both types are provided below.
 
 [[configsets-list]]
@@ -64,7 +64,7 @@ With the v2 API, the `list` command is implied when there is 
no data sent with t
 
 [source,bash]
 ----
-http://localhost:8983/api/cluster/configs?omitHeader=true
+http://localhost:8983/api/configsets?omitHeader=true
 ----
 ====
 ======
@@ -183,7 +183,7 @@ With the v2 API, the name of the configset to upload is 
provided as a path param
 $ (cd solr/server/solr/configsets/sample_techproducts_configs/conf && zip -r - 
*) > myconfigset.zip
 
 $ curl -X PUT --header "Content-Type:application/octet-stream" --data-binary 
@myconfigset.zip
-    "http://localhost:8983/api/cluster/configs/myConfigSet";
+    "http://localhost:8983/api/configsets/myConfigSet";
 ----
 
 With this  API, the default behavior is to overwrite the configset if it 
already exists.
@@ -213,14 +213,14 @@ V2 API::
 +
 ====
 With the v2 API, the name of the configset and file are both provided in the 
URL.
-They can be substituted in `/cluster/configs/__config_name__/__file_name__`.
+They can be substituted in `/configsets/__config_name__/__file_name__`.
 The filename may be nested and include `/` characters.
 
 [source,bash]
 ----
 curl -X PUT --header "Content-Type:application/octet-stream"
     --data-binary 
@solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
-    "http://localhost:8983/api/cluster/configs/myConfigSet/solrconfig.xml";
+    "http://localhost:8983/api/configsets/myConfigSet/solrconfig.xml";
 ----
 
 With this API, the default behavior is to overwrite the file if it already 
exists within the configset.
@@ -282,30 +282,18 @@ 
http://localhost:8983/solr/admin/configs?action=CREATE&name=myConfigSet&baseConf
 V2 API::
 +
 ====
-With the v2 API, the `create` command is provided as part of the JSON data 
that contains the required parameters:
+With the v2 API, the `create` command is implicit and parameters are specified 
in a `POST` request body.
 
 [source,bash]
 ----
 curl -X POST -H 'Content-type: application/json' -d '{
-  "create":{
-    "name": "myConfigSet",
-    "baseConfigSet": "predefinedTemplate",
-    "configSetProp.immutable": "false"}}'
-    http://localhost:8983/api/cluster/configs?omitHeader=true
-----
-
-With the v2 API, configset properties can also be provided via the 
`properties` map:
-
-[source,bash]
-----
-curl -X POST -H 'Content-type: application/json' -d '{
-  "create":{
-    "name": "myConfigSet",
-    "baseConfigSet": "predefinedTemplate",
-    "properties": {
-      "immutable": "false"
-    }}}'
-    http://localhost:8983/api/cluster/configs?omitHeader=true
+  "name": "myConfigSet",
+  "baseConfigSet": "predefinedTemplate",
+  "properties": {
+    "immutable": "false"
+  }
+}'
+  http://localhost:8983/api/configsets?omitHeader=true
 ----
 ====
 ======
@@ -361,7 +349,7 @@ The name of the configset to delete is provided as a path 
parameter:
 
 [source,bash]
 ----
-curl -X DELETE 
http://localhost:8983/api/cluster/configs/myConfigSet?omitHeader=true
+curl -X DELETE http://localhost:8983/api/configsets/myConfigSet?omitHeader=true
 ----
 ====
 ======

Reply via email to