Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a86cf71
Initial plan
Copilot Mar 1, 2026
9422c8c
Update TestPackages.java to use new JAX-RS PackageAPI endpoints
Copilot Mar 1, 2026
744e8ff
Migrate PackageAPI from @EndPoint/@Command to JAX-RS annotations
Copilot Mar 1, 2026
cc26412
Fix PackageAPIJaxRsTest to use generated request classes and check re…
Copilot Mar 1, 2026
5040f5b
Extract magic numbers as named constants in PackageAPIJaxRs
Copilot Mar 1, 2026
83c2f5c
Rename PackageAPIJaxRs to PackageAPI and old PackageAPI to PackageStore
Copilot Mar 1, 2026
d71f199
Additional migrations from the old "command" style to the new RESTful…
epugh Mar 1, 2026
8994e03
fix test
epugh Mar 1, 2026
199b0f3
doc change
epugh Mar 3, 2026
463f590
Merge remote-tracking branch 'upstream/main' into copilot/migrate-pac…
epugh Apr 25, 2026
ae94dd3
Updates from merging in latest code from main.
epugh Apr 25, 2026
62212c3
PackageAPI and PackageApis super confusing.
epugh Apr 25, 2026
30ab7cc
Fix up expectedVersion property that was missing.
epugh Apr 25, 2026
99d90e7
Use our generated solrj code instead of hand crafting.
epugh Apr 25, 2026
7967574
tidy
epugh Apr 25, 2026
7491730
Small cleanups.
epugh Apr 25, 2026
deb2142
Now have a JIRA issue
epugh Apr 25, 2026
2578d26
shockingly, we had these unused objects
epugh Apr 25, 2026
bf46dc7
Don't need fancy instantiate method!
epugh Apr 25, 2026
a0bef90
Use SolrJ methods where possible.
epugh Apr 25, 2026
ba61c1b
More conversion to SolrJ methods
epugh Apr 25, 2026
670562d
Rework the tests. I am a bit on the fence about detailed docs.
epugh Apr 25, 2026
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: The package management API now has OpenAPI and SolrJ support.
type: changed
authors:
- name: Eric Pugh
links:
- name: PR#4178
url: https://github.com/apache/solr/pull/4178
101 changes: 101 additions & 0 deletions solr/api/src/java/org/apache/solr/client/api/endpoint/PackageApis.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import org.apache.solr.client.api.model.AddPackageVersionRequestBody;
import org.apache.solr.client.api.model.PackagesResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;

/** V2 API definitions for managing Solr packages. */
@Path("/cluster/package")
public interface PackageApis {

@GET
@Operation(
summary = "List all packages registered in this Solr cluster.",
tags = {"package"})
PackagesResponse listPackages(
@Parameter(
description =
"Name of a package to refresh on the receiving node only (reloads its listeners from ZooKeeper). Used internally for inter-node fan-out by the refresh endpoint; not normally invoked directly.")
@QueryParam("refreshPackage")
String refreshPackage,
@Parameter(
description =
"If provided, the node waits until its package data matches this ZooKeeper version.")
@QueryParam("expectedVersion")
Integer expectedVersion);

@GET
@Path("/{packageName}")
@Operation(
summary = "Get information about a specific package in this Solr cluster.",
tags = {"package"})
PackagesResponse getPackage(
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.

[Q] Should 'expectedVersion' be supported here, in addition to the "list" method above?

In the current code on main it looks like the query param is supported for both the "list-all" and "get-single-package" usecases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep! It's kind of internal tooling, but yeah.

@Parameter(description = "The name of the package.", required = true)
@PathParam("packageName")
String packageName,
@Parameter(
description =
"If provided, the node waits until its package data matches this ZooKeeper version before responding.")
@QueryParam("expectedVersion")
Integer expectedVersion);

@POST
@Path("/{packageName}/versions")
@Operation(
summary = "Add a version of a package to this Solr cluster.",
tags = {"package"})
SolrJerseyResponse addPackageVersion(
@Parameter(description = "The name of the package.", required = true)
@PathParam("packageName")
String packageName,
@RequestBody(description = "Details of the package version to add.", required = true)
AddPackageVersionRequestBody requestBody);

@DELETE
@Path("/{packageName}/versions/{version}")
@Operation(
summary = "Delete a specific version of a package from this Solr cluster.",
tags = {"package"})
SolrJerseyResponse deletePackageVersion(
@Parameter(description = "The name of the package.", required = true)
@PathParam("packageName")
String packageName,
@Parameter(description = "The version of the package to delete.", required = true)
@PathParam("version")
String version);

@POST
@Path("/{packageName}/refresh")
@Operation(
summary = "Refresh a package on all nodes in this Solr cluster.",
tags = {"package"})
SolrJerseyResponse refreshPackage(
@Parameter(description = "The name of the package to refresh.", required = true)
@PathParam("packageName")
String packageName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.client.api.model;

package org.apache.solr.client.solrj.request.beans;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.util.ReflectMapWriter;

/** Just a container class for POJOs used in Package APIs */
public class PackagePayload {
public static class AddVersion implements ReflectMapWriter {
@JsonProperty(value = "package", required = true)
public String pkg;

@JsonProperty(required = true)
public String version;
/** Request body for adding a version of a package. */
public class AddPackageVersionRequestBody {

@JsonProperty(required = true)
public List<String> files;
@JsonProperty("version")
@Schema(description = "The version string for this package version.", required = true)
public String version;

@JsonProperty public String manifest;
@JsonProperty public String manifestSHA512;
}
@JsonProperty("files")
@Schema(
description = "File paths from the file store to include in this version.",
required = true)
public List<String> files;

public static class DelVersion implements ReflectMapWriter {
@JsonProperty(value = "package", required = true)
public String pkg;
@JsonProperty("manifest")
@Schema(description = "Optional path to a manifest file in the file store.")
public String manifest;

@JsonProperty(required = true)
public String version;
}
@JsonProperty("manifestSHA512")
@Schema(description = "Optional SHA-512 hash of the manifest file.")
public String manifestSHA512;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import java.util.Map;

/** Response for the package listing API. */
public class PackagesResponse extends SolrJerseyResponse {

@JsonProperty("result")
@Schema(description = "The package data including znode version and package definitions.")
public PackageData result;

/** Package data returned by the package API. */
public static class PackageData {
@JsonProperty("znodeVersion")
@Schema(description = "The ZooKeeper version of the packages.json node.")
public int znodeVersion;

@JsonProperty("packages")
@Schema(description = "Map from package name to list of package versions.")
public Map<String, List<PackageVersion>> packages;
}

/** Describes a single version of a package. */
public static class PackageVersion {
@JsonProperty("package")
@Schema(description = "The package name.")
public String pkg;

@JsonProperty("version")
@Schema(description = "The version string.")
public String version;

@JsonProperty("files")
@Schema(description = "List of file paths from the file store included in this version.")
public List<String> files;

@JsonProperty("manifest")
@Schema(description = "Optional manifest reference.")
public String manifest;

@JsonProperty("manifestSHA512")
@Schema(description = "Optional SHA-512 hash of the manifest.")
public String manifestSHA512;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public ApiInfo(PluginMetaHolder infoHolder, List<String> errs) {
coreContainer.getPackageLoader().getPackageVersion(pkg, info.version);
if (ver.isEmpty()) {
// may be we are a bit early. Do a refresh and try again
coreContainer.getPackageLoader().getPackageAPI().refreshPackages(null);
coreContainer.getPackageLoader().getPackageStore().refreshPackages(null);
ver = coreContainer.getPackageLoader().getPackageVersion(pkg, info.version);
}
if (ver.isEmpty()) {
Expand Down
4 changes: 2 additions & 2 deletions solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.metrics.otel.OtelUnit;
import org.apache.solr.pkg.ClusterPackage;
import org.apache.solr.pkg.SolrPackageLoader;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
Expand Down Expand Up @@ -847,8 +848,7 @@ private void loadInternal() {
registerV2Api(ClusterFileStore.class);

packageLoader = new SolrPackageLoader(this);
registerV2Api(packageLoader.getPackageAPI().editAPI);
registerV2Api(packageLoader.getPackageAPI().readAPI);
registerV2Api(ClusterPackage.class);
registerV2Api(ZookeeperRead.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
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.pkg.PackageStore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.PermissionNameProvider;
Expand Down Expand Up @@ -87,8 +87,8 @@ public ClusterFileStore(
public UploadToFileStoreResponse uploadFile(
String filePath, List<String> sig, InputStream requestBody) {
final var response = instantiateJerseyResponse(UploadToFileStoreResponse.class);
if (!coreContainer.getPackageLoader().getPackageAPI().isEnabled()) {
throw new RuntimeException(PackageAPI.ERR_MSG);
if (!coreContainer.getPackageLoader().getPackageStore().isEnabled()) {
throw new RuntimeException(PackageStore.ERR_MSG);
}
try {
coreContainer
Expand Down Expand Up @@ -304,12 +304,12 @@ private void doDelete(String filePath, Boolean localDelete) {
@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);
if (!coreContainer.getPackageLoader().getPackageStore().isEnabled()) {
throw new RuntimeException(PackageStore.ERR_MSG);
}

validateName(filePath, true);
if (coreContainer.getPackageLoader().getPackageAPI().isJarInuse(filePath)) {
if (coreContainer.getPackageLoader().getPackageStore().isJarInuse(filePath)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "jar in use, can't delete");
}
doDelete(filePath, localDelete);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
import org.apache.solr.handler.admin.api.GetConfigAPI;
import org.apache.solr.handler.admin.api.ModifyConfigComponentAPI;
import org.apache.solr.handler.admin.api.ModifyParamSetAPI;
import org.apache.solr.pkg.PackageAPI;
import org.apache.solr.pkg.PackageListeners;
import org.apache.solr.pkg.PackageStore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
Expand Down Expand Up @@ -302,7 +302,7 @@ private void handleGET() {
List<PackageListeners.Listener> listeners =
req.getCore().getPackageListeners().getListeners();
for (PackageListeners.Listener listener : listeners) {
Map<String, PackageAPI.PkgVersion> infos = listener.packageDetails();
Map<String, PackageStore.PkgVersion> infos = listener.packageDetails();
if (infos == null || infos.isEmpty()) continue;
infos.forEach(
(s, mapWriter) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.pkg.PackageAPI;
import org.apache.solr.pkg.PackageListeners;
import org.apache.solr.pkg.PackageStore;
import org.apache.solr.pkg.SolrPackageLoader;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
Expand Down Expand Up @@ -201,7 +201,7 @@ public String packageName() {
}

@Override
public Map<String, PackageAPI.PkgVersion> packageDetails() {
public Map<String, PackageStore.PkgVersion> packageDetails() {
return Map.of();
}

Expand Down
Loading
Loading