You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2020/10/13 21:55:54 UTC
[lucene-solr] branch branch_8x updated: SOLR-14907: Support single
file upload/overwrite in configSet API (#1977)
This is an automated email from the ASF dual-hosted git repository.
houston pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/branch_8x by this push:
new 0834589 SOLR-14907: Support single file upload/overwrite in configSet API (#1977)
0834589 is described below
commit 0834589399a682555b1f7b7294bdac1b05b72815
Author: Houston Putman <ho...@apache.org>
AuthorDate: Tue Oct 13 16:51:21 2020 -0400
SOLR-14907: Support single file upload/overwrite in configSet API (#1977)
---
solr/CHANGES.txt | 2 +
.../solr/handler/admin/ConfigSetsHandler.java | 62 +++++--
.../org/apache/solr/cloud/TestConfigSetsAPI.java | 190 ++++++++++++++++++++-
solr/solr-ref-guide/src/configsets-api.adoc | 11 +-
.../apache/solr/common/params/ConfigSetParams.java | 1 +
5 files changed, 252 insertions(+), 14 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 3c66723..e8d3611 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -38,6 +38,8 @@ New Features
* SOLR-11167: Avoid $SOLR_STOP_WAIT use during 'bin/solr start' if $SOLR_START_WAIT is supplied.
(Omar Abdelnabi, Christine Poerschke)
+* SOLR-14907: Support single file upload/overwrite in configSet API. (Houston Putman)
+
Improvements
---------------------
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 6d94fba..736aeb7 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
@@ -170,22 +170,52 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
boolean overwritesExisting = zkClient.exists(configPathInZk, true);
- if (overwritesExisting && !req.getParams().getBool(ConfigSetParams.OVERWRITE, false)) {
- throw new SolrException(ErrorCode.BAD_REQUEST,
- "The configuration " + configSetName + " already exists in zookeeper");
- }
+ 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");
+ "No stream found for the config data to be uploaded");
}
InputStream inputStream = contentStreamsIterator.next().getStream();
- // Create a node for the configuration in zookeeper
- boolean cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
+ // 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 filePath 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 filePath is used.");
+ } else {
+ try {
+ // 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.
+ createBaseZnode(zkClient, overwritesExisting, requestIsTrusted, configPathInZk);
+ String filePathInZk = configPathInZk + "/" + fixedSingleFilePath;
+ zkClient.makePath(filePathInZk, IOUtils.toByteArray(inputStream), CreateMode.PERSISTENT, null, !allowOverwrite, true);
+ } catch(KeeperException.NodeExistsException nodeExistsException) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,
+ "The path " + singleFilePath + " for configSet " + configSetName + " already exists. In order to overwrite, provide overwrite=true.");
+ }
+ }
+ return;
+ }
+
+ if (overwritesExisting && !allowOverwrite) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,
+ "The configuration " + configSetName + " already exists in zookeeper");
+ }
Set<String> filesToDelete;
if (overwritesExisting && cleanup) {
@@ -193,7 +223,10 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
} else {
filesToDelete = Collections.emptySet();
}
- createBaseZnode(zkClient, overwritesExisting, isTrusted(req, coreContainer.getAuthenticationPlugin()), cleanup, configPathInZk);
+
+ // 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.
+ createBaseZnode(zkClient, overwritesExisting, requestIsTrusted, configPathInZk);
ZipInputStream zis = new ZipInputStream(inputStream, StandardCharsets.UTF_8);
ZipEntry zipEntry = null;
@@ -213,17 +246,22 @@ public class ConfigSetsHandler extends RequestHandlerBase implements PermissionN
}
zis.close();
deleteUnusedFiles(zkClient, 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(zkClient, configPathInZk)) {
+ byte[] baseZnodeData = ("{\"trusted\": true}").getBytes(StandardCharsets.UTF_8);
+ zkClient.setData(configPathInZk, baseZnodeData, true);
+ }
}
- private void createBaseZnode(SolrZkClient zkClient, boolean overwritesExisting, boolean requestIsTrusted, boolean cleanup, String configPathInZk) throws KeeperException, InterruptedException {
+ private void createBaseZnode(SolrZkClient zkClient, boolean overwritesExisting, boolean requestIsTrusted, String configPathInZk) throws KeeperException, InterruptedException {
byte[] baseZnodeData = ("{\"trusted\": " + Boolean.toString(requestIsTrusted) + "}").getBytes(StandardCharsets.UTF_8);
if (overwritesExisting) {
- if (cleanup && requestIsTrusted) {
- zkClient.setData(configPathInZk, baseZnodeData, true);
- } else if (!requestIsTrusted) {
+ if (!requestIsTrusted) {
ensureOverwritingUntrustedConfigSet(zkClient, configPathInZk);
}
+ // If the request is trusted and cleanup=true, then the configSet will be set to trusted after the overwriting has been done.
} else {
zkClient.makePath(configPathInZk, baseZnodeData, true);
}
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 6b521d1..c84301f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
@@ -471,6 +471,14 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
solrconfigZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml");
+ // Was untrusted, overwrite with trusted with cleanup but fail on unzipping.
+ // Should not set trusted=true
+ assertEquals(500, uploadBadConfigSet(configsetName, configsetSuffix, "solr", zkClient, true, true));
+ assertEquals("Expecting version bump",
+ solrconfigZkVersion, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
+ solrconfigZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml");
+
// Was untrusted, overwrite with trusted with cleanup
assertEquals(0, uploadConfigSet(configsetName, configsetSuffix, "solr", zkClient, true, true));
assertTrue("Expecting version bump",
@@ -511,6 +519,158 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
}
+ @Test
+ public void testSingleFileOverwrite() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffix = "testSinglePathOverwrite-1";
+ uploadConfigSetWithAssertions(configsetName, configsetSuffix, null);
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ int solrconfigZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml");
+ ignoreException("The configuration regulartestOverwrite-1 already exists in zookeeper");
+ assertEquals("Can't overwrite an existing configset unless the overwrite parameter is set",
+ 400, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "solrconfig.xml", false, false));
+ unIgnoreException("The configuration regulartestOverwrite-1 already exists in zookeeper");
+ assertEquals("Expecting version to remain equal",
+ solrconfigZkVersion, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml"));
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "solrconfig.xml", true, false));
+ assertTrue("Expecting version bump",
+ solrconfigZkVersion < getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "solrconfig.xml"));
+ }
+ }
+
+ @Test
+ public void testNewSingleFile() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffix = "testSinglePathNew-1";
+ uploadConfigSetWithAssertions(configsetName, configsetSuffix, null);
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", false, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml"));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+ }
+ }
+
+ @Test
+ public void testSingleWithCleanup() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffix = "testSinglePathCleanup-1";
+ uploadConfigSetWithAssertions(configsetName, configsetSuffix, null);
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ ignoreException("ConfigSet uploads do not allow cleanup=true when filePath is used.");
+ assertEquals(400, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", false, true));
+ assertFalse("New file should not exist, since the trust check did not succeed.", zkClient.exists("/configs/"+configsetName+configsetSuffix+"/test/upload/path/solrconfig.xml", true));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+ unIgnoreException("ConfigSet uploads do not allow cleanup=true when filePath is used.");
+ }
+ }
+
+ @Test
+ public void testSingleFileTrusted() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffix = "testSinglePathTrusted-1";
+ uploadConfigSetWithAssertions(configsetName, configsetSuffix, "solr");
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, "solr", zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", true, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml"));
+ assertTrue(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+
+ ignoreException("Trying to make an unstrusted ConfigSet update on a trusted configSet");
+ assertEquals("Can't upload a trusted configset with an untrusted request",
+ 400, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/different/path/solrconfig.xml", true, false));
+ assertFalse("New file should not exist, since the trust check did not succeed.", zkClient.exists("/configs/"+configsetName+configsetSuffix+"/test/different/path/solrconfig.xml", true));
+ assertTrue(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+ unIgnoreException("Trying to make an unstrusted ConfigSet update on a trusted configSet");
+
+ ignoreException("Trying to make an unstrusted ConfigSet update on a trusted configSet");
+ int extraFileZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml");
+ assertEquals("Can't upload a trusted configset with an untrusted request",
+ 400, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", true, false));
+ assertEquals("Expecting version to remain equal",
+ extraFileZkVersion, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml"));
+ assertTrue(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+ unIgnoreException("Trying to make an unstrusted ConfigSet update on a trusted configSet");
+ }
+ }
+
+ @Test
+ public void testSingleFileUntrusted() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffix = "testSinglePathUntrusted-1";
+ uploadConfigSetWithAssertions(configsetName, configsetSuffix, null);
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ // New file with trusted request
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, "solr", zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", false, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+
+ // New file with untrusted request
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/different/path/solrconfig.xml", false, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/different/path/solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+
+ // Overwrite with trusted request
+ int extraFileZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/different/path/solrconfig.xml");
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, "solr", zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/different/path/solrconfig.xml", true, false));
+ assertTrue("Expecting version bump",
+ extraFileZkVersion < getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/different/path/solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+
+ // Overwrite with untrusted request
+ extraFileZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml");
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffix, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/upload/path/solrconfig.xml", true, false));
+ assertTrue("Expecting version bump",
+ extraFileZkVersion < getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/upload/path/solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+
+ // Make sure that cleanup flag does not result in configSet being trusted.
+ ignoreException("ConfigSet uploads do not allow cleanup=true when filePath is used.");
+ extraFileZkVersion = getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/different/path/solrconfig.xml");
+ assertEquals(400, uploadSingleConfigSetFile(configsetName, configsetSuffix, "solr", zkClient, "solr/configsets/upload/regular/solrconfig.xml", "/test/different/path/solrconfig.xml", true, true));
+ assertEquals("Expecting version to stay the same",
+ extraFileZkVersion, getConfigZNodeVersion(zkClient, configsetName, configsetSuffix, "test/different/path/solrconfig.xml"));
+ assertFalse("The cleanup=true flag allowed for trust overwriting in a filePath upload.", isTrusted(zkClient, configsetName, configsetSuffix));
+ assertConfigsetFiles(configsetName, configsetSuffix, zkClient);
+ unIgnoreException("ConfigSet uploads do not allow cleanup=true when filePath is used.");
+ }
+ }
+
+ @Test
+ public void testSingleFileNewConfig() throws Exception {
+ String configsetName = "regular";
+ String configsetSuffixTrusted = "testSinglePathNewConfig-1";
+ String configsetSuffixUntrusted = "testSinglePathNewConfig-2";
+ try (SolrZkClient zkClient = new SolrZkClient(cluster.getZkServer().getZkAddress(),
+ AbstractZkTestCase.TIMEOUT, 45000, null)) {
+ // New file with trusted request
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffixTrusted, "solr", zkClient, "solr/configsets/upload/regular/solrconfig.xml", "solrconfig.xml", false, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffixTrusted, "solrconfig.xml"));
+ assertTrue(isTrusted(zkClient, configsetName, configsetSuffixTrusted));
+ List<String> children = zkClient.getChildren(String.format(Locale.ROOT,"/configs/%s%s", configsetName, configsetSuffixTrusted), null, true);
+ assertEquals("The configSet should only have one file uploaded.", 1, children.size());
+ assertEquals("Incorrect file uploaded.", "solrconfig.xml", children.get(0));
+
+ // New file with trusted request
+ assertEquals(0, uploadSingleConfigSetFile(configsetName, configsetSuffixUntrusted, null, zkClient, "solr/configsets/upload/regular/solrconfig.xml", "solrconfig.xml", false, false));
+ assertEquals("Expecting first version of new file", 0, getConfigZNodeVersion(zkClient, configsetName, configsetSuffixUntrusted, "solrconfig.xml"));
+ assertFalse(isTrusted(zkClient, configsetName, configsetSuffixUntrusted));
+ children = zkClient.getChildren(String.format(Locale.ROOT,"/configs/%s%s", configsetName, configsetSuffixUntrusted), null, true);
+ assertEquals("The configSet should only have one file uploaded.", 1, children.size());
+ assertEquals("Incorrect file uploaded.", "solrconfig.xml", children.get(0));
+ }
+ }
+
private boolean isTrusted(SolrZkClient zkClient, String configsetName, String configsetSuffix) throws KeeperException, InterruptedException {
String configSetZkPath = String.format(Locale.ROOT,"/configs/%s%s", configsetName, configsetSuffix);
byte[] configSetNodeContent = zkClient.getData(configSetZkPath, null, null, true);;
@@ -646,7 +806,35 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
long statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status"));
return statusCode;
}
-
+
+ private long uploadBadConfigSet(String configSetName, String suffix, String username,
+ SolrZkClient zkClient, boolean overwrite, boolean cleanup) throws IOException {
+ // Read single file from sample configs. This should fail the unzipping
+ ByteBuffer sampleBadZippedFile = TestDynamicLoading.getFileContent(TestDynamicLoading.getFile("solr/configsets/upload/regular/solrconfig.xml").getAbsolutePath(), false);
+
+ @SuppressWarnings({"rawtypes"})
+ Map map = postDataAndGetResponse(cluster.getSolrClient(),
+ cluster.getJettySolrRunners().get(0).getBaseUrl().toString() + "/admin/configs?action=UPLOAD&name="+configSetName+suffix + (overwrite? "&overwrite=true" : "") + (cleanup? "&cleanup=true" : ""),
+ sampleBadZippedFile, username);
+ assertNotNull(map);
+ long statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status"));
+ return statusCode;
+ }
+
+ private long uploadSingleConfigSetFile(String configSetName, String suffix, String username,
+ SolrZkClient zkClient, String filePath, String uploadPath, boolean overwrite, boolean cleanup) throws IOException {
+ // Read single file from sample configs
+ ByteBuffer sampleConfigFile = TestDynamicLoading.getFileContent(TestDynamicLoading.getFile(filePath).getAbsolutePath(), false);
+
+ @SuppressWarnings({"rawtypes"})
+ Map map = postDataAndGetResponse(cluster.getSolrClient(),
+ cluster.getJettySolrRunners().get(0).getBaseUrl().toString() + "/admin/configs?action=UPLOAD&name="+configSetName+suffix+"&filePath="+uploadPath + (overwrite? "&overwrite=true" : "") + (cleanup? "&cleanup=true" : ""),
+ sampleConfigFile, username);
+ assertNotNull(map);
+ long statusCode = (long) getObjectByPath(map, false, Arrays.asList("responseHeader", "status"));
+ return statusCode;
+ }
+
/**
* Create a zip file (in the temp directory) containing all the files within the specified directory
* and return the path for the zip file.
diff --git a/solr/solr-ref-guide/src/configsets-api.adoc b/solr/solr-ref-guide/src/configsets-api.adoc
index dfda9f8..06a9337 100644
--- a/solr/solr-ref-guide/src/configsets-api.adoc
+++ b/solr/solr-ref-guide/src/configsets-api.adoc
@@ -81,6 +81,7 @@ The output will look like:
== Upload a Configset
Upload a configset, which is sent as a zipped file.
+A single, non-zipped file can also be uploaded with the `filePath` parameter.
This functionality is enabled by default, but can be disabled via a runtime parameter `-Dconfigset.upload.enabled=false`. Disabling this feature is advisable if you want to expose Solr installation to untrusted users (even though you should never do that!).
@@ -99,10 +100,18 @@ The `upload` command takes the following parameters:
The configset to be created when the upload is complete. This parameter is required.
`overwrite`::
-If set to `true`, Solr will overwrite an existing configset with the same name (if false, the request will fail). Default is `false`.
+If set to `true`, Solr will overwrite an existing configset with the same name (if false, the request will fail).
+If `filePath` is provided, then this option specifies whether the specified file within the configSet should be overwritten if it already exists.
+Default is `false`.
`cleanup`::
When overwriting an existing configset (`overwrite=true`), this parameter tells Solr to delete the files in ZooKeeper that existed in the old configset but not in the one being uploaded. Default is `false`.
+This parameter cannot be set to true when `filePath` is used.
+
+filePath::
+This parameter allows the uploading of a single, non-zipped file to the given path under the configSet in ZooKeeper.
+This functionality respects the `overwrite` parameter, so a request will fail if the given file path already exists in the configSet and overwrite is set to `false`.
+The `cleanup` parameter cannot be set to true when `filePath` is used.
The body of the request should be a zip file that contains the configset. The zip file must be created from within the `conf` directory (i.e., `solrconfig.xml` must be the top level entry in the zip file).
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/ConfigSetParams.java b/solr/solrj/src/java/org/apache/solr/common/params/ConfigSetParams.java
index 0443ea7..d4cd122 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/ConfigSetParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/ConfigSetParams.java
@@ -26,6 +26,7 @@ public interface ConfigSetParams
public final static String ACTION = "action";
public final static String OVERWRITE = "overwrite";
public final static String CLEANUP = "cleanup";
+ public final static String FILE_PATH = "filePath";
public enum ConfigSetAction {
CREATE,