Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
title: ConfigSet upload APIs can read a configset, or a single file, from a backup repository such as S3 or GCS.
type: added
authors:
- name: Houston Putman
links:
- name: SOLR-18618
url: https://issues.apache.org/jira/browse/SOLR-18618
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.apache.solr.client.api.util.Constants.RAW_OUTPUT_PROPERTY;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
Expand Down Expand Up @@ -119,18 +120,41 @@ StreamingOutput getConfigSetFile(
}

/**
* V2 API definition for uploading an entire configset as a ZIP archive.
* V2 API definition for uploading an entire configset.
*
* <p>The configset is read either from the ZIP request body, or from a directory in a backup
* repository such as S3 or GCS, identified by {@code repository} and {@code location}. The two
* sources are mutually exclusive.
*
* <p>Equivalent to the existing v1 API /admin/configs?action=UPLOAD
*/
@Path("/configsets/{configSetName}")
interface Upload {
@PUT
@Operation(summary = "Upload a configset as a ZIP archive.", tags = "configsets")
@Operation(
summary = "Upload a configset as a ZIP archive or from a backup repository.",
description =
"Provide exactly one source: either a ZIP archive as the request body, or"
+ " 'repository' and 'location' (both required if either is given) naming a"
+ " directory in a backup repository, with an empty request body. Providing both is"
+ " an error.",
tags = "configsets")
SolrJerseyResponse uploadConfigSet(
@PathParam("configSetName") String configSetName,
@QueryParam("overwrite") Boolean overwrite,
@QueryParam("cleanup") Boolean cleanup,
@Parameter(
description =
"Backup repository to read the configset from. Required if 'location' is"
+ " given; mutually exclusive with a request body.")
@QueryParam("repository")
String repository,
@Parameter(
description =
"Directory in the backup repository holding the configset files. Required if"
+ " 'repository' is given; mutually exclusive with a request body.")
@QueryParam("location")
String location,
@RequestBody(
required = true,
extensions = {
Expand All @@ -147,16 +171,36 @@ SolrJerseyResponse uploadConfigSet(
* V2 API definition for putting a single file to an existing configset.
*
* <p>This endpoint allows updating individual configuration files without re-uploading the entire
* configset. The file path is specified as part of the URL path.
* configset. The file path is specified as part of the URL path. The file contents are either the
* request body, or a file in a backup repository identified by {@code repository} and {@code
* location}.
*/
@Path("/configsets/{configSetName}")
interface PutFile {
@PUT
@Path("/files/{filePath:.+}")
@Operation(summary = "Upload a single file to a configset.", tags = "configsets")
@Operation(
summary = "Upload a single file to a configset.",
description =
"Provide exactly one source: either the file contents as the request body, or"
+ " 'repository' and 'location' (both required if either is given) naming a file in"
+ " a backup repository, with an empty request body. Providing both is an error.",
tags = "configsets")
SolrJerseyResponse uploadConfigSetFile(
@PathParam("configSetName") String configSetName,
@PathParam("filePath") String filePath,
@Parameter(
description =
"Backup repository to read the file from. Required if 'location' is given;"
+ " mutually exclusive with a request body.")
@QueryParam("repository")
String repository,
@Parameter(
description =
"Path of the file in the backup repository. Required if 'repository' is given;"
+ " mutually exclusive with a request body.")
@QueryParam("location")
String location,
@RequestBody(
required = true,
extensions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.ConfigSetParams;
import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.RequestHandlerBase;
Expand Down Expand Up @@ -96,13 +97,22 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
final var configSetName = req.getParams().required().get(NAME);
final var overwrite = req.getParams().getBool(ConfigSetParams.OVERWRITE, false);
final var cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
final var configSetData = ConfigSetAPIBase.ensureNonEmptyInputStream(req);
SolrJerseyResponse uploadResponse;
if (req.getParams()
.get(ConfigSetParams.FILE_PATH, "")
.isEmpty()) { // Uploading a whole configset
final var repository = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
final var location = req.getParams().get(CoreAdminParams.BACKUP_LOCATION);
if ((repository == null) != (location == null)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[0] This validation should probably live in the v2 API implementation class(es). Or if you have added it there elsewhere in this diff, then it probably isn't needed here.

throw new SolrException(
ErrorCode.BAD_REQUEST,
"The 'repository' and 'location' parameters must be provided together.");
}
final var fromRepository = repository != null;
final var filePath = req.getParams().get(ConfigSetParams.FILE_PATH, "");
final var configSetData =
fromRepository ? null : ConfigSetAPIBase.ensureNonEmptyInputStream(req);
if (filePath.isEmpty()) { // Uploading a whole configset
uploadResponse =
uploadApi.uploadConfigSet(configSetName, overwrite, cleanup, configSetData);
uploadApi.uploadConfigSet(
configSetName, overwrite, cleanup, repository, location, configSetData);
} else { // Uploading a single file
// Single file uploads do not support cleanup or overwrite parameters
if (cleanup) {
Expand All @@ -111,8 +121,9 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
"ConfigSet uploads do not allow cleanup=true when filePath is used.");
}
// Note: overwrite parameter is ignored for single file uploads (always overwrites)
final var filePath = req.getParams().get(ConfigSetParams.FILE_PATH);
uploadResponse = uploadApi.uploadConfigSetFile(configSetName, filePath, configSetData);
uploadResponse =
uploadApi.uploadConfigSetFile(
configSetName, filePath, repository, location, configSetData);
}
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, uploadResponse);
break;
Expand Down
Loading
Loading