You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2022/08/04 20:56:31 UTC
[solr] branch branch_9x updated: SOLR-15736: Move configset logic into v2 APIs (#955)
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 ee6e784b410 SOLR-15736: Move configset logic into v2 APIs (#955)
ee6e784b410 is described below
commit ee6e784b410907d98520a0621d6a945fdcd6f1c6
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Thu Aug 4 16:52:11 2022 -0400
SOLR-15736: Move configset logic into v2 APIs (#955)
---
...istributedCollectionConfigSetCommandRunner.java | 5 +-
.../cloud/api/collections/OverseerStatusCmd.java | 3 +-
.../java/org/apache/solr/core/CoreContainer.java | 2 +-
.../java/org/apache/solr/handler/ClusterAPI.java | 83 ----
.../solr/handler/admin/ConfigSetsHandler.java | 493 +++++----------------
.../org/apache/solr/handler/api/ApiRegistrar.java | 14 +
.../solr/handler/configsets/ConfigSetAPIBase.java | 209 +++++++++
.../handler/configsets/CreateConfigSetAPI.java | 88 ++++
.../handler/configsets/DeleteConfigSetAPI.java | 62 +++
.../solr/handler/configsets/ListConfigSetsAPI.java | 49 ++
.../handler/configsets/UploadConfigSetAPI.java | 130 ++++++
.../handler/configsets/UploadConfigSetFileAPI.java | 88 ++++
.../solr/handler/configsets/package-info.java} | 16 +-
.../solr/request/DelegatingSolrQueryRequest.java | 172 +++++++
.../solr/handler/V2ClusterAPIMappingTest.java | 67 ---
.../apache/solr/handler/admin/TestConfigsApi.java | 58 ---
.../solrj/request/beans/CreateConfigPayload.java | 5 +-
17 files changed, 922 insertions(+), 622 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DistributedCollectionConfigSetCommandRunner.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DistributedCollectionConfigSetCommandRunner.java
index 32a0faaa749..07fe08f377b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DistributedCollectionConfigSetCommandRunner.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DistributedCollectionConfigSetCommandRunner.java
@@ -52,7 +52,6 @@ import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.core.CoreContainer;
-import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.response.SolrQueryResponse;
import org.slf4j.Logger;
@@ -186,7 +185,7 @@ public class DistributedCollectionConfigSetCommandRunner {
*/
public void runConfigSetCommand(
SolrQueryResponse rsp,
- ConfigSetsHandler.ConfigSetOperation operation,
+ ConfigSetParams.ConfigSetAction action,
Map<String, Object> result,
long timeoutMs)
throws Exception {
@@ -198,8 +197,6 @@ public class DistributedCollectionConfigSetCommandRunner {
"Solr is shutting down, no more Config Set API tasks may be executed");
}
- ConfigSetParams.ConfigSetAction action = operation.getAction();
-
// never null
String configSetName = (String) result.get(NAME);
// baseConfigSetName will be null if we're not creating a new config set
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerStatusCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerStatusCmd.java
index 3cfe436878f..0b841fe8790 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerStatusCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerStatusCmd.java
@@ -70,8 +70,7 @@ import org.slf4j.LoggerFactory;
* <ul>
* <li>{@code am_i_leader} (Overseer checking it is still the elected Overseer as it
* processes cluster state update messages)
- * <li>{@code configset_}<i>{@code <config set operation>}</i> (from {@link
- * org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation})
+ * <li>{@code configset_}<i>{@code <config set operation>}</i>
* <li>Cluster state change operation names from {@link
* org.apache.solr.common.params.CollectionParams.CollectionAction} (not all of them!)
* and {@link org.apache.solr.cloud.overseer.OverseerAction} (the complete list: {@code
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 1f56b151945..798e07dcec1 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -808,7 +808,7 @@ public class CoreContainer {
ClusterAPI clusterAPI = new ClusterAPI(collectionsHandler, configSetsHandler);
containerHandlers.getApiBag().registerObject(clusterAPI);
containerHandlers.getApiBag().registerObject(clusterAPI.commands);
- containerHandlers.getApiBag().registerObject(clusterAPI.configSetCommands);
+ ApiRegistrar.registerConfigsetApis(containerHandlers.getApiBag(), this);
if (isZooKeeperAware()) {
containerHandlers.getApiBag().registerObject(new SchemaDesignerAPI(this));
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index e58ed8c6bd3..ca3f6dd4139 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -20,7 +20,6 @@ package org.apache.solr.handler;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.PUT;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.common.params.CollectionParams.ACTION;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
@@ -33,8 +32,6 @@ import static org.apache.solr.common.params.CollectionParams.CollectionAction.RE
import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
-import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
-import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
import com.google.common.collect.Maps;
import java.io.IOException;
@@ -49,16 +46,12 @@ import org.apache.solr.api.EndPoint;
import org.apache.solr.api.PayloadObj;
import org.apache.solr.client.solrj.cloud.DistribStateManager;
import org.apache.solr.client.solrj.request.beans.ClusterPropPayload;
-import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
import org.apache.solr.client.solrj.request.beans.RateLimiterPayload;
-import org.apache.solr.cloud.ConfigSetCmds;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ConfigSetParams;
import org.apache.solr.common.params.DefaultSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.ReflectMapWriter;
@@ -77,7 +70,6 @@ public class ClusterAPI {
private final ConfigSetsHandler configSetsHandler;
public final Commands commands = new Commands();
- public final ConfigSetCommands configSetCommands = new ConfigSetCommands();
public ClusterAPI(CollectionsHandler ch, ConfigSetsHandler configSetsHandler) {
this.collectionsHandler = ch;
@@ -235,81 +227,6 @@ public class ClusterAPI {
CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp, collectionsHandler);
}
- @EndPoint(method = DELETE, path = "/cluster/configs/{name}", permission = CONFIG_EDIT_PERM)
- public void deleteConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- req =
- wrapParams(
- req,
- "action",
- ConfigSetParams.ConfigSetAction.DELETE.toString(),
- CommonParams.NAME,
- req.getPathTemplateValues().get("name"));
- configSetsHandler.handleRequestBody(req, rsp);
- }
-
- @EndPoint(method = GET, path = "/cluster/configs", permission = CONFIG_READ_PERM)
- public void listConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- req = wrapParams(req, "action", ConfigSetParams.ConfigSetAction.LIST.toString());
- configSetsHandler.handleRequestBody(req, rsp);
- }
-
- @EndPoint(method = POST, path = "/cluster/configs", permission = CONFIG_EDIT_PERM)
- public class ConfigSetCommands {
-
- @Command(name = "create")
- @SuppressWarnings("unchecked")
- public void create(PayloadObj<CreateConfigPayload> obj) throws Exception {
- Map<String, Object> mapVals = obj.get().toMap(new HashMap<>());
- Map<String, Object> customProps = obj.get().properties;
- if (customProps != null) {
- customProps.forEach((k, o) -> mapVals.put(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX + k, o));
- }
- mapVals.put("action", ConfigSetParams.ConfigSetAction.CREATE.toString());
- configSetsHandler.handleRequestBody(wrapParams(obj.getRequest(), mapVals), obj.getResponse());
- }
- }
-
- @EndPoint(method = PUT, path = "/cluster/configs/{name}", permission = CONFIG_EDIT_PERM)
- public void uploadConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- req =
- wrapParams(
- req,
- "action",
- ConfigSetParams.ConfigSetAction.UPLOAD.toString(),
- CommonParams.NAME,
- req.getPathTemplateValues().get("name"),
- ConfigSetParams.OVERWRITE,
- true,
- ConfigSetParams.CLEANUP,
- false);
- configSetsHandler.handleRequestBody(req, rsp);
- }
-
- @EndPoint(method = PUT, path = "/cluster/configs/{name}/*", permission = CONFIG_EDIT_PERM)
- public void insertIntoConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- String path = req.getPathTemplateValues().get("*");
- if (path == null || path.isBlank()) {
- throw new SolrException(
- SolrException.ErrorCode.BAD_REQUEST,
- "In order to insert a file in a configSet, a filePath must be provided in the url after the name of the configSet.");
- }
- req =
- wrapParams(
- req,
- Map.of(
- "action",
- ConfigSetParams.ConfigSetAction.UPLOAD.toString(),
- CommonParams.NAME,
- req.getPathTemplateValues().get("name"),
- ConfigSetParams.FILE_PATH,
- path,
- ConfigSetParams.OVERWRITE,
- true,
- ConfigSetParams.CLEANUP,
- false));
- configSetsHandler.handleRequestBody(req, rsp);
- }
-
public static SolrQueryRequest wrapParams(SolrQueryRequest req, Object... def) {
Map<String, Object> m = Utils.makeMap(def);
return wrapParams(req, m);
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 bba86c0cbd3..737f15a5fc6 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
@@ -16,66 +16,47 @@
*/
package org.apache.solr.handler.admin;
-import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
-import static org.apache.solr.cloud.OverseerConfigSetMessageHandler.CONFIGSETS_ACTION_PREFIX;
import static org.apache.solr.common.params.CommonParams.NAME;
-import static org.apache.solr.common.params.ConfigSetParams.ConfigSetAction.CREATE;
-import static org.apache.solr.common.params.ConfigSetParams.ConfigSetAction.DELETE;
-import static org.apache.solr.common.params.ConfigSetParams.ConfigSetAction.LIST;
-import static org.apache.solr.common.params.ConfigSetParams.ConfigSetAction.UPLOAD;
+import static org.apache.solr.handler.configsets.UploadConfigSetFileAPI.FILEPATH_PLACEHOLDER;
-import java.io.IOException;
-import java.io.InputStream;
+import com.google.common.collect.Maps;
import java.lang.invoke.MethodHandles;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.api.PayloadObj;
+import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
import org.apache.solr.cloud.ConfigSetCmds;
-import org.apache.solr.cloud.OverseerSolrResponse;
-import org.apache.solr.cloud.OverseerSolrResponseSerializer;
-import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
-import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.common.cloud.ZkNodeProps;
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.common.util.ContentStream;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.ConfigSetService;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.configsets.CreateConfigSetAPI;
+import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
+import org.apache.solr.handler.configsets.ListConfigSetsAPI;
+import org.apache.solr.handler.configsets.UploadConfigSetAPI;
+import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
+import org.apache.solr.request.DelegatingSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
-import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** A {@link org.apache.solr.request.SolrRequestHandler} for ConfigSets API requests. */
public class ConfigSetsHandler extends RequestHandlerBase implements PermissionNameProvider {
+ // TODO refactor into o.a.s.handler.configsets package to live alongside actual API logic
public static final Boolean DISABLE_CREATE_AUTH_CHECKS =
Boolean.getBoolean("solr.disableConfigSetsCreateAuthChecks"); // this is for back compat only
public static final String DEFAULT_CONFIGSET_NAME = "_default";
public static final String AUTOCREATED_CONFIGSET_SUFFIX = ".AUTOCREATED";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final CoreContainer coreContainer;
- private final Optional<DistributedCollectionConfigSetCommandRunner>
- distributedCollectionConfigSetCommandRunner;
public static long CONFIG_SET_TIMEOUT = 300 * 1000;
/**
* Overloaded ctor to inject CoreContainer into the handler.
@@ -84,10 +65,6 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
*/
public ConfigSetsHandler(final CoreContainer coreContainer) {
this.coreContainer = coreContainer;
- distributedCollectionConfigSetCommandRunner =
- coreContainer != null
- ? coreContainer.getDistributedCollectionCommandRunner()
- : Optional.empty();
}
public static String getSuffixedNameForAutoGeneratedConfigSet(String configName) {
@@ -103,21 +80,93 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
checkErrors();
// Pick the action
- SolrParams params = req.getParams();
- String a = params.get(ConfigSetParams.ACTION);
- if (a != null) {
- ConfigSetAction action = ConfigSetAction.get(a);
- if (action == null)
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
- if (action == ConfigSetAction.UPLOAD) {
- handleConfigUploadRequest(req, rsp);
- return;
- }
- invokeAction(req, rsp, action);
- } else {
- throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
- }
+ final SolrParams requiredSolrParams = req.getParams().required();
+ final String actionStr = requiredSolrParams.get(ConfigSetParams.ACTION);
+ ConfigSetAction action = ConfigSetAction.get(actionStr);
+ if (action == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + actionStr);
+ }
+
+ 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);
+ break;
+ case UPLOAD:
+ final SolrQueryRequest v2UploadReq =
+ new DelegatingSolrQueryRequest(req) {
+ @Override
+ public Map<String, String> getPathTemplateValues() {
+ final Map<String, String> templateValsByName = Maps.newHashMap();
+
+ 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);
+ }
+ };
+ if (req.getParams()
+ .get(ConfigSetParams.FILE_PATH, "")
+ .isEmpty()) { // Uploading a whole configset
+ new UploadConfigSetAPI(coreContainer).uploadConfigSet(v2UploadReq, rsp);
+ } else { // Uploading a single file
+ new UploadConfigSetFileAPI(coreContainer).updateConfigSetFile(v2UploadReq, rsp);
+ }
+ break;
+ case LIST:
+ new ListConfigSetsAPI(coreContainer).listConfigSet(req, rsp);
+ break;
+ case CREATE:
+ final String newConfigSetName = req.getParams().get(NAME);
+ if (newConfigSetName == null || newConfigSetName.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "ConfigSet name not specified");
+ }
+ // Map v1 parameters into v2 format and process request
+ final CreateConfigPayload createPayload = new CreateConfigPayload();
+ createPayload.name = newConfigSetName;
+ if (req.getParams().get(ConfigSetCmds.BASE_CONFIGSET) != null) {
+ createPayload.baseConfigSet = req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
+ }
+ createPayload.properties = new HashMap<>();
+ req.getParams().stream()
+ .filter(entry -> entry.getKey().startsWith(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX))
+ .forEach(
+ entry -> {
+ final String newKey =
+ 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);
+ });
+ final CreateConfigSetAPI createConfigSetAPI = new CreateConfigSetAPI(coreContainer);
+ createConfigSetAPI.create(new PayloadObj<>("create", null, createPayload, req, rsp));
+ break;
+ default:
+ throw new IllegalStateException("Unexpected ConfigSetAction detected: " + action);
+ }
rsp.setHttpCaching(false);
}
@@ -133,261 +182,6 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
}
}
- void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetAction action)
- throws Exception {
- ConfigSetOperation operation = ConfigSetOperation.get(action);
- if (log.isInfoEnabled()) {
- log.info(
- "Invoked ConfigSet Action :{} with params {} ", action.toLower(), req.getParamString());
- }
- Map<String, Object> result = operation.call(req, rsp, this);
- if (result != null) {
- if (distributedCollectionConfigSetCommandRunner.isPresent()) {
- distributedCollectionConfigSetCommandRunner
- .get()
- .runConfigSetCommand(rsp, operation, result, CONFIG_SET_TIMEOUT);
- } else { // Sending the Collection API message to Overseer via a Zookeeper queue
- sendToOverseer(rsp, operation, result);
- }
- }
- }
-
- protected void sendToOverseer(
- SolrQueryResponse rsp, ConfigSetOperation operation, Map<String, Object> result)
- throws KeeperException, InterruptedException {
- // We need to differentiate between collection and configsets actions since they currently
- // use the same underlying queue.
- result.put(QUEUE_OPERATION, CONFIGSETS_ACTION_PREFIX + operation.action.toLower());
- ZkNodeProps props = new ZkNodeProps(result);
- handleResponse(operation.action.toLower(), props, rsp, CONFIG_SET_TIMEOUT);
- }
-
- private void handleConfigUploadRequest(SolrQueryRequest req, SolrQueryResponse rsp)
- throws Exception {
- if (!"true".equals(System.getProperty("configset.upload.enabled", "true"))) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "Configset upload feature is disabled. To enable this, start Solr with '-Dconfigset.upload.enabled=true'.");
- }
-
- String configSetName = req.getParams().get(NAME);
- if (StringUtils.isBlank(configSetName)) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "The configuration name should be provided in the \"name\" parameter");
- }
-
- ConfigSetService configSetService = coreContainer.getConfigSetService();
-
- boolean overwritesExisting = configSetService.checkConfigExists(configSetName);
-
- boolean requestIsTrusted = isTrusted(req, coreContainer.getAuthenticationPlugin());
-
- // Get upload parameters
- String singleFilePath = req.getParams().get(ConfigSetParams.FILE_PATH, "");
- boolean allowOverwrite = req.getParams().getBool(ConfigSetParams.OVERWRITE, false);
- boolean cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
-
- Iterator<ContentStream> contentStreamsIterator = req.getContentStreams().iterator();
-
- if (!contentStreamsIterator.hasNext()) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST, "No stream found for the config data to be uploaded");
- }
-
- InputStream inputStream = contentStreamsIterator.next().getStream();
-
- // Only Upload a single file
- if (!singleFilePath.isEmpty()) {
- String fixedSingleFilePath = singleFilePath;
- if (fixedSingleFilePath.charAt(0) == '/') {
- fixedSingleFilePath = fixedSingleFilePath.substring(1);
- }
- if (fixedSingleFilePath.isEmpty()) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "The file path provided for upload, '" + singleFilePath + "', is not valid.");
- } else if (cleanup) {
- // Cleanup is not allowed while using singleFilePath upload
- throw new 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, IOUtils.toByteArray(inputStream), allowOverwrite);
- }
- return;
- }
-
- if (overwritesExisting && !allowOverwrite) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "The configuration " + configSetName + " already exists in zookeeper");
- }
-
- List<String> filesToDelete;
- if (overwritesExisting && cleanup) {
- filesToDelete = configSetService.getAllConfigFiles(configSetName);
- } else {
- filesToDelete = Collections.emptyList();
- }
-
- // Create a node for the configuration in zookeeper
- // For creating the baseZnode, the cleanup parameter is only allowed to be true when
- // singleFilePath is not passed.
- createBaseNode(configSetService, overwritesExisting, requestIsTrusted, configSetName);
-
- try (ZipInputStream zis = new ZipInputStream(inputStream, StandardCharsets.UTF_8)) {
- boolean hasEntry = false;
- ZipEntry zipEntry;
- while ((zipEntry = zis.getNextEntry()) != null) {
- hasEntry = true;
- String filePath = zipEntry.getName();
- filesToDelete.remove(filePath);
- if (!zipEntry.isDirectory()) {
- configSetService.uploadFileToConfig(
- configSetName, filePath, IOUtils.toByteArray(zis), true);
- }
- }
- if (!hasEntry) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "Either empty zipped data, or non-zipped data was uploaded. In order to upload a configSet, you must zip a non-empty directory to upload.");
- }
- }
- deleteUnusedFiles(configSetService, configSetName, filesToDelete);
-
- // If the request is doing a full trusted overwrite of an untrusted configSet (overwrite=true,
- // cleanup=true), then trust the configSet.
- if (cleanup
- && requestIsTrusted
- && overwritesExisting
- && !isCurrentlyTrusted(configSetService, configSetName)) {
- Map<String, Object> metadata = Collections.singletonMap("trusted", true);
- configSetService.setConfigMetadata(configSetName, metadata);
- }
- }
-
- private void createBaseNode(
- ConfigSetService configSetService,
- boolean overwritesExisting,
- boolean requestIsTrusted,
- String configName)
- throws IOException {
- Map<String, Object> metadata = Collections.singletonMap("trusted", requestIsTrusted);
-
- if (overwritesExisting) {
- if (!requestIsTrusted) {
- ensureOverwritingUntrustedConfigSet(configSetService, configName);
- }
- // If the request is trusted and cleanup=true, then the configSet will be set to trusted after
- // the overwriting has been done.
- } else {
- configSetService.setConfigMetadata(configName, metadata);
- }
- }
-
- private void deleteUnusedFiles(
- ConfigSetService configSetService, String configName, List<String> filesToDelete)
- throws IOException {
- if (!filesToDelete.isEmpty()) {
- if (log.isInfoEnabled()) {
- log.info("Cleaning up {} unused files", filesToDelete.size());
- }
- if (log.isDebugEnabled()) {
- log.debug("Cleaning up unused files: {}", filesToDelete);
- }
- configSetService.deleteFilesFromConfig(configName, filesToDelete);
- }
- }
-
- /*
- * Fail if an untrusted request tries to update a trusted ConfigSet
- */
- private void ensureOverwritingUntrustedConfigSet(
- ConfigSetService configSetService, String configName) throws IOException {
- boolean isCurrentlyTrusted = isCurrentlyTrusted(configSetService, configName);
- if (isCurrentlyTrusted) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "Trying to make an untrusted ConfigSet update on a trusted configSet");
- }
- }
-
- private static boolean isCurrentlyTrusted(ConfigSetService configSetService, String configName)
- throws IOException {
- Map<String, Object> contentMap = configSetService.getConfigMetadata(configName);
- return (boolean) contentMap.getOrDefault("trusted", true);
- }
-
- static boolean isTrusted(SolrQueryRequest req, AuthenticationPlugin authPlugin) {
- if (authPlugin != null && req.getUserPrincipal() != null) {
- log.debug("Trusted configset request");
- return true;
- }
- log.debug("Untrusted configset request");
- return false;
- }
-
- private void handleResponse(String operation, ZkNodeProps m, SolrQueryResponse rsp, long timeout)
- throws KeeperException, InterruptedException {
- long time = System.nanoTime();
-
- QueueEvent event =
- coreContainer.getZkController().getOverseerConfigSetQueue().offer(Utils.toJSON(m), timeout);
- if (event.getBytes() != null) {
- SolrResponse response = OverseerSolrResponseSerializer.deserialize(event.getBytes());
- rsp.getValues().addAll(response.getResponse());
- SimpleOrderedMap<?> exp = (SimpleOrderedMap<?>) response.getResponse().get("exception");
- if (exp != null) {
- Integer code = (Integer) exp.get("rspCode");
- rsp.setException(
- new SolrException(
- code != null && code != -1 ? ErrorCode.getErrorCode(code) : ErrorCode.SERVER_ERROR,
- (String) exp.get("msg")));
- }
- } else {
- if (System.nanoTime() - time
- >= TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS)) {
- throw new SolrException(
- ErrorCode.SERVER_ERROR, operation + " the configset time out:" + timeout / 1000 + "s");
- } else if (event.getWatchedEvent() != null) {
- throw new SolrException(
- ErrorCode.SERVER_ERROR,
- operation
- + " the configset error [Watcher fired on path: "
- + event.getWatchedEvent().getPath()
- + " state: "
- + event.getWatchedEvent().getState()
- + " type "
- + event.getWatchedEvent().getType()
- + "]");
- } else {
- throw new SolrException(ErrorCode.SERVER_ERROR, operation + " the configset unknown case");
- }
- }
- }
-
- private static Map<String, Object> copyPropertiesWithPrefix(
- SolrParams params, Map<String, Object> props, String prefix) {
- Iterator<String> iter = params.getParameterNamesIterator();
- while (iter.hasNext()) {
- String param = iter.next();
- if (param.startsWith(prefix)) {
- props.put(param, params.get(param));
- }
- }
-
- // The configset created via an API should be mutable.
- props.put("immutable", "false");
-
- return props;
- }
-
@Override
public String getDescription() {
return "Manage SolrCloud ConfigSets";
@@ -398,93 +192,6 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
return Category.ADMIN;
}
- public enum ConfigSetOperation {
- UPLOAD_OP(UPLOAD) {
- @Override
- public Map<String, Object> call(
- SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception {
- h.handleConfigUploadRequest(req, rsp);
- return null;
- }
- },
- CREATE_OP(CREATE) {
- @Override
- public Map<String, Object> call(
- SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception {
- String baseConfigSetName =
- req.getParams().get(ConfigSetCmds.BASE_CONFIGSET, DEFAULT_CONFIGSET_NAME);
- String newConfigSetName = req.getParams().get(NAME);
- if (newConfigSetName == null || newConfigSetName.length() == 0) {
- throw new SolrException(ErrorCode.BAD_REQUEST, "ConfigSet name not specified");
- }
-
- if (h.coreContainer.getConfigSetService().checkConfigExists(newConfigSetName)) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST, "ConfigSet already exists: " + newConfigSetName);
- }
-
- // is there a base config that already exists
- if (!h.coreContainer.getConfigSetService().checkConfigExists(baseConfigSetName)) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST, "Base ConfigSet does not exist: " + baseConfigSetName);
- }
-
- Map<String, Object> props = CollectionsHandler.copy(req.getParams().required(), null, NAME);
- props.put(ConfigSetCmds.BASE_CONFIGSET, baseConfigSetName);
- if (!DISABLE_CREATE_AUTH_CHECKS
- && !isTrusted(req, h.coreContainer.getAuthenticationPlugin())
- && isCurrentlyTrusted(h.coreContainer.getConfigSetService(), baseConfigSetName)) {
- throw new SolrException(
- ErrorCode.UNAUTHORIZED,
- "Can't create a configset with an unauthenticated request from a trusted "
- + ConfigSetCmds.BASE_CONFIGSET);
- }
- return copyPropertiesWithPrefix(
- req.getParams(), props, ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX);
- }
- },
- DELETE_OP(DELETE) {
- @Override
- public Map<String, Object> call(
- SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception {
- return CollectionsHandler.copy(req.getParams().required(), null, NAME);
- }
- },
- @SuppressWarnings({"unchecked"})
- LIST_OP(LIST) {
- @Override
- public Map<String, Object> call(
- SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception {
- NamedList<Object> results = new NamedList<>();
- List<String> configSetsList = h.coreContainer.getConfigSetService().listConfigs();
- results.add("configSets", configSetsList);
- SolrResponse response = new OverseerSolrResponse(results);
- rsp.getValues().addAll(response.getResponse());
- return null;
- }
- };
-
- ConfigSetAction action;
-
- ConfigSetOperation(ConfigSetAction action) {
- this.action = action;
- }
-
- public ConfigSetAction getAction() {
- return action;
- }
-
- public abstract Map<String, Object> call(
- SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h) throws Exception;
-
- public static ConfigSetOperation get(ConfigSetAction action) {
- for (ConfigSetOperation op : values()) {
- if (op.action == action) return op;
- }
- throw new SolrException(ErrorCode.SERVER_ERROR, "No such action" + action);
- }
- }
-
@Override
public Name getPermissionName(AuthorizationContext ctx) {
String a = ctx.getParams().get(ConfigSetParams.ACTION);
diff --git a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
index a0de011d3ca..0fdbe40122d 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
@@ -18,6 +18,7 @@
package org.apache.solr.handler.api;
import org.apache.solr.api.ApiBag;
+import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.handler.admin.api.AddReplicaAPI;
import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
@@ -37,6 +38,11 @@ import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
import org.apache.solr.handler.admin.api.SetCollectionPropertyAPI;
import org.apache.solr.handler.admin.api.SplitShardAPI;
import org.apache.solr.handler.admin.api.SyncShardAPI;
+import org.apache.solr.handler.configsets.CreateConfigSetAPI;
+import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
+import org.apache.solr.handler.configsets.ListConfigSetsAPI;
+import org.apache.solr.handler.configsets.UploadConfigSetAPI;
+import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
/**
* Registers annotation-based V2 APIs with an {@link ApiBag}
@@ -73,4 +79,12 @@ public class ApiRegistrar {
// here for simplicity.
apiBag.registerObject(new DeleteReplicaAPI(collectionsHandler));
}
+
+ public static void registerConfigsetApis(ApiBag apiBag, CoreContainer container) {
+ apiBag.registerObject(new CreateConfigSetAPI(container));
+ apiBag.registerObject(new DeleteConfigSetAPI(container));
+ apiBag.registerObject(new ListConfigSetsAPI(container));
+ apiBag.registerObject(new UploadConfigSetAPI(container));
+ apiBag.registerObject(new UploadConfigSetFileAPI(container));
+ }
}
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
new file mode 100644
index 00000000000..c4eff442496
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/ConfigSetAPIBase.java
@@ -0,0 +1,209 @@
+/*
+ * 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.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.cloud.OverseerConfigSetMessageHandler.CONFIGSETS_ACTION_PREFIX;
+import static org.apache.solr.handler.admin.ConfigSetsHandler.CONFIG_SET_TIMEOUT;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.cloud.OverseerSolrResponseSerializer;
+import org.apache.solr.cloud.OverseerTaskQueue;
+import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.params.ConfigSetParams;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.ConfigSetService;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthenticationPlugin;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Parent class for all APIs that manipulate configsets
+ *
+ * <p>Contains utilities for tasks common in configset manipulation, including running configset
+ * "commands" and checking configset "trusted-ness".
+ */
+public class ConfigSetAPIBase {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ protected final CoreContainer coreContainer;
+ protected final Optional<DistributedCollectionConfigSetCommandRunner>
+ distributedCollectionConfigSetCommandRunner;
+
+ protected final ConfigSetService configSetService;
+
+ public ConfigSetAPIBase(CoreContainer coreContainer) {
+ this.coreContainer = coreContainer;
+ this.distributedCollectionConfigSetCommandRunner =
+ coreContainer.getDistributedCollectionCommandRunner();
+ this.configSetService = coreContainer.getConfigSetService();
+ }
+
+ protected void runConfigSetCommand(
+ SolrQueryResponse rsp,
+ ConfigSetParams.ConfigSetAction action,
+ Map<String, Object> messageToSend)
+ throws Exception {
+ if (log.isInfoEnabled()) {
+ log.info("Invoked ConfigSet Action :{} with params {} ", action.toLower(), messageToSend);
+ }
+
+ if (distributedCollectionConfigSetCommandRunner.isPresent()) {
+ distributedCollectionConfigSetCommandRunner
+ .get()
+ .runConfigSetCommand(rsp, action, messageToSend, CONFIG_SET_TIMEOUT);
+ } else {
+ sendToOverseer(rsp, action, messageToSend);
+ }
+ }
+
+ protected void ensureConfigSetUploadEnabled() {
+ if (!"true".equals(System.getProperty("configset.upload.enabled", "true"))) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Configset upload feature is disabled. To enable this, start Solr with '-Dconfigset.upload.enabled=true'.");
+ }
+ }
+
+ protected InputStream ensureNonEmptyInputStream(SolrQueryRequest req) throws IOException {
+ Iterator<ContentStream> contentStreamsIterator = req.getContentStreams().iterator();
+
+ if (!contentStreamsIterator.hasNext()) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "No stream found for the config data to be uploaded");
+ }
+
+ return contentStreamsIterator.next().getStream();
+ }
+
+ protected boolean isTrusted(Principal userPrincipal, AuthenticationPlugin authPlugin) {
+ if (authPlugin != null && userPrincipal != null) {
+ log.debug("Trusted configset request");
+ return true;
+ }
+ log.debug("Untrusted configset request");
+ return false;
+ }
+
+ protected boolean isCurrentlyTrusted(String configName) throws IOException {
+ Map<String, Object> contentMap = configSetService.getConfigMetadata(configName);
+ return (boolean) contentMap.getOrDefault("trusted", true);
+ }
+
+ protected void createBaseNode(
+ ConfigSetService configSetService,
+ boolean overwritesExisting,
+ boolean requestIsTrusted,
+ String configName)
+ throws IOException {
+ Map<String, Object> metadata = Collections.singletonMap("trusted", requestIsTrusted);
+
+ if (overwritesExisting) {
+ if (!requestIsTrusted) {
+ ensureOverwritingUntrustedConfigSet(configName);
+ }
+ // If the request is trusted and cleanup=true, then the configSet will be set to trusted after
+ // the overwriting has been done.
+ } else {
+ configSetService.setConfigMetadata(configName, metadata);
+ }
+ }
+
+ /*
+ * Fail if an untrusted request tries to update a trusted ConfigSet
+ */
+ private void ensureOverwritingUntrustedConfigSet(String configName) throws IOException {
+ boolean isCurrentlyTrusted = isCurrentlyTrusted(configName);
+ if (isCurrentlyTrusted) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Trying to make an untrusted ConfigSet update on a trusted configSet");
+ }
+ }
+
+ private void sendToOverseer(
+ SolrQueryResponse rsp, ConfigSetParams.ConfigSetAction action, Map<String, Object> result)
+ throws KeeperException, InterruptedException {
+ // We need to differentiate between collection and configsets actions since they currently
+ // use the same underlying queue.
+ result.put(QUEUE_OPERATION, CONFIGSETS_ACTION_PREFIX + action.toLower());
+ ZkNodeProps props = new ZkNodeProps(result);
+ handleResponse(action.toLower(), props, rsp, CONFIG_SET_TIMEOUT);
+ }
+
+ private void handleResponse(String operation, ZkNodeProps m, SolrQueryResponse rsp, long timeout)
+ throws KeeperException, InterruptedException {
+ long time = System.nanoTime();
+
+ OverseerTaskQueue.QueueEvent event =
+ coreContainer.getZkController().getOverseerConfigSetQueue().offer(Utils.toJSON(m), timeout);
+ if (event.getBytes() != null) {
+ SolrResponse response = OverseerSolrResponseSerializer.deserialize(event.getBytes());
+ rsp.getValues().addAll(response.getResponse());
+ SimpleOrderedMap<?> exp = (SimpleOrderedMap<?>) response.getResponse().get("exception");
+ if (exp != null) {
+ Integer code = (Integer) exp.get("rspCode");
+ rsp.setException(
+ new SolrException(
+ code != null && code != -1
+ ? SolrException.ErrorCode.getErrorCode(code)
+ : SolrException.ErrorCode.SERVER_ERROR,
+ (String) exp.get("msg")));
+ }
+ } else {
+ if (System.nanoTime() - time
+ >= TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS)) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ operation + " the configset time out:" + timeout / 1000 + "s");
+ } else if (event.getWatchedEvent() != null) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ operation
+ + " the configset error [Watcher fired on path: "
+ + event.getWatchedEvent().getPath()
+ + " state: "
+ + event.getWatchedEvent().getState()
+ + " type "
+ + event.getWatchedEvent().getType()
+ + "]");
+ } else {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR, operation + " the configset unknown case");
+ }
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java
new file mode 100644
index 00000000000..7f52a06a525
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/CreateConfigSetAPI.java
@@ -0,0 +1,88 @@
+/*
+ * 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.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 java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.collections4.MapUtils;
+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.cloud.ConfigSetCmds;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ConfigSetParams;
+import org.apache.solr.core.CoreContainer;
+
+/**
+ * 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 {
+
+ public CreateConfigSetAPI(CoreContainer coreContainer) {
+ super(coreContainer);
+ }
+
+ @Command(name = "create")
+ public void create(PayloadObj<CreateConfigPayload> obj) throws Exception {
+ final CreateConfigPayload createConfigPayload = obj.get();
+ if (configSetService.checkConfigExists(createConfigPayload.name)) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "ConfigSet already exists: " + createConfigPayload.name);
+ }
+
+ // is there a base config that already exists
+ if (!configSetService.checkConfigExists(createConfigPayload.baseConfigSet)) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Base ConfigSet does not exist: " + createConfigPayload.baseConfigSet);
+ }
+
+ if (!DISABLE_CREATE_AUTH_CHECKS
+ && !isTrusted(obj.getRequest().getUserPrincipal(), coreContainer.getAuthenticationPlugin())
+ && isCurrentlyTrusted(createConfigPayload.baseConfigSet)) {
+ throw new SolrException(
+ SolrException.ErrorCode.UNAUTHORIZED,
+ "Can't create a configset with an unauthenticated request from a trusted "
+ + ConfigSetCmds.BASE_CONFIGSET);
+ }
+
+ final Map<String, Object> configsetCommandMsg = new HashMap<>();
+ configsetCommandMsg.put(NAME, createConfigPayload.name);
+ configsetCommandMsg.put(ConfigSetCmds.BASE_CONFIGSET, createConfigPayload.baseConfigSet);
+ if (!MapUtils.isEmpty(createConfigPayload.properties)) {
+ for (Map.Entry<String, Object> e : createConfigPayload.properties.entrySet()) {
+ configsetCommandMsg.put(
+ ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX + e.getKey(), e.getValue());
+ }
+ }
+
+ runConfigSetCommand(
+ obj.getResponse(), ConfigSetParams.ConfigSetAction.CREATE, configsetCommandMsg);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java
new file mode 100644
index 00000000000..8c93871e6af
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/DeleteConfigSetAPI.java
@@ -0,0 +1,62 @@
+/*
+ * 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.DELETE;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.StringUtils;
+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;
+
+/**
+ * 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";
+
+ public DeleteConfigSetAPI(CoreContainer coreContainer) {
+ super(coreContainer);
+ }
+
+ @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");
+ if (StringUtils.isEmpty(configSetName)) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST, "No configset name provided to delete");
+ }
+ final Map<String, Object> configsetCommandMsg = Maps.newHashMap();
+ configsetCommandMsg.put(NAME, configSetName);
+
+ runConfigSetCommand(rsp, ConfigSetParams.ConfigSetAction.DELETE, configsetCommandMsg);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
new file mode 100644
index 00000000000..ae872de68af
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
@@ -0,0 +1,49 @@
+/*
+ * 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.GET;
+import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+
+import java.util.List;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.cloud.OverseerSolrResponse;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 API for adding or updating a single file within a configset.
+ *
+ * <p>This API (GET /v2/cluster/configs) is analogous to the v1 /admin/configs?action=LIST command.
+ */
+public class ListConfigSetsAPI extends ConfigSetAPIBase {
+ public ListConfigSetsAPI(CoreContainer coreContainer) {
+ super(coreContainer);
+ }
+
+ @EndPoint(method = GET, path = "/cluster/configs", permission = CONFIG_READ_PERM)
+ public void listConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ final NamedList<Object> results = new NamedList<>();
+ List<String> configSetsList = configSetService.listConfigs();
+ results.add("configSets", configSetsList);
+ SolrResponse response = new OverseerSolrResponse(results);
+ rsp.getValues().addAll(response.getResponse());
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java
new file mode 100644
index 00000000000..4cb08542386
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetAPI.java
@@ -0,0 +1,130 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.commons.io.IOUtils;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ConfigSetParams;
+import org.apache.solr.core.ConfigSetService;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+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";
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public UploadConfigSetAPI(CoreContainer coreContainer) {
+ super(coreContainer);
+ }
+
+ @EndPoint(method = PUT, path = "/cluster/configs/{name}", permission = CONFIG_EDIT_PERM)
+ public void uploadConfigSet(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
+ boolean allowOverwrite = req.getParams().getBool(ConfigSetParams.OVERWRITE, true);
+ boolean cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
+ final InputStream inputStream = ensureNonEmptyInputStream(req);
+
+ if (overwritesExisting && !allowOverwrite) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "The configuration " + configSetName + " already exists");
+ }
+
+ List<String> filesToDelete;
+ if (overwritesExisting && cleanup) {
+ filesToDelete = configSetService.getAllConfigFiles(configSetName);
+ } else {
+ filesToDelete = Collections.emptyList();
+ }
+
+ // Create a node for the configuration in zookeeper
+ // For creating the baseZnode, the cleanup parameter is only allowed to be true when
+ // singleFilePath is not passed.
+ createBaseNode(configSetService, overwritesExisting, requestIsTrusted, configSetName);
+
+ try (ZipInputStream zis = new ZipInputStream(inputStream, StandardCharsets.UTF_8)) {
+ boolean hasEntry = false;
+ ZipEntry zipEntry;
+ while ((zipEntry = zis.getNextEntry()) != null) {
+ hasEntry = true;
+ String filePath = zipEntry.getName();
+ filesToDelete.remove(filePath);
+ if (!zipEntry.isDirectory()) {
+ configSetService.uploadFileToConfig(
+ configSetName, filePath, IOUtils.toByteArray(zis), true);
+ }
+ }
+ if (!hasEntry) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Either empty zipped data, or non-zipped data was uploaded. In order to upload a configSet, you must zip a non-empty directory to upload.");
+ }
+ }
+ deleteUnusedFiles(configSetService, configSetName, filesToDelete);
+
+ // If the request is doing a full trusted overwrite of an untrusted configSet (overwrite=true,
+ // cleanup=true), then trust the configSet.
+ if (cleanup && requestIsTrusted && overwritesExisting && !isCurrentlyTrusted(configSetName)) {
+ Map<String, Object> metadata = Collections.singletonMap("trusted", true);
+ configSetService.setConfigMetadata(configSetName, metadata);
+ }
+ }
+
+ private void deleteUnusedFiles(
+ ConfigSetService configSetService, String configName, List<String> filesToDelete)
+ throws IOException {
+ if (!filesToDelete.isEmpty()) {
+ if (log.isInfoEnabled()) {
+ log.info("Cleaning up {} unused files", filesToDelete.size());
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Cleaning up unused files: {}", filesToDelete);
+ }
+ configSetService.deleteFilesFromConfig(configName, filesToDelete);
+ }
+ }
+}
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
new file mode 100644
index 00000000000..d44f3c5ff31
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSetFileAPI.java
@@ -0,0 +1,88 @@
+/*
+ * 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.commons.io.IOUtils;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.common.SolrException;
+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;
+
+/**
+ * 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&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);
+ }
+ if (fixedSingleFilePath.isEmpty()) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "The file path provided for upload, '" + singleFilePath + "', is not valid.");
+ } 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, IOUtils.toByteArray(inputStream), allowOverwrite);
+ }
+ }
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java b/solr/core/src/java/org/apache/solr/handler/configsets/package-info.java
similarity index 62%
copy from solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
copy to solr/core/src/java/org/apache/solr/handler/configsets/package-info.java
index 209b12971bd..bba0f21ed49 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/package-info.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,
@@ -14,16 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.solr.client.solrj.request.beans;
-import java.util.Map;
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
-
-public class CreateConfigPayload implements ReflectMapWriter {
- @JsonProperty(required = true)
- public String name;
-
- @JsonProperty public String baseConfigSet;
- @JsonProperty public Map<String, Object> properties;
-}
+/** V2 API classes for performing CRUD operations on configsets. */
+package org.apache.solr.handler.configsets;
diff --git a/solr/core/src/java/org/apache/solr/request/DelegatingSolrQueryRequest.java b/solr/core/src/java/org/apache/solr/request/DelegatingSolrQueryRequest.java
new file mode 100644
index 00000000000..2c9c20d4f06
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/request/DelegatingSolrQueryRequest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.request;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.cloud.CloudDescriptor;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.servlet.HttpSolrCall;
+import org.apache.solr.util.RTimerTree;
+
+/**
+ * A {@link SolrQueryRequest} implementation that defers to a delegate in all cases.
+ *
+ * <p>Used primarily in cases where developers want to customize one or more SolrQueryRequest
+ * methods while deferring the remainder to an existing instances.
+ */
+public class DelegatingSolrQueryRequest implements SolrQueryRequest {
+ private final SolrQueryRequest delegate;
+
+ public DelegatingSolrQueryRequest(SolrQueryRequest delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public SolrParams getParams() {
+ return delegate.getParams();
+ }
+
+ @Override
+ public void setParams(SolrParams params) {
+ delegate.setParams(params);
+ }
+
+ @Override
+ public Iterable<ContentStream> getContentStreams() {
+ return delegate.getContentStreams();
+ }
+
+ @Override
+ public SolrParams getOriginalParams() {
+ return delegate.getOriginalParams();
+ }
+
+ @Override
+ public Map<Object, Object> getContext() {
+ return delegate.getContext();
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ @Override
+ public long getStartTime() {
+ return delegate.getStartTime();
+ }
+
+ @Override
+ public RTimerTree getRequestTimer() {
+ return delegate.getRequestTimer();
+ }
+
+ @Override
+ public SolrIndexSearcher getSearcher() {
+ return delegate.getSearcher();
+ }
+
+ @Override
+ public SolrCore getCore() {
+ return delegate.getCore();
+ }
+
+ @Override
+ public IndexSchema getSchema() {
+ return delegate.getSchema();
+ }
+
+ @Override
+ public void updateSchemaToLatest() {
+ delegate.updateSchemaToLatest();
+ }
+
+ @Override
+ public String getParamString() {
+ return delegate.getParamString();
+ }
+
+ @Override
+ public Map<String, Object> getJSON() {
+ return delegate.getJSON();
+ }
+
+ @Override
+ public void setJSON(Map<String, Object> json) {
+ delegate.setJSON(json);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return delegate.getUserPrincipal();
+ }
+
+ @Override
+ public String getPath() {
+ return delegate.getPath();
+ }
+
+ @Override
+ public Map<String, String> getPathTemplateValues() {
+ return delegate.getPathTemplateValues();
+ }
+
+ @Override
+ public List<CommandOperation> getCommands(boolean validateInput) {
+ return delegate.getCommands(validateInput);
+ }
+
+ @Override
+ public String getHttpMethod() {
+ return delegate.getHttpMethod();
+ }
+
+ @Override
+ public HttpSolrCall getHttpSolrCall() {
+ return delegate.getHttpSolrCall();
+ }
+
+ @Override
+ public Tracer getTracer() {
+ return delegate.getTracer();
+ }
+
+ @Override
+ public Span getSpan() {
+ return delegate.getSpan();
+ }
+
+ @Override
+ public CoreContainer getCoreContainer() {
+ return delegate.getCoreContainer();
+ }
+
+ @Override
+ public CloudDescriptor getCloudDescriptor() {
+ return delegate.getCloudDescriptor();
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
index 96364f6c9fa..b2e2b976e8b 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -33,9 +33,7 @@ import java.util.List;
import java.util.Map;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
-import org.apache.solr.cloud.ConfigSetCmds;
import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.params.ConfigSetParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.ContentStreamBase;
@@ -71,7 +69,6 @@ public class V2ClusterAPIMappingTest {
final ClusterAPI clusterAPI = new ClusterAPI(mockCollectionsHandler, mockConfigSetHandler);
apiBag.registerObject(clusterAPI);
apiBag.registerObject(clusterAPI.commands);
- apiBag.registerObject(clusterAPI.configSetCommands);
}
@Test
@@ -113,70 +110,6 @@ public class V2ClusterAPIMappingTest {
assertEquals("someId", v1Params.get(REQUESTID));
}
- // TODO This should probably really get its own class.
- @Test
- public void testDeleteConfigetAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedConfigsetV1Params("/cluster/configs/someConfigset", "DELETE", null);
-
- assertEquals(ConfigSetParams.ConfigSetAction.DELETE.toString(), v1Params.get(ACTION));
- assertEquals("someConfigset", v1Params.get(NAME));
- }
-
- @Test
- public void testListConfigsetsAllParams() throws Exception {
- final SolrParams v1Params = captureConvertedConfigsetV1Params("/cluster/configs", "GET", null);
-
- assertEquals(ConfigSetParams.ConfigSetAction.LIST.toString(), v1Params.get(ACTION));
- }
-
- @Test
- public void testCreateConfigsetAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedConfigsetV1Params(
- "/cluster/configs",
- "POST",
- "{'create': {"
- + "'name': 'new_configset_name', "
- + "'baseConfigSet':'some_existing_configset', "
- + "'properties': {'prop1': 'val1', 'prop2': 'val2'}}}");
-
- assertEquals(ConfigSetParams.ConfigSetAction.CREATE.toString(), v1Params.get(ACTION));
- assertEquals("new_configset_name", v1Params.get(NAME));
- assertEquals("some_existing_configset", v1Params.get(ConfigSetCmds.BASE_CONFIGSET));
- assertEquals("val1", v1Params.get("configSetProp.prop1"));
- assertEquals("val2", v1Params.get("configSetProp.prop2"));
- }
-
- @Test
- public void testUploadConfigsetAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedConfigsetV1Params("/cluster/configs/someConfigSetName", "PUT", null);
-
- assertEquals(ConfigSetParams.ConfigSetAction.UPLOAD.toString(), v1Params.get(ACTION));
- assertEquals("someConfigSetName", v1Params.get(NAME));
- // Why does ClusterAPI set the values below as defaults? They disagree with the v1 defaults in
- // ConfigSetsHandler.handleConfigUploadRequest
- assertEquals(true, v1Params.getPrimitiveBool(ConfigSetParams.OVERWRITE));
- assertEquals(false, v1Params.getPrimitiveBool(ConfigSetParams.CLEANUP));
- }
-
- @Test
- public void testAddFileToConfigsetAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedConfigsetV1Params(
- "/cluster/configs/someConfigSetName/some/file/path", "PUT", null);
-
- assertEquals(ConfigSetParams.ConfigSetAction.UPLOAD.toString(), v1Params.get(ACTION));
- assertEquals("someConfigSetName", v1Params.get(NAME));
- assertEquals(
- "/some/file/path",
- v1Params.get(
- ConfigSetParams.FILE_PATH)); // Note the leading '/' that makes the path appear absolute
- assertEquals(true, v1Params.getPrimitiveBool(ConfigSetParams.OVERWRITE));
- assertEquals(false, v1Params.getPrimitiveBool(ConfigSetParams.CLEANUP));
- }
-
@Test
public void testAddRoleAllParams() throws Exception {
final SolrParams v1Params =
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
deleted file mode 100644
index 5c21c3a295e..00000000000
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
+++ /dev/null
@@ -1,58 +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.admin;
-
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
-import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
-import static org.apache.solr.handler.admin.TestCollectionAPIs.compareOutput;
-
-import java.util.Map;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.api.ApiBag;
-import org.apache.solr.common.cloud.ZkNodeProps;
-import org.apache.solr.handler.ClusterAPI;
-import org.apache.solr.response.SolrQueryResponse;
-
-public class TestConfigsApi extends SolrTestCaseJ4 {
-
- public void testCommands() throws Exception {
-
- try (ConfigSetsHandler handler =
- new ConfigSetsHandler(null) {
-
- @Override
- protected void checkErrors() {}
-
- @Override
- protected void sendToOverseer(
- SolrQueryResponse rsp, ConfigSetOperation operation, Map<String, Object> result) {
- result.put(QUEUE_OPERATION, operation.action.toLower());
- rsp.add(ZkNodeProps.class.getName(), new ZkNodeProps(result));
- }
- }) {
- ApiBag apiBag = new ApiBag(false);
-
- ClusterAPI o = new ClusterAPI(null, handler);
- apiBag.registerObject(o);
- apiBag.registerObject(o.configSetCommands);
- // for (Api api : handler.getApis()) apiBag.register(api, emptyMap());
- compareOutput(
- apiBag, "/cluster/configs/sample", DELETE, null, "{name :sample, operation:delete}");
- }
- }
-}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
index 209b12971bd..5f7f2e6687d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateConfigPayload.java
@@ -21,9 +21,12 @@ 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?
+
@JsonProperty(required = true)
public String name;
- @JsonProperty public String baseConfigSet;
+ @JsonProperty public String baseConfigSet = DEFAULT_CONFIGSET;
@JsonProperty public Map<String, Object> properties;
}