Skip to content

Commit

Permalink
SOLR-17302: Convert remaining filestore APIs to JAX-RS (#2532)
Browse files Browse the repository at this point in the history
This migrates the remainder of Solr's "filestore" APIs to the JAX-RS framework.
No cosmetic changes were made in the process, with the small exception of
folding the internal "local delete" functionality into the existing delete API using
a new `localDelete` boolean query param.
  • Loading branch information
gerlowskija authored Jul 1, 2024
1 parent e36a6de commit d0aad69
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 285 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,10 @@ UploadToFileStoreResponse uploadFile(
SolrJerseyResponse deleteFile(
@Parameter(description = "Path to a file or directory within the filestore")
@PathParam("path")
String path);
String path,
@Parameter(
description =
"Indicates whether the deletion should only be done on the receiving node. For internal use only")
@QueryParam("localDelete")
Boolean localDelete);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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;
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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/** Represents the metadata for a single filestore file or directory */
public class FileStoreEntryMetadata {
@JsonProperty public String name;
@JsonProperty public Boolean dir;
@JsonProperty public Long size;
@JsonProperty public Date timestamp;

private Map<String, Object> additionalMetadata = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> unknownProperties() {
return additionalMetadata;
}

@JsonAnySetter
public void setUnknownProperty(String field, Object value) {
additionalMetadata.put(field, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ private Constants() {
public static final String INDEX_PATH_PREFIX =
"/{" + INDEX_TYPE_PATH_PARAMETER + ":cores|collections}/{" + INDEX_NAME_PATH_PARAMETER + "}";

public static final String OMIT_FROM_CODEGEN_PROPERTY = "omitFromCodegen";
public static final String GENERIC_ENTITY_PROPERTY = "genericEntity";

public static final String BINARY_CONTENT_TYPE_V2 = "application/vnd.apache.solr.javabin";
Expand Down
7 changes: 2 additions & 5 deletions solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
import org.apache.solr.filestore.ClusterFileStore;
import org.apache.solr.filestore.DistribFileStore;
import org.apache.solr.filestore.FileStore;
import org.apache.solr.filestore.FileStoreAPI;
import org.apache.solr.filestore.NodeFileStore;
import org.apache.solr.handler.ClusterAPI;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.SnapShooter;
Expand Down Expand Up @@ -298,7 +298,6 @@ && getZkController().getOverseer() != null
private DelegatingPlacementPluginFactory placementPluginFactory;

private DistribFileStore fileStore;
private FileStoreAPI fileStoreAPI;
private ClusterFileStore clusterFileStoreAPI;
private SolrPackageLoader packageLoader;

Expand Down Expand Up @@ -862,10 +861,8 @@ private void loadInternal() {
pkiAuthenticationSecurityBuilder.initializeMetrics(solrMetricsContext, "/authentication/pki");

fileStore = new DistribFileStore(this);
fileStoreAPI = new FileStoreAPI(this);
registerV2ApiIfEnabled(fileStoreAPI.readAPI);
registerV2ApiIfEnabled(fileStoreAPI.writeAPI);
registerV2ApiIfEnabled(ClusterFileStore.class);
registerV2ApiIfEnabled(NodeFileStore.class);

packageLoader = new SolrPackageLoader(this);
registerV2ApiIfEnabled(packageLoader.getPackageAPI().editAPI);
Expand Down
51 changes: 33 additions & 18 deletions solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,32 +141,23 @@ public UploadToFileStoreResponse uploadFile(
return response;
}

@Override
@PermissionName(PermissionNameProvider.Name.FILESTORE_WRITE_PERM)
public SolrJerseyResponse deleteFile(String filePath) {
final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
if (!coreContainer.getPackageLoader().getPackageAPI().isEnabled()) {
throw new RuntimeException(PackageAPI.ERR_MSG);
private void doLocalDelete(String filePath) {
fileStore.deleteLocal(filePath);
}

private void doClusterDelete(String filePath) {
FileStore.FileType type = fileStore.getType(filePath, true);
if (type == FileStore.FileType.NOFILE) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "Path does not exist: " + filePath);
}

try {
coreContainer
.getZkController()
.getZkClient()
.create(TMP_ZK_NODE, "true".getBytes(UTF_8), CreateMode.EPHEMERAL, true);
validateName(filePath, true);
if (coreContainer.getPackageLoader().getPackageAPI().isJarInuse(filePath)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "jar in use, can't delete");
}
FileStore.FileType type = fileStore.getType(filePath, true);
if (type == FileStore.FileType.NOFILE) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "Path does not exist: " + filePath);
}
fileStore.delete(filePath);
return response;
} catch (SolrException e) {
throw e;
} catch (Exception e) {
log.error("Unknown error", e);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
Expand All @@ -179,6 +170,30 @@ public SolrJerseyResponse deleteFile(String filePath) {
}
}

private void doDelete(String filePath, Boolean localDelete) {
if (Boolean.TRUE.equals(localDelete)) {
doLocalDelete(filePath);
} else {
doClusterDelete(filePath);
}
}

@Override
@PermissionName(PermissionNameProvider.Name.FILESTORE_WRITE_PERM)
public SolrJerseyResponse deleteFile(String filePath, Boolean localDelete) {
final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
if (!coreContainer.getPackageLoader().getPackageAPI().isEnabled()) {
throw new RuntimeException(PackageAPI.ERR_MSG);
}

validateName(filePath, true);
if (coreContainer.getPackageLoader().getPackageAPI().isJarInuse(filePath)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "jar in use, can't delete");
}
doDelete(filePath, localDelete);
return response;
}

private List<String> readSignatures(List<String> signatures, byte[] buf)
throws SolrException, IOException {
if (signatures == null || signatures.isEmpty()) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
import org.apache.lucene.util.IOUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrPaths;
Expand Down Expand Up @@ -288,20 +287,6 @@ public long size() {
return realPath().toFile().length();
}

@Override
public void writeMap(EntryWriter ew) throws IOException {
MetaData metaData = readMetaData();
ew.put(CommonParams.NAME, getSimpleName());
if (type == FileType.DIRECTORY) {
ew.put("dir", true);
return;
}

ew.put("size", size());
ew.put("timestamp", getTimeStamp());
if (metaData != null) metaData.writeMap(ew);
}

@Override
public String getSimpleName() {
int idx = path.lastIndexOf('/');
Expand Down Expand Up @@ -503,7 +488,7 @@ public void delete(String path) {
for (String node : nodes) {
String baseUrl =
coreContainer.getZkController().getZkStateReader().getBaseUrlV2ForNodeName(node);
String url = baseUrl + "/node/files" + path;
String url = baseUrl + "/cluster/files" + path + "?localDelete=true";
HttpDelete del = new HttpDelete(url);
// invoke delete command on all nodes asynchronously
coreContainer.runAsync(() -> Utils.executeHttpMethod(client, url, null, del));
Expand Down
3 changes: 1 addition & 2 deletions solr/core/src/java/org/apache/solr/filestore/FileStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.solr.common.MapWriter;
import org.apache.solr.filestore.FileStoreAPI.MetaData;
import org.apache.zookeeper.server.ByteBufferInputStream;

Expand Down Expand Up @@ -109,7 +108,7 @@ enum FileType {
METADATA
}

interface FileDetails extends MapWriter {
interface FileDetails {

String getSimpleName();

Expand Down
Loading

0 comments on commit d0aad69

Please sign in to comment.