This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 2ab12431c04 SOLR-17351: Decompose filestore "get file" API (#3047)
2ab12431c04 is described below
commit 2ab12431c04a3d860d21d10cf45d2434ea50bbe8
Author: Jason Gerlowski <[email protected]>
AuthorDate: Fri Feb 14 08:55:00 2025 -0800
SOLR-17351: Decompose filestore "get file" API (#3047)
This PR splits up the "get file" endpoint into a number of different APIs.
Specifically:
- metadata-fetching has been moved out to the endpoint,
GET/api/cluster/filestore/metadata/some/path.txt
- Filestore commands such as pushing/pulling files are now available at:
POST /api/cluster/filestore/commands
- Support for "JSON-ified" file data has been dropped in this PR (but
will be
retained but deprecated in the eventual 9.x backport)
These divisions allow us to generate SolrRequest/SolrResponse classes
representing these APIs, meaning that SolrJ users no longer need to use
GenericSolrRequest/GenericSolrResponse.
---
solr/CHANGES.txt | 4 +
.../client/api/endpoint/ClusterFileStoreApis.java | 52 ++++-
.../client/api/endpoint/NodeFileStoreApis.java | 67 -------
.../model/FileStoreDirectoryListingResponse.java | 4 -
.../api/model/FileStoreJsonFileResponse.java | 30 ---
.../java/org/apache/solr/core/CoreContainer.java | 2 -
.../apache/solr/filestore/ClusterFileStore.java | 163 ++++++++++++++++
.../apache/solr/filestore/DistribFileStore.java | 79 ++++----
.../org/apache/solr/filestore/NodeFileStore.java | 210 ---------------------
.../apache/solr/packagemanager/PackageManager.java | 2 +-
.../apache/solr/packagemanager/PackageUtils.java | 41 ++--
.../solr/packagemanager/RepositoryManager.java | 12 +-
.../solr/filestore/TestDistribFileStore.java | 25 ++-
.../solr/pkg/PackageStoreSchemaPluginsTest.java | 2 +-
.../pages/package-manager-internals.adoc | 15 +-
.../solr/client/solrj/InputStreamResponse.java | 6 +-
.../apache/solr/client/solrj/util/ClientUtils.java | 2 +
.../solrj/src/resources/java-template/api.mustache | 11 ++
18 files changed, 322 insertions(+), 405 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 439ecef8d7c..b619c37bdeb 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -37,6 +37,10 @@ Improvements
* SOLR-17516: `LBHttp2SolrClient` is now generic, adding support for
`HttpJdkSolrClient`. (James Dyer)
+* SOLR-17351: Solr's filestore "get-file" API has been decomposed into several
separate endpoints. Traditional file-fetching is now available at
+ `GET /api/cluster/filestore/files/some/path.txt` and metadata fetching (and
directory listing) is now available at `GET
/api/cluster/filestore/metadata/some/path.txt`.
+ SolrJ now offers request and response bindings for these APIs in
`org.apache.solr.client.solrj.request.FileStoreApi`. (Jason Gerlowski)
+
Optimizations
---------------------
* SOLR-17568: The CLI bin/solr export tool now contacts the appropriate nodes
directly for data instead of proxying through one.
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java
index 11ee4e1e26d..2445b17e7aa 100644
---
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterFileStoreApis.java
@@ -17,6 +17,7 @@
package org.apache.solr.client.api.endpoint;
import static
org.apache.solr.client.api.util.Constants.GENERIC_ENTITY_PROPERTY;
+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;
@@ -24,16 +25,19 @@ import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import java.io.InputStream;
import java.util.List;
+import org.apache.solr.client.api.model.FileStoreDirectoryListingResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.UploadToFileStoreResponse;
-@Path("/cluster")
+@Path("/cluster/filestore")
public interface ClusterFileStoreApis {
// TODO Better understand the purpose of the 'sig' parameter and improve
docs here.
@PUT
@@ -56,6 +60,29 @@ public interface ClusterFileStoreApis {
})
InputStream requestBody);
+ @GET
+ @Operation(
+ summary = "Retrieve metadata about a file or directory in the
filestore.",
+ tags = {"file-store"})
+ @Path("/metadata{path:.+}")
+ FileStoreDirectoryListingResponse getMetadata(
+ @Parameter(description = "Path to a file or directory within the
filestore")
+ @PathParam("path")
+ String path);
+
+ @GET
+ @Operation(
+ summary = "Retrieve raw contents of a file in the filestore.",
+ tags = {"file-store"},
+ extensions = {
+ @Extension(properties = {@ExtensionProperty(name =
RAW_OUTPUT_PROPERTY, value = "true")})
+ })
+ @Path("/files{filePath:.+}")
+ SolrJerseyResponse getFile(
+ @Parameter(description = "Path to a file or directory within the
filestore")
+ @PathParam("filePath")
+ String path);
+
@DELETE
@Operation(
summary = "Delete a file or directory from the filestore.",
@@ -70,4 +97,27 @@ public interface ClusterFileStoreApis {
"Indicates whether the deletion should only be done on the
receiving node. For internal use only")
@QueryParam("localDelete")
Boolean localDelete);
+
+ @POST
+ @Operation(
+ summary = "Fetches a filestore entry from other nodes in the cluster.",
+ tags = {"file-store"})
+ @Path("/commands/fetch{path:.+}")
+ SolrJerseyResponse fetchFile(
+ @Parameter(description = "Path to a file or directory within the
filestore")
+ @PathParam("path")
+ String path,
+ @Parameter(description = "An optional Solr node name to fetch the file
from")
+ @QueryParam("getFrom")
+ String getFrom);
+
+ @POST
+ @Operation(
+ summary = "Syncs a file by pushing it to other nodes in the cluster.",
+ tags = {"file-store"})
+ @Path("/commands/sync{path:.+}")
+ SolrJerseyResponse syncFile(
+ @Parameter(description = "Path to a file or directory within the
filestore")
+ @PathParam("path")
+ String path);
}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeFileStoreApis.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeFileStoreApis.java
deleted file mode 100644
index 15f4a73cfb1..00000000000
---
a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeFileStoreApis.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.api.endpoint;
-
-import static
org.apache.solr.client.api.util.Constants.OMIT_FROM_CODEGEN_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 jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
-
-/**
- * V2 APIs for fetching filestore files, syncing them across nodes, or
fetching related metadata.
- */
-@Path("/node")
-public interface NodeFileStoreApis {
- @GET
- @Operation(
- summary = "Retrieve file contents or metadata from the filestore.",
- tags = {"file-store"},
- // The response of this v2 API is highly variable based on the
parameters specified. It can
- // return raw (potentially binary) file data, a JSON-ified
representation of that file data,
- // metadata regarding one or multiple file store entries, etc. This
variability can be
- // handled on the Jersey server side, but would be prohibitively
difficult to accommodate in
- // our code-generation templates. Ideally, cosmetic improvements (e.g.
splitting it up into
- // multiple endpoints) will make this unnecessary in the future. But
for now, the extension
- // property below ensures that this endpoint is ignored entirely when
doing code generation.
- extensions = {
- @Extension(
- properties = {@ExtensionProperty(name =
OMIT_FROM_CODEGEN_PROPERTY, value = "true")})
- })
- @Path("/files{path:.+}")
- SolrJerseyResponse getFile(
- @Parameter(description = "Path to a file or directory within the
filestore")
- @PathParam("path")
- String path,
- @Parameter(
- description =
- "If true, triggers syncing for this file across all nodes in
the filestore")
- @QueryParam("sync")
- Boolean sync,
- @Parameter(description = "An optional Solr node name to fetch the file
from")
- @QueryParam("getFrom")
- String getFrom,
- @Parameter(description = "Indicates that (only) file metadata should be
fetched")
- @QueryParam("meta")
- Boolean meta);
-}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/FileStoreDirectoryListingResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/FileStoreDirectoryListingResponse.java
index bcbc5f1f728..79e002b6cee 100644
---
a/solr/api/src/java/org/apache/solr/client/api/model/FileStoreDirectoryListingResponse.java
+++
b/solr/api/src/java/org/apache/solr/client/api/model/FileStoreDirectoryListingResponse.java
@@ -19,10 +19,6 @@ package org.apache.solr.client.api.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
-/**
- * One of several possible responses from {@link
- * org.apache.solr.client.api.endpoint.NodeFileStoreApis#getFile(String,
Boolean, String, Boolean)}
- */
public class FileStoreDirectoryListingResponse extends SolrJerseyResponse {
@JsonProperty public Map<String, Object> files;
}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/FileStoreJsonFileResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/FileStoreJsonFileResponse.java
deleted file mode 100644
index 321faec01ec..00000000000
---
a/solr/api/src/java/org/apache/solr/client/api/model/FileStoreJsonFileResponse.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.api.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * One of several possible responses from {@link
- * org.apache.solr.client.api.endpoint.NodeFileStoreApis#getFile(String,
Boolean, String, Boolean)}
- *
- * <p>Typically used when 'wt=json' is specified while retrieving an
individual file from the
- * filestore
- */
-public class FileStoreJsonFileResponse extends SolrJerseyResponse {
- @JsonProperty public String response;
-}
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 e791b3ca602..1ed72a71a7a 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -110,7 +110,6 @@ import
org.apache.solr.core.backup.repository.BackupRepositoryFactory;
import org.apache.solr.filestore.ClusterFileStore;
import org.apache.solr.filestore.DistribFileStore;
import org.apache.solr.filestore.FileStore;
-import org.apache.solr.filestore.NodeFileStore;
import org.apache.solr.handler.ClusterAPI;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.SnapShooter;
@@ -880,7 +879,6 @@ public class CoreContainer {
fileStore = new DistribFileStore(this);
registerV2ApiIfEnabled(ClusterFileStore.class);
- registerV2ApiIfEnabled(NodeFileStore.class);
packageLoader = new SolrPackageLoader(this);
registerV2ApiIfEnabled(packageLoader.getPackageAPI().editAPI);
diff --git a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java
b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java
index db422baf6bf..043192aa0d8 100644
--- a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java
+++ b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java
@@ -18,23 +18,33 @@
package org.apache.solr.filestore;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.solr.handler.admin.api.ReplicationAPIBase.FILE_STREAM;
+import static org.apache.solr.response.RawResponseWriter.CONTENT;
import jakarta.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.api.endpoint.ClusterFileStoreApis;
+import org.apache.solr.client.api.model.FileStoreDirectoryListingResponse;
+import org.apache.solr.client.api.model.FileStoreEntryMetadata;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.UploadToFileStoreResponse;
import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.pkg.PackageAPI;
import org.apache.solr.request.SolrQueryRequest;
@@ -141,6 +151,115 @@ public class ClusterFileStore extends JerseyResource
implements ClusterFileStore
return response;
}
+ @Override
+ @PermissionName(PermissionNameProvider.Name.FILESTORE_READ_PERM)
+ public SolrJerseyResponse getFile(String path) {
+ final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+
+ final var type = fileStore.getType(path, false);
+ if (type == FileStore.FileType.NOFILE) {
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND,
+ "Requested path [" + path + "] not found in filestore");
+ } else if (type == FileStore.FileType.DIRECTORY) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Requested path [" + path + "] is a directory and has no returnable
contents");
+ }
+
+ attachFileToResponse(path, fileStore, req, rsp);
+ return response;
+ }
+
+ @Override
+ @PermissionName(PermissionNameProvider.Name.FILESTORE_READ_PERM)
+ public FileStoreDirectoryListingResponse getMetadata(String path) {
+ if (path == null) {
+ path = "";
+ }
+ FileStore.FileType type = fileStore.getType(path, false);
+ return getMetadata(type, path, fileStore);
+ }
+
+ public static void attachFileToResponse(
+ String path, FileStore fileStore, SolrQueryRequest req,
SolrQueryResponse rsp) {
+ ModifiableSolrParams solrParams = new ModifiableSolrParams();
+ solrParams.add(CommonParams.WT, FILE_STREAM);
+ req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
+ rsp.add(
+ CONTENT,
+ (SolrCore.RawWriter)
+ os ->
+ fileStore.get(
+ path,
+ it -> {
+ try {
+ InputStream inputStream = it.getInputStream();
+ if (inputStream != null) {
+ inputStream.transferTo(os);
+ }
+ } catch (IOException e) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR, "Error
reading file " + path);
+ }
+ },
+ false));
+ }
+
+ @SuppressWarnings("fallthrough")
+ public static FileStoreDirectoryListingResponse getMetadata(
+ FileStore.FileType type, String path, FileStore fileStore) {
+ final var dirListingResponse = new FileStoreDirectoryListingResponse();
+ if (path == null) {
+ path = "";
+ }
+
+ switch (type) {
+ case NOFILE:
+ dirListingResponse.files = Collections.singletonMap(path, null);
+ break;
+ case METADATA:
+ case FILE:
+ int idx = path.lastIndexOf('/');
+ String fileName = path.substring(idx + 1);
+ String parentPath = path.substring(0, path.lastIndexOf('/'));
+ List<FileStore.FileDetails> l = fileStore.list(parentPath, s ->
s.equals(fileName));
+
+ dirListingResponse.files =
+ Collections.singletonMap(path, l.isEmpty() ? null :
convertToResponse(l.get(0)));
+ break;
+ case DIRECTORY:
+ final var directoryContents =
+ fileStore.list(path, null).stream()
+ .map(details -> convertToResponse(details))
+ .collect(Collectors.toList());
+ dirListingResponse.files = Collections.singletonMap(path,
directoryContents);
+ break;
+ }
+
+ return dirListingResponse;
+ }
+
+ // TODO Modify the filestore implementation itself to return this object, so
conversion isn't
+ // needed.
+ private static FileStoreEntryMetadata
convertToResponse(FileStore.FileDetails details) {
+ final var entryMetadata = new FileStoreEntryMetadata();
+
+ entryMetadata.name = details.getSimpleName();
+ if (details.isDir()) {
+ entryMetadata.dir = true;
+ return entryMetadata;
+ }
+
+ entryMetadata.size = details.size();
+ entryMetadata.timestamp = details.getTimeStamp();
+ if (details.getMetaData() != null) {
+ details.getMetaData().toMap(entryMetadata.unknownProperties());
+ }
+
+ return entryMetadata;
+ }
+
private void doLocalDelete(String filePath) {
fileStore.deleteLocal(filePath);
}
@@ -194,6 +313,50 @@ public class ClusterFileStore extends JerseyResource
implements ClusterFileStore
return response;
}
+ @Override
+ @PermissionName(PermissionNameProvider.Name.FILESTORE_WRITE_PERM)
+ public SolrJerseyResponse fetchFile(String path, String getFrom) {
+ final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+ if (path == null) {
+ path = "";
+ }
+ pullFileFromNode(coreContainer, fileStore, path, getFrom);
+ return response;
+ }
+
+ @Override
+ @PermissionName(PermissionNameProvider.Name.FILESTORE_WRITE_PERM)
+ public SolrJerseyResponse syncFile(String path) {
+ final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+ syncToAllNodes(fileStore, path);
+ return response;
+ }
+
+ public static void syncToAllNodes(FileStore fileStore, String path) {
+ try {
+ fileStore.syncToAllNodes(path);
+ } catch (IOException e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error
getting file ", e);
+ }
+ }
+
+ public static void pullFileFromNode(
+ CoreContainer coreContainer, FileStore fileStore, String path, String
getFrom) {
+ coreContainer
+ .getUpdateShardHandler()
+ .getUpdateExecutor()
+ .submit(
+ () -> {
+ log.debug("Downloading file {}", path);
+ try {
+ fileStore.fetch(path, getFrom);
+ } catch (Exception e) {
+ log.error("Failed to download file: {}", path, e);
+ }
+ log.info("downloaded file: {}", path);
+ });
+ }
+
private List<String> readSignatures(List<String> signatures, byte[] buf)
throws SolrException, IOException {
if (signatures == null || signatures.isEmpty()) return null;
diff --git a/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java
b/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java
index 00e71968250..42679fd51ce 100644
--- a/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java
+++ b/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java
@@ -18,8 +18,6 @@
package org.apache.solr.filestore;
import static java.nio.charset.StandardCharsets.UTF_8;
-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.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
@@ -36,7 +34,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -50,11 +47,9 @@ import net.jcip.annotations.NotThreadSafe;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
-import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.FileStoreApi;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrPaths;
@@ -192,26 +187,31 @@ public class DistribFileStore implements FileStore {
var solrClient = coreContainer.getDefaultHttpSolrClient();
try {
- GenericSolrRequest request = new GenericSolrRequest(GET, "/node/files"
+ getMetaPath());
- request.setResponseParser(new InputStreamResponseParser(null));
- var response = solrClient.requestWithBaseUrl(baseUrl,
request::process).getResponse();
- is = (InputStream) response.get("stream");
+ final var metadataRequest = new FileStoreApi.GetFile(getMetaPath());
+ final var client =
coreContainer.getSolrClientCache().getHttpSolrClient(baseUrl);
+ final var response = metadataRequest.process(client);
metadata =
- Utils.newBytesConsumer((int) MAX_PKG_SIZE).accept((InputStream)
response.get("stream"));
+ Utils.newBytesConsumer((int) MAX_PKG_SIZE)
+ .accept(response.getResponseStreamIfSuccessful());
m = (Map<?, ?>) Utils.fromJSON(metadata.array(),
metadata.arrayOffset(), metadata.limit());
- } catch (SolrServerException | IOException e) {
+ } catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error
fetching metadata", e);
} finally {
org.apache.solr.common.util.IOUtils.closeQuietly(is);
}
+ ByteBuffer filedata = null;
+ try {
+ final var fileRequest = new FileStoreApi.GetFile(path);
+ final var fileResponse = solrClient.requestWithBaseUrl(baseUrl, null,
fileRequest);
+ try (final var stream = fileResponse.getResponseStreamIfSuccessful()) {
+ filedata = Utils.newBytesConsumer((int) MAX_PKG_SIZE).accept(stream);
+ }
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error
fetching data", e);
+ }
+
try {
- GenericSolrRequest request = new GenericSolrRequest(GET, "/node/files"
+ path);
- request.setResponseParser(new InputStreamResponseParser(null));
- var response = solrClient.requestWithBaseUrl(baseUrl,
request::process).getResponse();
- is = (InputStream) response.get("stream");
- ByteBuffer filedata =
- Utils.newBytesConsumer((int) MAX_PKG_SIZE).accept((InputStream)
response.get("stream"));
filedata.mark();
String sha512 = DigestUtils.sha512Hex(new
ByteBufferInputStream(filedata));
String expected = (String) m.get("sha512");
@@ -222,8 +222,6 @@ public class DistribFileStore implements FileStore {
filedata.reset();
persistToFile(filedata, metadata);
return true;
- } catch (SolrServerException e) {
- throw new SolrException(SERVER_ERROR, "Error fetching data", e);
} catch (IOException ioe) {
throw new SolrException(SERVER_ERROR, "Error persisting file", ioe);
} finally {
@@ -238,18 +236,12 @@ public class DistribFileStore implements FileStore {
try {
String baseUrl =
coreContainer.getZkController().getZkStateReader().getBaseUrlV2ForNodeName(liveNode);
- final var solrParams = new ModifiableSolrParams();
- solrParams.add("meta", "true");
- solrParams.add("omitHeader", "true");
+ final var metadataRequest = new FileStoreApi.GetMetadata(path);
+ final var client =
coreContainer.getSolrClientCache().getHttpSolrClient(baseUrl);
+ final var metadataResponse =
metadataRequest.process(client).getParsed();
+ boolean nodeHasBlob =
+ metadataResponse.files != null &&
metadataResponse.files.containsKey(path);
- final var request = new GenericSolrRequest(GET, "/node/files" +
path, solrParams);
- boolean nodeHasBlob = false;
- var solrClient = coreContainer.getDefaultHttpSolrClient();
- var resp = solrClient.requestWithBaseUrl(baseUrl,
request::process).getResponse();
-
- if (Utils.getObjectByPath(resp, false, Arrays.asList("files", path))
!= null) {
- nodeHasBlob = true;
- }
if (nodeHasBlob) {
boolean success = fetchFileFromNodeAndPersist(liveNode);
if (success) return true;
@@ -367,11 +359,13 @@ public class DistribFileStore implements FileStore {
for (String node : nodes) {
String baseUrl =
coreContainer.getZkController().getZkStateReader().getBaseUrlV2ForNodeName(node);
+
+ String nodeToFetchFrom;
if (i < FETCHFROM_SRC) {
// this is to protect very large clusters from overwhelming a single
node
// the first FETCHFROM_SRC nodes will be asked to fetch from this
node.
// it's there in the memory now. So , it must be served fast
- getFrom = myNodeName;
+ nodeToFetchFrom = myNodeName;
} else {
if (i == FETCHFROM_SRC) {
// This is just an optimization
@@ -382,19 +376,17 @@ public class DistribFileStore implements FileStore {
} catch (Exception e) {
}
}
- // trying to avoid the thundering herd problem when there are a very
large no:of nodes
- // others should try to fetch it from any node where it is
available. By now,
+ // trying to avoid the thundering herd problem when there are a very
large number of
+ // nodes others should try to fetch it from any node where it is
available. By now,
// almost FETCHFROM_SRC other nodes may have it
- getFrom = "*";
+ nodeToFetchFrom = "*";
}
try {
- var solrClient = coreContainer.getDefaultHttpSolrClient();
- var solrParams = new ModifiableSolrParams();
- solrParams.set("getFrom", getFrom);
-
- var request = new GenericSolrRequest(GET, "/node/files" + info.path,
solrParams);
+ final var pullFileRequest = new FileStoreApi.FetchFile(info.path);
+ pullFileRequest.setGetFrom(nodeToFetchFrom);
+ final var client =
coreContainer.getSolrClientCache().getHttpSolrClient(baseUrl);
// fire and forget
- solrClient.requestWithBaseUrl(baseUrl, request::process);
+ pullFileRequest.process(client);
} catch (Exception e) {
log.info("Node: {} failed to respond for file fetch notification",
node, e);
// ignore the exception
@@ -510,9 +502,8 @@ public class DistribFileStore implements FileStore {
deleteLocal(path);
List<String> nodes =
FileStoreUtils.fetchAndShuffleRemoteLiveNodes(coreContainer);
- final var solrParams = new ModifiableSolrParams();
- solrParams.add("localDelete", "true");
- final var solrRequest = new GenericSolrRequest(DELETE, "/cluster/files" +
path, solrParams);
+ final var solrRequest = new FileStoreApi.DeleteFile(path);
+ solrRequest.setLocalDelete(Boolean.TRUE);
for (String node : nodes) {
String baseUrl =
diff --git a/solr/core/src/java/org/apache/solr/filestore/NodeFileStore.java
b/solr/core/src/java/org/apache/solr/filestore/NodeFileStore.java
deleted file mode 100644
index e6c61419437..00000000000
--- a/solr/core/src/java/org/apache/solr/filestore/NodeFileStore.java
+++ /dev/null
@@ -1,210 +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.filestore;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.solr.handler.admin.api.ReplicationAPIBase.FILE_STREAM;
-import static org.apache.solr.response.RawResponseWriter.CONTENT;
-import static
org.apache.solr.security.PermissionNameProvider.Name.FILESTORE_READ_PERM;
-
-import jakarta.inject.Inject;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.invoke.MethodHandles;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.solr.api.JerseyResource;
-import org.apache.solr.client.api.endpoint.NodeFileStoreApis;
-import org.apache.solr.client.api.model.FileStoreDirectoryListingResponse;
-import org.apache.solr.client.api.model.FileStoreEntryMetadata;
-import org.apache.solr.client.api.model.FileStoreJsonFileResponse;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** Implementation for {@link NodeFileStoreApis} */
-public class NodeFileStore extends JerseyResource implements NodeFileStoreApis
{
- private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private final CoreContainer coreContainer;
- private final SolrQueryRequest req;
- private final SolrQueryResponse rsp;
- private final FileStore fileStore;
-
- @Inject
- public NodeFileStore(
- CoreContainer coreContainer,
- DistribFileStore fileStore,
- SolrQueryRequest req,
- SolrQueryResponse rsp) {
- this.coreContainer = coreContainer;
- this.req = req;
- this.rsp = rsp;
- this.fileStore = fileStore;
- }
-
- // TODO SOLR-17351 - this single "get" operation actually supports several
different chunks of
- // functionality: syncing, directory listing, file-fetching,
metadata-fetching. We should split
- // it up into multiple distinct APIs
- @Override
- @PermissionName(FILESTORE_READ_PERM)
- public SolrJerseyResponse getFile(String path, Boolean sync, String getFrom,
Boolean meta) {
- final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
-
- if (Boolean.TRUE.equals(sync)) {
- try {
- fileStore.syncToAllNodes(path);
- return response;
- } catch (IOException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error
getting file ", e);
- }
- }
-
- if (path == null) {
- path = "";
- }
- final var pathCopy = path;
- if (getFrom != null) {
- coreContainer
- .getUpdateShardHandler()
- .getUpdateExecutor()
- .submit(
- () -> {
- log.debug("Downloading file {}", pathCopy);
- try {
- fileStore.fetch(pathCopy, getFrom);
- } catch (Exception e) {
- log.error("Failed to download file: {}", pathCopy, e);
- }
- log.info("downloaded file: {}", pathCopy);
- });
- return response;
- }
-
- FileStore.FileType type = fileStore.getType(path, false);
- if (type == FileStore.FileType.NOFILE) {
- final var fileMissingResponse =
- instantiateJerseyResponse(FileStoreDirectoryListingResponse.class);
- fileMissingResponse.files = Collections.singletonMap(path, null);
- return fileMissingResponse;
- }
- if (type == FileStore.FileType.DIRECTORY) {
- final var directoryListingResponse =
- instantiateJerseyResponse(FileStoreDirectoryListingResponse.class);
- final var directoryContents =
- fileStore.list(path, null).stream()
- .map(details -> convertToResponse(details))
- .collect(Collectors.toList());
- directoryListingResponse.files = Collections.singletonMap(path,
directoryContents);
- return directoryListingResponse;
- }
- if (Boolean.TRUE.equals(meta)) {
- if (type == FileStore.FileType.FILE) {
- int idx = path.lastIndexOf('/');
- String fileName = path.substring(idx + 1);
- String parentPath = path.substring(0, path.lastIndexOf('/'));
- List<FileStore.FileDetails> l = fileStore.list(parentPath, s ->
s.equals(fileName));
-
- final var fileMetaResponse =
- instantiateJerseyResponse(FileStoreDirectoryListingResponse.class);
- fileMetaResponse.files =
- Collections.singletonMap(path, l.isEmpty() ? null :
convertToResponse(l.get(0)));
- return fileMetaResponse;
- }
- } else { // User wants to get the "raw" file
- // TODO Should we be trying to json-ify otherwise "raw" files in this
way? It seems like a
- // pretty sketchy idea, esp. for code with very little test coverage.
Consider removing
- if ("json".equals(req.getParams().get(CommonParams.WT))) {
- final var jsonResponse =
instantiateJerseyResponse(FileStoreJsonFileResponse.class);
- try {
- fileStore.get(
- pathCopy,
- it -> {
- try {
- InputStream inputStream = it.getInputStream();
- if (inputStream != null) {
- jsonResponse.response = new
String(inputStream.readAllBytes(), UTF_8);
- }
- } catch (IOException e) {
- throw new SolrException(
- SolrException.ErrorCode.SERVER_ERROR, "Error reading
file " + pathCopy);
- }
- },
- false);
- return jsonResponse;
- } catch (IOException e) {
- throw new SolrException(
- SolrException.ErrorCode.SERVER_ERROR, "Error getting file from
path " + path);
- }
- } else {
- ModifiableSolrParams solrParams = new ModifiableSolrParams();
- solrParams.add(CommonParams.WT, FILE_STREAM);
- req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
- rsp.add(
- CONTENT,
- (SolrCore.RawWriter)
- os ->
- fileStore.get(
- pathCopy,
- it -> {
- try {
- InputStream inputStream = it.getInputStream();
- if (inputStream != null) {
- inputStream.transferTo(os);
- }
- } catch (IOException e) {
- throw new SolrException(
- SolrException.ErrorCode.SERVER_ERROR,
- "Error reading file " + pathCopy);
- }
- },
- false));
- }
- }
- return response;
- }
-
- // TODO Modify the filestore implementation itself to return this object, so
conversion isn't
- // needed.
- private FileStoreEntryMetadata convertToResponse(FileStore.FileDetails
details) {
- final var entryMetadata = new FileStoreEntryMetadata();
-
- entryMetadata.name = details.getSimpleName();
- if (details.isDir()) {
- entryMetadata.dir = true;
- return entryMetadata;
- }
-
- entryMetadata.size = details.size();
- entryMetadata.timestamp = details.getTimeStamp();
- if (details.getMetaData() != null) {
- details.getMetaData().toMap(entryMetadata.unknownProperties());
- }
-
- return entryMetadata;
- }
-}
diff --git
a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
index 1d69a9c429d..07fb72965eb 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
@@ -173,7 +173,7 @@ public class PackageManager implements Closeable {
String.format(Locale.ROOT, "/package/%s/%s/%s", packageName, version,
"manifest.json"));
for (String filePath : filesToDelete) {
DistribFileStore.deleteZKFileEntry(zkClient, filePath);
- String path = "/api/cluster/files" + filePath;
+ String path = "/api/cluster/filestore/files" + filePath;
printGreen("Deleting " + path);
solrClient.request(new GenericSolrRequest(SolrRequest.METHOD.DELETE,
path));
}
diff --git
a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
index 1d1270a2b84..b0bb80cef42 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import org.apache.commons.io.IOUtils;
import org.apache.lucene.util.SuppressForbidden;
import org.apache.solr.cli.CLIUtils;
import org.apache.solr.client.solrj.SolrClient;
@@ -40,7 +41,6 @@ import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
import org.apache.solr.client.solrj.request.FileStoreApi;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.client.solrj.request.GenericV2SolrRequest;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.ModifiableSolrParams;
@@ -182,26 +182,27 @@ public class PackageUtils {
public static Manifest fetchManifest(
SolrClient solrClient, String manifestFilePath, String expectedSHA512)
throws IOException, SolrServerException {
- GenericSolrRequest request =
- new GenericV2SolrRequest(SolrRequest.METHOD.GET, "/api/node/files" +
manifestFilePath);
- request.setResponseParser(new JsonMapResponseParser());
- NamedList<Object> response = solrClient.request(request);
- String manifestJson = (String) response.get("response");
- String calculatedSHA512 =
-
Utils.sha512Digest(ByteBuffer.wrap(manifestJson.getBytes(StandardCharsets.UTF_8)));
- if (expectedSHA512.equals(calculatedSHA512) == false) {
- throw new SolrException(
- ErrorCode.UNAUTHORIZED,
- "The manifest SHA512 doesn't match expected SHA512. Possible
unauthorized manipulation. "
- + "Expected: "
- + expectedSHA512
- + ", calculated: "
- + calculatedSHA512
- + ", manifest location: "
- + manifestFilePath);
+
+ final var manifestRequest = new FileStoreApi.GetFile(manifestFilePath);
+ final var manifestResponse = manifestRequest.process(solrClient);
+ try (final var manifestStream =
manifestResponse.getResponseStreamIfSuccessful()) {
+ final var manifestJson = IOUtils.toString(manifestStream,
StandardCharsets.UTF_8);
+ String calculatedSHA512 =
+
Utils.sha512Digest(ByteBuffer.wrap(manifestJson.getBytes(StandardCharsets.UTF_8)));
+ if (!expectedSHA512.equals(calculatedSHA512)) {
+ throw new SolrException(
+ ErrorCode.UNAUTHORIZED,
+ "The manifest SHA512 doesn't match expected SHA512. Possible
unauthorized manipulation. "
+ + "Expected: "
+ + expectedSHA512
+ + ", calculated: "
+ + calculatedSHA512
+ + ", manifest location: "
+ + manifestFilePath);
+ }
+ Manifest manifest = getMapper().readValue(manifestJson, Manifest.class);
+ return manifest;
}
- Manifest manifest = getMapper().readValue(manifestJson, Manifest.class);
- return manifest;
}
/**
diff --git
a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
index c855f52961e..7c5306ceabc 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
@@ -44,6 +44,7 @@ import org.apache.solr.client.api.util.SolrVersion;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.request.FileStoreApi;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
import org.apache.solr.client.solrj.request.GenericV2SolrRequest;
import org.apache.solr.client.solrj.request.RequestWriter;
@@ -51,7 +52,6 @@ import
org.apache.solr.client.solrj.request.beans.PackagePayload;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.Utils;
import org.apache.solr.filestore.ClusterFileStore;
@@ -151,8 +151,14 @@ public class RepositoryManager {
// put the public key into package store's trusted key store and request a
sync.
String path = ClusterFileStore.KEYS_DIR + "/" + destinationKeyFilename;
PackageUtils.uploadKey(key, path, Paths.get(solrHome));
- PackageUtils.getJsonStringFromNonCollectionApi(
- solrClient, "/api/node/files" + path, new
ModifiableSolrParams().add("sync", "true"));
+ final var syncRequest = new FileStoreApi.SyncFile(path);
+ final var syncResponse = syncRequest.process(solrClient).getParsed();
+ final var status = syncResponse.responseHeader.status;
+ if (status != 0) {
+ throw new SolrException(
+ ErrorCode.getErrorCode(status),
+ "Unexpected status " + status + " while syncing filestore upload.");
+ }
}
private String getRepositoriesJson(SolrZkClient zkClient)
diff --git
a/solr/core/src/test/org/apache/solr/filestore/TestDistribFileStore.java
b/solr/core/src/test/org/apache/solr/filestore/TestDistribFileStore.java
index ff8f68b7897..95732ea3a1c 100644
--- a/solr/core/src/test/org/apache/solr/filestore/TestDistribFileStore.java
+++ b/solr/core/src/test/org/apache/solr/filestore/TestDistribFileStore.java
@@ -40,6 +40,7 @@ import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import
org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteExecutionException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.FileStoreApi;
import org.apache.solr.client.solrj.request.V2Request;
import org.apache.solr.client.solrj.response.SimpleSolrResponse;
import org.apache.solr.client.solrj.response.V2Response;
@@ -115,7 +116,7 @@ public class TestDistribFileStore extends SolrCloudTestCase
{
assertResponseValues(
10,
cluster.getSolrClient(),
- new V2Request.Builder("/node/files/package/mypkg/v1.0")
+ new
V2Request.Builder("/cluster/filestore/metadata/package/mypkg/v1.0")
.withMethod(SolrRequest.METHOD.GET)
.build(),
Map.of(
@@ -128,7 +129,7 @@ public class TestDistribFileStore extends SolrCloudTestCase
{
assertResponseValues(
10,
cluster.getSolrClient(),
- new V2Request.Builder("/node/files/package/mypkg")
+ new V2Request.Builder("/cluster/filestore/metadata/package/mypkg")
.withMethod(SolrRequest.METHOD.GET)
.build(),
Map.of(
@@ -169,14 +170,15 @@ public class TestDistribFileStore extends
SolrCloudTestCase {
});
for (JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
String baseUrl = jettySolrRunner.getBaseURLV2().toString();
- String url = baseUrl + "/node/files/package/mypkg/v1.0?wt=javabin";
+ String url = baseUrl +
"/cluster/filestore/metadata/package/mypkg/v1.0?wt=javabin";
assertResponseValues(10, new Fetcher(url, jettySolrRunner), expected);
}
// Delete Jars
DistribFileStore.deleteZKFileEntry(
cluster.getZkClient(), "/package/mypkg/v1.0/runtimelibs.jar");
JettySolrRunner j = cluster.getRandomJetty(random());
- String path = j.getBaseURLV2() + "/cluster/files" +
"/package/mypkg/v1.0/runtimelibs.jar";
+ String path =
+ j.getBaseURLV2() + "/cluster/filestore/files" +
"/package/mypkg/v1.0/runtimelibs.jar";
HttpDelete del = new HttpDelete(path);
try (HttpSolrClient cl = (HttpSolrClient) j.newClient()) {
Utils.executeHttpMethod(cl.getHttpClient(), path, Utils.JSONCONSUMER,
del);
@@ -196,7 +198,7 @@ public class TestDistribFileStore extends SolrCloudTestCase
{
throws Exception {
for (JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
String baseUrl = jettySolrRunner.getBaseURLV2().toString();
- String url = baseUrl + "/node/files" + path + "?wt=javabin&meta=true";
+ String url = baseUrl + "/cluster/filestore/metadata" + path +
"?wt=javabin";
assertResponseValues(10, new Fetcher(url, jettySolrRunner), expected);
if (verifyContent) {
@@ -204,7 +206,7 @@ public class TestDistribFileStore extends SolrCloudTestCase
{
ByteBuffer buf =
Utils.executeGET(
solrClient.getHttpClient(),
- baseUrl + "/node/files" + path,
+ baseUrl + "/cluster/filestore/files" + path,
Utils.newBytesConsumer(Integer.MAX_VALUE));
assertEquals(
"d01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420",
@@ -319,9 +321,12 @@ public class TestDistribFileStore extends
SolrCloudTestCase {
JettySolrRunner jetty = cluster.getRandomJetty(random());
try (HttpSolrClient client = (HttpSolrClient) jetty.newClient()) {
PackageUtils.uploadKey(bytes, path,
Paths.get(jetty.getCoreContainer().getSolrHome()));
- String url = jetty.getBaseURLV2() + "/node/files" + path + "?sync=true";
- Object resp = Utils.executeGET(client.getHttpClient(), url, null);
- log.info("sync resp: {} was {}", url, resp);
+
+ final var syncReq = new FileStoreApi.SyncFile(path);
+ final var syncRsp = syncReq.process(client);
+ if (log.isInfoEnabled()) {
+ log.info("sync resp for path {} was {}", path,
syncRsp.getParsed().responseHeader.status);
+ }
}
checkAllNodesForFile(
cluster,
@@ -333,7 +338,7 @@ public class TestDistribFileStore extends SolrCloudTestCase
{
public static NavigableObject postFile(
SolrClient client, ByteBuffer buffer, String name, String sig)
throws SolrServerException, IOException {
- String resource = "/cluster/files" + name;
+ String resource = "/cluster/filestore/files" + name;
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("sig", sig);
V2Response rsp =
diff --git
a/solr/core/src/test/org/apache/solr/pkg/PackageStoreSchemaPluginsTest.java
b/solr/core/src/test/org/apache/solr/pkg/PackageStoreSchemaPluginsTest.java
index 26d8261c07f..5fa62780a6b 100644
--- a/solr/core/src/test/org/apache/solr/pkg/PackageStoreSchemaPluginsTest.java
+++ b/solr/core/src/test/org/apache/solr/pkg/PackageStoreSchemaPluginsTest.java
@@ -124,7 +124,7 @@ public class PackageStoreSchemaPluginsTest extends
SolrCloudTestCase {
private void uploadPluginJar(String version, Path jarPath) throws Exception {
var pluginRequest =
- new V2Request.Builder("/cluster/files/my-plugin/plugin-" + version +
".jar")
+ new V2Request.Builder("/cluster/filestore/files/my-plugin/plugin-" +
version + ".jar")
.PUT()
.withParams(params("sig", signature(Files.readAllBytes(jarPath))))
.withPayload(Files.newInputStream(jarPath))
diff --git
a/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager-internals.adoc
b/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager-internals.adoc
index df8ed1f3907..33b6afcc2a4 100644
---
a/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager-internals.adoc
+++
b/solr/solr-ref-guide/modules/configuration-guide/pages/package-manager-internals.adoc
@@ -88,10 +88,9 @@ The metadata file stores the sha512 and signatures of the
jar files too.
The end points are:
-* `PUT /api/cluster/files/{full/path/to/file}` in each node
-* `GET /api/node/files/{full/path/to/file}` to download the file
-* `GET /api/node/files/{full/path/to/file}?meta=true` get the metadata of the
file
-* `GET /api/node/files/{full/path/to/}` get the list of files in
`/full/path/to`
+* `PUT /api/cluster/filestore/files/{full/path/to/file}` to upload a new file
and add it to the file store
+* `GET /api/cluster/filestore/files/{full/path/to/file}` to download a file
already in the filestore
+* `GET /api/cluster/filestore/metadata/{full/path/to/file}` to get the
metadata of a particular file, or a list of files available in a directory
=== Signing Your Artifacts
@@ -115,14 +114,14 @@ openssl dgst -sha1 -sign my_key.pem runtimelibs.jar |
openssl enc -base64 | sed
+
[source, bash]
----
-curl --data-binary @runtimelibs.jar -X PUT
http://localhost:8983/api/cluster/files/mypkg/1.0/myplugins.jar?sig=<signature-of-jar>
+curl --data-binary @runtimelibs.jar -X PUT
http://localhost:8983/api/cluster/filestore/files/mypkg/1.0/myplugins.jar?sig=<signature-of-jar>
----
. Verify your jar upload:
+
[source, bash]
----
-curl http://localhost:8983/api/node/files/mypkg/1.0?omitHeader=true
+curl
http://localhost:8983/api/cluster/filestore/metadata/mypkg/1.0?omitHeader=true
----
+
[source, json]
@@ -328,14 +327,14 @@ curl -o runtimelibs3.jar -LO
https://github.com/apache/solr/blob/releases/solr
openssl dgst -sha1 -sign my_key.pem runtimelibs.jar | openssl enc -base64 |
sed 's/+/%2B/g' | tr -d \\n | sed
-curl --data-binary @runtimelibs3.jar -X PUT
http://localhost:8983/api/cluster/files/mypkg/2.0/myplugins.jar?sig=<signature>
+curl --data-binary @runtimelibs3.jar -X PUT
http://localhost:8983/api/cluster/filestore/files/mypkg/2.0/myplugins.jar?sig=<signature>
----
. Verify it:
+
[source, bash]
----
-curl http://localhost:8983/api/node/files/mypkg/2.0?omitHeader=true
+curl
http://localhost:8983/api/cluster/filestore/metadata/mypkg/2.0?omitHeader=true
----
+
[source, json]
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/InputStreamResponse.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/InputStreamResponse.java
index b86e76f13ac..6770ce81c53 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/InputStreamResponse.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/InputStreamResponse.java
@@ -70,12 +70,11 @@ public class InputStreamResponse extends SimpleSolrResponse
{
/**
* Access the server response as an {@link InputStream}, regardless of the
HTTP status code
*
- * <p>Caller is responsible for consuming and closing the stream, and
releasing it from the
- * tracking done by {@link ObjectReleaseTracker}. No validation is done on
the HTTP status code.
+ * <p>Caller is responsible for consuming and closing the stream. No
validation is done on the
+ * HTTP status code.
*/
public InputStream getResponseStream() {
final NamedList<Object> resp = getResponse();
-
return (InputStream) resp.get(STREAM_KEY);
}
@@ -124,7 +123,6 @@ public class InputStreamResponse extends SimpleSolrResponse
{
inputStream.transferTo(baos);
return baos.toString(Charset.defaultCharset());
} finally {
- ObjectReleaseTracker.release(inputStream);
IOUtils.closeQuietly(baos);
IOUtils.closeQuietly(inputStream);
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
index 646ef546c99..7940b8ddce3 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
@@ -97,6 +97,8 @@ public class ClientUtils {
private static String buildReplacementV2Path(String existingPath) {
if (existingPath.contains("/solr")) {
return existingPath.replaceFirst("/solr", "/api");
+ } else if (existingPath.endsWith("/api")) {
+ return existingPath;
} else {
return existingPath + "/api";
}
diff --git a/solr/solrj/src/resources/java-template/api.mustache
b/solr/solrj/src/resources/java-template/api.mustache
index f276f3f0a72..0ed28650056 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -23,10 +23,12 @@ import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.response.SimpleSolrResponse;
@@ -239,6 +241,15 @@ public class {{classname}} {
return params;
}
+ @Override
+ public Set<String> getQueryParams() {
+ final var queryParams = new HashSet<String>();
+ {{#queryParams}}
+ queryParams.add("{{baseName}}");
+ {{/queryParams}}
+ return queryParams;
+ }
+
@Override
protected {{operationIdCamelCase}}Response
createResponse(SolrClient client) {
return new {{operationIdCamelCase}}Response();