diff --git a/dev-docs/apis.adoc b/dev-docs/apis.adoc
new file mode 100644
index 00000000000..49ff7df5a11
--- /dev/null
+++ b/dev-docs/apis.adoc
@@ -0,0 +1,83 @@
+= APIs in Solr
+:toc: left
+
+Solr's codebase currently has a handful of ways of defining APIs.
+This complexity stems largely from two ongoing transitions:
+1. Away from our "v1" APIs and towards the new "v2" API.
+2. Away from a legacy API framework towards the off-the-shelf JAX-RS library for implementing our v2 APIs
+
+As we finish these transitions, this complexity should simplify considerably.
+But in the interim, this document can help guide developers who need to understand or modify APIs in Solr.
+
+== API Types
+
+APIs in Solr (regardless of whether v1 or v2) can typically be classified as either "per-core" or "cluster" APIs.
+
+Per-core APIs, as the name suggests, typically affect only a single core or collection, usually used to search or analyze that core's contents in some way.
+Implementation-wise, they're registered on the `SolrCore` object itself.
+They are configured in solrconfig.xml, which also means they can differ from one core to another based on the configset being used.
+
+Alternatively "cluster" APIs potentially affect the entire Solr instance or cluster.
+They're registered on the `CoreContainer` object itself.
+It's much less common to provide configuration for these APIs, but it is possible to do so using `solr.xml`.
+
+== V1 APIs
+
+v1 APIs are the primary way that users consume Solr, as our v2 APIs remain "experimental".
+Many new APIs are added as "v2 only", however updates to existing v1 APIs still happen frequently.
+
+v1 APIs exist in Solr as implementations of the `SolrRequestHandler` interface, usually making use of the `RequestHandlerBase` base class.
+RequestHandlers have two methods of primary interest:
+1. `init`, often used to parse per-API configuration or otherwise setup the class
+2. `handleRequest` (or `handleRequestBody` if using `RequestHandlerBase`), the main entrypoint for the API, containing the business logic and constructing the response.
+
+While they do define many aspects of the endpoint's interface (e.g. query parameters, request body format, response format, etc.), RequestHandler's don't actually specify the URL path that they're located at.
+These paths are instead either hardcoded at registration time (see `CoreContainer.load` and `ImplicitPlugins.json`), or specified by users in configuration files (typically `solrconfig.xml`).
+
+== V2 APIs
+
+v2 APIs are currently still "experimental", and not necessarily recommended yet for users.
+But they're approaching parity with v1 and will eventually replace Solr's "RequestHandler"-based APIs.
+
+=== New "JAX-RS" APIs
+
+New v2 APIs in Solr are written in compliance with "JAX-RS", a library and specification that uses annotations to define APIs.
+Many libraries implement the JAX-RS spec: Solr currently uses the implementation provided by the "Jersey" project.
+
+These v2 API definitions consist of two parts: a JAX-RS annotated interface in the `api` module "defining" the API, and a class in `core` "implementing" the interface.
+Separating the API "definition" and "implementation" in this way allows us to only define each API in a single place, and use code generators to produce other API-related bits such as SolrJ code and ref-guide documentation.
+
+=== Writing JAX-RS APIs
+
+Writing a new v2 API may appear daunting, but additions in reality are actually pretty simple:
+
+1. *Create POJO ("Plain Old Java Object") classes as needed to represent the API's request and response*:
+** POJOs are used to represent both the body of the API request (for some `POST` and `PUT` endpoints), as well as the response from the API.
+** Re-use of existing classes here is preferred where possible. A library of available POJOs can be found in the `org.apache.solr.client.api.model` package of the `api` gradle project.
+** POJO class fields are typically "public" and annotated with the Jackson `@JsonProperty` annotations to allow serialization/deserialization.
+** POJO class fields should also have a Swagger `@Schema` annotation where possible, describing the purpose of the field. These descriptions are technically non-functional, but add lots of value to our OpenAPI spec and any artifacts generated downstream.
+2. *Find or create an interface to hold the v2 API definition*:
+** API interfaces live in the `org.apache.solr.client.api.endpoint` package of the `api` gradle project. Interfaces are usually given an "-Api" suffix to indicate their role.
+** If a new API is similar enough to existing APIs, it may make sense to add the new API definition into an existing interface instead of creating a wholly new one. Use your best judgement.
+3. *Add a method to the chosen interface representing the API*:
+** The method should take an argument representing each path and query parameter (annotated with `@PathParam` or `@QueryParam` as appropriate). If the API is a `PUT` or `POST` that expects a request body, the method should take the request body POJO as its final argument, annotated with `@RequestBody`.
+** Each method parameter should also be annotated with the Swagger `@Parameter` annotation. Like the `@Schema` annotation mentioned above, `@Parameter` isn't strictly required for correct operation, but they add lots of value to our OpenAPI spec and generated artifacts.
+** As a return value, the method should return the response-body POJO.
+4. *Futher JAX-RS Annotation*: The interface method in step (3) has specified its inputs and outputs, but several additional annotations are needed to define how users access the API, and to make it play nice with the code-generation done by Solr's build.
+** Each interface must have a `@Path` annotation describing the path that the API is accessed from. Specific interface methods can also be given `@Path` annotations, making the "effective path" a concatenation of the interface and method-level values. `@Path` supports a limited regex syntax, and curly-brackets can be used to create named placeholders for path-parameters.
+** Each interface method should be given an HTTP-method annotation (e.g. `@GET`, `@POST`, etc.)
+** Each interface method must be marked with a Swagger `@Operation` annotation. This annotation is used to provide metadata about the API that appears in the OpenAPI specification and in any artifacts generated from that downstream. At a minimum, `summary` and `tags` values should be specified on the annotation. (`tags` is used by our SolrJ code generation to group similar APIs together. Typically APIs are only given a single tag representing the plural name of the most relevant "resource" (e.g. `tags = {"aliases"}`, `tags = {"replica-properties"}`)
+5. *Create a class implementing the API interface*: Implementation classes live in the `core` gradle project, typically in the `org.apache.solr.handler` package or one of its descendants.
+** Implementing classes must extent `JerseyResource`, and are typically named similarly to the API interface created in (2) above without the "-Api" suffix. e.g. `class AddReplicaProperty extends JerseyResource implements AddReplicaPropertyApi`)
+** Solr's use of Jersey offers us some limited dependency-injection ("DI") capabilities. Class constructors annotated with `@Inject` can depend on a selection of types made available through DI, such as `CoreContainer`, `SolrQueryRequest`, `SolrCore`, etc. See the factory-bindings in `JerseyApplications` (or other API classes) for a sense of which types are available for constructor injection.
+** Add a body to your classes method(s). For the most part this is "normal" Java development.
+6. *Register your API*: APIs must be registered to be available at runtime. If the v2 API is associated with an existing v1 RequestHandler, the API class name can be added to the handler's `getJerseyResources` method. If there is no associated RequestHandler, the API should be registered similar to other APIs in `CoreContainer.load`.
+
+A good example for each of these steps can be seen in Solr's v2 "add-replica-property" API, which has a defining interface https://github.com/apache/solr/blob/9426902acb7081a2e9a1fa29699c5286459e1365/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java[AddReplicaPropertyApi], an implementing class https://github.com/apache/solr/blob/9426902acb7081a2e9a1fa29699c5286459e1365/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaProperty.java[AddReplicaProperty], and the two POJOs https://github.com/apache/solr/blob/main/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java[AddReplicaPropertyRequestBody] and https://github.com/apache/solr/blob/main/solr/api/src/java/org/apache/solr/client/api/model/SolrJerseyResponse.java[SolrJerseyResponse].
+
+=== Legacy v2 API Framework
+
+While we've settled on JAX-RS as our framework for defining v2 APIs going forward, Solr still retains many v2 APIs that were written using an older homegrown framework.
+This framework defines APIs using annotations (e.g. `@EndPoint`) similar to those used by JAX-RS, but lacks the full range of features and 3rd-party tooling.
+We're in the process of migrating these API definitions to JAX-RS and hope to remove all support for this legacy framework in a future release.
+
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index e09b1849394..7f36982d3ab 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -19,6 +19,8 @@ Improvements
* SOLR-16536: Replace OpenTracing instrumentation with OpenTelemetry (Alex Deparvu, janhoy)
+* SOLR-15367: Convert "rid" functionality into a default Tracer (Alex Deparvu, David Smiley)
+
Optimizations
---------------------
(No changes)
@@ -68,7 +70,9 @@ Other Changes
================== 9.4.0 ==================
New Features
---------------------
-(No changes)
+* SOLR-16654: Add support for node-level caches (Michael Gibney)
+
+* SOLR-16954: Make Circuit Breakers available for Update Requests (janhoy, Christine Poerschke, Pierre Salagnac)
Improvements
---------------------
@@ -101,8 +105,23 @@ Improvements
* SOLR-16940: Users can pass Java system properties to the SolrCLI via the SOLR_TOOL_OPTS environment variable. (Houston Putman)
+* SOLR-15474: Make Circuit breakers individually pluggable (Atri Sharma, Christine Poerschke, janhoy)
+
* SOLR-16927: Allow SolrClientCache clients to use Jetty HTTP2 clients (Alex Deparvu, David Smiley)
+* SOLR-16896, SOLR-16897: Add support of OAuth 2.0/OIDC 'code with PKCE' flow (Lamine Idjeraoui, janhoy, Kevin Risden)
+
+* SOLR-16879: Limit the number of concurrent expensive core admin operations by running them in a
+ dedicated thread pool. Backup, Restore and Split are expensive operations.
+ (Pierre Salagnac, David Smiley)
+
+* SOLR-16964: The solr.jetty.ssl.sniHostCheck option now defaults to the value of SOLR_SSL_CHECK_PEER_NAME, if it is provided.
+ This will enable client and server hostName check settings to be governed by the same environment variable.
+ If users want separate client/server settings, they can manually override the solr.jetty.ssl.sniHostCheck option in SOLR_OPTS. (Houston Putman)
+
+* SOLR-16970: SOLR_OPTS is now able to override options set by the Solr control scripts, "bin/solr" and "bin/solr.cmd". (Houston Putman)
+
+
Optimizations
---------------------
@@ -128,8 +147,30 @@ Bug Fixes
* PR#1826: Allow looking up Solr Package repo when that URL references a raw repository.json hosted on Github when the file is JSON but the mimetype used is text/plain. (Eric Pugh)
+* SOLR-16944: V2 API /api/node/health should be governed by "health" permission, not "config-read" (janhoy)
+
* SOLR-16859: Missing Proxy support for Http2SolrClient (Alex Deparvu)
+* SOLR-16929: SolrStream propagates undecoded error message (Alex Deparvu)
+
+* SOLR-16934: Allow Solr to read client (javax.net.ssl.*) trustStores and keyStores via SecurityManager. (Houston Putman)
+
+* SOLR-16946: Updated Cluster Singleton plugins are stopped correctly when the Overseer is closed. (Paul McArthur)
+
+* SOLR-16933: Include the full query response when using the API tool, and fix serialization issues for SolrDocumentList. (Houston Putman)
+
+* SOLR-16916: Use of the JSON Query DSL should ignore the defType parameter
+ (Christina Chortaria, Max Kadel, Ryan Laddusaw, Jane Sandberg, David Smiley)
+
+* SOLR-16958: Fix spurious warning about LATEST luceneMatchVersion (Colvin Cowie)
+
+* SOLR-16955: Tracing v2 apis breaks SecurityConfHandler (Alex Deparvu, David Smiley)
+
+* SOLR-16044: SlowRequest logging is no longer disabled if SolrCore logger set to ERROR (janhoy, hossman)
+
+* SOLR-16415: asyncId must not have '/'; enforce this. Enhance ZK cleanup to process directories
+ instead of fail. (David Smiley, Paul McArthur)
+
Dependency Upgrades
---------------------
@@ -148,6 +189,8 @@ Other Changes
* SOLR-16915: Lower the AffinityPlacementPlugin's default minimalFreeDiskGB to 5 GB (Houston Putman)
+* SOLR-16623: new SolrJettyTestRule for tests needing HTTP or Jetty. (David Smiley, Joshua Ouma)
+
================== 9.3.0 ==================
Upgrade Notes
@@ -271,7 +314,7 @@ Improvements
* SOLR-16687: Add support of SolrClassLoader to SolrZkClient (Lamine Idjeraoui via Jason Gerlowski & Houston Putman)
-* SOLR-9378: Internal shard requests no longer include the wasteful shard.url param. [shard] transformer now defaults to returning
+* SOLR-9378: Internal shard requests no longer include the wasteful shard.url param. [shard] transformer now defaults to returning
only the shard id (based on luceneMatchVersion), but can be configured to return the legacy list of replicas. (hossman)
* SOLR-16816: Update node metrics while making affinityPlacement selections. Therefore selections can be made given the expected cluster
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
index d29ee057502..8dce6a28e54 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
@@ -17,15 +17,12 @@
package org.apache.solr.client.api.endpoint;
-import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
-
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
@@ -33,10 +30,9 @@
public interface AddReplicaPropertyApi {
@PUT
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
@Operation(
summary = "Adds a property to the specified replica",
- tags = {"replicas"})
+ tags = {"replica-properties"})
public SolrJerseyResponse addReplicaProperty(
@Parameter(
description = "The name of the collection the replica belongs to.",
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AliasPropertyApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/AliasPropertyApis.java
new file mode 100644
index 00000000000..7bc167a2b36
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/AliasPropertyApis.java
@@ -0,0 +1,86 @@
+/*
+ * 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 javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.GetAliasPropertyResponse;
+import org.apache.solr.client.api.model.GetAllAliasPropertiesResponse;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
+import org.apache.solr.client.api.model.UpdateAliasPropertyRequestBody;
+
+/** V2 API definitions for managing and inspecting properties for collection aliases */
+@Path("/aliases/{aliasName}/properties")
+public interface AliasPropertyApis {
+
+ @GET
+ @Operation(
+ summary = "Get properties for a collection alias.",
+ tags = {"alias-properties"})
+ GetAllAliasPropertiesResponse getAllAliasProperties(
+ @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName)
+ throws Exception;
+
+ @GET
+ @Path("/{propName}")
+ @Operation(
+ summary = "Get a specific property for a collection alias.",
+ tags = {"alias-properties"})
+ GetAliasPropertyResponse getAliasProperty(
+ @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
+ @Parameter(description = "Property Name") @PathParam("propName") String propName)
+ throws Exception;
+
+ @PUT
+ @Operation(
+ summary = "Update properties for a collection alias.",
+ tags = {"alias-properties"})
+ SolrJerseyResponse updateAliasProperties(
+ @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
+ @RequestBody(description = "Properties that need to be updated", required = true)
+ UpdateAliasPropertiesRequestBody requestBody)
+ throws Exception;
+
+ @PUT
+ @Path("/{propName}")
+ @Operation(
+ summary = "Update a specific property for a collection alias.",
+ tags = {"alias-properties"})
+ SolrJerseyResponse createOrUpdateAliasProperty(
+ @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
+ @Parameter(description = "Property Name") @PathParam("propName") String propName,
+ @RequestBody(description = "Property value that needs to be updated", required = true)
+ UpdateAliasPropertyRequestBody requestBody)
+ throws Exception;
+
+ @DELETE
+ @Path("/{propName}")
+ @Operation(
+ summary = "Delete a specific property for a collection alias.",
+ tags = {"alias-properties"})
+ SolrJerseyResponse deleteAliasProperty(
+ @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
+ @Parameter(description = "Property Name") @PathParam("propName") String propName)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceReplicasApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceReplicasApi.java
new file mode 100644
index 00000000000..d05719efea8
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceReplicasApi.java
@@ -0,0 +1,36 @@
+/*
+ * 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.parameters.RequestBody;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import org.apache.solr.client.api.model.BalanceReplicasRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+@Path("cluster/replicas/balance")
+public interface BalanceReplicasApi {
+ @POST
+ @Operation(
+ summary = "Balance Replicas across the given set of Nodes.",
+ tags = {"cluster"})
+ SolrJerseyResponse balanceReplicas(
+ @RequestBody(description = "Contains user provided parameters")
+ BalanceReplicasRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java
index 24759ead2ad..05d32068683 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java
@@ -17,14 +17,11 @@
package org.apache.solr.client.api.endpoint;
-import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
-
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import javax.ws.rs.DELETE;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import org.apache.solr.client.api.model.SolrJerseyResponse;
@@ -32,7 +29,6 @@
public interface DeleteAliasApi {
@DELETE
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
@Operation(
summary = "Deletes an alias by its name",
tags = {"aliases"})
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java
index 9e28e7b1722..8cf0010066e 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java
@@ -17,14 +17,11 @@
package org.apache.solr.client.api.endpoint;
-import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
-
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import javax.ws.rs.DELETE;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
@@ -32,7 +29,6 @@
public interface DeleteCollectionApi {
@DELETE
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
@Operation(
summary = "Deletes a collection from SolrCloud",
tags = {"collections"})
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java
new file mode 100644
index 00000000000..e53f8ea96d7
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java
@@ -0,0 +1,78 @@
+/*
+ * 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.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.BACKUP_LOCATION;
+import static org.apache.solr.client.api.model.Constants.BACKUP_REPOSITORY;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.BackupDeletionResponseBody;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
+import org.apache.solr.client.api.model.PurgeUnusedResponse;
+
+@Path("/backups/{backupName}")
+public interface DeleteCollectionBackupApi {
+
+ @Path("/versions/{backupId}")
+ @DELETE
+ @Operation(
+ summary = "Delete incremental backup point by ID",
+ tags = {"collection-backups"})
+ BackupDeletionResponseBody deleteSingleBackupById(
+ @PathParam("backupName") String backupName,
+ @PathParam("backupId") String backupId,
+ // Optional parameters below
+ @QueryParam(BACKUP_LOCATION) String location,
+ @QueryParam(BACKUP_REPOSITORY) String repositoryName,
+ @QueryParam(ASYNC) String asyncId)
+ throws Exception;
+
+ @Path("/versions")
+ @DELETE
+ @Operation(
+ summary = "Delete all incremental backup points older than the most recent N",
+ tags = {"collection-backups"})
+ BackupDeletionResponseBody deleteMultipleBackupsByRecency(
+ @PathParam("backupName") String backupName,
+ @QueryParam("retainLatest") Integer versionsToRetain,
+ // Optional parameters below
+ @QueryParam(BACKUP_LOCATION) String location,
+ @QueryParam(BACKUP_REPOSITORY) String repositoryName,
+ @QueryParam(ASYNC) String asyncId)
+ throws Exception;
+
+ @Path("/purgeUnused")
+ @PUT
+ @Operation(
+ summary = "Garbage collect orphaned incremental backup files",
+ tags = {"collection-backups"})
+ PurgeUnusedResponse garbageCollectUnusedBackupFiles(
+ @PathParam("backupName") String backupName,
+ @RequestBody(
+ description = "Request body parameters for the orphaned file cleanup",
+ required = false)
+ PurgeUnusedFilesRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
new file mode 100644
index 00000000000..fb77fbd6dc8
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
@@ -0,0 +1,46 @@
+/*
+ * 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.Parameter;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
+
+public interface DeleteCollectionSnapshotApi {
+
+ /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
+ @DELETE
+ @Path("/collections/{collName}/snapshots/{snapshotName}")
+ DeleteCollectionSnapshotResponse deleteSnapshot(
+ @Parameter(description = "The name of the collection.", required = true)
+ @PathParam("collName")
+ String collName,
+ @Parameter(description = "The name of the snapshot to be deleted.", required = true)
+ @PathParam("snapshotName")
+ String snapshotName,
+ @Parameter(description = "A flag that treats the collName parameter as a collection alias.")
+ @DefaultValue("false")
+ @QueryParam("followAliases")
+ boolean followAliases,
+ @QueryParam("async") String asyncId)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java
new file mode 100644
index 00000000000..0495d298cb9
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java
@@ -0,0 +1,46 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+@Path("cluster/nodes/{nodeName}/clear/")
+public interface DeleteNodeApi {
+
+ @POST
+ @Operation(
+ summary = "Delete all replicas off of the specified SolrCloud node",
+ tags = {"node"})
+ SolrJerseyResponse deleteNode(
+ @Parameter(
+ description =
+ "The name of the node to be cleared. Usually of the form 'host:1234_solr'.",
+ required = true)
+ @PathParam("nodeName")
+ String nodeName,
+ @RequestBody(description = "Contains user provided parameters", required = true)
+ DeleteNodeRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java
new file mode 100644
index 00000000000..f17abdfab26
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java
@@ -0,0 +1,89 @@
+/*
+ * 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.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.COUNT_PROP;
+import static org.apache.solr.client.api.model.Constants.DELETE_DATA_DIR;
+import static org.apache.solr.client.api.model.Constants.DELETE_INDEX;
+import static org.apache.solr.client.api.model.Constants.DELETE_INSTANCE_DIR;
+import static org.apache.solr.client.api.model.Constants.FOLLOW_ALIASES;
+import static org.apache.solr.client.api.model.Constants.ONLY_IF_DOWN;
+
+import io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.ScaleCollectionRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/**
+ * V2 API definition for deleting one or more existing replicas from one or more shards.
+ *
+ *
These APIs are analogous to the v1 /admin/collections?action=DELETEREPLICA command.
+ */
+@Path("/collections/{collectionName}")
+public interface DeleteReplicaApi {
+
+ @DELETE
+ @Path("/shards/{shardName}/replicas/{replicaName}")
+ @Operation(
+ summary = "Delete an single replica by name",
+ tags = {"replicas"})
+ SubResponseAccumulatingJerseyResponse deleteReplicaByName(
+ @PathParam("collectionName") String collectionName,
+ @PathParam("shardName") String shardName,
+ @PathParam("replicaName") String replicaName,
+ // Optional params below
+ @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
+ @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
+ @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
+ @QueryParam(DELETE_INDEX) Boolean deleteIndex,
+ @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
+ @QueryParam(ASYNC) String async)
+ throws Exception;
+
+ @DELETE
+ @Path("/shards/{shardName}/replicas")
+ @Operation(
+ summary = "Delete one or more replicas from the specified collection and shard",
+ tags = {"replicas"})
+ SubResponseAccumulatingJerseyResponse deleteReplicasByCount(
+ @PathParam("collectionName") String collectionName,
+ @PathParam("shardName") String shardName,
+ @QueryParam(COUNT_PROP) Integer numToDelete,
+ // Optional params below
+ @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
+ @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
+ @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
+ @QueryParam(DELETE_INDEX) Boolean deleteIndex,
+ @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
+ @QueryParam(ASYNC) String async)
+ throws Exception;
+
+ @PUT
+ @Path("/scale")
+ @Operation(
+ summary = "Scale the replica count for all shards in the specified collection",
+ tags = {"replicas"})
+ SubResponseAccumulatingJerseyResponse deleteReplicasByCountAllShards(
+ @PathParam("collectionName") String collectionName, ScaleCollectionRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java
new file mode 100644
index 00000000000..8e8f52fd33a
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java
@@ -0,0 +1,55 @@
+/*
+ * 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 javax.ws.rs.DELETE;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API definition for removing a property from a collection replica
+ *
+ *
This API is analogous to the v1 /admin/collections?action=DELETEREPLICAPROP command.
+ */
+@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
+public interface DeleteReplicaPropertyApi {
+
+ @DELETE
+ @Operation(
+ summary = "Delete an existing replica property",
+ tags = {"replica-properties"})
+ SolrJerseyResponse deleteReplicaProperty(
+ @Parameter(
+ description = "The name of the collection the replica belongs to.",
+ required = true)
+ @PathParam("collName")
+ String collName,
+ @Parameter(description = "The name of the shard the replica belongs to.", required = true)
+ @PathParam("shardName")
+ String shardName,
+ @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
+ @PathParam("replicaName")
+ String replicaName,
+ @Parameter(description = "The name of the property to delete.", required = true)
+ @PathParam("propName")
+ String propertyName)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ForceLeaderApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ForceLeaderApi.java
new file mode 100644
index 00000000000..2a0336a2d3d
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ForceLeaderApi.java
@@ -0,0 +1,34 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/** V2 API definition for triggering a leader election on a particular collection and shard. */
+@Path("/collections/{collectionName}/shards/{shardName}/force-leader")
+public interface ForceLeaderApi {
+ @POST
+ @Operation(
+ summary = "Force leader election to occur on the specified collection and shard",
+ tags = {"shards"})
+ SolrJerseyResponse forceShardLeader(
+ @PathParam("collectionName") String collectionName, @PathParam("shardName") String shardName);
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetPublicKeyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetPublicKeyApi.java
new file mode 100644
index 00000000000..562a2945d9e
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetPublicKeyApi.java
@@ -0,0 +1,33 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import org.apache.solr.client.api.model.PublicKeyResponse;
+
+/** V2 API definition to fetch the public key of the receiving node. */
+@Path("/node/key")
+public interface GetPublicKeyApi {
+
+ @GET
+ @Operation(
+ summary = "Retrieve the public key of the receiving Solr node.",
+ tags = {"node"})
+ PublicKeyResponse getPublicKey();
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListAliasesApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListAliasesApi.java
new file mode 100644
index 00000000000..3f786b9d5e7
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListAliasesApi.java
@@ -0,0 +1,46 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.GetAliasByNameResponse;
+import org.apache.solr.client.api.model.ListAliasesResponse;
+
+/** V2 API definition for listing and inspecting collection aliases */
+@Path("/aliases")
+public interface ListAliasesApi {
+
+ @GET
+ @Operation(
+ summary = "List the existing collection aliases.",
+ tags = {"aliases"})
+ ListAliasesResponse getAliases() throws Exception;
+
+ @GET
+ @Path("/{aliasName}")
+ @Operation(
+ summary = "Get details for a specific collection alias.",
+ tags = {"aliases"})
+ GetAliasByNameResponse getAliasByName(
+ @Parameter(description = "Alias name.", required = true) @PathParam("aliasName")
+ String aliasName)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionBackupsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionBackupsApi.java
new file mode 100644
index 00000000000..59657085b95
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionBackupsApi.java
@@ -0,0 +1,43 @@
+/*
+ * 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.model.Constants.BACKUP_LOCATION;
+import static org.apache.solr.client.api.model.Constants.BACKUP_REPOSITORY;
+
+import io.swagger.v3.oas.annotations.Operation;
+import java.io.IOException;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.ListCollectionBackupsResponse;
+
+/** V2 API definitions for collection-backup "listing". */
+@Path("/backups/{backupName}/versions")
+public interface ListCollectionBackupsApi {
+
+ @GET
+ @Operation(
+ summary = "List existing incremental backups at the specified location.",
+ tags = {"collection-backups"})
+ ListCollectionBackupsResponse listBackupsAtLocation(
+ @PathParam("backupName") String backupName,
+ @QueryParam(BACKUP_LOCATION) String location,
+ @QueryParam(BACKUP_REPOSITORY) String repositoryName)
+ throws IOException;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java
new file mode 100644
index 00000000000..bb8baa210f3
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java
@@ -0,0 +1,31 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import org.apache.solr.client.api.model.ListCollectionsResponse;
+
+@Path("/collections")
+public interface ListCollectionsApi {
+ @GET
+ @Operation(
+ summary = "List all collections in this Solr cluster",
+ tags = {"collections"})
+ ListCollectionsResponse listCollections();
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java
new file mode 100644
index 00000000000..b00a47cac19
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import org.apache.solr.client.api.model.ListConfigsetsResponse;
+
+/** V2 API definition for listing configsets. */
+@Path("/cluster/configs")
+public interface ListConfigsetsApi {
+ @GET
+ @Operation(
+ summary = "List the configsets available to Solr.",
+ tags = {"configsets"})
+ ListConfigsetsResponse listConfigSet() throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReloadCollectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReloadCollectionApi.java
new file mode 100644
index 00000000000..2bf85b90626
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReloadCollectionApi.java
@@ -0,0 +1,36 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.ReloadCollectionRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/** V2 API definition for reloading collections. */
+@Path("/collections/{collectionName}/reload")
+public interface ReloadCollectionApi {
+ @POST
+ @Operation(
+ summary = "Reload all cores in the specified collection.",
+ tags = {"collections"})
+ SubResponseAccumulatingJerseyResponse reloadCollection(
+ @PathParam("collectionName") String collectionName, ReloadCollectionRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RenameCollectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RenameCollectionApi.java
new file mode 100644
index 00000000000..6eee0d431f2
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RenameCollectionApi.java
@@ -0,0 +1,37 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.RenameCollectionRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/** V2 API definition for "renaming" an existing collection */
+@Path("/collections/{collectionName}/rename")
+public interface RenameCollectionApi {
+
+ @POST
+ @Operation(
+ summary = "Rename a SolrCloud collection",
+ tags = {"collections"})
+ SubResponseAccumulatingJerseyResponse renameCollection(
+ @PathParam("collectionName") String collectionName, RenameCollectionRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplaceNodeApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplaceNodeApi.java
new file mode 100644
index 00000000000..56762fa524d
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ReplaceNodeApi.java
@@ -0,0 +1,45 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API definition for recreating replicas in one node (the source) on another node(s) (the
+ * target).
+ */
+@Path("cluster/nodes/{sourceNodeName}/replace/")
+public interface ReplaceNodeApi {
+ @POST
+ @Operation(
+ summary = "'Replace' a specified node by moving all replicas elsewhere",
+ tags = {"node"})
+ SolrJerseyResponse replaceNode(
+ @Parameter(description = "The name of the node to be replaced.", required = true)
+ @PathParam("sourceNodeName")
+ String sourceNodeName,
+ @RequestBody(description = "Contains user provided parameters", required = true)
+ ReplaceNodeRequestBody requestBody)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/SyncShardApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/SyncShardApi.java
new file mode 100644
index 00000000000..1e9805be65a
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/SyncShardApi.java
@@ -0,0 +1,37 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API definition for triggering a shard-sync operation within a particular collection and shard.
+ */
+@Path("/collections/{collectionName}/shards/{shardName}/sync")
+public interface SyncShardApi {
+ @POST
+ @Operation(
+ summary = "Trigger a 'sync' operation for the specified shard",
+ tags = {"shards"})
+ SolrJerseyResponse syncShard(
+ @PathParam("collectionName") String collectionName, @PathParam("shardName") String shardName)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java
index f78c7bebd9c..603524e4ad1 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java
@@ -19,9 +19,8 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.util.ReflectWritable;
-public class AddReplicaPropertyRequestBody implements ReflectWritable {
+public class AddReplicaPropertyRequestBody {
public AddReplicaPropertyRequestBody() {}
public AddReplicaPropertyRequestBody(String value) {
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java
new file mode 100644
index 00000000000..b27ce790c89
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public class BackupDeletionData {
+ @JsonProperty public String startTime;
+ @JsonProperty public Integer backupId;
+ @JsonProperty public Long size;
+ @JsonProperty public Integer numFiles;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java
new file mode 100644
index 00000000000..29563599ab2
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java
@@ -0,0 +1,26 @@
+/*
+ * 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.List;
+
+public class BackupDeletionResponseBody extends SubResponseAccumulatingJerseyResponse {
+ @JsonProperty public String collection;
+ @JsonProperty public List deleted;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/BalanceReplicasRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/BalanceReplicasRequestBody.java
new file mode 100644
index 00000000000..f270a48b269
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BalanceReplicasRequestBody.java
@@ -0,0 +1,50 @@
+/*
+ * 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.Set;
+
+public class BalanceReplicasRequestBody {
+
+ public BalanceReplicasRequestBody() {}
+
+ public BalanceReplicasRequestBody(Set nodes, Boolean waitForFinalState, String async) {
+ this.nodes = nodes;
+ this.waitForFinalState = waitForFinalState;
+ this.async = async;
+ }
+
+ @Schema(
+ description =
+ "The set of nodes across which replicas will be balanced. Defaults to all live data nodes.")
+ @JsonProperty(value = "nodes")
+ public Set nodes;
+
+ @Schema(
+ description =
+ "If true, the request will complete only when all affected replicas become active. "
+ + "If false, the API will return the status of the single action, which may be "
+ + "before the new replica is online and active.")
+ @JsonProperty("waitForFinalState")
+ public Boolean waitForFinalState = false;
+
+ @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+ @JsonProperty("async")
+ public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/CollectionBackupDetails.java b/solr/api/src/java/org/apache/solr/client/api/model/CollectionBackupDetails.java
new file mode 100644
index 00000000000..a46529baab4
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CollectionBackupDetails.java
@@ -0,0 +1,38 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.COLL_CONF;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+
+public class CollectionBackupDetails {
+ @JsonProperty public Integer backupId;
+ @JsonProperty public String indexVersion;
+ @JsonProperty public String startTime;
+ @JsonProperty public String endTime;
+ @JsonProperty public Integer indexFileCount;
+ @JsonProperty public Double indexSizeMB;
+
+ @JsonProperty public Map shardBackupIds;
+
+ @JsonProperty(COLL_CONF)
+ public String configsetName;
+
+ @JsonProperty public String collectionAlias;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/Constants.java b/solr/api/src/java/org/apache/solr/client/api/model/Constants.java
new file mode 100644
index 00000000000..0992c2c3a17
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/Constants.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+public class Constants {
+
+ private Constants() {
+ /* Private ctor prevents instantiation */
+ }
+
+ /** A parameter to specify the name of the backup repository to be used. */
+ public static final String BACKUP_REPOSITORY = "repository";
+
+ /** A parameter to specify the location where the backup should be stored. */
+ public static final String BACKUP_LOCATION = "location";
+
+ /** Async or not? * */
+ public static final String ASYNC = "async";
+
+ /** The name of a collection referenced by an API call */
+ public static final String COLLECTION = "collection";
+
+ public static final String COUNT_PROP = "count";
+
+ /** Option to follow aliases when deciding the target of a collection admin command. */
+ public static final String FOLLOW_ALIASES = "followAliases";
+
+ /** If you unload a core, delete the index too */
+ public static final String DELETE_INDEX = "deleteIndex";
+
+ public static final String DELETE_DATA_DIR = "deleteDataDir";
+
+ public static final String DELETE_INSTANCE_DIR = "deleteInstanceDir";
+
+ public static final String ONLY_IF_DOWN = "onlyIfDown";
+
+ /** The name of the config set to be used for a collection */
+ public static final String COLL_CONF = "collection.configName";
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
new file mode 100644
index 00000000000..2b134a2877c
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
@@ -0,0 +1,42 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.COLLECTION;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+
+/**
+ * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
+ * String)}
+ */
+public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
+ @Schema(description = "The name of the collection.")
+ @JsonProperty(COLLECTION)
+ public String collection;
+
+ @Schema(description = "The name of the snapshot to be deleted.")
+ @JsonProperty("snapshot")
+ public String snapshotName;
+
+ @Schema(description = "A flag that treats the collName parameter as a collection alias.")
+ @JsonProperty("followAliases")
+ public boolean followAliases;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java
new file mode 100644
index 00000000000..4b05f2f04e9
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public class DeleteNodeRequestBody {
+ @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+ @JsonProperty("async")
+ public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/GetAliasByNameResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/GetAliasByNameResponse.java
new file mode 100644
index 00000000000..df0498d9f12
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/GetAliasByNameResponse.java
@@ -0,0 +1,32 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+public class GetAliasByNameResponse extends SolrJerseyResponse {
+ @JsonProperty("name")
+ public String alias;
+
+ @JsonProperty("collections")
+ public List collections;
+
+ @JsonProperty("properties")
+ public Map properties;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/GetAliasPropertyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/GetAliasPropertyResponse.java
new file mode 100644
index 00000000000..93e62ba04c6
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/GetAliasPropertyResponse.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+public class GetAliasPropertyResponse extends SolrJerseyResponse {
+ @JsonProperty("value")
+ @Schema(description = "Property value.")
+ public String value;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/GetAllAliasPropertiesResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/GetAllAliasPropertiesResponse.java
new file mode 100644
index 00000000000..17b93d45471
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/GetAllAliasPropertiesResponse.java
@@ -0,0 +1,27 @@
+/*
+ * 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.Map;
+
+public class GetAllAliasPropertiesResponse extends SolrJerseyResponse {
+ @JsonProperty("properties")
+ @Schema(description = "Properties and values associated with alias.")
+ public Map properties;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ListAliasesResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/ListAliasesResponse.java
new file mode 100644
index 00000000000..3d9c93af85e
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ListAliasesResponse.java
@@ -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;
+
+public class ListAliasesResponse extends SolrJerseyResponse {
+ @JsonProperty("aliases")
+ public Map aliases;
+
+ @JsonProperty("properties")
+ public Map> properties;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionBackupsResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionBackupsResponse.java
new file mode 100644
index 00000000000..038a3df2ff4
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionBackupsResponse.java
@@ -0,0 +1,25 @@
+/*
+ * 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.List;
+
+public class ListCollectionBackupsResponse extends SolrJerseyResponse {
+ @JsonProperty public String collection;
+ @JsonProperty public List backups;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
new file mode 100644
index 00000000000..17bf44c44ec
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
@@ -0,0 +1,25 @@
+/*
+ * 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.List;
+
+public class ListCollectionsResponse extends SolrJerseyResponse {
+ @JsonProperty("collections")
+ public List collections;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ListConfigsetsResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/ListConfigsetsResponse.java
new file mode 100644
index 00000000000..09b99dfbdc9
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ListConfigsetsResponse.java
@@ -0,0 +1,26 @@
+/*
+ * 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.List;
+
+public class ListConfigsetsResponse extends SolrJerseyResponse {
+
+ @JsonProperty("configSets")
+ public List configSets;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/PublicKeyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/PublicKeyResponse.java
new file mode 100644
index 00000000000..d561b16fcbb
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/PublicKeyResponse.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+public class PublicKeyResponse extends SolrJerseyResponse {
+ @JsonProperty("key")
+ @Schema(description = "The public key of the receiving Solr node.")
+ public String key;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java
new file mode 100644
index 00000000000..1e3c6a26324
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java
@@ -0,0 +1,42 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.BACKUP_LOCATION;
+import static org.apache.solr.client.api.model.Constants.BACKUP_REPOSITORY;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.endpoint.DeleteCollectionBackupApi;
+
+/**
+ * Request body for the {@link DeleteCollectionBackupApi#garbageCollectUnusedBackupFiles(String,
+ * PurgeUnusedFilesRequestBody)} API.
+ */
+public class PurgeUnusedFilesRequestBody {
+ @JsonProperty(BACKUP_LOCATION)
+ public String location;
+
+ @Schema(name = "repositoryName")
+ @JsonProperty(BACKUP_REPOSITORY)
+ public String repositoryName;
+
+ @JsonProperty(ASYNC)
+ public String async;
+}
diff --git a/solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java
similarity index 51%
rename from solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java
rename to solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java
index 9fe01b09b14..5415060149c 100644
--- a/solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java
@@ -15,32 +15,16 @@
* limitations under the License.
*/
-package org.apache.solr.common;
+package org.apache.solr.client.api.model;
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.io.IOException;
-import org.apache.solr.common.util.Utils;
-public class DelegateMapWriter implements MapWriter {
+public class PurgeUnusedResponse extends SubResponseAccumulatingJerseyResponse {
+ @JsonProperty public PurgeUnusedStats deleted;
- private final Object delegate;
-
- public DelegateMapWriter(Object delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void writeMap(EntryWriter ew) throws IOException {
- Utils.reflectWrite(
- ew,
- delegate,
- // TODO Should we be lenient here and accept both the Jackson and our homegrown annotation?
- field -> field.getAnnotation(JsonProperty.class) != null,
- JsonAnyGetter.class,
- field -> {
- final JsonProperty prop = field.getAnnotation(JsonProperty.class);
- return prop.value().isEmpty() ? field.getName() : prop.value();
- });
+ public static class PurgeUnusedStats {
+ @JsonProperty public Integer numBackupIds;
+ @JsonProperty public Integer numShardBackupIds;
+ @JsonProperty public Integer numIndexFiles;
}
}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReloadCollectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReloadCollectionRequestBody.java
new file mode 100644
index 00000000000..287e5453381
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ReloadCollectionRequestBody.java
@@ -0,0 +1,27 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.ASYNC;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+// TODO Is it worth having this in a request body, or should we just make it a query param?
+public class ReloadCollectionRequestBody {
+ @JsonProperty(ASYNC)
+ public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/RenameCollectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/RenameCollectionRequestBody.java
new file mode 100644
index 00000000000..145618d029e
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/RenameCollectionRequestBody.java
@@ -0,0 +1,31 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.ASYNC;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RenameCollectionRequestBody {
+ @JsonProperty(required = true)
+ public String to;
+
+ @JsonProperty(ASYNC)
+ public String async;
+
+ @JsonProperty public Boolean followAliases;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ReplaceNodeRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ReplaceNodeRequestBody.java
new file mode 100644
index 00000000000..303fd64e8db
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ReplaceNodeRequestBody.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+public class ReplaceNodeRequestBody {
+
+ public ReplaceNodeRequestBody() {}
+
+ public ReplaceNodeRequestBody(String targetNodeName, Boolean waitForFinalState, String async) {
+ this.targetNodeName = targetNodeName;
+ this.waitForFinalState = waitForFinalState;
+ this.async = async;
+ }
+
+ @Schema(
+ description =
+ "The target node where replicas will be copied. If this parameter is not provided, Solr "
+ + "will identify nodes automatically based on policies or number of cores in each node.")
+ @JsonProperty("targetNodeName")
+ public String targetNodeName;
+
+ @Schema(
+ description =
+ "If true, the request will complete only when all affected replicas become active. "
+ + "If false, the API will return the status of the single action, which may be "
+ + "before the new replica is online and active.")
+ @JsonProperty("waitForFinalState")
+ public Boolean waitForFinalState = false;
+
+ @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+ @JsonProperty("async")
+ public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java
new file mode 100644
index 00000000000..c1c84ca22e1
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java
@@ -0,0 +1,41 @@
+/*
+ * 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 static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.COUNT_PROP;
+import static org.apache.solr.client.api.model.Constants.DELETE_DATA_DIR;
+import static org.apache.solr.client.api.model.Constants.DELETE_INDEX;
+import static org.apache.solr.client.api.model.Constants.DELETE_INSTANCE_DIR;
+import static org.apache.solr.client.api.model.Constants.FOLLOW_ALIASES;
+import static org.apache.solr.client.api.model.Constants.ONLY_IF_DOWN;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/** Request body used by {@link org.apache.solr.client.api.endpoint.DeleteReplicaApi} */
+public class ScaleCollectionRequestBody {
+ public @JsonProperty(value = COUNT_PROP, required = true) @Schema(name = "numToDelete") Integer
+ numToDelete;
+ public @JsonProperty(FOLLOW_ALIASES) Boolean followAliases;
+ public @JsonProperty(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir;
+ public @JsonProperty(DELETE_DATA_DIR) Boolean deleteDataDir;
+ public @JsonProperty(DELETE_INDEX) Boolean deleteIndex;
+ public @JsonProperty(ONLY_IF_DOWN) Boolean onlyIfDown;
+ public @JsonProperty(ASYNC) String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertiesRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertiesRequestBody.java
new file mode 100644
index 00000000000..de6a48a0a50
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertiesRequestBody.java
@@ -0,0 +1,32 @@
+/*
+ * 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.Map;
+
+public class UpdateAliasPropertiesRequestBody {
+
+ @Schema(description = "Properties and values to be updated on alias.")
+ @JsonProperty(value = "properties", required = true)
+ public Map properties;
+
+ @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+ @JsonProperty("async")
+ public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertyRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertyRequestBody.java
new file mode 100644
index 00000000000..a7a82536b71
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/UpdateAliasPropertyRequestBody.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+public class UpdateAliasPropertyRequestBody {
+ @JsonProperty(required = true)
+ public Object value;
+}
diff --git a/solr/bin/solr b/solr/bin/solr
index a5073f32f60..0836a6d5639 100644
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -259,7 +259,7 @@ if [ "$SOLR_SSL_ENABLED" == "true" ]; then
fi
if [ -n "$SOLR_SSL_CHECK_PEER_NAME" ]; then
- SOLR_SSL_OPTS+=" -Dsolr.ssl.checkPeerName=$SOLR_SSL_CHECK_PEER_NAME"
+ SOLR_SSL_OPTS+=" -Dsolr.ssl.checkPeerName=$SOLR_SSL_CHECK_PEER_NAME -Dsolr.jetty.ssl.sniHostCheck=$SOLR_SSL_CHECK_PEER_NAME"
fi
if [ -n "$SOLR_SSL_CLIENT_TRUST_STORE" ]; then
@@ -628,7 +628,7 @@ function jetty_port() {
function run_tool() {
# shellcheck disable=SC2086
- "$JAVA" ${SOLR_TOOL_OPTS:-} $SOLR_SSL_OPTS $AUTHC_OPTS ${SOLR_ZK_CREDS_AND_ACLS:-} -Dsolr.install.dir="$SOLR_TIP" \
+ "$JAVA" $SOLR_SSL_OPTS $AUTHC_OPTS ${SOLR_ZK_CREDS_AND_ACLS:-} ${SOLR_TOOL_OPTS:-} -Dsolr.install.dir="$SOLR_TIP" \
-Dlog4j.configurationFile="$DEFAULT_SERVER_DIR/resources/log4j2-console.xml" \
-classpath "$DEFAULT_SERVER_DIR/solr-webapp/webapp/WEB-INF/lib/*:$DEFAULT_SERVER_DIR/lib/ext/*:$DEFAULT_SERVER_DIR/lib/*" \
org.apache.solr.cli.SolrCLI "$@"
@@ -742,7 +742,7 @@ function stop_solr() {
if [ -n "$SOLR_PID" ]; then
echo -e "Sending stop command to Solr running on port $SOLR_PORT ... waiting up to $SOLR_STOP_WAIT seconds to allow Jetty process $SOLR_PID to stop gracefully."
# shellcheck disable=SC2086
- "$JAVA" ${SOLR_TOOL_OPTS:-} $SOLR_SSL_OPTS $AUTHC_OPTS -jar "$DIR/start.jar" "STOP.PORT=$THIS_STOP_PORT" "STOP.KEY=$STOP_KEY" --stop || true
+ "$JAVA" $SOLR_SSL_OPTS $AUTHC_OPTS ${SOLR_TOOL_OPTS:-} -jar "$DIR/start.jar" "STOP.PORT=$THIS_STOP_PORT" "STOP.KEY=$STOP_KEY" --stop || true
(loops=0
while true
do
@@ -1222,6 +1222,7 @@ fi
FG="false"
FORCE=false
SOLR_OPTS=(${SOLR_OPTS:-})
+SCRIPT_SOLR_OPTS=()
PASS_TO_RUN_EXAMPLE=()
if [ $# -gt 0 ]; then
@@ -1302,6 +1303,7 @@ if [ $# -gt 0 ]; then
exit 1
fi
SOLR_PORT="$2"
+ PROVIDED_SOLR_PORT="${SOLR_PORT}"
PASS_TO_RUN_EXAMPLE+=("-p" "$SOLR_PORT")
shift 2
;;
@@ -1370,6 +1372,7 @@ if [ $# -gt 0 ]; then
break # out-of-args, stop looping
elif [ "${1:0:2}" == "-D" ]; then
# pass thru any opts that begin with -D (java system props)
+ # These should go to the end of SOLR_OPTS, as they should override everything else
SOLR_OPTS+=("$1")
PASS_TO_RUN_EXAMPLE+=("$1")
shift
@@ -1388,20 +1391,20 @@ fi
# Solr modules option
if [[ -n "${SOLR_MODULES:-}" ]] ; then
- SOLR_OPTS+=("-Dsolr.modules=$SOLR_MODULES")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.modules=$SOLR_MODULES")
fi
# Default placement plugin
if [[ -n "${SOLR_PLACEMENTPLUGIN_DEFAULT:-}" ]] ; then
- SOLR_OPTS+=("-Dsolr.placementplugin.default=$SOLR_PLACEMENTPLUGIN_DEFAULT")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.placementplugin.default=$SOLR_PLACEMENTPLUGIN_DEFAULT")
fi
# Remote streaming and stream body
if [ "${SOLR_ENABLE_REMOTE_STREAMING:-false}" == "true" ]; then
- SOLR_OPTS+=("-Dsolr.enableRemoteStreaming=true")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.enableRemoteStreaming=true")
fi
if [ "${SOLR_ENABLE_STREAM_BODY:-false}" == "true" ]; then
- SOLR_OPTS+=("-Dsolr.enableStreamBody=true")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.enableStreamBody=true")
fi
: ${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}
@@ -1494,15 +1497,15 @@ if [[ "$SCRIPT_CMD" == "stop" ]]; then
fi
if [ -n "${SOLR_PORT_ADVERTISE:-}" ]; then
- SOLR_OPTS+=("-Dsolr.port.advertise=$SOLR_PORT_ADVERTISE")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.port.advertise=$SOLR_PORT_ADVERTISE")
fi
if [ -n "${SOLR_JETTY_HOST:-}" ]; then
- SOLR_OPTS+=("-Dsolr.jetty.host=$SOLR_JETTY_HOST")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.jetty.host=$SOLR_JETTY_HOST")
fi
if [ -n "${SOLR_ZK_EMBEDDED_HOST:-}" ]; then
- SOLR_OPTS+=("-Dsolr.zk.embedded.host=$SOLR_ZK_EMBEDDED_HOST")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.zk.embedded.host=$SOLR_ZK_EMBEDDED_HOST")
fi
: "${STOP_PORT:=$((SOLR_PORT - 1000))}"
@@ -1736,8 +1739,8 @@ else
JAVA_MEM_OPTS=("-Xms$SOLR_HEAP" "-Xmx$SOLR_HEAP")
fi
-# Pick default for Java thread stack size, and then add to SOLR_OPTS
-SOLR_OPTS+=(${SOLR_JAVA_STACK_SIZE:-"-Xss256k"})
+# Pick default for Java thread stack size, and then add to SCRIPT_SOLR_OPTS
+SCRIPT_SOLR_OPTS+=(${SOLR_JAVA_STACK_SIZE:-"-Xss256k"})
: "${SOLR_TIMEZONE:=UTC}"
@@ -1778,79 +1781,83 @@ function start_solr() {
fi
if [ -n "${SOLR_WAIT_FOR_ZK:-}" ]; then
- SOLR_OPTS+=("-DwaitForZk=$SOLR_WAIT_FOR_ZK")
+ SCRIPT_SOLR_OPTS+=("-DwaitForZk=$SOLR_WAIT_FOR_ZK")
fi
if [ -n "${SOLR_DATA_HOME:-}" ]; then
- SOLR_OPTS+=("-Dsolr.data.home=$SOLR_DATA_HOME")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.data.home=$SOLR_DATA_HOME")
fi
if [ -n "${SOLR_DELETE_UNKNOWN_CORES:-}" ]; then
- SOLR_OPTS+=("-Dsolr.deleteUnknownCores=$SOLR_DELETE_UNKNOWN_CORES")
+ SCRIPT_SOLR_OPTS+=("-Dsolr.deleteUnknownCores=$SOLR_DELETE_UNKNOWN_CORES")
fi
- # If SSL-related system props are set, add them to SOLR_OPTS
+ # If SSL-related system props are set, add them to SCRIPT_SOLR_OPTS
if [ "$SOLR_SSL_ENABLED" == "true" ]; then
# If using SSL and solr.jetty.https.port not set explicitly, use the jetty.port
SSL_PORT_PROP="-Dsolr.jetty.https.port=$SOLR_PORT"
- SOLR_OPTS+=($SOLR_SSL_OPTS "$SSL_PORT_PROP")
+ SCRIPT_SOLR_OPTS+=($SOLR_SSL_OPTS "$SSL_PORT_PROP")
fi
- # If authentication system props are set, add them to SOLR_OPTS
+ # If authentication system props are set, add them to SCRIPT_SOLR_OPTS
if [ -n "$AUTHC_OPTS" ]; then
- SOLR_OPTS+=($AUTHC_OPTS)
+ SCRIPT_SOLR_OPTS+=($AUTHC_OPTS)
fi
- # If there are internal options set by Solr (users should not use this variable), add them to SOLR_OPTS
+ # If there are internal options set by Solr (users should not use this variable), add them to SCRIPT_SOLR_OPTS
if [ -n "$SOLR_OPTS_INTERNAL" ]; then
- SOLR_OPTS+=($SOLR_OPTS_INTERNAL)
+ SCRIPT_SOLR_OPTS+=($SOLR_OPTS_INTERNAL)
fi
- # If a heap dump directory is specified, enable it in SOLR_OPTS
+ # If a heap dump directory is specified, enable it in SCRIPT_SOLR_OPTS
if [[ -z "${SOLR_HEAP_DUMP_DIR:-}" ]] && [[ "${SOLR_HEAP_DUMP:-}" == "true" ]]; then
SOLR_HEAP_DUMP_DIR="${SOLR_LOGS_DIR}/dumps"
fi
if [[ -n "${SOLR_HEAP_DUMP_DIR:-}" ]]; then
- SOLR_OPTS+=("-XX:+HeapDumpOnOutOfMemoryError")
- SOLR_OPTS+=("-XX:HeapDumpPath=$SOLR_HEAP_DUMP_DIR/solr-$(date +%s)-pid$$.hprof")
+ SCRIPT_SOLR_OPTS+=("-XX:+HeapDumpOnOutOfMemoryError")
+ SCRIPT_SOLR_OPTS+=("-XX:HeapDumpPath=$SOLR_HEAP_DUMP_DIR/solr-$(date +%s)-pid$$.hprof")
fi
if $verbose ; then
echo -e "\nStarting Solr using the following settings:"
- echo -e " JAVA = $JAVA"
- echo -e " SOLR_SERVER_DIR = $SOLR_SERVER_DIR"
- echo -e " SOLR_HOME = $SOLR_HOME"
- echo -e " SOLR_HOST = ${SOLR_HOST:-}"
- echo -e " SOLR_PORT = $SOLR_PORT"
- echo -e " STOP_PORT = $STOP_PORT"
- echo -e " JAVA_MEM_OPTS = ${JAVA_MEM_OPTS[*]}"
- echo -e " GC_TUNE = ${GC_TUNE_ARR[*]}"
- echo -e " GC_LOG_OPTS = ${GC_LOG_OPTS[*]}"
- echo -e " SOLR_TIMEZONE = $SOLR_TIMEZONE"
+ echo -e " JAVA = $JAVA"
+ echo -e " SOLR_SERVER_DIR = $SOLR_SERVER_DIR"
+ echo -e " SOLR_HOME = $SOLR_HOME"
+ echo -e " SOLR_HOST = ${SOLR_HOST:-}"
+ echo -e " SOLR_PORT = $SOLR_PORT"
+ echo -e " STOP_PORT = $STOP_PORT"
+ echo -e " JAVA_MEM_OPTS = ${JAVA_MEM_OPTS[*]}"
+ echo -e " GC_TUNE = ${GC_TUNE_ARR[*]}"
+ echo -e " GC_LOG_OPTS = ${GC_LOG_OPTS[*]}"
+ echo -e " SOLR_TIMEZONE = $SOLR_TIMEZONE"
if [ "$SOLR_MODE" == "solrcloud" ]; then
- echo -e " CLOUD_MODE_OPTS = ${CLOUD_MODE_OPTS[*]}"
+ echo -e " CLOUD_MODE_OPTS = ${CLOUD_MODE_OPTS[*]}"
fi
if [ -n "${SOLR_OPTS:-}" ]; then
- echo -e " SOLR_OPTS = ${SOLR_OPTS[*]}"
+ echo -e " SOLR_OPTS (USER) = ${SOLR_OPTS[*]}"
+ fi
+
+ if [ -n "${SCRIPT_SOLR_OPTS:-}" ]; then
+ echo -e " SOLR_OPTS (SCRIPT) = ${SCRIPT_SOLR_OPTS[*]}"
fi
if [ -n "${SOLR_ADDL_ARGS:-}" ]; then
- echo -e " SOLR_ADDL_ARGS = $SOLR_ADDL_ARGS"
+ echo -e " SOLR_ADDL_ARGS = $SOLR_ADDL_ARGS"
fi
if [ "${ENABLE_REMOTE_JMX_OPTS:-false}" == "true" ]; then
- echo -e " RMI_PORT = ${RMI_PORT:-}"
- echo -e " REMOTE_JMX_OPTS = ${REMOTE_JMX_OPTS[*]}"
+ echo -e " RMI_PORT = ${RMI_PORT:-}"
+ echo -e " REMOTE_JMX_OPTS = ${REMOTE_JMX_OPTS[*]}"
fi
if [ -n "${SOLR_LOG_LEVEL:-}" ]; then
- echo -e " SOLR_LOG_LEVEL = $SOLR_LOG_LEVEL"
+ echo -e " SOLR_LOG_LEVEL = $SOLR_LOG_LEVEL"
fi
if [ -n "${SOLR_DATA_HOME:-}" ]; then
- echo -e " SOLR_DATA_HOME = $SOLR_DATA_HOME"
+ echo -e " SOLR_DATA_HOME = $SOLR_DATA_HOME"
fi
echo
fi
@@ -1865,14 +1872,14 @@ function start_solr() {
# Workaround for JIT crash, see https://issues.apache.org/jira/browse/SOLR-16463
if [[ "$JAVA_VER_NUM" -ge "17" ]] ; then
- SOLR_OPTS+=("-XX:CompileCommand=exclude,com.github.benmanes.caffeine.cache.BoundedLocalCache::put")
+ SCRIPT_SOLR_OPTS+=("-XX:CompileCommand=exclude,com.github.benmanes.caffeine.cache.BoundedLocalCache::put")
echo "Java $JAVA_VER_NUM detected. Enabled workaround for SOLR-16463"
fi
# Vector optimizations are only supported for Java 20 and 21 for now.
# This will need to change as Lucene is upgraded and newer Java versions are released
if [[ "$JAVA_VER_NUM" -ge "20" ]] && [[ "$JAVA_VER_NUM" -le "21" ]] ; then
- SOLR_OPTS+=("--add-modules" "jdk.incubator.vector")
+ SCRIPT_SOLR_OPTS+=("--add-modules" "jdk.incubator.vector")
echo "Java $JAVA_VER_NUM detected. Incubating Panama Vector APIs have been enabled"
fi
@@ -1886,7 +1893,7 @@ function start_solr() {
# OOME is thrown. Program operation after OOME is unpredictable.
"-XX:+CrashOnOutOfMemoryError" "-XX:ErrorFile=${SOLR_LOGS_DIR}/jvm_crash_%p.log" \
"-Djetty.home=$SOLR_SERVER_DIR" "-Dsolr.solr.home=$SOLR_HOME" "-Dsolr.install.dir=$SOLR_TIP" "-Dsolr.install.symDir=$SOLR_TIP_SYM" \
- "-Dsolr.default.confdir=$DEFAULT_CONFDIR" "${LOG4J_CONFIG[@]}" "${SOLR_OPTS[@]}" "${SECURITY_MANAGER_OPTS[@]}" "${SOLR_ADMIN_UI}")
+ "-Dsolr.default.confdir=$DEFAULT_CONFDIR" "${LOG4J_CONFIG[@]}" "${SCRIPT_SOLR_OPTS[@]}" "${SECURITY_MANAGER_OPTS[@]}" "${SOLR_ADMIN_UI}" "${SOLR_OPTS[@]}")
mk_writable_dir "$SOLR_LOGS_DIR" "Logs"
if [[ -n "${SOLR_HEAP_DUMP_DIR:-}" ]]; then
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index ce37a20d20e..bb297a64d62 100755
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -146,7 +146,7 @@ IF "%SOLR_SSL_ENABLED%"=="true" (
)
)
IF DEFINED SOLR_SSL_CHECK_PEER_NAME (
- set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.ssl.checkPeerName=%SOLR_SSL_CHECK_PEER_NAME%"
+ set "SOLR_SSL_OPTS=!SOLR_SSL_OPTS! -Dsolr.ssl.checkPeerName=%SOLR_SSL_CHECK_PEER_NAME% -Dsolr.jetty.ssl.sniHostCheck=%SOLR_SSL_CHECK_PEER_NAME%"
)
) ELSE (
set SOLR_SSL_OPTS=
@@ -805,22 +805,24 @@ IF NOT "%SOLR_HOST%"=="" (
set SOLR_HOST_ARG=
)
+set SCRIPT_SOLR_OPTS=
+
REM Solr modules option
IF DEFINED SOLR_MODULES (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.modules=%SOLR_MODULES%"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.modules=%SOLR_MODULES%"
)
REM Default placement plugin
IF DEFINED SOLR_PLACEMENTPLUGIN_DEFAULT (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.placementplugin.default=%SOLR_PLACEMENTPLUGIN_DEFAULT%"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.placementplugin.default=%SOLR_PLACEMENTPLUGIN_DEFAULT%"
)
REM Remote streaming and stream body
IF "%SOLR_ENABLE_REMOTE_STREAMING%"=="true" (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.enableRemoteStreaming=true"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.enableRemoteStreaming=true"
)
IF "%SOLR_ENABLE_STREAM_BODY%"=="true" (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.enableStreamBody=true"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.enableStreamBody=true"
)
IF "%SOLR_SERVER_DIR%"=="" set "SOLR_SERVER_DIR=%DEFAULT_SERVER_DIR%"
@@ -923,7 +925,7 @@ IF "%SCRIPT_CMD%"=="stop" (
set found_it=1
@echo Stopping Solr process %%N running on port %SOLR_PORT%
IF "%STOP_PORT%"=="" set /A STOP_PORT=%SOLR_PORT% - 1000
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% -Djetty.home="%SOLR_SERVER_DIR%" -jar "%SOLR_SERVER_DIR%\start.jar" %SOLR_JETTY_CONFIG% STOP.PORT=!STOP_PORT! STOP.KEY=%STOP_KEY% --stop
+ "%JAVA%" %SOLR_SSL_OPTS% %SOLR_TOOL_OPTS% -Djetty.home="%SOLR_SERVER_DIR%" -jar "%SOLR_SERVER_DIR%\start.jar" %SOLR_JETTY_CONFIG% STOP.PORT=!STOP_PORT! STOP.KEY=%STOP_KEY% --stop
del "%SOLR_TIP%"\bin\solr-%SOLR_PORT%.port
REM wait for the process to terminate
CALL :wait_for_process_exit %%N !SOLR_STOP_WAIT!
@@ -948,15 +950,15 @@ IF "%SOLR_PORT%"=="" set SOLR_PORT=8983
IF "%STOP_PORT%"=="" set /A STOP_PORT=%SOLR_PORT% - 1000
IF DEFINED SOLR_PORT_ADVERTISE (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.port.advertise=%SOLR_PORT_ADVERTISE%"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.port.advertise=%SOLR_PORT_ADVERTISE%"
)
IF DEFINED SOLR_JETTY_HOST (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.jetty.host=%SOLR_JETTY_HOST%"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.jetty.host=%SOLR_JETTY_HOST%"
)
IF DEFINED SOLR_ZK_EMBEDDED_HOST (
- set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.zk.embedded.host=%SOLR_ZK_EMBEDDED_HOST%"
+ set "SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -Dsolr.zk.embedded.host=%SOLR_ZK_EMBEDDED_HOST%"
)
IF "%SCRIPT_CMD%"=="start" (
@@ -1097,7 +1099,7 @@ IF "%SOLR_ADMIN_UI_DISABLED%"=="true" (
IF NOT "%SOLR_HEAP%"=="" set SOLR_JAVA_MEM=-Xms%SOLR_HEAP% -Xmx%SOLR_HEAP%
IF "%SOLR_JAVA_MEM%"=="" set SOLR_JAVA_MEM=-Xms512m -Xmx512m
IF "%SOLR_JAVA_STACK_SIZE%"=="" set SOLR_JAVA_STACK_SIZE=-Xss256k
-set SOLR_OPTS=%SOLR_JAVA_STACK_SIZE% %SOLR_OPTS%
+set SCRIPT_SOLR_OPTS=%SOLR_JAVA_STACK_SIZE% %SCRIPT_SOLR_OPTS%
IF "%SOLR_TIMEZONE%"=="" set SOLR_TIMEZONE=UTC
IF "%GC_TUNE%"=="" (
@@ -1112,14 +1114,14 @@ IF "%GC_TUNE%"=="" (
REM Workaround for JIT crash, see https://issues.apache.org/jira/browse/SOLR-16463
if !JAVA_MAJOR_VERSION! GEQ 17 (
- set SOLR_OPTS=%SOLR_OPTS% -XX:CompileCommand=exclude,com.github.benmanes.caffeine.cache.BoundedLocalCache::put
+ set SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% -XX:CompileCommand=exclude,com.github.benmanes.caffeine.cache.BoundedLocalCache::put
echo Java %JAVA_MAJOR_VERSION% detected. Enabled workaround for SOLR-16463
)
REM Vector optimizations are only supported for Java 20 and 21 for now.
REM This will need to change as Lucene is upgraded and newer Java versions are released
if !JAVA_MAJOR_VERSION! GEQ 20 if !JAVA_MAJOR_VERSION! LEQ 21 (
- set SOLR_OPTS=%SOLR_OPTS% --add-modules jdk.incubator.vector
+ set SCRIPT_SOLR_OPTS=%SCRIPT_SOLR_OPTS% --add-modules jdk.incubator.vector
echo Java %JAVA_MAJOR_VERSION% detected. Incubating Panama Vector APIs have been enabled
)
@@ -1149,44 +1151,48 @@ if !JAVA_MAJOR_VERSION! GEQ 9 if NOT "%JAVA_VENDOR%" == "OpenJ9" (
IF "%verbose%"=="1" (
@echo Starting Solr using the following settings:
- CALL :safe_echo " JAVA = %JAVA%"
- CALL :safe_echo " SOLR_SERVER_DIR = %SOLR_SERVER_DIR%"
- CALL :safe_echo " SOLR_HOME = %SOLR_HOME%"
- @echo SOLR_HOST = %SOLR_HOST%
- @echo SOLR_PORT = %SOLR_PORT%
- @echo STOP_PORT = %STOP_PORT%
- @echo SOLR_JAVA_MEM = %SOLR_JAVA_MEM%
- @echo GC_TUNE = !GC_TUNE!
- @echo GC_LOG_OPTS = %GC_LOG_OPTS%
- @echo SOLR_TIMEZONE = %SOLR_TIMEZONE%
+ CALL :safe_echo " JAVA = %JAVA%"
+ CALL :safe_echo " SOLR_SERVER_DIR = %SOLR_SERVER_DIR%"
+ CALL :safe_echo " SOLR_HOME = %SOLR_HOME%"
+ @echo SOLR_HOST = %SOLR_HOST%
+ @echo SOLR_PORT = %SOLR_PORT%
+ @echo STOP_PORT = %STOP_PORT%
+ @echo SOLR_JAVA_MEM = %SOLR_JAVA_MEM%
+ @echo GC_TUNE = !GC_TUNE!
+ @echo GC_LOG_OPTS = %GC_LOG_OPTS%
+ @echo SOLR_TIMEZONE = %SOLR_TIMEZONE%
IF "%SOLR_MODE%"=="solrcloud" (
- @echo CLOUD_MODE_OPTS = %CLOUD_MODE_OPTS%
+ @echo CLOUD_MODE_OPTS = %CLOUD_MODE_OPTS%
)
IF NOT "%SOLR_OPTS%"=="" (
- @echo SOLR_OPTS = %SOLR_OPTS%
+ @echo SOLR_OPTS (USER) = %SOLR_OPTS%
+ )
+
+ IF NOT "%SCRIPT_SOLR_OPTS%"=="" (
+ @echo SOLR_OPTS (SCRIPT) = %SCRIPT_SOLR_OPTS%
)
IF NOT "%SOLR_ADDL_ARGS%"=="" (
- CALL :safe_echo " SOLR_ADDL_ARGS = %SOLR_ADDL_ARGS%"
+ CALL :safe_echo " SOLR_ADDL_ARGS = %SOLR_ADDL_ARGS%"
)
IF NOT "%SOLR_JETTY_ADDL_CONFIG%"=="" (
- CALL :safe_echo " SOLR_JETTY_ADDL_CONFIG = %SOLR_JETTY_ADDL_CONFIG%"
+ CALL :safe_echo " SOLR_JETTY_ADDL_CONFIG = %SOLR_JETTY_ADDL_CONFIG%"
)
IF "%ENABLE_REMOTE_JMX_OPTS%"=="true" (
- @echo RMI_PORT = !RMI_PORT!
- @echo REMOTE_JMX_OPTS = %REMOTE_JMX_OPTS%
+ @echo RMI_PORT = !RMI_PORT!
+ @echo REMOTE_JMX_OPTS = %REMOTE_JMX_OPTS%
)
IF NOT "%SOLR_LOG_LEVEL%"=="" (
- @echo SOLR_LOG_LEVEL = !SOLR_LOG_LEVEL!
+ @echo SOLR_LOG_LEVEL = !SOLR_LOG_LEVEL!
)
IF NOT "%SOLR_DATA_HOME%"=="" (
- @echo SOLR_DATA_HOME = !SOLR_DATA_HOME!
+ @echo SOLR_DATA_HOME = !SOLR_DATA_HOME!
)
@echo.
@@ -1207,7 +1213,7 @@ IF NOT "!IP_ACL_OPTS!"=="" set "START_OPTS=%START_OPTS% !IP_ACL_OPTS!"
IF NOT "%REMOTE_JMX_OPTS%"=="" set "START_OPTS=%START_OPTS% %REMOTE_JMX_OPTS%"
IF NOT "%SOLR_ADDL_ARGS%"=="" set "START_OPTS=%START_OPTS% %SOLR_ADDL_ARGS%"
IF NOT "%SOLR_HOST_ARG%"=="" set "START_OPTS=%START_OPTS% %SOLR_HOST_ARG%"
-IF NOT "%SOLR_OPTS%"=="" set "START_OPTS=%START_OPTS% %SOLR_OPTS%"
+IF NOT "%SCRIPT_SOLR_OPTS%"=="" set "START_OPTS=%START_OPTS% %SCRIPT_SOLR_OPTS%"
IF NOT "%SOLR_OPTS_INTERNAL%"=="" set "START_OPTS=%START_OPTS% %SOLR_OPTS_INTERNAL%"
IF NOT "!SECURITY_MANAGER_OPTS!"=="" set "START_OPTS=%START_OPTS% !SECURITY_MANAGER_OPTS!"
IF "%SOLR_SSL_ENABLED%"=="true" (
@@ -1223,6 +1229,9 @@ set "START_OPTS=%START_OPTS% -Dsolr.log.dir=%SOLR_LOGS_DIR_QUOTED% -Djava.util.l
IF NOT "%SOLR_DATA_HOME%"=="" set "START_OPTS=%START_OPTS% -Dsolr.data.home=%SOLR_DATA_HOME_QUOTED%"
IF NOT DEFINED LOG4J_CONFIG set "LOG4J_CONFIG=%SOLR_SERVER_DIR%\resources\log4j2.xml"
+REM This should be the last thing added to START_OPTS, so that users can override as much as possible
+IF NOT "%SOLR_OPTS%"=="" set "START_OPTS=%START_OPTS% %SOLR_OPTS%"
+
cd /d "%SOLR_SERVER_DIR%"
IF NOT EXIST "%SOLR_LOGS_DIR%" (
@@ -1272,7 +1281,7 @@ IF "%FG%"=="1" (
set SOLR_START_WAIT=30
)
REM now wait to see Solr come online ...
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" -Dsolr.default.confdir="%DEFAULT_CONFDIR%"^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" -Dsolr.default.confdir="%DEFAULT_CONFDIR%"^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI status -maxWaitSecs !SOLR_START_WAIT! -solrUrl !SOLR_URL_SCHEME!://%SOLR_TOOL_HOST%:%SOLR_PORT%/solr
@@ -1287,7 +1296,7 @@ goto done
:run_example
REM Run the requested example
-"%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+"%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-Dsolr.install.symDir="%SOLR_TIP%" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
@@ -1311,7 +1320,7 @@ for /f "usebackq" %%i in (`dir /b "%SOLR_TIP%\bin" ^| findstr /i "^solr-.*\.port
set has_info=1
echo Found Solr process %%k running on port !SOME_SOLR_PORT!
REM Passing in %2 (-h or -help) directly is captured by a custom help path for usage output
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI status -solrUrl !SOLR_URL_SCHEME!://%SOLR_TOOL_HOST%:!SOME_SOLR_PORT!/solr %2
@@ -1326,7 +1335,7 @@ set has_info=
goto done
:run_solrcli
-"%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+"%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI %*
@@ -1475,7 +1484,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG="The -d option must be set for upconfig."
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -confname !CONFIGSET_NAME! -confdir !CONFIGSET_DIR! -zkHost !ZK_HOST! %ZK_VERBOSE%^
@@ -1489,7 +1498,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG="The -d option must be set for downconfig."
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -confname !CONFIGSET_NAME! -confdir !CONFIGSET_DIR! -zkHost !ZK_HOST! %ZK_VERBOSE%
@@ -1508,7 +1517,7 @@ IF "!ZK_OP!"=="upconfig" (
goto zk_short_usage
)
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -zkHost !ZK_HOST! -src !ZK_SRC! -dst !ZK_DST! -recurse !ZK_RECURSE! %ZK_VERBOSE%
@@ -1521,7 +1530,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG=" must be specified for 'mv' command"
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -zkHost !ZK_HOST! -src !ZK_SRC! -dst !ZK_DST! %ZK_VERBOSE%
@@ -1530,7 +1539,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG="Zookeeper path to remove must be specified when using the 'rm' command"
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -zkHost !ZK_HOST! -path !ZK_SRC! -recurse !ZK_RECURSE! %ZK_VERBOSE%
@@ -1539,7 +1548,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG="Zookeeper path to remove must be specified when using the 'ls' command"
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -zkHost !ZK_HOST! -path !ZK_SRC! -recurse !ZK_RECURSE! %ZK_VERBOSE%
@@ -1548,7 +1557,7 @@ IF "!ZK_OP!"=="upconfig" (
set ERROR_MSG="Zookeeper path to create must be specified when using the 'mkroot' command"
goto zk_short_usage
)
- "%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+ "%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%SOLR_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI !ZK_OP! -zkHost !ZK_HOST! -path !ZK_SRC! %ZK_VERBOSE%
@@ -1609,7 +1618,7 @@ if "!AUTH_PORT!"=="" (
)
)
)
-"%JAVA%" %SOLR_TOOL_OPTS% %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% -Dsolr.install.dir="%SOLR_TIP%" ^
+"%JAVA%" %SOLR_SSL_OPTS% %AUTHC_OPTS% %SOLR_ZK_CREDS_AND_ACLS% %SOLR_TOOL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" ^
-Dlog4j.configurationFile="file:///%DEFAULT_SERVER_DIR%\resources\log4j2-console.xml" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.cli.SolrCLI auth %AUTH_PARAMS% -solrIncludeFile "%SOLR_INCLUDE%" -authConfDir "%SOLR_HOME%" ^
diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd
index 5f982cb16b5..f9892d33d66 100755
--- a/solr/bin/solr.in.cmd
+++ b/solr/bin/solr.in.cmd
@@ -159,7 +159,8 @@ REM set SOLR_SSL_WANT_CLIENT_AUTH=false
REM Verify client hostname during SSL handshake
REM set SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=false
REM SSL Certificates contain host/ip "peer name" information that is validated by default. Setting
-REM this to false can be useful to disable these checks when re-using a certificate on many hosts
+REM this to false can be useful to disable these checks when re-using a certificate on many hosts.
+REM This will also be used for the default value of whether SNI Host checking should be enabled.
REM set SOLR_SSL_CHECK_PEER_NAME=true
REM Override Key/Trust Store types if necessary
REM set SOLR_SSL_KEY_STORE_TYPE=PKCS12
diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh
index 117ef1761a9..f6da91c2f3b 100644
--- a/solr/bin/solr.in.sh
+++ b/solr/bin/solr.in.sh
@@ -173,7 +173,8 @@
# Verify client's hostname during SSL handshake
#SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=false
# SSL Certificates contain host/ip "peer name" information that is validated by default. Setting
-# this to false can be useful to disable these checks when re-using a certificate on many hosts
+# this to false can be useful to disable these checks when re-using a certificate on many hosts.
+# This will also be used for the default value of whether SNI Host checking should be enabled.
#SOLR_SSL_CHECK_PEER_NAME=true
# Override Key/Trust Store types if necessary
#SOLR_SSL_KEY_STORE_TYPE=PKCS12
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
index 92008542a60..a57b286a9a3 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -44,7 +44,6 @@
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.JsonSchemaValidator;
import org.apache.solr.common.util.PathTrie;
import org.apache.solr.common.util.SuppressForbidden;
@@ -503,22 +502,7 @@ protected void populateTracingSpan(Span span) {
// Get the templatize-ed path, ex: "/c/{collection}"
final String path = computeEndpointPath();
-
- String verb = null;
- // if this api has commands ...
- final Map validators = getValidators(); // should be cached
- if (validators != null && validators.isEmpty() == false && solrReq != null) {
- boolean validateInput = true; // because getCommands caches it; and we want it validated later
- // does this request have one command?
- List cmds = solrReq.getCommands(validateInput);
- if (cmds.size() == 1) {
- verb = cmds.get(0).name;
- }
- }
- if (verb == null) {
- verb = req.getMethod().toLowerCase(Locale.ROOT);
- }
-
+ final String verb = req.getMethod().toLowerCase(Locale.ROOT);
span.updateName(verb + ":" + path);
}
diff --git a/solr/core/src/java/org/apache/solr/cli/ApiTool.java b/solr/core/src/java/org/apache/solr/cli/ApiTool.java
index 6122000211b..8ecfcd36a40 100644
--- a/solr/core/src/java/org/apache/solr/cli/ApiTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/ApiTool.java
@@ -23,6 +23,7 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
@@ -61,29 +62,42 @@ public List getOptions() {
@Override
public void runImpl(CommandLine cli) throws Exception {
+ String response = null;
String getUrl = cli.getOptionValue("get");
if (getUrl != null) {
- getUrl = getUrl.replace("+", "%20");
- URI uri = new URI(getUrl);
- String solrUrl = getSolrUrlFromUri(uri);
- String path = uri.getPath();
- try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
- NamedList response =
- solrClient.request(
- // For path parameter we need the path without the root so from the second / char
- // (because root can be configured)
- // E.g URL is http://localhost:8983/solr/admin/info/system path is
- // /solr/admin/info/system and the path without root is /admin/info/system
- new GenericSolrRequest(
- SolrRequest.METHOD.GET,
- path.substring(path.indexOf("/", path.indexOf("/") + 1)),
- getSolrParamsFromUri(uri)));
+ response = callGet(getUrl);
+ }
+ if (response != null) {
+ // pretty-print the response to stdout
+ echo(response);
+ }
+ }
- // pretty-print the response to stdout
- CharArr arr = new CharArr();
- new JSONWriter(arr, 2).write(response.asMap());
- echo(arr.toString());
- }
+ protected String callGet(String url) throws Exception {
+ URI uri = new URI(url.replace("+", "%20"));
+ String solrUrl = getSolrUrlFromUri(uri);
+ String path = uri.getPath();
+ try (var solrClient = SolrCLI.getSolrClient(solrUrl)) {
+ // For path parameter we need the path without the root so from the second / char
+ // (because root can be configured)
+ // E.g URL is http://localhost:8983/solr/admin/info/system path is
+ // /solr/admin/info/system and the path without root is /admin/info/system
+ var req =
+ new GenericSolrRequest(
+ SolrRequest.METHOD.GET,
+ path.substring(path.indexOf("/", path.indexOf("/") + 1)),
+ getSolrParamsFromUri(uri) // .add("indent", "true")
+ );
+ // Using the "smart" solr parsers won't work, because they decode into Solr objects.
+ // When trying to re-write into JSON, the JSONWriter doesn't have the right info to print it
+ // correctly.
+ // All we want to do is pass the JSON response to the user, so do that.
+ req.setResponseParser(new JsonMapResponseParser());
+ NamedList response = solrClient.request(req);
+ // pretty-print the response to stdout
+ CharArr arr = new CharArr();
+ new JSONWriter(arr, 2).write(response.asMap());
+ return arr.toString();
}
}
diff --git a/solr/core/src/java/org/apache/solr/cli/AssertTool.java b/solr/core/src/java/org/apache/solr/cli/AssertTool.java
index 9ece33ccb8d..44b4dce1250 100644
--- a/solr/core/src/java/org/apache/solr/cli/AssertTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/AssertTool.java
@@ -30,7 +30,6 @@
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.HealthCheckRequest;
-import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -231,8 +230,6 @@ public static int assertSolrNotRunning(String url) throws Exception {
NamedList response = solrClient.request(new HealthCheckRequest());
Integer statusCode = (Integer) response.findRecursive("responseHeader", "status");
SolrCLI.checkCodeForAuthError(statusCode);
- } catch (SolrException se) {
- throw se;
} catch (IOException | SolrServerException e) {
log.debug("Opening connection to {} failed, Solr does not seem to be running", url, e);
return 0;
diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
index 91a2214eeb4..e62c42d185a 100644
--- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
@@ -38,6 +38,7 @@
import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
@@ -287,16 +288,18 @@ protected void createCollection(CloudSolrClient cloudSolrClient, CommandLine cli
NamedList response;
try {
- response =
- cloudSolrClient.request(
- CollectionAdminRequest.createCollection(
- collectionName, confName, numShards, replicationFactor));
+ var req =
+ CollectionAdminRequest.createCollection(
+ collectionName, confName, numShards, replicationFactor);
+ req.setResponseParser(new JsonMapResponseParser());
+ response = cloudSolrClient.request(req);
} catch (SolrServerException sse) {
throw new Exception(
"Failed to create collection '" + collectionName + "' due to: " + sse.getMessage());
}
if (cli.hasOption(SolrCLI.OPTION_VERBOSE.getOpt())) {
+ // pretty-print the response to stdout
CharArr arr = new CharArr();
new JSONWriter(arr, 2).write(response.asMap());
echo(arr.toString());
diff --git a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
index 5e556f02017..bae371226ae 100644
--- a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
@@ -32,6 +32,8 @@
import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
+import org.apache.solr.client.solrj.impl.NoOpResponseParser;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.common.cloud.ZkStateReader;
@@ -173,7 +175,9 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli
NamedList response;
try {
- response = cloudSolrClient.request(CollectionAdminRequest.deleteCollection(collectionName));
+ var req = CollectionAdminRequest.deleteCollection(collectionName);
+ req.setResponseParser(new JsonMapResponseParser());
+ response = cloudSolrClient.request(req);
} catch (SolrServerException sse) {
throw new Exception(
"Failed to delete collection '" + collectionName + "' due to: " + sse.getMessage());
@@ -194,6 +198,7 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli
}
if (response != null) {
+ // pretty-print the response to stdout
CharArr arr = new CharArr();
new JSONWriter(arr, 2).write(response.asMap());
echo(arr.toString());
@@ -215,15 +220,14 @@ protected void deleteCore(CommandLine cli, SolrClient solrClient) throws Excepti
unloadRequest.setDeleteDataDir(true);
unloadRequest.setDeleteInstanceDir(true);
unloadRequest.setCoreName(coreName);
+ unloadRequest.setResponseParser(new NoOpResponseParser("json"));
response = solrClient.request(unloadRequest);
} catch (SolrServerException sse) {
throw new Exception("Failed to delete core '" + coreName + "' due to: " + sse.getMessage());
}
if (response != null) {
- CharArr arr = new CharArr();
- new JSONWriter(arr, 2).write(response.asMap());
- echoIfVerbose(arr.toString(), cli);
+ echoIfVerbose((String) response.get("response"), cli);
echoIfVerbose("\n", cli);
}
}
diff --git a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
index d15650e1636..6def73ad789 100644
--- a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
@@ -519,14 +519,7 @@ public int postFiles(String[] args, int startIndexInArgs, OutputStream out, Stri
int filesPosted = 0;
for (int j = startIndexInArgs; j < args.length; j++) {
File srcFile = new File(args[j]);
- boolean isValidPath = checkIsValidPath(srcFile);
- if (isValidPath && srcFile.isDirectory() && srcFile.canRead()) {
- filesPosted += postDirectory(srcFile, out, type);
- } else if (isValidPath && srcFile.isFile() && srcFile.canRead()) {
- filesPosted += postFiles(new File[] {srcFile}, out, type);
- } else {
- filesPosted += handleGlob(srcFile, out, type);
- }
+ filesPosted = getFilesPosted(out, type, srcFile);
}
return filesPosted;
}
@@ -544,14 +537,20 @@ public int postFiles(File[] files, int startIndexInArgs, OutputStream out, Strin
reset();
int filesPosted = 0;
for (File srcFile : files) {
- boolean isValidPath = checkIsValidPath(srcFile);
- if (isValidPath && srcFile.isDirectory() && srcFile.canRead()) {
- filesPosted += postDirectory(srcFile, out, type);
- } else if (isValidPath && srcFile.isFile() && srcFile.canRead()) {
- filesPosted += postFiles(new File[] {srcFile}, out, type);
- } else {
- filesPosted += handleGlob(srcFile, out, type);
- }
+ filesPosted = getFilesPosted(out, type, srcFile);
+ }
+ return filesPosted;
+ }
+
+ private int getFilesPosted(final OutputStream out, final String type, final File srcFile) {
+ int filesPosted = 0;
+ boolean isValidPath = checkIsValidPath(srcFile);
+ if (isValidPath && srcFile.isDirectory() && srcFile.canRead()) {
+ filesPosted += postDirectory(srcFile, out, type);
+ } else if (isValidPath && srcFile.isFile() && srcFile.canRead()) {
+ filesPosted += postFiles(new File[] {srcFile}, out, type);
+ } else {
+ filesPosted += handleGlob(srcFile, out, type);
}
return filesPosted;
}
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index 0459892eeea..d7a424f6215 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -27,7 +27,6 @@
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
-import java.net.ConnectException;
import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
@@ -378,11 +377,7 @@ private static Set findClasses(String path, String packageName) throws E
*/
public static boolean checkCommunicationError(Exception exc) {
Throwable rootCause = SolrException.getRootCause(exc);
- boolean wasCommError =
- (rootCause instanceof ConnectException
- || rootCause instanceof SolrServerException
- || rootCause instanceof SocketException);
- return wasCommError;
+ return (rootCause instanceof SolrServerException || rootCause instanceof SocketException);
}
public static void checkCodeForAuthError(int code) {
@@ -600,8 +595,8 @@ public static boolean safeCheckCoreExists(String solrUrl, String coreName) {
}
NamedList existsCheckResult =
CoreAdminRequest.getStatus(coreName, solrClient).getResponse();
- NamedList status = (NamedList) existsCheckResult.get("status");
- NamedList coreStatus = (NamedList) status.get(coreName);
+ NamedList status = (NamedList) existsCheckResult.get("status");
+ NamedList coreStatus = (NamedList) status.get(coreName);
Map failureStatus =
(Map) existsCheckResult.get("initFailures");
String errorMsg = (String) failureStatus.get(coreName);
diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
index 546dc742f3d..3758a60b3fe 100644
--- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
@@ -24,7 +24,6 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import javax.net.ssl.SSLPeerUnverifiedException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
@@ -130,8 +129,6 @@ public Map waitToSeeSolrUp(String solrUrl, long maxWait, TimeUni
while (System.nanoTime() < timeout) {
try {
return getStatus(solrUrl);
- } catch (SSLPeerUnverifiedException exc) {
- throw exc;
} catch (Exception exc) {
if (SolrCLI.exceptionIsAuthRelated(exc)) {
throw exc;
@@ -204,7 +201,7 @@ protected Map getCloudStatus(SolrClient solrClient, String zkHos
cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
Map collections =
- ((NamedList) json.findRecursive("cluster", "collections")).asMap();
+ ((NamedList) json.findRecursive("cluster", "collections")).asMap();
cloudStatus.put("collections", String.valueOf(collections.size()));
return cloudStatus;
diff --git a/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java b/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
index 323c378f71d..fb2347c3e82 100644
--- a/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
+++ b/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.cloud;
+import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -27,12 +28,17 @@
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.KeeperException.NodeExistsException;
import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A distributed map. This supports basic map functions e.g. get, put, contains for interaction with
* zk which don't have to be ordered i.e. DistributedQueue.
*/
public class DistributedMap {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
protected final String dir;
protected SolrZkClient zookeeper;
@@ -54,7 +60,14 @@ public DistributedMap(SolrZkClient zookeeper, String dir) {
this.zookeeper = zookeeper;
}
+ private void assertKeyFormat(String trackingId) {
+ if (trackingId == null || trackingId.length() == 0 || trackingId.contains("/")) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Unsupported key format: " + trackingId);
+ }
+ }
+
public void put(String trackingId, byte[] data) throws KeeperException, InterruptedException {
+ assertKeyFormat(trackingId);
zookeeper.makePath(
dir + "/" + PREFIX + trackingId, data, CreateMode.PERSISTENT, null, false, true);
}
@@ -62,10 +75,11 @@ public void put(String trackingId, byte[] data) throws KeeperException, Interrup
/**
* Puts an element in the map only if there isn't one with the same trackingId already
*
- * @return True if the the element was added. False if it wasn't (because the key already exists)
+ * @return True if the element was added. False if it wasn't (because the key already exists)
*/
public boolean putIfAbsent(String trackingId, byte[] data)
throws KeeperException, InterruptedException {
+ assertKeyFormat(trackingId);
try {
zookeeper.makePath(
dir + "/" + PREFIX + trackingId, data, CreateMode.PERSISTENT, null, true, true);
@@ -94,10 +108,15 @@ public int size() throws KeeperException, InterruptedException {
* not deleted exception an exception occurred while deleting
*/
public boolean remove(String trackingId) throws KeeperException, InterruptedException {
+ final var path = dir + "/" + PREFIX + trackingId;
try {
- zookeeper.delete(dir + "/" + PREFIX + trackingId, -1, true);
+ zookeeper.delete(path, -1, true);
} catch (KeeperException.NoNodeException e) {
return false;
+ } catch (KeeperException.NotEmptyException hack) {
+ // because dirty data before we enforced the rules on put() (trackingId shouldn't have slash)
+ log.warn("Cleaning malformed key ID starting with {}", path);
+ zookeeper.clean(path);
}
return true;
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/SizeLimitedDistributedMap.java b/solr/core/src/java/org/apache/solr/cloud/SizeLimitedDistributedMap.java
index ab495fffea8..c4c8923b218 100644
--- a/solr/core/src/java/org/apache/solr/cloud/SizeLimitedDistributedMap.java
+++ b/solr/core/src/java/org/apache/solr/cloud/SizeLimitedDistributedMap.java
@@ -91,13 +91,11 @@ protected boolean lessThan(Long a, Long b) {
for (String child : children) {
Long id = childToModificationZxid.get(child);
if (id != null && id <= topElementMzxId) {
- try {
- zookeeper.delete(dir + "/" + child, -1, true);
- if (onOverflowObserver != null)
- onOverflowObserver.onChildDelete(child.substring(PREFIX.length()));
- } catch (KeeperException.NoNodeException ignored) {
- // this could happen if multiple threads try to clean the same map
- }
+ String trackingId = child.substring(PREFIX.length());
+ boolean removed = remove(trackingId);
+ if (removed && onOverflowObserver != null) {
+ onOverflowObserver.onChildDelete(trackingId);
+ } // else, probably multiple threads cleaning the queue simultaneously
}
}
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
index a055bbd618f..5597841d115 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
@@ -19,7 +19,6 @@
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
-import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
import static org.apache.solr.common.params.CommonParams.NAME;
import java.io.IOException;
@@ -113,7 +112,11 @@ public OverseerCollectionMessageHandler(
@Override
public OverseerSolrResponse processMessage(ZkNodeProps message, String operation) {
- MDCLoggingContext.setCollection(message.getStr(COLLECTION));
+ // sometimes overseer messages have the collection name in 'name' field, not 'collection'
+ MDCLoggingContext.setCollection(
+ message.getStr(COLLECTION_PROP) != null
+ ? message.getStr(COLLECTION_PROP)
+ : message.getStr(NAME));
MDCLoggingContext.setShard(message.getStr(SHARD_ID_PROP));
MDCLoggingContext.setReplica(message.getStr(REPLICA_PROP));
log.debug("OverseerCollectionMessageHandler.processMessage : {} , {}", operation, message);
diff --git a/solr/core/src/java/org/apache/solr/core/ClusterSingletons.java b/solr/core/src/java/org/apache/solr/core/ClusterSingletons.java
index 6e2c0417818..0c71c853400 100644
--- a/solr/core/src/java/org/apache/solr/core/ClusterSingletons.java
+++ b/solr/core/src/java/org/apache/solr/core/ClusterSingletons.java
@@ -99,8 +99,8 @@ public void deleted(ContainerPluginsRegistry.ApiInfo plugin) {
@Override
public void modified(
ContainerPluginsRegistry.ApiInfo old, ContainerPluginsRegistry.ApiInfo replacement) {
- added(replacement);
deleted(old);
+ added(replacement);
}
};
}
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 5606d93dc85..0bc9ad59d27 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -39,6 +39,7 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -94,6 +95,7 @@
import org.apache.solr.common.cloud.Replica.State;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.util.CollectionUtil;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.ObjectCache;
@@ -136,6 +138,8 @@
import org.apache.solr.pkg.SolrPackageLoader;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.SolrRequestInfo;
+import org.apache.solr.search.CacheConfig;
+import org.apache.solr.search.SolrCache;
import org.apache.solr.search.SolrFieldCacheBean;
import org.apache.solr.security.AllowListUrlChecker;
import org.apache.solr.security.AuditLoggerPlugin;
@@ -272,6 +276,8 @@ public JerseyAppHandlerCache getJerseyAppHandlerCache() {
private volatile SolrClientCache solrClientCache;
+ private volatile Map> caches;
+
private final ObjectCache objectCache = new ObjectCache();
public final NodeRoles nodeRoles = new NodeRoles(System.getProperty(NodeRoles.NODE_ROLES_PROP));
@@ -710,6 +716,10 @@ public PackageStoreAPI getPackageStoreAPI() {
return packageStoreAPI;
}
+ public SolrCache, ?> getCache(String name) {
+ return caches.get(name);
+ }
+
public SolrClientCache getSolrClientCache() {
return solrClientCache;
}
@@ -796,6 +806,20 @@ private void loadInternal() {
solrClientCache = new SolrClientCache(updateShardHandler.getDefaultHttpClient());
+ Map cachesConfig = cfg.getCachesConfig();
+ if (cachesConfig.isEmpty()) {
+ this.caches = Collections.emptyMap();
+ } else {
+ Map> m = CollectionUtil.newHashMap(cachesConfig.size());
+ for (Map.Entry e : cachesConfig.entrySet()) {
+ SolrCache, ?> c = e.getValue().newInstance();
+ String cacheName = e.getKey();
+ c.initializeMetrics(solrMetricsContext, "nodeLevelCache/" + cacheName);
+ m.put(cacheName, c);
+ }
+ this.caches = Collections.unmodifiableMap(m);
+ }
+
StartupLoggingUtils.checkRequestLogging();
hostName = cfg.getNodeName();
@@ -1263,6 +1287,17 @@ public void shutdown() {
// Now clear all the cores that are being operated upon.
solrCores.close();
+ final Map> closeCaches = caches;
+ if (closeCaches != null) {
+ for (Map.Entry> e : caches.entrySet()) {
+ try {
+ e.getValue().close();
+ } catch (Exception ex) {
+ log.warn("error closing node-level cache: {}", e.getKey(), ex);
+ }
+ }
+ }
+
objectCache.clear();
// It's still possible that one of the pending dynamic load operation is waiting, so wake it
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index 8117aad4523..581a2d07c0a 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -41,6 +41,7 @@
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.logging.DeprecationLog;
import org.apache.solr.logging.LogWatcherConfig;
+import org.apache.solr.search.CacheConfig;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.update.UpdateShardHandlerConfig;
import org.apache.solr.util.ModuleUtils;
@@ -109,6 +110,8 @@ public class NodeConfig {
private final MetricsConfig metricsConfig;
+ private final Map cachesConfig;
+
private final PluginInfo tracerConfig;
// Track if this config was loaded from zookeeper so that we can skip validating the zookeeper
@@ -144,6 +147,7 @@ private NodeConfig(
Properties solrProperties,
PluginInfo[] backupRepositoryPlugins,
MetricsConfig metricsConfig,
+ Map cachesConfig,
PluginInfo tracerConfig,
boolean fromZookeeper,
String defaultZkHost,
@@ -179,6 +183,7 @@ private NodeConfig(
this.solrProperties = solrProperties;
this.backupRepositoryPlugins = backupRepositoryPlugins;
this.metricsConfig = metricsConfig;
+ this.cachesConfig = cachesConfig == null ? Collections.emptyMap() : cachesConfig;
this.tracerConfig = tracerConfig;
this.fromZookeeper = fromZookeeper;
this.defaultZkHost = defaultZkHost;
@@ -395,6 +400,10 @@ public MetricsConfig getMetricsConfig() {
return metricsConfig;
}
+ public Map getCachesConfig() {
+ return cachesConfig;
+ }
+
public PluginInfo getTracerConfiguratorPluginInfo() {
return tracerConfig;
}
@@ -600,6 +609,7 @@ public static class NodeConfigBuilder {
private Properties solrProperties = new Properties();
private PluginInfo[] backupRepositoryPlugins;
private MetricsConfig metricsConfig;
+ private Map cachesConfig;
private PluginInfo tracerConfig;
private boolean fromZookeeper = false;
private String defaultZkHost;
@@ -769,6 +779,11 @@ public NodeConfigBuilder setMetricsConfig(MetricsConfig metricsConfig) {
return this;
}
+ public NodeConfigBuilder setCachesConfig(Map cachesConfig) {
+ this.cachesConfig = cachesConfig;
+ return this;
+ }
+
public NodeConfigBuilder setTracerConfig(PluginInfo tracerConfig) {
this.tracerConfig = tracerConfig;
return this;
@@ -881,6 +896,7 @@ public NodeConfig build() {
solrProperties,
backupRepositoryPlugins,
metricsConfig,
+ cachesConfig,
tracerConfig,
fromZookeeper,
defaultZkHost,
diff --git a/solr/core/src/java/org/apache/solr/core/SolrConfig.java b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
index c976f1a8c7f..7fb27347818 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrConfig.java
@@ -90,7 +90,7 @@
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
import org.apache.solr.util.DOMConfigNode;
import org.apache.solr.util.DataConfigNode;
-import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
+import org.apache.solr.util.circuitbreaker.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -346,7 +346,8 @@ private SolrConfig(
for (SolrPluginInfo plugin : plugins) loadPluginInfo(plugin);
Map userCacheConfigs =
- CacheConfig.getMultipleConfigs(this, "query/cache", get("query").getAll("cache"));
+ CacheConfig.getMultipleConfigs(
+ getResourceLoader(), this, "query/cache", get("query").getAll("cache"));
List caches = getPluginInfos(SolrCache.class.getName());
if (!caches.isEmpty()) {
for (PluginInfo c : caches) {
@@ -408,6 +409,7 @@ private IndexSchemaFactory.VersionedConfig readXml(SolrResourceLoader loader, St
private static final AtomicBoolean versionWarningAlreadyLogged = new AtomicBoolean(false);
+ @SuppressWarnings("ReferenceEquality") // Use of == is intentional here
public static final Version parseLuceneVersionString(final String matchVersion) {
final Version version;
try {
@@ -419,7 +421,9 @@ public static final Version parseLuceneVersionString(final String matchVersion)
pe);
}
- if (Objects.equals(version, Version.LATEST) && !versionWarningAlreadyLogged.getAndSet(true)) {
+ // The use of == is intentional here because the latest 'V.V.V' version will be equal() to
+ // Version.LATEST, but will not be == to Version.LATEST unless 'LATEST' was supplied.
+ if (version == Version.LATEST && !versionWarningAlreadyLogged.getAndSet(true)) {
log.warn(
"You should not use LATEST as luceneMatchVersion property: "
+ "if you use this setting, and then Solr upgrades to a newer release of Lucene, "
@@ -503,7 +507,7 @@ public static final Version parseLuceneVersionString(final String matchVersion)
new SolrPluginInfo(IndexSchemaFactory.class, "schemaFactory", REQUIRE_CLASS),
new SolrPluginInfo(RestManager.class, "restManager"),
new SolrPluginInfo(StatsCache.class, "statsCache", REQUIRE_CLASS),
- new SolrPluginInfo(CircuitBreakerManager.class, "circuitBreaker"));
+ new SolrPluginInfo(CircuitBreaker.class, "circuitBreaker", REQUIRE_CLASS, MULTI_OK));
public static final Map classVsSolrPluginInfo;
static {
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 661e9f1e29f..944d4684016 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -173,7 +173,8 @@
import org.apache.solr.util.PropertiesOutputStream;
import org.apache.solr.util.RefCounted;
import org.apache.solr.util.TestInjection;
-import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
+import org.apache.solr.util.circuitbreaker.CircuitBreaker;
+import org.apache.solr.util.circuitbreaker.CircuitBreakerRegistry;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.apache.solr.util.plugin.SolrCoreAware;
@@ -245,7 +246,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
private final ConfigSet configSet;
// singleton listener for all packages used in schema
- private final CircuitBreakerManager circuitBreakerManager;
+ private final CircuitBreakerRegistry circuitBreakerRegistry = new CircuitBreakerRegistry();
private final List confListeners = new CopyOnWriteArrayList<>();
@@ -1084,10 +1085,12 @@ private SolrCore(
this.configSetProperties = configSet.getProperties();
// Initialize the metrics manager
this.coreMetricManager = initCoreMetricManager(solrConfig);
- this.circuitBreakerManager = initCircuitBreakerManager();
solrMetricsContext = coreMetricManager.getSolrMetricsContext();
this.coreMetricManager.loadReporters();
+ // init pluggable circuit breakers
+ initPlugins(null, CircuitBreaker.class);
+
if (updateHandler == null) {
directoryFactory = initDirectoryFactory();
recoveryStrategyBuilder = initRecoveryStrategyBuilder();
@@ -1324,13 +1327,6 @@ private SolrCoreMetricManager initCoreMetricManager(SolrConfig config) {
return coreMetricManager;
}
- private CircuitBreakerManager initCircuitBreakerManager() {
- final PluginInfo info = solrConfig.getPluginInfo(CircuitBreakerManager.class.getName());
- CircuitBreakerManager circuitBreakerManager = CircuitBreakerManager.build(info);
-
- return circuitBreakerManager;
- }
-
@Override
public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
newSearcherCounter = parentContext.counter("new", Category.SEARCHER.toString());
@@ -1710,8 +1706,8 @@ public PluginBag getUpdateProcessors() {
return updateProcessors;
}
- public CircuitBreakerManager getCircuitBreakerManager() {
- return circuitBreakerManager;
+ public CircuitBreakerRegistry getCircuitBreakerRegistry() {
+ return circuitBreakerRegistry;
}
// this core current usage count
@@ -2643,6 +2639,7 @@ public RefCounted getSearcher(
future =
searcherExecutor.submit(
() -> {
+ newSearcher.bootstrapFirstSearcher();
for (SolrEventListener listener : firstSearcherListeners) {
try {
listener.newSearcher(newSearcher, null);
@@ -2904,7 +2901,7 @@ public void execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryR
}
/* slowQueryThresholdMillis defaults to -1 in SolrConfig -- not enabled.*/
- if (log.isWarnEnabled() && slowQueryThresholdMillis >= 0) {
+ if (slowLog.isWarnEnabled() && slowQueryThresholdMillis >= 0) {
final long qtime = (long) (req.getRequestTimer().getTime());
if (qtime >= slowQueryThresholdMillis) {
slowLog.warn("slow: {}", rsp.getToLogAsString());
@@ -3164,11 +3161,14 @@ public T initPlugins(
T def = null;
for (PluginInfo info : pluginInfos) {
T o = createInitInstance(info, type, type.getSimpleName(), defClassName);
- registry.put(info.name, o);
+ if (registry != null) registry.put(info.name, o);
if (o instanceof SolrMetricProducer) {
coreMetricManager.registerMetricProducer(
type.getSimpleName() + "." + info.name, (SolrMetricProducer) o);
}
+ if (o instanceof CircuitBreaker) {
+ circuitBreakerRegistry.register((CircuitBreaker) o);
+ }
if (info.isDefault()) {
def = o;
}
diff --git a/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java b/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
index e82a05d66b2..ffe7da03481 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrResourceLoader.java
@@ -97,6 +97,7 @@ public class SolrResourceLoader
"request.",
"update.processor.",
"util.",
+ "util.circuitbreaker.",
"spelling.",
"handler.component.",
"spelling.suggest.",
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 32f36091101..85fbaeeb193 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -46,6 +46,7 @@
import org.apache.solr.common.util.Utils;
import org.apache.solr.logging.LogWatcherConfig;
import org.apache.solr.metrics.reporters.SolrJmxReporter;
+import org.apache.solr.search.CacheConfig;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.update.UpdateShardHandlerConfig;
import org.apache.solr.util.DOMConfigNode;
@@ -177,6 +178,7 @@ public static NodeConfig fromConfig(
// Remove this line in 10.0
configBuilder.setHiddenSysProps(getHiddenSysProps(root.get("metrics")));
configBuilder.setMetricsConfig(getMetricsConfig(root.get("metrics")));
+ configBuilder.setCachesConfig(getCachesConfig(loader, root.get("caches")));
configBuilder.setFromZookeeper(fromZookeeper);
configBuilder.setDefaultZkHost(defaultZkHost);
configBuilder.setCoreAdminHandlerActions(coreAdminHandlerActions);
@@ -688,6 +690,20 @@ private static MetricsConfig getMetricsConfig(ConfigNode metrics) {
return builder.setMetricReporterPlugins(reporterPlugins).build();
}
+ private static Map getCachesConfig(
+ SolrResourceLoader loader, ConfigNode caches) {
+ Map ret =
+ CacheConfig.getMultipleConfigs(loader, null, null, caches.getAll("cache"));
+ for (CacheConfig c : ret.values()) {
+ if (c.getRegenerator() != null) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ "node-level caches should not be configured with a regenerator!");
+ }
+ }
+ return Collections.unmodifiableMap(ret);
+ }
+
private static Object decodeNullValue(Object o) {
if (o instanceof String) { // check if it's a JSON object
String str = (String) o;
diff --git a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java b/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
index bc1798863f7..633201db82f 100644
--- a/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
+++ b/solr/core/src/java/org/apache/solr/core/TracerConfigurator.java
@@ -19,17 +19,24 @@
import io.opentelemetry.api.trace.Tracer;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
+import org.apache.solr.util.tracing.SimplePropagator;
import org.apache.solr.util.tracing.TraceUtils;
/** Produces a {@link Tracer} from configuration. */
public abstract class TracerConfigurator implements NamedListInitializedPlugin {
+ public static final boolean TRACE_ID_GEN_ENABLED =
+ Boolean.parseBoolean(System.getProperty("solr.alwaysOnTraceId", "true"));
+
public static Tracer loadTracer(SolrResourceLoader loader, PluginInfo info) {
if (info != null && info.isEnabled()) {
TracerConfigurator configurator =
loader.newInstance(info.className, TracerConfigurator.class);
configurator.init(info.initArgs);
return configurator.getTracer();
+
+ } else if (TRACE_ID_GEN_ENABLED) {
+ return SimplePropagator.load();
} else {
return TraceUtils.noop();
}
diff --git a/solr/core/src/java/org/apache/solr/handler/ContentStreamHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/ContentStreamHandlerBase.java
index f00c68b39e4..fb40af3c081 100644
--- a/solr/core/src/java/org/apache/solr/handler/ContentStreamHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/ContentStreamHandlerBase.java
@@ -16,6 +16,11 @@
*/
package org.apache.solr.handler;
+import static org.apache.solr.common.params.CommonParams.FAILURE;
+import static org.apache.solr.common.params.CommonParams.STATUS;
+
+import java.util.List;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
@@ -26,6 +31,8 @@
import org.apache.solr.update.SolrCoreState;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
+import org.apache.solr.util.circuitbreaker.CircuitBreaker;
+import org.apache.solr.util.circuitbreaker.CircuitBreakerRegistry;
/**
* Shares common code between various handlers that manipulate {@link
@@ -49,6 +56,10 @@ public void init(NamedList> args) {
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ if (checkCircuitBreakers(req, rsp)) {
+ return; // Circuit breaker tripped, return immediately
+ }
+
/*
We track update requests so that we can preserve consistency by waiting for them to complete
on a node shutdown and then immediately trigger a leader election without waiting for the core to close.
@@ -101,6 +112,30 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
}
}
+ /**
+ * Check if {@link SolrRequestType#UPDATE} circuit breakers are tripped. Override this method in
+ * sub classes that do not want to check circuit breakers.
+ *
+ * @return true if circuit breakers are tripped, false otherwise.
+ */
+ protected boolean checkCircuitBreakers(SolrQueryRequest req, SolrQueryResponse rsp) {
+ CircuitBreakerRegistry circuitBreakerRegistry = req.getCore().getCircuitBreakerRegistry();
+ if (circuitBreakerRegistry.isEnabled(SolrRequestType.UPDATE)) {
+ List trippedCircuitBreakers =
+ circuitBreakerRegistry.checkTripped(SolrRequestType.UPDATE);
+ if (trippedCircuitBreakers != null) {
+ String errorMessage = CircuitBreakerRegistry.toErrorMessage(trippedCircuitBreakers);
+ rsp.add(STATUS, FAILURE);
+ rsp.setException(
+ new SolrException(
+ CircuitBreaker.getErrorCode(trippedCircuitBreakers),
+ "Circuit Breakers tripped " + errorMessage));
+ return true;
+ }
+ }
+ return false;
+ }
+
protected abstract ContentStreamLoader newLoader(
SolrQueryRequest req, UpdateRequestProcessor processor);
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
index bfd939c43b4..788fb494757 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
@@ -28,6 +28,11 @@
class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
+ @Override
+ public boolean isExpensive() {
+ return true;
+ }
+
@Override
public void execute(CoreAdminHandler.CallInfo it) throws Exception {
final SolrParams params = it.req.getParams();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 4b63e0c5138..97de7a36bb3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -123,7 +123,9 @@
import org.apache.solr.api.Api;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
+import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.RequestStatusState;
@@ -164,8 +166,8 @@
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.admin.api.AddReplicaProperty;
import org.apache.solr.handler.admin.api.AdminAPIBase;
-import org.apache.solr.handler.admin.api.AliasPropertyAPI;
-import org.apache.solr.handler.admin.api.BalanceReplicasAPI;
+import org.apache.solr.handler.admin.api.AliasProperty;
+import org.apache.solr.handler.admin.api.BalanceReplicas;
import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
import org.apache.solr.handler.admin.api.CollectionPropertyAPI;
import org.apache.solr.handler.admin.api.CollectionStatusAPI;
@@ -177,29 +179,29 @@
import org.apache.solr.handler.admin.api.CreateShardAPI;
import org.apache.solr.handler.admin.api.DeleteAlias;
import org.apache.solr.handler.admin.api.DeleteCollection;
-import org.apache.solr.handler.admin.api.DeleteCollectionBackupAPI;
-import org.apache.solr.handler.admin.api.DeleteCollectionSnapshotAPI;
-import org.apache.solr.handler.admin.api.DeleteNodeAPI;
-import org.apache.solr.handler.admin.api.DeleteReplicaAPI;
-import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.DeleteCollectionBackup;
+import org.apache.solr.handler.admin.api.DeleteCollectionSnapshot;
+import org.apache.solr.handler.admin.api.DeleteNode;
+import org.apache.solr.handler.admin.api.DeleteReplica;
+import org.apache.solr.handler.admin.api.DeleteReplicaProperty;
import org.apache.solr.handler.admin.api.DeleteShardAPI;
-import org.apache.solr.handler.admin.api.ForceLeaderAPI;
+import org.apache.solr.handler.admin.api.ForceLeader;
import org.apache.solr.handler.admin.api.InstallShardDataAPI;
-import org.apache.solr.handler.admin.api.ListAliasesAPI;
-import org.apache.solr.handler.admin.api.ListCollectionBackupsAPI;
+import org.apache.solr.handler.admin.api.ListAliases;
+import org.apache.solr.handler.admin.api.ListCollectionBackups;
import org.apache.solr.handler.admin.api.ListCollectionSnapshotsAPI;
-import org.apache.solr.handler.admin.api.ListCollectionsAPI;
+import org.apache.solr.handler.admin.api.ListCollections;
import org.apache.solr.handler.admin.api.MigrateDocsAPI;
import org.apache.solr.handler.admin.api.MigrateReplicasAPI;
import org.apache.solr.handler.admin.api.ModifyCollectionAPI;
import org.apache.solr.handler.admin.api.MoveReplicaAPI;
import org.apache.solr.handler.admin.api.RebalanceLeadersAPI;
import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
-import org.apache.solr.handler.admin.api.RenameCollectionAPI;
-import org.apache.solr.handler.admin.api.ReplaceNodeAPI;
+import org.apache.solr.handler.admin.api.RenameCollection;
+import org.apache.solr.handler.admin.api.ReplaceNode;
import org.apache.solr.handler.admin.api.RestoreCollectionAPI;
import org.apache.solr.handler.admin.api.SplitShardAPI;
-import org.apache.solr.handler.admin.api.SyncShardAPI;
+import org.apache.solr.handler.admin.api.SyncShard;
import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.request.SolrQueryRequest;
@@ -606,7 +608,7 @@ public enum CollectionOperation implements CollectionOp {
SYNCSHARD_OP(
SYNCSHARD,
(req, rsp, h) -> {
- SyncShardAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+ SyncShard.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
@@ -640,11 +642,10 @@ public enum CollectionOperation implements CollectionOp {
(req, rsp, h) -> {
String name = req.getParams().required().get(NAME);
Map properties = collectToMap(req.getParams(), "property");
- AliasPropertyAPI.UpdateAliasPropertiesRequestBody requestBody =
- new AliasPropertyAPI.UpdateAliasPropertiesRequestBody();
+ final var requestBody = new UpdateAliasPropertiesRequestBody();
requestBody.properties = properties;
requestBody.async = req.getParams().get(ASYNC);
- final AliasPropertyAPI aliasPropertyAPI = new AliasPropertyAPI(h.coreContainer, req, rsp);
+ final AliasProperty aliasPropertyAPI = new AliasProperty(h.coreContainer, req, rsp);
final SolrJerseyResponse getAliasesResponse =
aliasPropertyAPI.updateAliasProperties(name, requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
@@ -655,7 +656,7 @@ public enum CollectionOperation implements CollectionOp {
LISTALIASES_OP(
LISTALIASES,
(req, rsp, h) -> {
- final ListAliasesAPI getAliasesAPI = new ListAliasesAPI(h.coreContainer, req, rsp);
+ final ListAliases getAliasesAPI = new ListAliases(h.coreContainer, req, rsp);
final SolrJerseyResponse getAliasesResponse = getAliasesAPI.getAliases();
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
return null;
@@ -722,7 +723,7 @@ public enum CollectionOperation implements CollectionOp {
FORCELEADER_OP(
FORCELEADER,
(req, rsp, h) -> {
- ForceLeaderAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+ ForceLeader.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
CREATESHARD_OP(
@@ -734,7 +735,7 @@ public enum CollectionOperation implements CollectionOp {
DELETEREPLICA_OP(
DELETEREPLICA,
(req, rsp, h) -> {
- DeleteReplicaAPI.invokeWithV1Params(h.coreContainer, req, rsp);
+ DeleteReplica.invokeWithV1Params(h.coreContainer, req, rsp);
return null;
}),
MIGRATE_OP(
@@ -965,8 +966,7 @@ public Map execute(
LIST_OP(
LIST,
(req, rsp, h) -> {
- final ListCollectionsAPI listCollectionsAPI =
- new ListCollectionsAPI(h.coreContainer, req, rsp);
+ final ListCollections listCollectionsAPI = new ListCollections(h.coreContainer, req, rsp);
final SolrJerseyResponse listCollectionsResponse = listCollectionsAPI.listCollections();
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, listCollectionsResponse);
return null;
@@ -1013,9 +1013,9 @@ public Map execute(
DELETEREPLICAPROP_OP(
DELETEREPLICAPROP,
(req, rsp, h) -> {
- final var api = new DeleteReplicaPropertyAPI(h.coreContainer, req, rsp);
+ final var api = new DeleteReplicaProperty(h.coreContainer, req, rsp);
final var deleteReplicaPropResponse =
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, req.getParams());
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, req.getParams());
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteReplicaPropResponse);
return null;
}),
@@ -1096,14 +1096,14 @@ public Map execute(
DELETEBACKUP_OP(
DELETEBACKUP,
(req, rsp, h) -> {
- DeleteCollectionBackupAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+ DeleteCollectionBackup.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
LISTBACKUP_OP(
LISTBACKUP,
(req, rsp, h) -> {
req.getParams().required().check(NAME);
- ListCollectionBackupsAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+ ListCollectionBackups.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
CREATESNAPSHOT_OP(
@@ -1142,10 +1142,10 @@ public Map execute(
final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
final String asyncId = req.getParams().get(ASYNC);
- final DeleteCollectionSnapshotAPI deleteCollectionSnapshotAPI =
- new DeleteCollectionSnapshotAPI(h.coreContainer, req, rsp);
+ final DeleteCollectionSnapshot deleteCollectionSnapshotAPI =
+ new DeleteCollectionSnapshot(h.coreContainer, req, rsp);
- final DeleteCollectionSnapshotAPI.DeleteSnapshotResponse deleteSnapshotResponse =
+ final var deleteSnapshotResponse =
deleteCollectionSnapshotAPI.deleteSnapshot(
extCollectionName, commitName, followAliases, asyncId);
@@ -1176,12 +1176,11 @@ public Map execute(
(req, rsp, h) -> {
final SolrParams params = req.getParams();
final RequiredSolrParams requiredParams = req.getParams().required();
- final ReplaceNodeAPI.ReplaceNodeRequestBody requestBody =
- new ReplaceNodeAPI.ReplaceNodeRequestBody();
+ final var requestBody = new ReplaceNodeRequestBody();
requestBody.targetNodeName = params.get(TARGET_NODE);
requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
requestBody.async = params.get(ASYNC);
- final ReplaceNodeAPI replaceNodeAPI = new ReplaceNodeAPI(h.coreContainer, req, rsp);
+ final ReplaceNode replaceNodeAPI = new ReplaceNode(h.coreContainer, req, rsp);
final SolrJerseyResponse replaceNodeResponse =
replaceNodeAPI.replaceNode(requiredParams.get(SOURCE_NODE), requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, replaceNodeResponse);
@@ -1207,9 +1206,9 @@ public Map execute(
DELETENODE_OP(
DELETENODE,
(req, rsp, h) -> {
- final DeleteNodeAPI deleteNodeAPI = new DeleteNodeAPI(h.coreContainer, req, rsp);
+ final DeleteNode deleteNodeAPI = new DeleteNode(h.coreContainer, req, rsp);
final SolrJerseyResponse deleteNodeResponse =
- DeleteNodeAPI.invokeUsingV1Inputs(deleteNodeAPI, req.getParams());
+ DeleteNode.invokeUsingV1Inputs(deleteNodeAPI, req.getParams());
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteNodeResponse);
return null;
}),
@@ -1371,29 +1370,29 @@ public Collection> getJerseyResources() {
CreateCollectionBackupAPI.class,
CreateShardAPI.class,
DeleteAlias.class,
- DeleteCollectionBackupAPI.class,
+ DeleteCollectionBackup.class,
DeleteCollection.class,
- DeleteReplicaAPI.class,
- DeleteReplicaPropertyAPI.class,
+ DeleteReplica.class,
+ DeleteReplicaProperty.class,
DeleteShardAPI.class,
- ForceLeaderAPI.class,
+ ForceLeader.class,
InstallShardDataAPI.class,
- ListCollectionsAPI.class,
- ListCollectionBackupsAPI.class,
+ ListCollections.class,
+ ListCollectionBackups.class,
ReloadCollectionAPI.class,
- RenameCollectionAPI.class,
- ReplaceNodeAPI.class,
+ RenameCollection.class,
+ ReplaceNode.class,
MigrateReplicasAPI.class,
- BalanceReplicasAPI.class,
+ BalanceReplicas.class,
RestoreCollectionAPI.class,
- SyncShardAPI.class,
+ SyncShard.class,
CollectionPropertyAPI.class,
- DeleteNodeAPI.class,
- ListAliasesAPI.class,
- AliasPropertyAPI.class,
+ DeleteNode.class,
+ ListAliases.class,
+ AliasProperty.class,
ListCollectionSnapshotsAPI.class,
CreateCollectionSnapshotAPI.class,
- DeleteCollectionSnapshotAPI.class);
+ DeleteCollectionSnapshot.class);
}
@Override
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 96015b2486e..e5bd2945b43 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
@@ -43,7 +43,7 @@
import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.handler.configsets.CreateConfigSetAPI;
import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
-import org.apache.solr.handler.configsets.ListConfigSetsAPI;
+import org.apache.solr.handler.configsets.ListConfigSets;
import org.apache.solr.handler.configsets.UploadConfigSetAPI;
import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
import org.apache.solr.request.DelegatingSolrQueryRequest;
@@ -142,7 +142,7 @@ public SolrParams getParams() {
}
break;
case LIST:
- final ListConfigSetsAPI listConfigSetsAPI = new ListConfigSetsAPI(coreContainer);
+ final ListConfigSets listConfigSetsAPI = new ListConfigSets(coreContainer);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, listConfigSetsAPI.listConfigSet());
break;
case CREATE:
@@ -217,7 +217,7 @@ public Collection getApis() {
@Override
public Collection> getJerseyResources() {
- return List.of(ListConfigSetsAPI.class);
+ return List.of(ListConfigSets.class);
}
@Override
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index 94212c883cf..f9819dcaf80 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -97,6 +97,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
protected final CoreAdminAsyncTracker coreAdminAsyncTracker;
public static String RESPONSE_STATUS = "STATUS";
+
public static String RESPONSE_MESSAGE = "msg";
public static String OPERATION_RESPONSE = "response";
@@ -129,13 +130,21 @@ public final void init(NamedList> args) {
@Override
public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
super.initializeMetrics(parentContext, scope);
- coreAdminAsyncTracker.parallelExecutor =
+ coreAdminAsyncTracker.standardExecutor =
MetricUtils.instrumentedExecutorService(
- coreAdminAsyncTracker.parallelExecutor,
+ coreAdminAsyncTracker.standardExecutor,
this,
solrMetricsContext.getMetricRegistry(),
SolrMetricManager.mkName(
"parallelCoreAdminExecutor", getCategory().name(), scope, "threadPool"));
+
+ coreAdminAsyncTracker.expensiveExecutor =
+ MetricUtils.instrumentedExecutorService(
+ coreAdminAsyncTracker.expensiveExecutor,
+ this,
+ solrMetricsContext.getMetricRegistry(),
+ SolrMetricManager.mkName(
+ "parallelCoreExpensiveAdminExecutor", getCategory().name(), scope, "threadPool"));
}
@Override
@@ -196,8 +205,6 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
}
// boolean doPersist = false;
final String taskId = req.getParams().get(CommonAdminParams.ASYNC);
- final CoreAdminAsyncTracker.TaskObject taskObject =
- new CoreAdminAsyncTracker.TaskObject(taskId);
// Pick the action
final String action = req.getParams().get(ACTION, STATUS.toString()).toLowerCase(Locale.ROOT);
@@ -220,13 +227,16 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
if (taskId == null) {
callInfo.call();
} else {
- coreAdminAsyncTracker.submitAsyncTask(
- taskObject,
- action,
+ Callable task =
() -> {
callInfo.call();
return callInfo.rsp;
- });
+ };
+
+ var taskObject =
+ new CoreAdminAsyncTracker.TaskObject(taskId, action, op.isExpensive(), task);
+
+ coreAdminAsyncTracker.submitAsyncTask(taskObject);
}
} finally {
rsp.setHttpCaching(false);
@@ -332,7 +342,7 @@ public Name getPermissionName(AuthorizationContext ctx) {
/** Method to ensure shutting down of the ThreadPool Executor. */
public void shutdown() {
- if (coreAdminAsyncTracker.parallelExecutor != null) coreAdminAsyncTracker.shutdown();
+ coreAdminAsyncTracker.shutdown();
}
private static final Map opMap = new HashMap<>();
@@ -393,6 +403,11 @@ public Collection> getJerseyResources() {
}
public interface CoreAdminOp {
+
+ default boolean isExpensive() {
+ return false;
+ }
+
/**
* @param it request/response object
* If the request is invalid throw a SolrException with
@@ -411,10 +426,18 @@ public static class CoreAdminAsyncTracker {
public static final String FAILED = "failed";
public final Map> requestStatusMap;
- private ExecutorService parallelExecutor =
+ // Executor for all standard tasks (the ones that are not flagged as expensive)
+ // We always keep 50 live threads
+ private ExecutorService standardExecutor =
ExecutorUtil.newMDCAwareFixedThreadPool(
50, new SolrNamedThreadFactory("parallelCoreAdminAPIBaseExecutor"));
+ // Executor for expensive tasks
+ // We keep the number number of max threads very low to have throttling for expensive tasks
+ private ExecutorService expensiveExecutor =
+ ExecutorUtil.newMDCAwareCachedThreadPool(
+ 5, new SolrNamedThreadFactory("parallelCoreAdminAPIExpensiveExecutor"));
+
public CoreAdminAsyncTracker() {
HashMap> map = new HashMap<>(3, 1.0f);
map.put(RUNNING, Collections.synchronizedMap(new LinkedHashMap<>()));
@@ -424,36 +447,41 @@ public CoreAdminAsyncTracker() {
}
public void shutdown() {
- ExecutorUtil.shutdownAndAwaitTermination(parallelExecutor);
+ ExecutorUtil.shutdownAndAwaitTermination(standardExecutor);
+ ExecutorUtil.shutdownAndAwaitTermination(expensiveExecutor);
}
public Map getRequestStatusMap(String key) {
return requestStatusMap.get(key);
}
- public void submitAsyncTask(
- TaskObject taskObject, String action, Callable task)
- throws SolrException {
+ public void submitAsyncTask(TaskObject taskObject) throws SolrException {
ensureTaskIdNotInUse(taskObject.taskId);
addTask(RUNNING, taskObject);
+ Runnable command =
+ () -> {
+ boolean exceptionCaught = false;
+ try {
+ final SolrQueryResponse response = taskObject.task.call();
+ taskObject.setRspObject(response);
+ taskObject.setOperationRspObject(response);
+ } catch (Exception e) {
+ exceptionCaught = true;
+ taskObject.setRspObjectFromException(e);
+ } finally {
+ finishTask(taskObject, !exceptionCaught);
+ }
+ };
+
try {
MDC.put("CoreAdminHandler.asyncId", taskObject.taskId);
- MDC.put("CoreAdminHandler.action", action);
- parallelExecutor.execute(
- () -> {
- boolean exceptionCaught = false;
- try {
- final SolrQueryResponse response = task.call();
- taskObject.setRspObject(response);
- taskObject.setOperationRspObject(response);
- } catch (Exception e) {
- exceptionCaught = true;
- taskObject.setRspObjectFromException(e);
- } finally {
- finishTask(taskObject, !exceptionCaught);
- }
- });
+ MDC.put("CoreAdminHandler.action", taskObject.action);
+ if (taskObject.expensive) {
+ expensiveExecutor.execute(command);
+ } else {
+ standardExecutor.execute(command);
+ }
} finally {
MDC.remove("CoreAdminHandler.asyncId");
MDC.remove("CoreAdminHandler.action");
@@ -503,12 +531,19 @@ private void finishTask(TaskObject taskObject, boolean successful) {
* response (if available).
*/
public static class TaskObject {
- public String taskId;
+ final String taskId;
+ final String action;
+ final boolean expensive;
+ final Callable task;
public String rspInfo;
public Object operationRspInfo;
- public TaskObject(String taskId) {
+ public TaskObject(
+ String taskId, String action, boolean expensive, Callable task) {
this.taskId = taskId;
+ this.action = action;
+ this.expensive = expensive;
+ this.task = task;
}
public String getRspObject() {
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
index bf82ae38c4e..31251759c76 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
@@ -309,6 +309,12 @@ static Logger log() {
return log;
}
+ @Override
+ public boolean isExpensive() {
+ // delegates this to the actual implementation
+ return fun.isExpensive();
+ }
+
/**
* Returns the core status for a particular core.
*
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/RestoreCoreOp.java b/solr/core/src/java/org/apache/solr/handler/admin/RestoreCoreOp.java
index b7f97469b49..c95af84d3d7 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/RestoreCoreOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/RestoreCoreOp.java
@@ -24,6 +24,12 @@
import org.apache.solr.handler.api.V2ApiUtils;
class RestoreCoreOp implements CoreAdminHandler.CoreAdminOp {
+
+ @Override
+ public boolean isExpensive() {
+ return true;
+ }
+
@Override
public void execute(CoreAdminHandler.CallInfo it) throws Exception {
final SolrParams params = it.req.getParams();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index 9142449cba6..95e1d10f351 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -28,6 +28,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Collectors;
import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
@@ -52,6 +53,7 @@
import org.apache.solr.security.AuthorizationPlugin;
import org.apache.solr.security.ConfigEditablePlugin;
import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.util.tracing.TraceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -147,6 +149,7 @@ private void doEdit(
if (persistConf(securityConfig)) {
securityConfEdited();
+ updateTraceOps(req, configEditablePlugin.getClass().getSimpleName(), commandsCopy);
return;
}
}
@@ -156,6 +159,11 @@ private void doEdit(
SERVER_ERROR, "Failed to persist security config after 3 attempts. Giving up");
}
+ private void updateTraceOps(SolrQueryRequest req, String clazz, List commands) {
+ TraceUtils.setOperations(
+ req, clazz, commands.stream().map(c -> c.name).collect(Collectors.toUnmodifiableList()));
+ }
+
/** Hook where you can do stuff after a config has been edited. Defaults to NOP */
protected void securityConfEdited() {}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SplitOp.java b/solr/core/src/java/org/apache/solr/handler/admin/SplitOp.java
index 3b66794c5d5..fd29d31fcd0 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SplitOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SplitOp.java
@@ -70,6 +70,11 @@ class SplitOp implements CoreAdminHandler.CoreAdminOp {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ @Override
+ public boolean isExpensive() {
+ return true;
+ }
+
@Override
public void execute(CoreAdminHandler.CallInfo it) throws Exception {
SolrParams params = it.req.getParams();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AliasProperty.java
similarity index 62%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/AliasProperty.java
index baeb6ddc7ac..1184751cb9a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AliasProperty.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonParams.NAME;
@@ -24,22 +23,15 @@
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.endpoint.AliasPropertyApis;
+import org.apache.solr.client.api.model.GetAliasPropertyResponse;
+import org.apache.solr.client.api.model.GetAllAliasPropertiesResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
+import org.apache.solr.client.api.model.UpdateAliasPropertyRequestBody;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.Aliases;
@@ -48,32 +40,24 @@
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/** V2 APIs for managing and inspecting properties for collection aliases */
-@Path("/aliases/{aliasName}/properties")
-public class AliasPropertyAPI extends AdminAPIBase {
+public class AliasProperty extends AdminAPIBase implements AliasPropertyApis {
@Inject
- public AliasPropertyAPI(
+ public AliasProperty(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @GET
+ @Override
@PermissionName(COLL_READ_PERM)
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
- @Operation(
- summary = "Get properties for a collection alias.",
- tags = {"aliases"})
- public GetAllAliasPropertiesResponse getAllAliasProperties(
- @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName)
- throws Exception {
+ public GetAllAliasPropertiesResponse getAllAliasProperties(String aliasName) throws Exception {
recordCollectionForLogAndTracing(null, solrQueryRequest);
final GetAllAliasPropertiesResponse response =
@@ -88,16 +72,9 @@ public GetAllAliasPropertiesResponse getAllAliasProperties(
return response;
}
- @GET
- @Path("/{propName}")
+ @Override
@PermissionName(COLL_READ_PERM)
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
- @Operation(
- summary = "Get a specific property for a collection alias.",
- tags = {"aliases"})
- public GetAliasPropertyResponse getAliasProperty(
- @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
- @Parameter(description = "Property Name") @PathParam("propName") String propName)
+ public GetAliasPropertyResponse getAliasProperty(String aliasName, String propName)
throws Exception {
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -124,17 +101,10 @@ private Aliases readAliasesFromZk() throws Exception {
return zkStateReader.getAliases();
}
- @PUT
+ @Override
@PermissionName(COLL_EDIT_PERM)
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
- @Operation(
- summary = "Update properties for a collection alias.",
- tags = {"aliases"})
public SolrJerseyResponse updateAliasProperties(
- @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
- @RequestBody(description = "Properties that need to be updated", required = true)
- UpdateAliasPropertiesRequestBody requestBody)
- throws Exception {
+ String aliasName, UpdateAliasPropertiesRequestBody requestBody) throws Exception {
if (requestBody == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -147,18 +117,10 @@ public SolrJerseyResponse updateAliasProperties(
return response;
}
- @PUT
- @Path("/{propName}")
+ @Override
@PermissionName(COLL_EDIT_PERM)
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
- @Operation(
- summary = "Update a specific property for a collection alias.",
- tags = {"aliases"})
public SolrJerseyResponse createOrUpdateAliasProperty(
- @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
- @Parameter(description = "Property Name") @PathParam("propName") String propName,
- @RequestBody(description = "Property value that needs to be updated", required = true)
- UpdateAliasPropertyRequestBody requestBody)
+ String aliasName, String propName, UpdateAliasPropertyRequestBody requestBody)
throws Exception {
if (requestBody == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -171,16 +133,9 @@ public SolrJerseyResponse createOrUpdateAliasProperty(
return response;
}
- @DELETE
- @Path("/{propName}")
+ @Override
@PermissionName(COLL_EDIT_PERM)
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
- @Operation(
- summary = "Delete a specific property for a collection alias.",
- tags = {"aliases"})
- public SolrJerseyResponse deleteAliasProperty(
- @Parameter(description = "Alias Name") @PathParam("aliasName") String aliasName,
- @Parameter(description = "Property Name") @PathParam("propName") String propName)
+ public SolrJerseyResponse deleteAliasProperty(String aliasName, String propName)
throws Exception {
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -233,32 +188,4 @@ public ZkNodeProps createRemoteMessage(
}
return new ZkNodeProps(remoteMessage);
}
-
- public static class UpdateAliasPropertiesRequestBody implements JacksonReflectMapWriter {
-
- @Schema(description = "Properties and values to be updated on alias.")
- @JsonProperty(value = "properties", required = true)
- public Map properties;
-
- @Schema(description = "Request ID to track this action which will be processed asynchronously.")
- @JsonProperty("async")
- public String async;
- }
-
- public static class UpdateAliasPropertyRequestBody implements JacksonReflectMapWriter {
- @JsonProperty(required = true)
- public Object value;
- }
-
- public static class GetAllAliasPropertiesResponse extends SolrJerseyResponse {
- @JsonProperty("properties")
- @Schema(description = "Properties and values associated with alias.")
- public Map properties;
- }
-
- public static class GetAliasPropertyResponse extends SolrJerseyResponse {
- @JsonProperty("value")
- @Schema(description = "Property value.")
- public String value;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
index 8f57c58f2d9..580f2849b2f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
@@ -56,6 +56,11 @@ public BackupCoreAPI(
super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse);
}
+ @Override
+ boolean isExpensive() {
+ return true;
+ }
+
@POST
@Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
@PermissionName(COLL_EDIT_PERM)
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicas.java
similarity index 63%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicas.java
index 53e23886585..6e163857296 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicas.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.params.CollectionParams.NODES;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -24,47 +23,35 @@
import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
+import org.apache.solr.client.api.endpoint.BalanceReplicasApi;
+import org.apache.solr.client.api.model.BalanceReplicasRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/** V2 API for balancing the replicas that already exist across a set of nodes. */
-@Path("cluster/replicas/balance")
-public class BalanceReplicasAPI extends AdminAPIBase {
+public class BalanceReplicas extends AdminAPIBase implements BalanceReplicasApi {
@Inject
- public BalanceReplicasAPI(
+ public BalanceReplicas(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- @Operation(summary = "Balance Replicas across the given set of Nodes.")
- public SolrJerseyResponse balanceReplicas(
- @RequestBody(description = "Contains user provided parameters")
- BalanceReplicasRequestBody requestBody)
+ public SolrJerseyResponse balanceReplicas(BalanceReplicasRequestBody requestBody)
throws Exception {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
@@ -96,33 +83,4 @@ public ZkNodeProps createRemoteMessage(BalanceReplicasRequestBody requestBody) {
return new ZkNodeProps(remoteMessage);
}
-
- public static class BalanceReplicasRequestBody implements JacksonReflectMapWriter {
-
- public BalanceReplicasRequestBody() {}
-
- public BalanceReplicasRequestBody(Set nodes, Boolean waitForFinalState, String async) {
- this.nodes = nodes;
- this.waitForFinalState = waitForFinalState;
- this.async = async;
- }
-
- @Schema(
- description =
- "The set of nodes across which replicas will be balanced. Defaults to all live data nodes.")
- @JsonProperty(value = "nodes")
- public Set nodes;
-
- @Schema(
- description =
- "If true, the request will complete only when all affected replicas become active. "
- + "If false, the API will return the status of the single action, which may be "
- + "before the new replica is online and active.")
- @JsonProperty("waitForFinalState")
- public Boolean waitForFinalState = false;
-
- @Schema(description = "Request ID to track this action which will be processed asynchronously.")
- @JsonProperty("async")
- public String async;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
index 6a4130e9132..69fe4c993ce 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.handler.admin.api;
+import java.util.concurrent.Callable;
import java.util.function.Supplier;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.api.model.SolrJerseyResponse;
@@ -52,6 +53,14 @@ public CoreAdminAPIBase(
this.rsp = rsp;
}
+ /**
+ * Can be overridden by operations that are expensive, so we don't execute too many of them
+ * concurrently.
+ */
+ boolean isExpensive() {
+ return false;
+ }
+
/**
* Wraps the subclasses logic with extra bookkeeping logic.
*
@@ -79,22 +88,23 @@ public T handlePotentiallyAsynchronousTask(
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "Core container instance missing");
}
- final CoreAdminHandler.CoreAdminAsyncTracker.TaskObject taskObject =
- new CoreAdminHandler.CoreAdminAsyncTracker.TaskObject(taskId);
MDCLoggingContext.setCoreName(coreName);
TraceUtils.setDbInstance(req, coreName);
if (taskId == null) {
return supplier.get();
} else {
- coreAdminAsyncTracker.submitAsyncTask(
- taskObject,
- actionName,
+ Callable task =
() -> {
T response = supplier.get();
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
return rsp;
- });
+ };
+
+ final CoreAdminHandler.CoreAdminAsyncTracker.TaskObject taskObject =
+ new CoreAdminHandler.CoreAdminAsyncTracker.TaskObject(
+ taskId, actionName, isExpensive(), task);
+ coreAdminAsyncTracker.submitAsyncTask(taskObject);
}
} catch (CoreAdminAPIBaseException e) {
throw e.trueException;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
index 5dd9fd96e09..0339b29a887 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
@@ -43,6 +43,7 @@
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.BackupDeletionData;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
@@ -220,11 +221,4 @@ public static class CollectionBackupDetails implements JacksonReflectMapWriter {
@JsonProperty public List shardBackupIds;
}
-
- public static class BackupDeletionData implements JacksonReflectMapWriter {
- @JsonProperty public String startTime;
- @JsonProperty public Integer backupId;
- @JsonProperty public Long size;
- @JsonProperty public Integer numFiles;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
similarity index 73%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
index 48a482dce3e..88afdc1eb50 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
@@ -17,7 +17,6 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -30,7 +29,6 @@
import static org.apache.solr.common.params.CoreAdminParams.NAME;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.HashMap;
@@ -38,14 +36,12 @@
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteCollectionBackupApi;
+import org.apache.solr.client.api.model.BackupDeletionData;
+import org.apache.solr.client.api.model.BackupDeletionResponseBody;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
+import org.apache.solr.client.api.model.PurgeUnusedResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;
-import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -54,7 +50,6 @@
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.jersey.SolrJacksonMapper;
import org.apache.solr.request.SolrQueryRequest;
@@ -65,12 +60,12 @@
*
* These APIs are equivalent to the v1 '/admin/collections?action=DELETEBACKUP' command.
*/
-public class DeleteCollectionBackupAPI extends BackupAPIBase {
+public class DeleteCollectionBackup extends BackupAPIBase implements DeleteCollectionBackupApi {
private final ObjectMapper objectMapper;
@Inject
- public DeleteCollectionBackupAPI(
+ public DeleteCollectionBackup(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
@@ -79,17 +74,10 @@ public DeleteCollectionBackupAPI(
this.objectMapper = SolrJacksonMapper.getObjectMapper();
}
- @Path("/backups/{backupName}/versions/{backupId}")
- @DELETE
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public BackupDeletionResponseBody deleteSingleBackupById(
- @PathParam("backupName") String backupName,
- @PathParam(BACKUP_ID) String backupId,
- // Optional parameters below
- @QueryParam(BACKUP_LOCATION) String location,
- @QueryParam(BACKUP_REPOSITORY) String repositoryName,
- @QueryParam(ASYNC) String asyncId)
+ String backupName, String backupId, String location, String repositoryName, String asyncId)
throws Exception {
final var response = instantiateJerseyResponse(BackupDeletionResponseBody.class);
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -108,17 +96,14 @@ public BackupDeletionResponseBody deleteSingleBackupById(
return response;
}
- @Path("/backups/{backupName}/versions")
- @DELETE
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public BackupDeletionResponseBody deleteMultipleBackupsByRecency(
- @PathParam("backupName") String backupName,
- @QueryParam("retainLatest") Integer versionsToRetain,
- // Optional parameters below
- @QueryParam(BACKUP_LOCATION) String location,
- @QueryParam(BACKUP_REPOSITORY) String repositoryName,
- @QueryParam(ASYNC) String asyncId)
+ String backupName,
+ Integer versionsToRetain,
+ String location,
+ String repositoryName,
+ String asyncId)
throws Exception {
final var response = instantiateJerseyResponse(BackupDeletionResponseBody.class);
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -138,13 +123,10 @@ public BackupDeletionResponseBody deleteMultipleBackupsByRecency(
return response;
}
- @Path("/backups/{backupName}/purgeUnused")
- @PUT
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public PurgeUnusedResponse garbageCollectUnusedBackupFiles(
- @PathParam("backupName") String backupName, PurgeUnusedFilesRequestBody requestBody)
- throws Exception {
+ String backupName, PurgeUnusedFilesRequestBody requestBody) throws Exception {
final var response = instantiateJerseyResponse(PurgeUnusedResponse.class);
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -163,31 +145,22 @@ public PurgeUnusedResponse garbageCollectUnusedBackupFiles(
Boolean.TRUE,
requestBody.location,
requestBody.repositoryName,
- requestBody.asyncId);
+ requestBody.async);
final var remoteResponse =
submitRemoteMessageAndHandleResponse(
response,
CollectionParams.CollectionAction.DELETEBACKUP,
remoteMessage,
- requestBody.asyncId);
+ requestBody.async);
final Object remoteDeleted = remoteResponse.getResponse().get("deleted");
if (remoteDeleted != null) {
- response.deleted = objectMapper.convertValue(remoteDeleted, PurgeUnusedStats.class);
+ response.deleted =
+ objectMapper.convertValue(remoteDeleted, PurgeUnusedResponse.PurgeUnusedStats.class);
}
return response;
}
- public static class PurgeUnusedResponse extends SubResponseAccumulatingJerseyResponse {
- @JsonProperty public PurgeUnusedStats deleted;
- }
-
- public static class PurgeUnusedStats implements JacksonReflectMapWriter {
- @JsonProperty public Integer numBackupIds;
- @JsonProperty public Integer numShardBackupIds;
- @JsonProperty public Integer numIndexFiles;
- }
-
public static ZkNodeProps createRemoteMessage(
String backupName,
String backupId,
@@ -231,48 +204,26 @@ public static void invokeFromV1Params(
BACKUP_ID));
}
- final var deleteApi = new DeleteCollectionBackupAPI(coreContainer, req, rsp);
+ final var deleteApi = new DeleteCollectionBackup(coreContainer, req, rsp);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, invokeApi(deleteApi, req.getParams()));
}
- public static class BackupDeletionResponseBody extends SubResponseAccumulatingJerseyResponse {
- @JsonProperty public String collection;
- @JsonProperty public List deleted;
- }
-
@SuppressWarnings("unchecked")
- public static List fromRemoteResponse(
+ public static List fromRemoteResponse(
ObjectMapper objectMapper, SolrResponse response) {
final var deleted = (List>) response.getResponse().get("deleted");
if (deleted == null) {
return null;
}
- final List statList = new ArrayList<>();
+ final List statList = new ArrayList<>();
for (SimpleOrderedMap remoteStat : deleted) {
- statList.add(
- objectMapper.convertValue(
- remoteStat, CreateCollectionBackupAPI.BackupDeletionData.class));
+ statList.add(objectMapper.convertValue(remoteStat, BackupDeletionData.class));
}
return statList;
}
- /**
- * Request body for the {@link DeleteCollectionBackupAPI#garbageCollectUnusedBackupFiles(String,
- * PurgeUnusedFilesRequestBody)} API.
- */
- public static class PurgeUnusedFilesRequestBody implements JacksonReflectMapWriter {
- @JsonProperty(BACKUP_LOCATION)
- public String location;
-
- @JsonProperty(BACKUP_REPOSITORY)
- public String repositoryName;
-
- @JsonProperty(ASYNC)
- public String asyncId;
- }
-
- private static SolrJerseyResponse invokeApi(DeleteCollectionBackupAPI api, SolrParams params)
+ private static SolrJerseyResponse invokeApi(DeleteCollectionBackup api, SolrParams params)
throws Exception {
if (params.get(MAX_NUM_BACKUP_POINTS) != null) {
return api.deleteMultipleBackupsByRecency(
@@ -285,7 +236,7 @@ private static SolrJerseyResponse invokeApi(DeleteCollectionBackupAPI api, SolrP
final var requestBody = new PurgeUnusedFilesRequestBody();
requestBody.location = params.get(BACKUP_LOCATION);
requestBody.repositoryName = params.get(BACKUP_REPOSITORY);
- requestBody.asyncId = params.get(ASYNC);
+ requestBody.async = params.get(ASYNC);
return api.garbageCollectUnusedBackupFiles(params.get(NAME), requestBody);
} else { // BACKUP_ID != null
return api.deleteSingleBackupById(
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
similarity index 62%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
index d77a91cafca..e591f85db6f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
@@ -24,19 +23,11 @@
import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.AsyncJerseyResponse;
+import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CollectionParams;
@@ -47,37 +38,23 @@
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
-/** V2 API for Deleting Collection Snapshots. */
-@Path("/collections/{collName}/snapshots")
-public class DeleteCollectionSnapshotAPI extends AdminAPIBase {
+/** V2 API impl for Deleting Collection Snapshots. */
+public class DeleteCollectionSnapshot extends AdminAPIBase implements DeleteCollectionSnapshotApi {
@Inject
- public DeleteCollectionSnapshotAPI(
+ public DeleteCollectionSnapshot(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
- @DELETE
- @Path("/{snapshotName}")
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- public DeleteSnapshotResponse deleteSnapshot(
- @Parameter(description = "The name of the collection.", required = true)
- @PathParam("collName")
- String collName,
- @Parameter(description = "The name of the snapshot to be deleted.", required = true)
- @PathParam("snapshotName")
- String snapshotName,
- @Parameter(description = "A flag that treats the collName parameter as a collection alias.")
- @DefaultValue("false")
- @QueryParam("followAliases")
- boolean followAliases,
- @QueryParam("async") String asyncId)
+ public DeleteCollectionSnapshotResponse deleteSnapshot(
+ String collName, String snapshotName, boolean followAliases, String asyncId)
throws Exception {
- final DeleteSnapshotResponse response = instantiateJerseyResponse(DeleteSnapshotResponse.class);
+ final var response = instantiateJerseyResponse(DeleteCollectionSnapshotResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
recordCollectionForLogAndTracing(collName, solrQueryRequest);
@@ -105,24 +82,6 @@ public DeleteSnapshotResponse deleteSnapshot(
return response;
}
- /**
- * The Response for {@link DeleteCollectionSnapshotAPI}'s {@link #deleteSnapshot(String, String,
- * boolean, String)}
- */
- public static class DeleteSnapshotResponse extends AsyncJerseyResponse {
- @Schema(description = "The name of the collection.")
- @JsonProperty(COLLECTION_PROP)
- String collection;
-
- @Schema(description = "The name of the snapshot to be deleted.")
- @JsonProperty("snapshot")
- String snapshotName;
-
- @Schema(description = "A flag that treats the collName parameter as a collection alias.")
- @JsonProperty("followAliases")
- boolean followAliases;
- }
-
public static ZkNodeProps createRemoteMessage(
String collectionName, boolean followAliases, String snapshotName, String asyncId) {
final Map remoteMessage = new HashMap<>();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
similarity index 70%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
index 5416793ddba..76ace8384aa 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
@@ -16,24 +16,17 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CoreAdminParams.NODE;
import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import org.apache.solr.client.api.endpoint.DeleteNodeApi;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -42,7 +35,6 @@
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
@@ -53,29 +45,19 @@
*
* This API is analogous to the V1 /admin/collections?action=DELETENODE
*/
-@Path("cluster/nodes/{nodeName}/clear/")
-public class DeleteNodeAPI extends AdminAPIBase {
+public class DeleteNode extends AdminAPIBase implements DeleteNodeApi {
@Inject
- public DeleteNodeAPI(
+ public DeleteNode(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- public SolrJerseyResponse deleteNode(
- @Parameter(
- description =
- "The name of the node to be cleared. Usually of the form 'host:1234_solr'.",
- required = true)
- @PathParam("nodeName")
- String nodeName,
- @RequestBody(description = "Contains user provided parameters", required = true)
- DeleteNodeRequestBody requestBody)
+ public SolrJerseyResponse deleteNode(String nodeName, DeleteNodeRequestBody requestBody)
throws Exception {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
@@ -94,10 +76,11 @@ public SolrJerseyResponse deleteNode(
return response;
}
- public static SolrJerseyResponse invokeUsingV1Inputs(DeleteNodeAPI apiInstance, SolrParams params)
+ public static SolrJerseyResponse invokeUsingV1Inputs(DeleteNode apiInstance, SolrParams params)
throws Exception {
final RequiredSolrParams requiredParams = params.required();
- final DeleteNodeRequestBody requestBody = new DeleteNodeRequestBody(params.get(ASYNC));
+ final var requestBody = new DeleteNodeRequestBody();
+ requestBody.async = params.get(ASYNC);
return apiInstance.deleteNode(requiredParams.get(NODE), requestBody);
}
@@ -114,17 +97,4 @@ public static ZkNodeProps createRemoteMessage(
return new ZkNodeProps(remoteMessage);
}
-
- public static class DeleteNodeRequestBody implements JacksonReflectMapWriter {
-
- public DeleteNodeRequestBody() {}
-
- public DeleteNodeRequestBody(String async) {
- this.async = async;
- }
-
- @Schema(description = "Request ID to track this action which will be processed asynchronously.")
- @JsonProperty("async")
- public String async;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
similarity index 73%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
index 143acf95134..3f3f9d44b27 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
@@ -31,15 +31,11 @@
import static org.apache.solr.common.params.CoreAdminParams.REPLICA;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteReplicaApi;
+import org.apache.solr.client.api.model.ScaleCollectionRequestBody;
import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -47,7 +43,6 @@
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
@@ -57,30 +52,28 @@
*
*
These APIs are analogous to the v1 /admin/collections?action=DELETEREPLICA command.
*/
-public class DeleteReplicaAPI extends AdminAPIBase {
+public class DeleteReplica extends AdminAPIBase implements DeleteReplicaApi {
@Inject
- public DeleteReplicaAPI(
+ public DeleteReplica(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @DELETE
- @Path("/collections/{collectionName}/shards/{shardName}/replicas/{replicaName}")
+ @Override
@PermissionName(COLL_EDIT_PERM)
public SubResponseAccumulatingJerseyResponse deleteReplicaByName(
- @PathParam("collectionName") String collectionName,
- @PathParam("shardName") String shardName,
- @PathParam("replicaName") String replicaName,
- // Optional params below
- @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
- @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
- @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
- @QueryParam(DELETE_INDEX) Boolean deleteIndex,
- @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
- @QueryParam(ASYNC) String asyncId)
+ String collectionName,
+ String shardName,
+ String replicaName, /* Optional params below */
+ Boolean followAliases,
+ Boolean deleteInstanceDir,
+ Boolean deleteDataDir,
+ Boolean deleteIndex,
+ Boolean onlyIfDown,
+ String async)
throws Exception {
final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
@@ -100,26 +93,24 @@ public SubResponseAccumulatingJerseyResponse deleteReplicaByName(
deleteDataDir,
deleteIndex,
onlyIfDown,
- asyncId);
+ async);
submitRemoteMessageAndHandleResponse(
- response, CollectionParams.CollectionAction.DELETEREPLICA, remoteMessage, asyncId);
+ response, CollectionParams.CollectionAction.DELETEREPLICA, remoteMessage, async);
return response;
}
- @DELETE
- @Path("/collections/{collectionName}/shards/{shardName}/replicas")
+ @Override
@PermissionName(COLL_EDIT_PERM)
public SubResponseAccumulatingJerseyResponse deleteReplicasByCount(
- @PathParam("collectionName") String collectionName,
- @PathParam("shardName") String shardName,
- @QueryParam(COUNT_PROP) Integer numToDelete,
- // Optional params below
- @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
- @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
- @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
- @QueryParam(DELETE_INDEX) Boolean deleteIndex,
- @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
- @QueryParam(ASYNC) String asyncId)
+ String collectionName,
+ String shardName,
+ Integer numToDelete,
+ Boolean followAliases,
+ Boolean deleteInstanceDir,
+ Boolean deleteDataDir,
+ Boolean deleteIndex,
+ Boolean onlyIfDown,
+ String asyncId)
throws Exception {
final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
@@ -145,35 +136,23 @@ public SubResponseAccumulatingJerseyResponse deleteReplicasByCount(
return response;
}
- public static class ScaleCollectionRequestBody implements JacksonReflectMapWriter {
- public @JsonProperty(value = COUNT_PROP, required = true) Integer numToDelete;
- public @JsonProperty(FOLLOW_ALIASES) Boolean followAliases;
- public @JsonProperty(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir;
- public @JsonProperty(DELETE_DATA_DIR) Boolean deleteDataDir;
- public @JsonProperty(DELETE_INDEX) Boolean deleteIndex;
- public @JsonProperty(ONLY_IF_DOWN) Boolean onlyIfDown;
- public @JsonProperty(ASYNC) String asyncId;
-
- public static ScaleCollectionRequestBody fromV1Params(SolrParams v1Params) {
- final var requestBody = new ScaleCollectionRequestBody();
- requestBody.numToDelete = v1Params.getInt(COUNT_PROP);
- requestBody.followAliases = v1Params.getBool(FOLLOW_ALIASES);
- requestBody.deleteInstanceDir = v1Params.getBool(DELETE_INSTANCE_DIR);
- requestBody.deleteDataDir = v1Params.getBool(DELETE_DATA_DIR);
- requestBody.deleteIndex = v1Params.getBool(DELETE_INDEX);
- requestBody.onlyIfDown = v1Params.getBool(ONLY_IF_DOWN);
- requestBody.asyncId = v1Params.get(ASYNC);
+ public static ScaleCollectionRequestBody createScaleRequestBodyFromV1Params(SolrParams v1Params) {
+ final var requestBody = new ScaleCollectionRequestBody();
+ requestBody.numToDelete = v1Params.getInt(COUNT_PROP);
+ requestBody.followAliases = v1Params.getBool(FOLLOW_ALIASES);
+ requestBody.deleteInstanceDir = v1Params.getBool(DELETE_INSTANCE_DIR);
+ requestBody.deleteDataDir = v1Params.getBool(DELETE_DATA_DIR);
+ requestBody.deleteIndex = v1Params.getBool(DELETE_INDEX);
+ requestBody.onlyIfDown = v1Params.getBool(ONLY_IF_DOWN);
+ requestBody.async = v1Params.get(ASYNC);
- return requestBody;
- }
+ return requestBody;
}
- @PUT
- @Path("/collections/{collectionName}/scale")
+ @Override
@PermissionName(COLL_EDIT_PERM)
public SubResponseAccumulatingJerseyResponse deleteReplicasByCountAllShards(
- @PathParam("collectionName") String collectionName, ScaleCollectionRequestBody requestBody)
- throws Exception {
+ String collectionName, ScaleCollectionRequestBody requestBody) throws Exception {
final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
if (requestBody == null) {
throw new SolrException(BAD_REQUEST, "Request body is required but missing");
@@ -194,12 +173,12 @@ public SubResponseAccumulatingJerseyResponse deleteReplicasByCountAllShards(
requestBody.deleteDataDir,
requestBody.deleteIndex,
requestBody.onlyIfDown,
- requestBody.asyncId);
+ requestBody.async);
submitRemoteMessageAndHandleResponse(
response,
CollectionParams.CollectionAction.DELETEREPLICA,
remoteMessage,
- requestBody.asyncId);
+ requestBody.async);
return response;
}
@@ -239,13 +218,13 @@ public static void invokeWithV1Params(
v1Params.required().check(COLLECTION_PROP);
final var deleteReplicaApi =
- new DeleteReplicaAPI(coreContainer, solrQueryRequest, solrQueryResponse);
+ new DeleteReplica(coreContainer, solrQueryRequest, solrQueryResponse);
final var v2Response = invokeApiMethod(deleteReplicaApi, v1Params);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(solrQueryResponse, v2Response);
}
private static SubResponseAccumulatingJerseyResponse invokeApiMethod(
- DeleteReplicaAPI deleteReplicaApi, SolrParams v1Params) throws Exception {
+ DeleteReplica deleteReplicaApi, SolrParams v1Params) throws Exception {
if (v1Params.get(REPLICA) != null && v1Params.get(SHARD_ID_PROP) != null) {
return deleteReplicaApi.deleteReplicaByName(
v1Params.get(COLLECTION_PROP),
@@ -271,7 +250,7 @@ private static SubResponseAccumulatingJerseyResponse invokeApiMethod(
v1Params.get(ASYNC));
} else if (v1Params.get(COUNT_PROP) != null) {
return deleteReplicaApi.deleteReplicasByCountAllShards(
- v1Params.get(COLLECTION_PROP), ScaleCollectionRequestBody.fromV1Params(v1Params));
+ v1Params.get(COLLECTION_PROP), createScaleRequestBodyFromV1Params(v1Params));
} else {
throw new SolrException(
BAD_REQUEST,
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
similarity index 77%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
index bce721ce087..30651f83b6c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
@@ -25,11 +25,9 @@
import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
-import io.swagger.v3.oas.annotations.Parameter;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.endpoint.DeleteReplicaPropertyApi;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -38,41 +36,30 @@
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.PermissionNameProvider;
/**
- * V2 API for removing a property from a collection replica
+ * V2 API implementation for removing a property from a collection replica
*
- *
This API is analogous to the v1 /admin/collections?action=DELETEREPLICAPROP command.
+ * @see DeleteReplicaPropertyApi
*/
-@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
-public class DeleteReplicaPropertyAPI extends AdminAPIBase {
+public class DeleteReplicaProperty extends AdminAPIBase implements DeleteReplicaPropertyApi {
@Inject
- public DeleteReplicaPropertyAPI(
+ public DeleteReplicaProperty(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
+ @Override
+ @PermissionName(PermissionNameProvider.Name.COLL_EDIT_PERM)
public SolrJerseyResponse deleteReplicaProperty(
- @Parameter(
- description = "The name of the collection the replica belongs to.",
- required = true)
- @PathParam("collName")
- String collName,
- @Parameter(description = "The name of the shard the replica belongs to.", required = true)
- @PathParam("shardName")
- String shardName,
- @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
- @PathParam("replicaName")
- String replicaName,
- @Parameter(description = "The name of the property to delete.", required = true)
- @PathParam("propName")
- String propertyName)
- throws Exception {
+ String collName, String shardName, String replicaName, String propertyName) throws Exception {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
recordCollectionForLogAndTracing(collName, solrQueryRequest);
@@ -94,7 +81,7 @@ public SolrJerseyResponse deleteReplicaProperty(
}
public static SolrJerseyResponse invokeUsingV1Inputs(
- DeleteReplicaPropertyAPI apiInstance, SolrParams solrParams) throws Exception {
+ DeleteReplicaProperty apiInstance, SolrParams solrParams) throws Exception {
final RequiredSolrParams requiredParams = solrParams.required();
final String propNameToDelete = requiredParams.get(PROPERTY_PROP);
final String trimmedPropNameToDelete =
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeader.java
similarity index 88%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeader.java
index b0881a34bac..c3080e3ef16 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeader.java
@@ -17,7 +17,6 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
@@ -27,11 +26,7 @@
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.endpoint.ForceLeaderApi;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkShardTerms;
@@ -49,29 +44,25 @@
import org.slf4j.LoggerFactory;
/**
- * V2 API for triggering a leader election on a particular collection and shard.
+ * V2 API implementation for triggering a leader election on a particular collection and shard.
*
*
This API (POST /v2/collections/collectionName/shards/shardName/force-leader) is analogous to
* the v1 /admin/collections?action=FORCELEADER command.
*/
-@Path("/collections/{collectionName}/shards/{shardName}/force-leader")
-public class ForceLeaderAPI extends AdminAPIBase {
+public class ForceLeader extends AdminAPIBase implements ForceLeaderApi {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Inject
- public ForceLeaderAPI(
+ public ForceLeader(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- public SolrJerseyResponse forceLeader(
- @PathParam("collectionName") String collectionName,
- @PathParam("shardName") String shardName) {
+ public SolrJerseyResponse forceShardLeader(String collectionName, String shardName) {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
ensureRequiredParameterProvided(SHARD_ID_PROP, shardName);
@@ -84,12 +75,12 @@ public SolrJerseyResponse forceLeader(
public static void invokeFromV1Params(
CoreContainer coreContainer, SolrQueryRequest request, SolrQueryResponse response) {
- final var api = new ForceLeaderAPI(coreContainer, request, response);
+ final var api = new ForceLeader(coreContainer, request, response);
final var params = request.getParams();
params.required().check(COLLECTION_PROP, SHARD_ID_PROP);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
- response, api.forceLeader(params.get(COLLECTION_PROP), params.get(SHARD_ID_PROP)));
+ response, api.forceShardLeader(params.get(COLLECTION_PROP), params.get(SHARD_ID_PROP)));
}
private void doForceLeaderElection(String extCollectionName, String shardName) {
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliases.java
similarity index 65%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/ListAliases.java
index 0e2626e9668..d8995ce16ae 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliases.java
@@ -16,22 +16,15 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ListAliasesApi;
+import org.apache.solr.client.api.model.GetAliasByNameResponse;
+import org.apache.solr.client.api.model.ListAliasesResponse;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.core.CoreContainer;
@@ -39,12 +32,11 @@
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
-/** V2 APIs for managing and inspecting collection aliases */
-@Path("/aliases")
-public class ListAliasesAPI extends AdminAPIBase {
+/** V2 API implementation for listing and inspecting collection aliases */
+public class ListAliases extends AdminAPIBase implements ListAliasesApi {
@Inject
- public ListAliasesAPI(
+ public ListAliases(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
@@ -57,12 +49,8 @@ public ListAliasesAPI(
*
This API GET /api/aliases
is analogous to the v1 GET /api/cluster/aliases
*
API.
*/
- @GET
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_READ_PERM)
- @Operation(
- summary = "List the existing collection aliases.",
- tags = {"aliases"})
public ListAliasesResponse getAliases() throws Exception {
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -86,17 +74,9 @@ public ListAliasesResponse getAliases() throws Exception {
return response;
}
- @GET
- @Path("/{aliasName}")
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_READ_PERM)
- @Operation(
- summary = "Get details for a specific collection alias.",
- tags = {"aliases"})
- public GetAliasByNameResponse getAliasByName(
- @Parameter(description = "Alias name.", required = true) @PathParam("aliasName")
- String aliasName)
- throws Exception {
+ public GetAliasByNameResponse getAliasByName(String aliasName) throws Exception {
recordCollectionForLogAndTracing(null, solrQueryRequest);
final GetAliasByNameResponse response = instantiateJerseyResponse(GetAliasByNameResponse.class);
@@ -119,25 +99,4 @@ private Aliases readAliasesFromZk() throws Exception {
return zkStateReader.getAliases();
}
-
- /** Response for {@link ListAliasesAPI#getAliases()}. */
- public static class ListAliasesResponse extends SolrJerseyResponse {
- @JsonProperty("aliases")
- public Map aliases;
-
- @JsonProperty("properties")
- public Map> properties;
- }
-
- /** Response for {@link ListAliasesAPI#getAliasByName(String)}. */
- public static class GetAliasByNameResponse extends SolrJerseyResponse {
- @JsonProperty("name")
- public String alias;
-
- @JsonProperty("collections")
- public List collections;
-
- @JsonProperty("properties")
- public Map properties;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackups.java
similarity index 70%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackups.java
index 3ec3c599e0d..859e0e38941 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackups.java
@@ -17,15 +17,12 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
-import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_ID;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_LOCATION;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_REPOSITORY;
import static org.apache.solr.common.params.CoreAdminParams.NAME;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
@@ -33,12 +30,9 @@
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ListCollectionBackupsApi;
+import org.apache.solr.client.api.model.CollectionBackupDetails;
+import org.apache.solr.client.api.model.ListCollectionBackupsResponse;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
@@ -46,23 +40,22 @@
import org.apache.solr.core.backup.BackupId;
import org.apache.solr.core.backup.BackupProperties;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.jersey.SolrJacksonMapper;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/**
- * V2 API definitions for collection-backup "listing".
+ * V2 API implementations for collection-backup "listing".
*
* These APIs are equivalent to the v1 '/admin/collections?action=LISTBACKUP' command.
*/
-public class ListCollectionBackupsAPI extends BackupAPIBase {
+public class ListCollectionBackups extends BackupAPIBase implements ListCollectionBackupsApi {
private final ObjectMapper objectMapper;
@Inject
- public ListCollectionBackupsAPI(
+ public ListCollectionBackups(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
@@ -76,22 +69,17 @@ public static void invokeFromV1Params(
final SolrParams v1Params = req.getParams();
v1Params.required().check(CommonParams.NAME);
- final var listApi = new ListCollectionBackupsAPI(coreContainer, req, rsp);
+ final var listApi = new ListCollectionBackups(coreContainer, req, rsp);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
rsp,
listApi.listBackupsAtLocation(
v1Params.get(NAME), v1Params.get(BACKUP_LOCATION), v1Params.get(BACKUP_REPOSITORY)));
}
- @Path("/backups/{backupName}/versions")
- @GET
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public ListCollectionBackupsResponse listBackupsAtLocation(
- @PathParam("backupName") String backupName,
- @QueryParam(BACKUP_LOCATION) String location,
- @QueryParam(BACKUP_REPOSITORY) String repositoryName)
- throws IOException {
+ String backupName, String location, String repositoryName) throws IOException {
final var response = instantiateJerseyResponse(ListCollectionBackupsResponse.class);
recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -123,27 +111,4 @@ public ListCollectionBackupsResponse listBackupsAtLocation(
}
return response;
}
-
- public static class ListCollectionBackupsResponse extends SolrJerseyResponse {
- @JsonProperty public String collection;
- @JsonProperty public List backups;
- }
-
- // TODO Merge with CreateCollectionBackupAPI.CollectionBackupDetails, which seems very
- // conceptually similar...
- public static class CollectionBackupDetails implements JacksonReflectMapWriter {
- @JsonProperty public Integer backupId;
- @JsonProperty public String indexVersion;
- @JsonProperty public String startTime;
- @JsonProperty public String endTime;
- @JsonProperty public Integer indexFileCount;
- @JsonProperty public Double indexSizeMB;
-
- @JsonProperty public Map shardBackupIds;
-
- @JsonProperty(COLL_CONF)
- public String configsetName;
-
- @JsonProperty public String collectionAlias;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
similarity index 75%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
index e03b05a6f70..2f08a7323ac 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
@@ -17,19 +17,15 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ListCollectionsApi;
+import org.apache.solr.client.api.model.ListCollectionsResponse;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.jersey.PermissionName;
@@ -41,17 +37,14 @@
*
* This API (GET /v2/collections) is equivalent to the v1 /admin/collections?action=LIST command
*/
-@Path("/collections")
-public class ListCollectionsAPI extends AdminAPIBase {
+public class ListCollections extends AdminAPIBase implements ListCollectionsApi {
@Inject
- public ListCollectionsAPI(
- CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse rsp) {
+ public ListCollections(CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse rsp) {
super(coreContainer, req, rsp);
}
- @GET
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_READ_PERM)
public ListCollectionsResponse listCollections() {
final ListCollectionsResponse response =
@@ -67,9 +60,4 @@ public ListCollectionsResponse listCollections() {
return response;
}
-
- public static class ListCollectionsResponse extends SolrJerseyResponse {
- @JsonProperty("collections")
- public List collections;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java
index db5e97039b2..df5f64900f0 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java
@@ -18,7 +18,7 @@
package org.apache.solr.handler.admin.api;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
-import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+import static org.apache.solr.security.PermissionNameProvider.Name.HEALTH_PERM;
import org.apache.solr.api.EndPoint;
import org.apache.solr.handler.admin.HealthCheckHandler;
@@ -41,7 +41,7 @@ public NodeHealthAPI(HealthCheckHandler handler) {
@EndPoint(
path = {"/node/health"},
method = GET,
- permission = CONFIG_READ_PERM)
+ permission = HEALTH_PERM)
public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
handler.handleRequestBody(req, rsp);
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
index 22c2abdaca3..be80062d87a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
@@ -16,29 +16,23 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.endpoint.ReloadCollectionApi;
+import org.apache.solr.client.api.model.ReloadCollectionRequestBody;
import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
@@ -46,13 +40,12 @@
import org.slf4j.LoggerFactory;
/**
- * V2 API for reloading collections.
+ * V2 API implementation for reloading collections.
*
* The new API (POST /v2/collections/collectionName/reload {...}) is analogous to the v1
* /admin/collections?action=RELOAD command.
*/
-@Path("/collections/{collectionName}/reload")
-public class ReloadCollectionAPI extends AdminAPIBase {
+public class ReloadCollectionAPI extends AdminAPIBase implements ReloadCollectionApi {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -64,12 +57,10 @@ public ReloadCollectionAPI(
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public SubResponseAccumulatingJerseyResponse reloadCollection(
- @PathParam("collectionName") String collectionName, ReloadCollectionRequestBody requestBody)
- throws Exception {
+ String collectionName, ReloadCollectionRequestBody requestBody) throws Exception {
final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
fetchAndValidateZooKeeperAwareCoreContainer();
@@ -80,7 +71,7 @@ public SubResponseAccumulatingJerseyResponse reloadCollection(
response,
CollectionParams.CollectionAction.RELOAD,
remoteMessage,
- requestBody != null ? requestBody.asyncId : null);
+ requestBody != null ? requestBody.async : null);
return response;
}
@@ -90,7 +81,7 @@ public static ZkNodeProps createRemoteMessage(
remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.RELOAD.toLower());
remoteMessage.put(NAME, collectionName);
if (requestBody != null) {
- insertIfNotNull(remoteMessage, ASYNC, requestBody.asyncId);
+ insertIfNotNull(remoteMessage, ASYNC, requestBody.async);
}
return new ZkNodeProps(remoteMessage);
@@ -103,15 +94,9 @@ public static void invokeFromV1Params(
final var params = request.getParams();
params.required().check(NAME);
final var requestBody = new ReloadCollectionRequestBody();
- requestBody.asyncId = params.get(ASYNC); // Note, 'async' may or may not have been provided.
+ requestBody.async = params.get(ASYNC); // Note, 'async' may or may not have been provided.
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
response, api.reloadCollection(params.get(NAME), requestBody));
}
-
- // TODO Is it worth having this in a request body, or should we just make it a query param?
- public static class ReloadCollectionRequestBody implements JacksonReflectMapWriter {
- @JsonProperty(ASYNC)
- public String asyncId;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollection.java
similarity index 76%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollection.java
index 2fd2d7fb405..8cec8535a7f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollection.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
@@ -25,48 +24,40 @@
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.endpoint.RenameCollectionApi;
+import org.apache.solr.client.api.model.RenameCollectionRequestBody;
import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/**
- * V2 API for "renaming" an existing collection
+ * V2 API implementation to "rename" an existing collection
*
*
This API is analogous to the v1 /admin/collections?action=RENAME command.
*/
-@Path("/collections/{collectionName}/rename")
-public class RenameCollectionAPI extends AdminAPIBase {
+public class RenameCollection extends AdminAPIBase implements RenameCollectionApi {
@Inject
- public RenameCollectionAPI(
+ public RenameCollection(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
public SubResponseAccumulatingJerseyResponse renameCollection(
- @PathParam("collectionName") String collectionName, RenameCollectionRequestBody requestBody)
- throws Exception {
+ String collectionName, RenameCollectionRequestBody requestBody) throws Exception {
final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
if (requestBody == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -81,7 +72,7 @@ public SubResponseAccumulatingJerseyResponse renameCollection(
response,
CollectionParams.CollectionAction.RENAME,
remoteMessage,
- requestBody != null ? requestBody.asyncId : null);
+ requestBody != null ? requestBody.async : null);
return response;
}
@@ -92,7 +83,7 @@ public static ZkNodeProps createRemoteMessage(
remoteMessage.put(NAME, collectionName);
remoteMessage.put(TARGET, requestBody.to);
insertIfNotNull(remoteMessage, FOLLOW_ALIASES, requestBody.followAliases);
- insertIfNotNull(remoteMessage, ASYNC, requestBody.asyncId);
+ insertIfNotNull(remoteMessage, ASYNC, requestBody.async);
return new ZkNodeProps(remoteMessage);
}
@@ -100,26 +91,16 @@ public static ZkNodeProps createRemoteMessage(
public static void invokeFromV1Params(
CoreContainer coreContainer, SolrQueryRequest request, SolrQueryResponse response)
throws Exception {
- final var api = new RenameCollectionAPI(coreContainer, request, response);
+ final var api = new RenameCollection(coreContainer, request, response);
final var params = request.getParams();
params.required().check(COLLECTION_PROP, TARGET);
final var requestBody = new RenameCollectionRequestBody();
requestBody.to = params.get(TARGET);
// Optional parameters
- requestBody.asyncId = params.get(ASYNC);
+ requestBody.async = params.get(ASYNC);
requestBody.followAliases = params.getBool(FOLLOW_ALIASES);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
response, api.renameCollection(params.get(COLLECTION_PROP), requestBody));
}
-
- public static class RenameCollectionRequestBody implements JacksonReflectMapWriter {
- @JsonProperty(required = true)
- public String to;
-
- @JsonProperty(ASYNC)
- public String asyncId;
-
- @JsonProperty public Boolean followAliases;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNode.java
similarity index 64%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNode.java
index e2e63760ab7..ab597b54038 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNode.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.common.params.CollectionParams.SOURCE_NODE;
import static org.apache.solr.common.params.CollectionParams.TARGET_NODE;
@@ -25,17 +24,11 @@
import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import org.apache.solr.client.api.endpoint.ReplaceNodeApi;
+import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -43,7 +36,6 @@
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
@@ -53,26 +45,19 @@
*
*
This API is analogous to the v1 /admin/collections?action=REPLACENODE command.
*/
-@Path("cluster/nodes/{sourceNodeName}/replace/")
-public class ReplaceNodeAPI extends AdminAPIBase {
+public class ReplaceNode extends AdminAPIBase implements ReplaceNodeApi {
@Inject
- public ReplaceNodeAPI(
+ public ReplaceNode(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- public SolrJerseyResponse replaceNode(
- @Parameter(description = "The name of the node to be replaced.", required = true)
- @PathParam("sourceNodeName")
- String sourceNodeName,
- @RequestBody(description = "Contains user provided parameters", required = true)
- ReplaceNodeRequestBody requestBody)
+ public SolrJerseyResponse replaceNode(String sourceNodeName, ReplaceNodeRequestBody requestBody)
throws Exception {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
@@ -111,34 +96,4 @@ private void insertIfValueNotNull(Map dest, String key, Object v
dest.put(key, value);
}
}
-
- public static class ReplaceNodeRequestBody implements JacksonReflectMapWriter {
-
- public ReplaceNodeRequestBody() {}
-
- public ReplaceNodeRequestBody(String targetNodeName, Boolean waitForFinalState, String async) {
- this.targetNodeName = targetNodeName;
- this.waitForFinalState = waitForFinalState;
- this.async = async;
- }
-
- @Schema(
- description =
- "The target node where replicas will be copied. If this parameter is not provided, Solr "
- + "will identify nodes automatically based on policies or number of cores in each node.")
- @JsonProperty("targetNodeName")
- public String targetNodeName;
-
- @Schema(
- description =
- "If true, the request will complete only when all affected replicas become active. "
- + "If false, the API will return the status of the single action, which may be "
- + "before the new replica is online and active.")
- @JsonProperty("waitForFinalState")
- public Boolean waitForFinalState = false;
-
- @Schema(description = "Request ID to track this action which will be processed asynchronously.")
- @JsonProperty("async")
- public String async;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
index 03358880450..7e8ccf29862 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
@@ -61,6 +61,11 @@ public RestoreCoreAPI(
super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse);
}
+ @Override
+ boolean isExpensive() {
+ return true;
+ }
+
@POST
@Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
@PermissionName(CORE_EDIT_PERM)
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShard.java
similarity index 83%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/SyncShard.java
index 1b7b44b0dea..b506f585673 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShard.java
@@ -17,7 +17,6 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
@@ -25,11 +24,7 @@
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.endpoint.SyncShardApi;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
@@ -46,28 +41,25 @@
import org.apache.solr.response.SolrQueryResponse;
/**
- * V2 API for triggering a shard-sync operation within a particular collection and shard.
+ * V2 API implementation for triggering a shard-sync operation within a particular collection and
+ * shard.
*
* This API (POST /v2/collections/cName/shards/sName/sync {...}) is analogous to the v1
* /admin/collections?action=SYNCSHARD command.
*/
-@Path("/collections/{collectionName}/shards/{shardName}/sync")
-public class SyncShardAPI extends AdminAPIBase {
+public class SyncShard extends AdminAPIBase implements SyncShardApi {
@Inject
- public SyncShardAPI(
+ public SyncShard(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
SolrQueryResponse solrQueryResponse) {
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
- @POST
- @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(COLL_EDIT_PERM)
- public SolrJerseyResponse syncShard(
- @PathParam("collectionName") String collectionName, @PathParam("shardName") String shardName)
- throws Exception {
+ public SolrJerseyResponse syncShard(String collectionName, String shardName) throws Exception {
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
ensureRequiredParameterProvided(SHARD_ID_PROP, shardName);
@@ -105,7 +97,7 @@ private void doSyncShard(String extCollectionName, String shardName)
public static void invokeFromV1Params(
CoreContainer coreContainer, SolrQueryRequest request, SolrQueryResponse response)
throws Exception {
- final var api = new SyncShardAPI(coreContainer, request, response);
+ final var api = new SyncShard(coreContainer, request, response);
final var params = request.getParams();
params.required().check(COLLECTION_PROP, SHARD_ID_PROP);
diff --git a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
index 73cb880a7fc..10f99ac49f8 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
@@ -35,6 +35,7 @@
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.index.ExitableDirectoryReader;
import org.apache.lucene.search.TotalHits;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrDocumentList;
@@ -67,7 +68,7 @@
import org.apache.solr.util.RTimerTree;
import org.apache.solr.util.SolrPluginUtils;
import org.apache.solr.util.circuitbreaker.CircuitBreaker;
-import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
+import org.apache.solr.util.circuitbreaker.CircuitBreakerRegistry;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
@@ -329,6 +330,44 @@ protected ResponseBuilder newResponseBuilder(
return new ResponseBuilder(req, rsp, components);
}
+ /**
+ * Check if {@link SolrRequestType#QUERY} circuit breakers are tripped. Override this method in
+ * sub classes that do not want to check circuit breakers.
+ *
+ * @return true if circuit breakers are tripped, false otherwise.
+ */
+ protected boolean checkCircuitBreakers(
+ SolrQueryRequest req, SolrQueryResponse rsp, ResponseBuilder rb) {
+ final RTimerTree timer = rb.isDebug() ? req.getRequestTimer() : null;
+
+ final CircuitBreakerRegistry circuitBreakerRegistry = req.getCore().getCircuitBreakerRegistry();
+ if (circuitBreakerRegistry.isEnabled(SolrRequestType.QUERY)) {
+ List trippedCircuitBreakers;
+
+ if (timer != null) {
+ RTimerTree subt = timer.sub("circuitbreaker");
+ rb.setTimer(subt);
+
+ trippedCircuitBreakers = circuitBreakerRegistry.checkTripped(SolrRequestType.QUERY);
+
+ rb.getTimer().stop();
+ } else {
+ trippedCircuitBreakers = circuitBreakerRegistry.checkTripped(SolrRequestType.QUERY);
+ }
+
+ if (trippedCircuitBreakers != null) {
+ String errorMessage = CircuitBreakerRegistry.toErrorMessage(trippedCircuitBreakers);
+ rsp.add(STATUS, FAILURE);
+ rsp.setException(
+ new SolrException(
+ CircuitBreaker.getErrorCode(trippedCircuitBreakers),
+ "Circuit Breakers tripped " + errorMessage));
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
if (req.getParams().getBool(ShardParams.IS_SHARD, false)) {
@@ -354,30 +393,8 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
final RTimerTree timer = rb.isDebug() ? req.getRequestTimer() : null;
- final CircuitBreakerManager circuitBreakerManager = req.getCore().getCircuitBreakerManager();
- if (circuitBreakerManager.isEnabled()) {
- List trippedCircuitBreakers;
-
- if (timer != null) {
- RTimerTree subt = timer.sub("circuitbreaker");
- rb.setTimer(subt);
-
- trippedCircuitBreakers = circuitBreakerManager.checkTripped();
-
- rb.getTimer().stop();
- } else {
- trippedCircuitBreakers = circuitBreakerManager.checkTripped();
- }
-
- if (trippedCircuitBreakers != null) {
- String errorMessage = CircuitBreakerManager.toErrorMessage(trippedCircuitBreakers);
- rsp.add(STATUS, FAILURE);
- rsp.setException(
- new SolrException(
- SolrException.ErrorCode.SERVICE_UNAVAILABLE,
- "Circuit Breakers tripped " + errorMessage));
- return;
- }
+ if (checkCircuitBreakers(req, rsp, rb)) {
+ return; // Circuit breaker tripped, return immediately
}
// creates a ShardHandler object only if it's needed
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
similarity index 66%
rename from solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
rename to solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
index 58dcdfec5e9..c3403eb3af8 100644
--- a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSets.java
@@ -18,53 +18,36 @@
import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Operation;
-import java.util.List;
import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import org.apache.solr.api.JerseyResource;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.ListConfigsetsApi;
+import org.apache.solr.client.api.model.ListConfigsetsResponse;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.jersey.PermissionName;
/**
- * V2 API for adding or updating a single file within a configset.
+ * V2 API implementation for listing all available configsets.
*
* This API (GET /v2/cluster/configs) is analogous to the v1 /admin/configs?action=LIST command.
*/
-@Path("/cluster/configs")
-public class ListConfigSetsAPI extends JerseyResource {
+public class ListConfigSets extends JerseyResource implements ListConfigsetsApi {
@Context public HttpHeaders headers;
private final CoreContainer coreContainer;
@Inject
- public ListConfigSetsAPI(CoreContainer coreContainer) {
+ public ListConfigSets(CoreContainer coreContainer) {
this.coreContainer = coreContainer;
}
- @GET
- @Produces({"application/json", "application/javabin"})
- @Operation(
- summary = "List the configsets available to Solr.",
- tags = {"configset"})
+ @Override
@PermissionName(CONFIG_READ_PERM)
public ListConfigsetsResponse listConfigSet() throws Exception {
final ListConfigsetsResponse response = instantiateJerseyResponse(ListConfigsetsResponse.class);
response.configSets = coreContainer.getConfigSetService().listConfigs();
return response;
}
-
- /** Response body POJO for the {@link ListConfigSetsAPI} resource. */
- public static class ListConfigsetsResponse extends SolrJerseyResponse {
-
- @JsonProperty("configSets")
- public List configSets;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java b/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
index 4ce9490c115..3925b297d6a 100644
--- a/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
@@ -101,7 +101,7 @@ public void filter(
response.responseHeader.qTime);
/* slowQueryThresholdMillis defaults to -1 in SolrConfig -- not enabled.*/
- if (log.isWarnEnabled()
+ if (slowCoreRequestLogger.isWarnEnabled()
&& solrConfig != null
&& solrConfig.slowQueryThresholdMillis >= 0
&& response.responseHeader.qTime >= solrConfig.slowQueryThresholdMillis) {
diff --git a/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java b/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
index ace858667bc..d5c46bbb9a1 100644
--- a/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
+++ b/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
@@ -33,6 +33,7 @@
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.macro.MacroExpander;
+import org.apache.solr.search.QueryParsing;
import org.noggit.JSONParser;
import org.noggit.ObjectBuilder;
@@ -217,6 +218,8 @@ public static void processParams(
if ("query".equals(key)) {
out = "q";
isQuery = true;
+ String[] queryParsers = {"lucene"};
+ newMap.put(QueryParsing.DEFTYPE, queryParsers);
} else if ("filter".equals(key)) {
out = "fq";
arr = true;
diff --git a/solr/core/src/java/org/apache/solr/search/CacheConfig.java b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
index e7e2063f031..aa4d61a2204 100644
--- a/solr/core/src/java/org/apache/solr/search/CacheConfig.java
+++ b/solr/core/src/java/org/apache/solr/search/CacheConfig.java
@@ -80,13 +80,13 @@ public void setRegenerator(CacheRegenerator regenerator) {
}
public static Map getMultipleConfigs(
- SolrConfig solrConfig, String configPath, List nodes) {
+ SolrResourceLoader loader, SolrConfig solrConfig, String configPath, List nodes) {
if (nodes == null || nodes.size() == 0) return new LinkedHashMap<>();
Map result = CollectionUtil.newHashMap(nodes.size());
for (ConfigNode node : nodes) {
if (node.boolAttr("enabled", true)) {
CacheConfig config =
- getConfig(solrConfig, node.name(), node.attributes().asMap(), configPath);
+ getConfig(loader, solrConfig, node.name(), node.attributes().asMap(), configPath);
result.put(config.args.get(NAME), config);
}
}
@@ -105,6 +105,15 @@ public static CacheConfig getConfig(SolrConfig solrConfig, ConfigNode node, Stri
public static CacheConfig getConfig(
SolrConfig solrConfig, String nodeName, Map attrs, String xpath) {
+ return getConfig(solrConfig.getResourceLoader(), solrConfig, nodeName, attrs, xpath);
+ }
+
+ public static CacheConfig getConfig(
+ SolrResourceLoader loader,
+ SolrConfig solrConfig,
+ String nodeName,
+ Map attrs,
+ String xpath) {
CacheConfig config = new CacheConfig();
config.nodeName = nodeName;
Map attrsCopy = CollectionUtil.newLinkedHashMap(attrs.size());
@@ -128,7 +137,6 @@ public static CacheConfig getConfig(
config.args.put(NAME, config.nodeName);
}
- SolrResourceLoader loader = solrConfig.getResourceLoader();
config.cacheImpl = config.args.get("class");
if (config.cacheImpl == null) config.cacheImpl = "solr.CaffeineCache";
config.clazz =
diff --git a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
index 146a6c37326..1d921030ff7 100644
--- a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
+++ b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
@@ -379,6 +379,12 @@ public void setMaxRamMB(int maxRamMB) {
}
}
+ protected void adjustMetrics(long hitsAdjust, long insertsAdjust, long lookupsAdjust) {
+ hits.add(-hitsAdjust);
+ inserts.add(-insertsAdjust);
+ lookups.add(-lookupsAdjust);
+ }
+
@Override
public void warm(SolrIndexSearcher searcher, SolrCache old) {
if (regenerator == null) {
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCache.java b/solr/core/src/java/org/apache/solr/search/SolrCache.java
index 870d18ea2c9..5b09399f295 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrCache.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCache.java
@@ -134,6 +134,16 @@ public enum State {
*/
public State getState();
+ /**
+ * A hook for caches that would like to perform some initialization for the first registered
+ * searcher. This method is analogous to {@link #warm(SolrIndexSearcher, SolrCache)}. The default
+ * implementation is a no-op. Implementers should not retain object references to the specified
+ * searcher.
+ */
+ default void initialSearcher(SolrIndexSearcher initialSearcher) {
+ // no-op
+ }
+
/**
* Warm this cache associated with searcher
using the old
cache object.
* this
and old
will have the same concrete type.
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 6fe7dc4570c..79bc89eec9f 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -2374,6 +2374,18 @@ public boolean intersects(DocSet a, DocsEnumState deState) throws IOException {
return a.intersects(getDocSet(deState));
}
+ /**
+ * Called on the initial searcher for each core, immediately before firstSearcherListeners
+ *
are called for the searcher. This provides the opportunity to perform initialization on
+ * the first registered searcher before the searcher begins to see any firstSearcher
+ * -triggered events.
+ */
+ public void bootstrapFirstSearcher() {
+ for (SolrCache, ?> solrCache : cacheList) {
+ solrCache.initialSearcher(this);
+ }
+ }
+
/** Warm this searcher based on an old one (primarily for auto-cache warming). */
@SuppressWarnings({"unchecked"})
public void warm(SolrIndexSearcher old) {
diff --git a/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java b/solr/core/src/java/org/apache/solr/security/GetPublicKey.java
similarity index 63%
rename from solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java
rename to solr/core/src/java/org/apache/solr/security/GetPublicKey.java
index 46483462e5f..1f7e991667d 100644
--- a/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java
+++ b/solr/core/src/java/org/apache/solr/security/GetPublicKey.java
@@ -17,45 +17,31 @@
package org.apache.solr.security;
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
import org.apache.solr.api.JerseyResource;
-import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.endpoint.GetPublicKeyApi;
+import org.apache.solr.client.api.model.PublicKeyResponse;
import org.apache.solr.jersey.PermissionName;
/**
- * V2 API for fetching the public key of the receiving node.
+ * V2 API implementation to fetch the public key of the receiving node.
*
* This API is analogous to the v1 /admin/info/key endpoint.
*/
-@Path("/node/key")
-public class PublicKeyAPI extends JerseyResource {
+public class GetPublicKey extends JerseyResource implements GetPublicKeyApi {
private final SolrNodeKeyPair nodeKeyPair;
@Inject
- public PublicKeyAPI(SolrNodeKeyPair nodeKeyPair) {
+ public GetPublicKey(SolrNodeKeyPair nodeKeyPair) {
this.nodeKeyPair = nodeKeyPair;
}
- @GET
- @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+ @Override
@PermissionName(PermissionNameProvider.Name.ALL)
public PublicKeyResponse getPublicKey() {
final PublicKeyResponse response = instantiateJerseyResponse(PublicKeyResponse.class);
response.key = nodeKeyPair.getKeyPair().getPublicKeyStr();
return response;
}
-
- public static class PublicKeyResponse extends SolrJerseyResponse {
- @JsonProperty("key")
- @Schema(description = "The public key of the receiving Solr node.")
- public String key;
- }
}
diff --git a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
index 9dbde9d0966..5f2941fec23 100644
--- a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
+++ b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
@@ -50,7 +50,7 @@ public CryptoKeys.RSAKeyPair getKeyPair() {
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
V2ApiUtils.squashIntoSolrResponseWithoutHeader(
- rsp, new PublicKeyAPI(nodeKeyPair).getPublicKey());
+ rsp, new GetPublicKey(nodeKeyPair).getPublicKey());
}
@Override
@@ -80,6 +80,6 @@ public Collection getApis() {
@Override
public Collection> getJerseyResources() {
- return List.of(PublicKeyAPI.class);
+ return List.of(GetPublicKey.class);
}
}
diff --git a/solr/core/src/java/org/apache/solr/servlet/LoadAdminUiServlet.java b/solr/core/src/java/org/apache/solr/servlet/LoadAdminUiServlet.java
index 5a7e797f297..9da4bc84775 100644
--- a/solr/core/src/java/org/apache/solr/servlet/LoadAdminUiServlet.java
+++ b/solr/core/src/java/org/apache/solr/servlet/LoadAdminUiServlet.java
@@ -16,11 +16,15 @@
*/
package org.apache.solr.servlet;
+import com.google.common.net.HttpHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.output.CloseShieldOutputStream;
@@ -37,6 +41,8 @@ public final class LoadAdminUiServlet extends BaseSolrServlet {
// check system properties for whether or not admin UI is disabled, default is false
private static final boolean disabled =
Boolean.parseBoolean(System.getProperty("disableAdminUI", "false"));
+ // list of comma separated URLs to inject into the CSP connect-src directive
+ public static final String SYSPROP_CSP_CONNECT_SRC_URLS = "solr.ui.headers.csp.connect-src.urls";
@Override
public void doGet(HttpServletRequest _request, HttpServletResponse _response) throws IOException {
@@ -60,15 +66,20 @@ public void doGet(HttpServletRequest _request, HttpServletResponse _response) th
if (in != null && cores != null) {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
+ String connectSrc = generateCspConnectSrc();
+ response.setHeader(
+ HttpHeaders.CONTENT_SECURITY_POLICY,
+ "default-src 'none'; base-uri 'none'; connect-src "
+ + connectSrc
+ + "; form-action 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self' data:; media-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; worker-src 'self';");
// We have to close this to flush OutputStreamWriter buffer
try (Writer out =
new OutputStreamWriter(
CloseShieldOutputStream.wrap(response.getOutputStream()), StandardCharsets.UTF_8)) {
- Package pack = SolrCore.class.getPackage();
String html =
new String(in.readAllBytes(), StandardCharsets.UTF_8)
- .replace("${version}", pack.getSpecificationVersion());
+ .replace("${version}", getSolrCorePackageSpecVersion());
out.write(html);
}
} else {
@@ -76,4 +87,26 @@ public void doGet(HttpServletRequest _request, HttpServletResponse _response) th
}
}
}
+
+ /**
+ * Retrieves the specification version of the SolrCore package.
+ *
+ * @return The specification version of the SolrCore class's package or Unknown if it's
+ * unavailable.
+ */
+ private String getSolrCorePackageSpecVersion() {
+ Package pack = SolrCore.class.getPackage();
+ return pack.getSpecificationVersion() != null ? pack.getSpecificationVersion() : "Unknown";
+ }
+
+ /**
+ * Fetch the value of {@link #SYSPROP_CSP_CONNECT_SRC_URLS} system property, split by comma, and
+ * concatenate them into a space-separated string that can be used in CSP connect-src directive
+ */
+ private String generateCspConnectSrc() {
+ String cspURLs = System.getProperty(SYSPROP_CSP_CONNECT_SRC_URLS, "");
+ List props = new ArrayList<>(Arrays.asList(cspURLs.split(",")));
+ props.add("'self'");
+ return String.join(" ", props);
+ }
}
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index 7b9c2b7e90c..4bb869ca1ec 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -239,7 +239,13 @@ private void dispatch(
if (log.isDebugEnabled()) {
log.debug("User principal: {}", request.getUserPrincipal());
}
- TraceUtils.setUser(span, String.valueOf(request.getUserPrincipal()));
+ final String principalName;
+ if (request.getUserPrincipal() != null) {
+ principalName = request.getUserPrincipal().getName();
+ } else {
+ principalName = null;
+ }
+ TraceUtils.setUser(span, String.valueOf(principalName));
}
HttpSolrCall call = getHttpSolrCall(request, response, retry);
diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java
index 70c2fbda507..262bd80444d 100644
--- a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java
@@ -38,8 +38,7 @@ public class CPUCircuitBreaker extends CircuitBreaker {
private static final OperatingSystemMXBean operatingSystemMXBean =
ManagementFactory.getOperatingSystemMXBean();
- private final boolean enabled;
- private final double cpuUsageThreshold;
+ private double cpuUsageThreshold;
// Assumption -- the value of these parameters will be set correctly before invoking
// getDebugInfo()
@@ -47,22 +46,16 @@ public class CPUCircuitBreaker extends CircuitBreaker {
private static final ThreadLocal allowedCPUUsage = ThreadLocal.withInitial(() -> 0.0);
- public CPUCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
+ public CPUCircuitBreaker() {
+ super();
+ }
- this.enabled = config.getCpuCBEnabled();
- this.cpuUsageThreshold = config.getCpuCBThreshold();
+ public void setThreshold(double threshold) {
+ this.cpuUsageThreshold = threshold;
}
@Override
public boolean isTripped() {
- if (!isEnabled()) {
- return false;
- }
-
- if (!enabled) {
- return false;
- }
double localAllowedCPUUsage = getCpuUsageThreshold();
double localSeenCPUUsage = calculateLiveCPUUsage();
@@ -96,7 +89,7 @@ public String getDebugInfo() {
@Override
public String getErrorMessage() {
- return "CPU Circuit Breaker triggered as seen CPU usage is above allowed threshold."
+ return "CPU Circuit Breaker triggered as seen CPU usage is above allowed threshold. "
+ "Seen CPU usage "
+ seenCPUUsage.get()
+ " and allocated threshold "
diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreaker.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreaker.java
index 79c3bcf6abf..3082bf82e4a 100644
--- a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreaker.java
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreaker.java
@@ -17,32 +17,42 @@
package org.apache.solr.util.circuitbreaker;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.SolrPluginUtils;
+import org.apache.solr.util.plugin.NamedListInitializedPlugin;
+
/**
- * Default class to define circuit breakers for Solr.
+ * Default base class to define circuit breaker plugins for Solr. Still experimental, may
+ * change
*
- * There are two (typical) ways to use circuit breakers: 1. Have them checked at admission
- * control by default (use CircuitBreakerManager for the same). 2. Use the circuit breaker in a
- * specific code path(s).
+ *
There are two (typical) ways to use circuit breakers:
*
- *
TODO: This class should be grown as the scope of circuit breakers grow.
+ *
+ * Have them checked at admission control by default (use CircuitBreakerRegistry for the
+ * same).
+ * Use the circuit breaker in a specific code path(s).
+ *
*
- * The class and its derivatives raise a standard exception when a circuit breaker is triggered.
- * We should make it into a dedicated exception (https://issues.apache.org/jira/browse/SOLR-14755)
+ * @lucene.experimental
*/
-public abstract class CircuitBreaker {
- public static final String NAME = "circuitbreaker";
-
- protected final CircuitBreakerConfig config;
+public abstract class CircuitBreaker implements NamedListInitializedPlugin {
+ // Only query requests are checked by default
+ private Set requestTypes = Set.of(SolrRequestType.QUERY);
+ private final List SUPPORTED_TYPES =
+ List.of(SolrRequestType.QUERY, SolrRequestType.UPDATE);
- public CircuitBreaker(CircuitBreakerConfig config) {
- this.config = config;
+ @Override
+ public void init(NamedList> args) {
+ SolrPluginUtils.invokeSetters(this, args);
}
- // Global config for all circuit breakers. For specific circuit breaker configs, define
- // your own config.
- protected boolean isEnabled() {
- return config.isEnabled();
- }
+ public CircuitBreaker() {}
/** Check if circuit breaker is tripped. */
public abstract boolean isTripped();
@@ -53,44 +63,47 @@ protected boolean isEnabled() {
/** Get error message when the circuit breaker triggers */
public abstract String getErrorMessage();
- public static class CircuitBreakerConfig {
- private final boolean enabled;
- private final boolean memCBEnabled;
- private final int memCBThreshold;
- private final boolean cpuCBEnabled;
- private final int cpuCBThreshold;
-
- public CircuitBreakerConfig(
- final boolean enabled,
- final boolean memCBEnabled,
- final int memCBThreshold,
- final boolean cpuCBEnabled,
- final int cpuCBThreshold) {
- this.enabled = enabled;
- this.memCBEnabled = memCBEnabled;
- this.memCBThreshold = memCBThreshold;
- this.cpuCBEnabled = cpuCBEnabled;
- this.cpuCBThreshold = cpuCBThreshold;
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public boolean getMemCBEnabled() {
- return memCBEnabled;
- }
-
- public int getMemCBThreshold() {
- return memCBThreshold;
- }
+ /**
+ * Set the request types for which this circuit breaker should be checked. If not called, the
+ * circuit breaker will be checked for the {@link SolrRequestType#QUERY} request type only.
+ *
+ * @param requestTypes list of strings representing request types
+ * @throws IllegalArgumentException if the request type is not valid
+ */
+ public void setRequestTypes(List requestTypes) {
+ this.requestTypes =
+ requestTypes.stream()
+ .map(t -> SolrRequestType.valueOf(t.toUpperCase(Locale.ROOT)))
+ .peek(
+ t -> {
+ if (!SUPPORTED_TYPES.contains(t)) {
+ throw new IllegalArgumentException(
+ String.format(
+ Locale.ROOT,
+ "Request type %s is not supported for circuit breakers",
+ t.name()));
+ }
+ })
+ .collect(Collectors.toSet());
+ }
- public boolean getCpuCBEnabled() {
- return cpuCBEnabled;
- }
+ public Set getRequestTypes() {
+ return requestTypes;
+ }
- public int getCpuCBThreshold() {
- return cpuCBThreshold;
+ /**
+ * Return the proper error code to use in exception. For legacy use of {@link CircuitBreaker} we
+ * return 503 for backward compatibility, else return 429.
+ *
+ * @deprecated Remove in 10.0
+ */
+ @Deprecated(since = "9.4")
+ public static SolrException.ErrorCode getErrorCode(List trippedCircuitBreakers) {
+ if (trippedCircuitBreakers != null
+ && trippedCircuitBreakers.stream().anyMatch(cb -> cb instanceof CircuitBreakerManager)) {
+ return SolrException.ErrorCode.SERVICE_UNAVAILABLE;
+ } else {
+ return SolrException.ErrorCode.TOO_MANY_REQUESTS;
}
}
}
diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerManager.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerManager.java
index 3b81a0542e0..201b1ffaa73 100644
--- a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerManager.java
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerManager.java
@@ -17,163 +17,102 @@
package org.apache.solr.util.circuitbreaker;
-import com.google.common.annotations.VisibleForTesting;
-import java.util.ArrayList;
-import java.util.List;
+import java.lang.invoke.MethodHandles;
import org.apache.solr.common.util.NamedList;
-import org.apache.solr.core.PluginInfo;
-import org.apache.solr.util.plugin.PluginInfoInitialized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Manages all registered circuit breaker instances. Responsible for a holistic view of whether a
- * circuit breaker has tripped or not.
+ * Single CircuitBreaker that registers both a Memory and a CPU CircuitBreaker. This is only for
+ * backward compatibility with the 9.x versions prior to 9.4.
*
- * There are two typical ways of using this class's instance: 1. Check if any circuit breaker has
- * triggered -- and know which circuit breaker has triggered. 2. Get an instance of a specific
- * circuit breaker and perform checks.
- *
- *
It is a good practice to register new circuit breakers here if you want them checked for every
- * request.
- *
- *
NOTE: The current way of registering new default circuit breakers is minimal and not a long
- * term solution. There will be a follow up with a SIP for a schema API design.
+ * @deprecated Use individual Circuit Breakers instead
*/
-public class CircuitBreakerManager implements PluginInfoInitialized {
- // Class private to potentially allow "family" of circuit breakers to be enabled or disabled
- private final boolean enableCircuitBreakerManager;
-
- private final List circuitBreakerList = new ArrayList<>();
-
- public CircuitBreakerManager(final boolean enableCircuitBreakerManager) {
- this.enableCircuitBreakerManager = enableCircuitBreakerManager;
+@Deprecated(since = "9.4")
+public class CircuitBreakerManager extends CircuitBreaker {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private boolean cpuEnabled;
+ private boolean memEnabled;
+ private int memThreshold = 100;
+ private int cpuThreshold = 100;
+ private MemoryCircuitBreaker memCB;
+ private CPUCircuitBreaker cpuCB;
+
+ public CircuitBreakerManager() {
+ super();
}
@Override
- public void init(PluginInfo pluginInfo) {
- CircuitBreaker.CircuitBreakerConfig circuitBreakerConfig = buildCBConfig(pluginInfo);
-
- // Install the default circuit breakers
- CircuitBreaker memoryCircuitBreaker = new MemoryCircuitBreaker(circuitBreakerConfig);
- CircuitBreaker cpuCircuitBreaker = new CPUCircuitBreaker(circuitBreakerConfig);
-
- register(memoryCircuitBreaker);
- register(cpuCircuitBreaker);
- }
-
- public void register(CircuitBreaker circuitBreaker) {
- circuitBreakerList.add(circuitBreaker);
- }
-
- public void deregisterAll() {
- circuitBreakerList.clear();
+ public boolean isTripped() {
+ return (memEnabled && memCB.isTripped()) || (cpuEnabled && cpuCB.isTripped());
}
- /**
- * Check and return circuit breakers that have triggered
- *
- * @return CircuitBreakers which have triggered, null otherwise.
- */
- public List checkTripped() {
- List triggeredCircuitBreakers = null;
-
- if (enableCircuitBreakerManager) {
- for (CircuitBreaker circuitBreaker : circuitBreakerList) {
- if (circuitBreaker.isEnabled() && circuitBreaker.isTripped()) {
- if (triggeredCircuitBreakers == null) {
- triggeredCircuitBreakers = new ArrayList<>();
- }
- triggeredCircuitBreakers.add(circuitBreaker);
- }
- }
+ @Override
+ public String getDebugInfo() {
+ StringBuilder sb = new StringBuilder();
+ if (memEnabled) {
+ sb.append(memCB.getDebugInfo());
}
-
- return triggeredCircuitBreakers;
- }
-
- /**
- * Returns true if *any* circuit breaker has triggered, false if none have triggered.
- *
- * NOTE: This method short circuits the checking of circuit breakers -- the method will return
- * as soon as it finds a circuit breaker that is enabled and has triggered.
- */
- public boolean checkAnyTripped() {
- if (enableCircuitBreakerManager) {
- for (CircuitBreaker circuitBreaker : circuitBreakerList) {
- if (circuitBreaker.isEnabled() && circuitBreaker.isTripped()) {
- return true;
- }
- }
+ if (memEnabled && cpuEnabled) {
+ sb.append("\n");
}
-
- return false;
+ if (cpuEnabled) {
+ sb.append(cpuCB.getDebugInfo());
+ }
+ return sb.toString();
}
- /**
- * Construct the final error message to be printed when circuit breakers trip.
- *
- * @param circuitBreakerList Input list for circuit breakers.
- * @return Constructed error message.
- */
- public static String toErrorMessage(List circuitBreakerList) {
+ @Override
+ public String getErrorMessage() {
StringBuilder sb = new StringBuilder();
-
- for (CircuitBreaker circuitBreaker : circuitBreakerList) {
- sb.append(circuitBreaker.getErrorMessage());
+ if (memEnabled) {
+ sb.append(memCB.getErrorMessage());
+ }
+ if (memEnabled && cpuEnabled) {
sb.append("\n");
}
-
+ if (cpuEnabled) {
+ sb.append(cpuCB.getErrorMessage());
+ }
return sb.toString();
}
- /**
- * Register default circuit breakers and return a constructed CircuitBreakerManager instance which
- * serves the given circuit breakers.
- *
- * Any default circuit breakers should be registered here.
- */
- public static CircuitBreakerManager build(PluginInfo pluginInfo) {
- boolean enabled =
- pluginInfo == null
- ? false
- : Boolean.parseBoolean(pluginInfo.attributes.getOrDefault("enabled", "false"));
- CircuitBreakerManager circuitBreakerManager = new CircuitBreakerManager(enabled);
-
- circuitBreakerManager.init(pluginInfo);
-
- return circuitBreakerManager;
+ @Override
+ public void init(NamedList> args) {
+ super.init(args);
+ log.warn("CircuitBreakerManager is deprecated. Use individual Circuit Breakers instead");
+ if (memEnabled) {
+ memCB = new MemoryCircuitBreaker();
+ memCB.setThreshold(memThreshold);
+ }
+ if (cpuEnabled) {
+ cpuCB = new CPUCircuitBreaker();
+ cpuCB.setThreshold(cpuThreshold);
+ }
}
- @VisibleForTesting
- public static CircuitBreaker.CircuitBreakerConfig buildCBConfig(PluginInfo pluginInfo) {
- boolean enabled = false;
- boolean cpuCBEnabled = false;
- boolean memCBEnabled = false;
- int memCBThreshold = 100;
- int cpuCBThreshold = 100;
-
- if (pluginInfo != null) {
- NamedList> args = pluginInfo.initArgs;
+ // The methods below will be called by super class during init
+ public void setMemEnabled(String enabled) {
+ this.memEnabled = Boolean.getBoolean(enabled);
+ }
- enabled = Boolean.parseBoolean(pluginInfo.attributes.getOrDefault("enabled", "false"));
+ public void setMemThreshold(int threshold) {
+ this.memThreshold = threshold;
+ }
- if (args != null) {
- cpuCBEnabled = Boolean.parseBoolean(args._getStr("cpuEnabled", "false"));
- memCBEnabled = Boolean.parseBoolean(args._getStr("memEnabled", "false"));
- memCBThreshold = Integer.parseInt(args._getStr("memThreshold", "100"));
- cpuCBThreshold = Integer.parseInt(args._getStr("cpuThreshold", "100"));
- }
- }
+ public void setMemThreshold(String threshold) {
+ this.memThreshold = Integer.parseInt(threshold);
+ }
- return new CircuitBreaker.CircuitBreakerConfig(
- enabled, memCBEnabled, memCBThreshold, cpuCBEnabled, cpuCBThreshold);
+ public void setCpuEnabled(String enabled) {
+ this.cpuEnabled = Boolean.getBoolean(enabled);
}
- public boolean isEnabled() {
- return enableCircuitBreakerManager;
+ public void setCpuThreshold(int threshold) {
+ this.cpuThreshold = threshold;
}
- @VisibleForTesting
- public List getRegisteredCircuitBreakers() {
- return circuitBreakerList;
+ public void setCpuThreshold(String threshold) {
+ this.cpuThreshold = Integer.parseInt(threshold);
}
}
diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerRegistry.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerRegistry.java
new file mode 100644
index 00000000000..84c2f61fb9b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CircuitBreakerRegistry.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util.circuitbreaker;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
+
+/**
+ * Keeps track of all registered circuit breaker instances for various request types. Responsible
+ * for a holistic view of whether a circuit breaker has tripped or not.
+ *
+ * @lucene.experimental
+ * @since 9.4
+ */
+public class CircuitBreakerRegistry {
+
+ private final Map> circuitBreakerMap = new HashMap<>();
+
+ public CircuitBreakerRegistry() {}
+
+ public void register(CircuitBreaker circuitBreaker) {
+ circuitBreaker
+ .getRequestTypes()
+ .forEach(
+ r -> {
+ List list =
+ circuitBreakerMap.computeIfAbsent(r, k -> new ArrayList<>());
+ list.add(circuitBreaker);
+ });
+ }
+
+ @VisibleForTesting
+ public void deregisterAll() {
+ circuitBreakerMap.clear();
+ }
+
+ /**
+ * Check and return circuit breakers that have triggered
+ *
+ * @param requestType {@link SolrRequestType} to check for.
+ * @return CircuitBreakers which have triggered, null otherwise.
+ */
+ public List checkTripped(SolrRequestType requestType) {
+ List triggeredCircuitBreakers = null;
+
+ for (CircuitBreaker circuitBreaker :
+ circuitBreakerMap.getOrDefault(requestType, Collections.emptyList())) {
+ if (circuitBreaker.isTripped()) {
+ if (triggeredCircuitBreakers == null) {
+ triggeredCircuitBreakers = new ArrayList<>();
+ }
+
+ triggeredCircuitBreakers.add(circuitBreaker);
+ }
+ }
+
+ return triggeredCircuitBreakers;
+ }
+
+ /**
+ * Construct the final error message to be printed when circuit breakers trip.
+ *
+ * @param circuitBreakerList Input list for circuit breakers.
+ * @return Constructed error message.
+ */
+ public static String toErrorMessage(List circuitBreakerList) {
+ StringBuilder sb = new StringBuilder();
+
+ for (CircuitBreaker circuitBreaker : circuitBreakerList) {
+ sb.append(circuitBreaker.getErrorMessage());
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ public boolean isEnabled(SolrRequestType requestType) {
+ return circuitBreakerMap.containsKey(requestType);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/MemoryCircuitBreaker.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/MemoryCircuitBreaker.java
index 129ea94579f..0a568e5bf9e 100644
--- a/solr/core/src/java/org/apache/solr/util/circuitbreaker/MemoryCircuitBreaker.java
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/MemoryCircuitBreaker.java
@@ -26,7 +26,7 @@
/**
* Tracks the current JVM heap usage and triggers if it exceeds the defined percentage of the
* maximum heap size allocated to the JVM. This circuit breaker is a part of the default
- * CircuitBreakerManager so is checked for every request -- hence it is realtime. Once the memory
+ * CircuitBreakerRegistry so is checked for every request -- hence it is realtime. Once the memory
* usage goes below the threshold, it will start allowing queries again.
*
* The memory threshold is defined as a percentage of the maximum memory allocated -- see
@@ -36,26 +36,24 @@ public class MemoryCircuitBreaker extends CircuitBreaker {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final MemoryMXBean MEMORY_MX_BEAN = ManagementFactory.getMemoryMXBean();
- private boolean enabled;
- private final long heapMemoryThreshold;
+ private long heapMemoryThreshold;
// Assumption -- the value of these parameters will be set correctly before invoking
// getDebugInfo()
private static final ThreadLocal seenMemory = ThreadLocal.withInitial(() -> 0L);
private static final ThreadLocal allowedMemory = ThreadLocal.withInitial(() -> 0L);
- public MemoryCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
-
- this.enabled = config.getMemCBEnabled();
+ public MemoryCircuitBreaker() {
+ super();
+ }
+ public void setThreshold(double thresholdValueInPercentage) {
long currentMaxHeap = MEMORY_MX_BEAN.getHeapMemoryUsage().getMax();
if (currentMaxHeap <= 0) {
throw new IllegalArgumentException("Invalid JVM state for the max heap usage");
}
- int thresholdValueInPercentage = config.getMemCBThreshold();
double thresholdInFraction = thresholdValueInPercentage / (double) 100;
heapMemoryThreshold = (long) (currentMaxHeap * thresholdInFraction);
@@ -69,13 +67,6 @@ public MemoryCircuitBreaker(CircuitBreakerConfig config) {
// overhead of calculating the condition parameters but can result in false positives.
@Override
public boolean isTripped() {
- if (!isEnabled()) {
- return false;
- }
-
- if (!enabled) {
- return false;
- }
long localAllowedMemory = getCurrentMemoryThreshold();
long localSeenMemory = calculateLiveMemoryUsage();
@@ -98,7 +89,7 @@ public String getDebugInfo() {
@Override
public String getErrorMessage() {
- return "Memory Circuit Breaker triggered as JVM heap usage values are greater than allocated threshold."
+ return "Memory Circuit Breaker triggered as JVM heap usage values are greater than allocated threshold. "
+ "Seen JVM heap memory usage "
+ seenMemory.get()
+ " and allocated threshold "
diff --git a/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
new file mode 100644
index 00000000000..01a72e0a1ff
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/tracing/SimplePropagator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.util.tracing;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.ContextKey;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.context.propagation.TextMapSetter;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple Http Header Propagator. When enabled, this will only propagate the trace id from the
+ * client to all internal requests. It is also in charge of generating a trace id if none exists.
+ *
+ * Note: this is very similar in impl to
+ * io.opentelemetry.extension.incubator.propagation.PassThroughPropagator. we should consider
+ * replacing/upgrading once that becomes generally available
+ */
+public class SimplePropagator implements TextMapPropagator {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final String TRACE_HOST_NAME =
+ System.getProperty("trace_host_name", System.getProperty("host"));
+ private static final TextMapPropagator INSTANCE = new SimplePropagator();
+ private static final ContextKey TRACE_ID_KEY = ContextKey.named("trace_id");
+
+ static final String TRACE_ID = System.getProperty("TRACE_ID", "X-Trace-Id");
+ private static final List FIELDS = List.of(TRACE_ID);
+
+ private static final AtomicLong traceCounter = new AtomicLong(0);
+
+ private static volatile boolean loaded = false;
+
+ public static synchronized Tracer load() {
+ if (!loaded) {
+ log.info("OpenTelemetry tracer enabled with simple propagation only.");
+ OpenTelemetry otel =
+ OpenTelemetry.propagating(ContextPropagators.create(SimplePropagator.getInstance()));
+ GlobalOpenTelemetry.set(otel);
+ loaded = true;
+ }
+ return GlobalOpenTelemetry.getTracer("solr");
+ }
+
+ public static TextMapPropagator getInstance() {
+ return INSTANCE;
+ }
+
+ private SimplePropagator() {}
+
+ @Override
+ public Collection fields() {
+ return FIELDS;
+ }
+
+ @Override
+ public void inject(Context context, C carrier, TextMapSetter setter) {
+ if (setter == null) {
+ return;
+ }
+ String traceId = context.get(TRACE_ID_KEY);
+ if (traceId != null) {
+ setter.set(carrier, TRACE_ID, traceId);
+ }
+ }
+
+ @Override
+ public Context extract(Context context, C carrier, TextMapGetter getter) {
+ String traceId = getter.get(carrier, TRACE_ID);
+ if (traceId == null) {
+ traceId = newTraceId();
+ }
+
+ MDCLoggingContext.setTracerId(traceId);
+ return context.with(TRACE_ID_KEY, traceId);
+ }
+
+ private static String newTraceId() {
+ return TRACE_HOST_NAME + "-" + traceCounter.incrementAndGet();
+ }
+
+ @Override
+ public String toString() {
+ return "SimplePropagator";
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java b/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
index 3505f4daac6..e63d8c6cbde 100644
--- a/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/tracing/TraceUtils.java
@@ -25,6 +25,7 @@
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator;
+import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
import org.apache.solr.client.solrj.SolrRequest;
@@ -52,6 +53,8 @@ public class TraceUtils {
public static final AttributeKey TAG_RESPONSE_WRITER =
AttributeKey.stringKey("responseWriter");
public static final AttributeKey TAG_CONTENT_TYPE = AttributeKey.stringKey("contentType");
+ public static final AttributeKey> TAG_OPS = AttributeKey.stringArrayKey("ops");
+ public static final AttributeKey TAG_CLASS = AttributeKey.stringKey("class");
@Deprecated
private static final AttributeKey TAG_HTTP_METHOD_DEP =
@@ -140,4 +143,11 @@ public static Span startHttpRequestSpan(HttpServletRequest request, Context cont
spanBuilder.setAttribute(TAG_DB_TYPE, TAG_DB_TYPE_SOLR);
return spanBuilder.startSpan();
}
+
+ public static void setOperations(SolrQueryRequest req, String clazz, List ops) {
+ if (!ops.isEmpty()) {
+ req.getSpan().setAttribute(TAG_OPS, ops);
+ req.getSpan().setAttribute(TAG_CLASS, clazz);
+ }
+ }
}
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-memory-circuitbreaker.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-legacy-circuitbreaker.xml
similarity index 96%
rename from solr/core/src/test-files/solr/collection1/conf/solrconfig-memory-circuitbreaker.xml
rename to solr/core/src/test-files/solr/collection1/conf/solrconfig-legacy-circuitbreaker.xml
index 6ab9f893022..fb00756b86c 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-memory-circuitbreaker.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-legacy-circuitbreaker.xml
@@ -78,7 +78,10 @@
-
+
+
true
75
true
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-pluggable-circuitbreaker.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-pluggable-circuitbreaker.xml
new file mode 100644
index 00000000000..8719a00ea7b
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-pluggable-circuitbreaker.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+ ${tests.luceneMatchVersion:LATEST}
+ ${solr.data.dir:}
+
+
+
+
+
+
+
+ ${solr.max.booleanClauses:1024}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ 10
+
+
+
+
+
+
+
+ 99
+
+ update
+
+
+
+
+ 80
+
+
+
+
+ 75
+
+
+
+
+ text
+
+
+
+
diff --git a/solr/core/src/test/org/apache/solr/TestSolrCoreProperties.java b/solr/core/src/test/org/apache/solr/TestSolrCoreProperties.java
index b55b368cb3f..d469c532328 100644
--- a/solr/core/src/test/org/apache/solr/TestSolrCoreProperties.java
+++ b/solr/core/src/test/org/apache/solr/TestSolrCoreProperties.java
@@ -24,7 +24,6 @@
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
-import org.apache.solr.embedded.JettySolrRunner;
import org.junit.BeforeClass;
/**
@@ -73,12 +72,8 @@ public static void beforeTest() throws Exception {
if (System.getProperty("solr.data.dir") == null) {
nodeProperties.setProperty("solr.data.dir", createTempDir().toFile().getCanonicalPath());
}
- jetty =
- new JettySolrRunner(
- homeDir.toAbsolutePath().toString(), nodeProperties, buildJettyConfig());
- jetty.start();
- port = jetty.getLocalPort();
+ solrClientTestRule.startSolr(homeDir, nodeProperties, buildJettyConfig());
// createJetty(homeDir.getAbsolutePath(), null, null);
}
diff --git a/solr/core/src/test/org/apache/solr/TestTolerantSearch.java b/solr/core/src/test/org/apache/solr/TestTolerantSearch.java
index a28fc90ece1..29658475281 100644
--- a/solr/core/src/test/org/apache/solr/TestTolerantSearch.java
+++ b/solr/core/src/test/org/apache/solr/TestTolerantSearch.java
@@ -62,12 +62,12 @@ public static void createThings() throws Exception {
systemSetPropertySolrDisableUrlAllowList("true");
File solrHome = createSolrHome();
createAndStartJetty(solrHome.getAbsolutePath());
- String url = jetty.getBaseUrl().toString();
+ String url = getBaseUrl();
collection1 = getHttpSolrClient(url + "/collection1");
collection2 = getHttpSolrClient(url + "/collection2");
- String urlCollection1 = jetty.getBaseUrl().toString() + "/" + "collection1";
- String urlCollection2 = jetty.getBaseUrl().toString() + "/" + "collection2";
+ String urlCollection1 = getBaseUrl() + "/" + "collection1";
+ String urlCollection2 = getBaseUrl() + "/" + "collection2";
shard1 = urlCollection1.replaceAll("https?://", "");
shard2 = urlCollection2.replaceAll("https?://", "");
@@ -109,10 +109,6 @@ public static void destroyThings() throws Exception {
collection2.close();
collection2 = null;
}
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
resetExceptionIgnores();
systemClearPropertySolrDisableUrlAllowList();
}
diff --git a/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java b/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
index 82e9b99a473..d817845854a 100644
--- a/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
+++ b/solr/core/src/test/org/apache/solr/cli/ApiToolTest.java
@@ -16,14 +16,30 @@
*/
package org.apache.solr.cli;
+import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
-import org.apache.solr.SolrTestCase;
+import java.util.Locale;
+import org.apache.lucene.tests.util.TestUtil;
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.UpdateRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.params.ModifiableSolrParams;
+import org.junit.BeforeClass;
import org.junit.Test;
-public class ApiToolTest extends SolrTestCase {
+public class ApiToolTest extends SolrCloudTestCase {
+ static String COLLECTION_NAME = "globalLoaderColl";
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig(
+ "config", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+ .configure();
+ }
@Test
public void testParsingGetUrl() throws URISyntaxException {
@@ -42,4 +58,48 @@ public void testParsingGetUrl() throws URISyntaxException {
assertEquals("select id from COLL_NAME limit 10", params.get("stmt"));
}
}
+
+ @Test
+ public void testQueryResponse() throws Exception {
+ int docCount = 1000;
+ CollectionAdminRequest.createCollection(COLLECTION_NAME, "config", 2, 1)
+ .process(cluster.getSolrClient());
+ cluster.waitForActiveCollection(COLLECTION_NAME, 2, 2);
+
+ String tmpFileLoc =
+ new File(cluster.getBaseDir().toFile().getAbsolutePath() + File.separator).getPath();
+
+ UpdateRequest ur = new UpdateRequest();
+ ur.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+
+ for (int i = 0; i < docCount; i++) {
+ ur.add(
+ "id",
+ String.valueOf(i),
+ "desc_s",
+ TestUtil.randomSimpleString(random(), 10, 50),
+ "a_dt",
+ "2019-09-30T05:58:03Z");
+ }
+ cluster.getSolrClient().request(ur, COLLECTION_NAME);
+
+ ApiTool tool = new ApiTool();
+
+ String response =
+ tool.callGet(
+ cluster.getJettySolrRunner(0).getBaseUrl()
+ + "/"
+ + COLLECTION_NAME
+ + "/select?q=*:*&rows=1&fl=id&sort=id+asc");
+ // Fields that could be missed because of serialization
+ assertFindInJson(response, "\"numFound\":1000,");
+ // Correct formatting
+ assertFindInJson(response, "\"docs\":[{");
+ }
+
+ private void assertFindInJson(String json, String find) {
+ assertTrue(
+ String.format(Locale.ROOT, "Could not find string %s in response: \n%s", find, json),
+ json.contains(find));
+ }
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/BalanceReplicasTest.java b/solr/core/src/test/org/apache/solr/cloud/BalanceReplicasTest.java
index 82a75027e68..a14b372df2e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/BalanceReplicasTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/BalanceReplicasTest.java
@@ -33,14 +33,15 @@
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
+import org.apache.solr.client.api.model.BalanceReplicasRequestBody;
import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.common.MapWriterMap;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
-import org.apache.solr.handler.admin.api.BalanceReplicasAPI;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -101,7 +102,7 @@ public void testAllNodes() throws Exception {
postDataAndGetResponse(
cluster.getSolrClient(),
"/api/cluster/replicas/balance",
- BalanceReplicasAPI.BalanceReplicasRequestBody.EMPTY);
+ new MapWriterMap(Collections.emptyMap()));
collection = cloudClient.getClusterState().getCollectionOrNull(coll, false);
log.debug("### After balancing: {}", collection);
@@ -149,8 +150,8 @@ public void testSomeNodes() throws Exception {
postDataAndGetResponse(
cluster.getSolrClient(),
"/api/cluster/replicas/balance",
- new BalanceReplicasAPI.BalanceReplicasRequestBody(
- new HashSet<>(l.subList(1, 4)), true, null));
+ Utils.getReflectWriter(
+ new BalanceReplicasRequestBody(new HashSet<>(l.subList(1, 4)), true, null)));
collection = cloudClient.getClusterState().getCollectionOrNull(coll, false);
log.debug("### After balancing: {}", collection);
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestDistributedMap.java b/solr/core/src/test/org/apache/solr/cloud/TestDistributedMap.java
index 0b3325e96e5..aa5915f007b 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestDistributedMap.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestDistributedMap.java
@@ -23,6 +23,7 @@
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.file.PathUtils;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
@@ -218,6 +219,36 @@ public void testClear() throws KeeperException, InterruptedException {
}
}
+ public void testMalformed() throws InterruptedException, KeeperException {
+ try (SolrZkClient zkClient =
+ new SolrZkClient.Builder()
+ .withUrl(zkServer.getZkHost())
+ .withTimeout(10000, TimeUnit.MILLISECONDS)
+ .build()) {
+ String path = getAndMakeInitialPath(zkClient);
+ DistributedMap map = createMap(zkClient, path);
+ expectThrows(SolrException.class, () -> map.put("has/slash", new byte[0]));
+ }
+ }
+
+ public void testRemoveMalformed() throws InterruptedException, KeeperException {
+ try (SolrZkClient zkClient =
+ new SolrZkClient.Builder()
+ .withUrl(zkServer.getZkHost())
+ .withTimeout(10000, TimeUnit.MILLISECONDS)
+ .build()) {
+ String path = getAndMakeInitialPath(zkClient);
+ // Add a "legacy" / malformed key
+ final var key = "slash/test/0";
+ zkClient.makePath(path + "/" + DistributedMap.PREFIX + key, new byte[0], true);
+
+ DistributedMap map = createMap(zkClient, path);
+ assertEquals(1, map.size());
+ map.remove("slash");
+ assertEquals(0, map.size());
+ }
+ }
+
protected DistributedMap createMap(SolrZkClient zkClient, String path) {
return new DistributedMap(zkClient, path);
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestSizeLimitedDistributedMap.java b/solr/core/src/test/org/apache/solr/cloud/TestSizeLimitedDistributedMap.java
index 493b7bf062b..a534e2e2a30 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestSizeLimitedDistributedMap.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestSizeLimitedDistributedMap.java
@@ -25,9 +25,11 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.SolrNamedThreadFactory;
+import org.apache.zookeeper.KeeperException;
public class TestSizeLimitedDistributedMap extends TestDistributedMap {
@@ -136,4 +138,32 @@ public void testConcurrentCleanup() throws Exception {
protected DistributedMap createMap(SolrZkClient zkClient, String path) {
return new SizeLimitedDistributedMap(zkClient, path, Overseer.NUM_RESPONSES_TO_STORE, null);
}
+
+ public void testCleanupForKeysWithSlashes() throws InterruptedException, KeeperException {
+ try (SolrZkClient zkClient =
+ new SolrZkClient.Builder()
+ .withUrl(zkServer.getZkHost())
+ .withTimeout(10000, TimeUnit.MILLISECONDS)
+ .build()) {
+ int maxEntries = 10;
+ String path = getAndMakeInitialPath(zkClient);
+
+ // Add a "legacy" / malformed key
+ zkClient.makePath(path + "/" + DistributedMap.PREFIX + "slash/test/0", new byte[0], true);
+
+ AtomicInteger overFlowCounter = new AtomicInteger();
+ DistributedMap map =
+ new SizeLimitedDistributedMap(
+ zkClient, path, maxEntries, (element) -> overFlowCounter.incrementAndGet());
+
+ // Now add regular keys until we reach the size limit of the map.
+ // Once we hit the limit, the oldest item (the one we added above with slashes) is deleted,
+ // but that fails.
+ for (int i = 1; i <= maxEntries; ++i) {
+ map.put(String.valueOf(i), new byte[0]);
+ }
+ assertTrue(map.size() <= maxEntries);
+ assertEquals(1, overFlowCounter.get());
+ }
+ }
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java b/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
index 90a0f1a4580..c84d180a6b9 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
@@ -30,7 +30,6 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
-import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterProperties;
@@ -83,7 +82,7 @@ public void setUp() throws Exception {
log.info("####SETUP_START {}", getTestName());
}
- String exampleHome = SolrJettyTestBase.legacyExampleCollection1SolrHome();
+ String exampleHome = legacyExampleCollection1SolrHome();
Path tmpDir = createTempDir();
solrHome = exampleHome;
diff --git a/solr/core/src/test/org/apache/solr/core/PluginBagTest.java b/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
index bfc585b8840..d3831489edb 100644
--- a/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
+++ b/solr/core/src/test/org/apache/solr/core/PluginBagTest.java
@@ -28,7 +28,7 @@
import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.solr.handler.admin.api.CollectionPropertyAPI;
import org.apache.solr.handler.component.SearchComponent;
-import org.apache.solr.handler.configsets.ListConfigSetsAPI;
+import org.apache.solr.handler.configsets.ListConfigSets;
import org.apache.solr.jersey.APIConfigProvider;
import org.apache.solr.jersey.APIConfigProvider.APIConfig;
import org.apache.solr.jersey.APIConfigProviderBinder;
@@ -95,12 +95,12 @@ public void testCreatesContainerSpecificJerseyAppIfNoCoreProvided() {
public void testRegistersJerseyResourcesAssociatedWithRequestHandlers() {
final PluginBag handlerPluginBag =
new PluginBag<>(SolrRequestHandler.class, null);
- assertFalse(handlerPluginBag.getJerseyEndpoints().isRegistered(ListConfigSetsAPI.class));
+ assertFalse(handlerPluginBag.getJerseyEndpoints().isRegistered(ListConfigSets.class));
handlerPluginBag.put("/foo", new ConfigSetsHandler(coreContainer));
final ResourceConfig config = handlerPluginBag.getJerseyEndpoints();
- assertTrue(handlerPluginBag.getJerseyEndpoints().isRegistered(ListConfigSetsAPI.class));
+ assertTrue(handlerPluginBag.getJerseyEndpoints().isRegistered(ListConfigSets.class));
}
@Test
diff --git a/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java b/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java
index 682469f85f3..a3282439ac3 100644
--- a/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java
+++ b/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java
@@ -66,11 +66,8 @@ public void before() throws Exception {
@After
public void after() throws Exception {
- if (jetty != null) {
- jetty.stop();
- jetty = null;
- }
- client = null;
+ solrClientTestRule.reset();
+
if (restTestHarness != null) {
restTestHarness.close();
}
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index 44ad982c714..515f28c86eb 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -133,17 +133,14 @@ public void before() throws Exception {
if (random().nextBoolean()) {
log.info("These tests are run with V2 API");
restTestHarness.setServerProvider(
- () -> jetty.getBaseUrl().toString() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
+ () -> getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
}
}
@After
public void after() throws Exception {
- if (jetty != null) {
- jetty.stop();
- jetty = null;
- }
- client = null;
+ solrClientTestRule.reset();
+
if (restTestHarness != null) {
restTestHarness.close();
}
@@ -973,7 +970,7 @@ public void testReqParams() throws Exception {
TIMEOUT_S);
RESTfulServerProvider oldProvider = restTestHarness.getServerProvider();
restTestHarness.setServerProvider(
- () -> jetty.getBaseUrl().toString() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
+ () -> getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
Map, ?> rsp =
TestSolrConfigHandler.testForResponseElement(
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
index 46e6c6ba9b9..51ac2bb0e65 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
@@ -31,6 +31,11 @@
import org.apache.lucene.tests.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
+import org.apache.solr.search.CacheConfig;
+import org.apache.solr.search.CaffeineCache;
+import org.apache.solr.search.SolrCache;
+import org.apache.solr.search.TestThinCache;
+import org.apache.solr.search.ThinCache;
import org.apache.solr.update.UpdateShardHandlerConfig;
import org.hamcrest.MatcherAssert;
import org.junit.Before;
@@ -151,6 +156,16 @@ public void testPropertySub() throws IOException {
assertFalse("schema cache", cfg.hasSchemaCache());
}
+ public void testNodeLevelCache() {
+ NodeConfig cfg =
+ SolrXmlConfig.fromString(createTempDir(), TestThinCache.SOLR_NODE_LEVEL_CACHE_XML);
+ Map cachesConfig = cfg.getCachesConfig();
+ SolrCache, ?> nodeLevelCache = cachesConfig.get("myNodeLevelCache").newInstance();
+ assertTrue(nodeLevelCache instanceof CaffeineCache);
+ SolrCache, ?> nodeLevelCacheThin = cachesConfig.get("myNodeLevelCacheThin").newInstance();
+ assertTrue(nodeLevelCacheThin instanceof ThinCache.NodeLevelCache);
+ }
+
public void testExplicitNullGivesDefaults() {
System.setProperty("jetty.port", "8000");
String solrXml =
diff --git a/solr/core/src/test/org/apache/solr/handler/TestContainerPlugin.java b/solr/core/src/test/org/apache/solr/handler/TestContainerPlugin.java
index 9fdb0936e88..27daaab4d9a 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestContainerPlugin.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestContainerPlugin.java
@@ -220,8 +220,11 @@ public void testApi() throws Exception {
MatcherAssert.assertThat(msg, containsString("Cannot find API for the path"));
// test ClusterSingleton plugin
+ CConfig c6Cfg = new CConfig();
+ c6Cfg.strVal = "added";
plugin.name = "clusterSingleton";
plugin.klass = C6.class.getName();
+ plugin.config = c6Cfg;
addPlugin.process(cluster.getSolrClient());
version = phaser.awaitAdvanceInterruptibly(version, 10, TimeUnit.SECONDS);
@@ -233,6 +236,16 @@ public void testApi() throws Exception {
assertTrue("startCalled", C6.startCalled);
assertFalse("stopCalled", C6.stopCalled);
+ // update the clusterSingleton config
+ c6Cfg.strVal = "updated";
+ postPlugin(singletonMap("update", plugin)).process(cluster.getSolrClient());
+ version = phaser.awaitAdvanceInterruptibly(version, 10, TimeUnit.SECONDS);
+
+ assertTrue("stopCalled", C6.stopCalled);
+
+ // Clear stopCalled, it will be verified again when the Overseer Jetty is killed
+ C6.stopCalled = false;
+
assertEquals(CConfig.class, ContainerPluginsRegistry.getConfigClass(new CC()));
assertEquals(CConfig.class, ContainerPluginsRegistry.getConfigClass(new CC1()));
assertEquals(CConfig.class, ContainerPluginsRegistry.getConfigClass(new CC2()));
@@ -394,7 +407,7 @@ public static class CConfig implements ReflectMapWriter {
@JsonProperty public Boolean boolVal;
}
- public static class C6 implements ClusterSingleton {
+ public static class C6 implements ClusterSingleton, ConfigurablePlugin {
static boolean startCalled = false;
static boolean stopCalled = false;
static boolean ccProvided = false;
@@ -430,6 +443,9 @@ public void stop() {
stopCalled = true;
state = State.STOPPED;
}
+
+ @Override
+ public void configure(CConfig cfg) {}
}
public static class C5 implements ResourceLoaderAware {
diff --git a/solr/core/src/test/org/apache/solr/handler/TestHttpRequestId.java b/solr/core/src/test/org/apache/solr/handler/TestHttpRequestId.java
index 7e8a15004d7..b97b9f9a143 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestHttpRequestId.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestHttpRequestId.java
@@ -105,7 +105,7 @@ public void onFailure(Throwable throwable) {
new SolrNamedThreadFactory("httpShardExecutor"),
false);
try (Http2SolrClient client =
- new Http2SolrClient.Builder(jetty.getBaseUrl().toString() + collection)
+ new Http2SolrClient.Builder(getBaseUrl() + collection)
.withExecutor(commExecutor)
.build()) {
MDC.put(key, value);
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
index 02b90ba8384..7d083d1c0cf 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
@@ -27,10 +27,11 @@
import static org.apache.solr.common.params.CoreAdminParams.MAX_NUM_BACKUP_POINTS;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
import org.apache.solr.common.SolrException;
import org.junit.Test;
-/** Unit tests for {@link DeleteCollectionBackupAPI} */
+/** Unit tests for {@link DeleteCollectionBackup} */
public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
@Test
public void testReportsErrorIfBackupNameMissing() {
@@ -40,7 +41,7 @@ public void testReportsErrorIfBackupNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteCollectionBackupAPI(null, null, null);
+ final var api = new DeleteCollectionBackup(null, null, null);
api.deleteSingleBackupById(
null, "someBackupId", "someLocation", "someRepository", "someAsyncId");
});
@@ -55,7 +56,7 @@ public void testReportsErrorIfBackupNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteCollectionBackupAPI(null, null, null);
+ final var api = new DeleteCollectionBackup(null, null, null);
api.deleteMultipleBackupsByRecency(
null, 123, "someLocation", "someRepository", "someAsyncId");
});
@@ -66,15 +67,15 @@ public void testReportsErrorIfBackupNameMissing() {
// Garbage collect unused files
{
- final var requestBody = new DeleteCollectionBackupAPI.PurgeUnusedFilesRequestBody();
+ final var requestBody = new PurgeUnusedFilesRequestBody();
requestBody.location = "someLocation";
requestBody.repositoryName = "someRepository";
- requestBody.asyncId = "someAsyncId";
+ requestBody.async = "someAsyncId";
final SolrException thrown =
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteCollectionBackupAPI(null, null, null);
+ final var api = new DeleteCollectionBackup(null, null, null);
api.garbageCollectUnusedBackupFiles(null, requestBody);
});
@@ -89,7 +90,7 @@ public void testDeletionByIdReportsErrorIfIdMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteCollectionBackupAPI(null, null, null);
+ final var api = new DeleteCollectionBackup(null, null, null);
api.deleteSingleBackupById(
"someBackupName", null, "someLocation", "someRepository", "someAsyncId");
});
@@ -104,7 +105,7 @@ public void testMultiVersionDeletionReportsErrorIfRetainParamMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteCollectionBackupAPI(null, null, null);
+ final var api = new DeleteCollectionBackup(null, null, null);
api.deleteMultipleBackupsByRecency(
"someBackupName", null, "someLocation", "someRepository", "someAsyncId");
});
@@ -118,7 +119,7 @@ public void testMultiVersionDeletionReportsErrorIfRetainParamMissing() {
@Test
public void testCreateRemoteMessageAllParams() {
final var remoteMessage =
- DeleteCollectionBackupAPI.createRemoteMessage(
+ DeleteCollectionBackup.createRemoteMessage(
"someBackupName",
"someBackupId",
123,
@@ -142,7 +143,7 @@ public void testCreateRemoteMessageAllParams() {
@Test
public void testCreateRemoteMessageOnlyRequiredParams() {
final var remoteMessage =
- DeleteCollectionBackupAPI.createRemoteMessage(
+ DeleteCollectionBackup.createRemoteMessage(
"someBackupName", "someBackupId", null, null, null, null, null)
.getProperties();
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
index ac363a02bae..d6e1769e09c 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
@@ -34,8 +34,7 @@ public class DeleteCollectionSnapshotAPITest extends SolrTestCaseJ4 {
@Test
public void testConstructsValidOverseerMessage() {
final ZkNodeProps messageOne =
- DeleteCollectionSnapshotAPI.createRemoteMessage(
- "myCollName", false, "mySnapshotName", null);
+ DeleteCollectionSnapshot.createRemoteMessage("myCollName", false, "mySnapshotName", null);
final Map rawMessageOne = messageOne.getProperties();
assertEquals(4, rawMessageOne.size());
MatcherAssert.assertThat(
@@ -48,7 +47,7 @@ public void testConstructsValidOverseerMessage() {
assertEquals(false, rawMessageOne.get(FOLLOW_ALIASES));
final ZkNodeProps messageTwo =
- DeleteCollectionSnapshotAPI.createRemoteMessage(
+ DeleteCollectionSnapshot.createRemoteMessage(
"myCollName", true, "mySnapshotName", "myAsyncId");
final Map rawMessageTwo = messageTwo.getProperties();
assertEquals(5, rawMessageTwo.size());
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
index f306881bf41..74b6b3e927b 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
@@ -20,13 +20,14 @@
import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.junit.BeforeClass;
import org.junit.Test;
-/** Unit tests for {@link DeleteNodeAPI} */
+/** Unit tests for {@link DeleteNode} */
public class DeleteNodeAPITest extends SolrTestCaseJ4 {
@BeforeClass
@@ -36,22 +37,22 @@ public static void ensureWorkingMockito() {
@Test
public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
- final var api = mock(DeleteNodeAPI.class);
+ final var api = mock(DeleteNode.class);
final SolrException e =
expectThrows(
SolrException.class,
() -> {
- DeleteNodeAPI.invokeUsingV1Inputs(api, new ModifiableSolrParams());
+ DeleteNode.invokeUsingV1Inputs(api, new ModifiableSolrParams());
});
assertEquals("Missing required parameter: node", e.getMessage());
}
@Test
public void testValidOverseerMessageIsCreated() {
- DeleteNodeAPI.DeleteNodeRequestBody requestBody =
- new DeleteNodeAPI.DeleteNodeRequestBody("async");
+ final var requestBody = new DeleteNodeRequestBody();
+ requestBody.async = "async";
final ZkNodeProps createdMessage =
- DeleteNodeAPI.createRemoteMessage("nodeNameToDelete", requestBody);
+ DeleteNode.createRemoteMessage("nodeNameToDelete", requestBody);
final Map createdMessageProps = createdMessage.getProperties();
assertEquals(3, createdMessageProps.size());
assertEquals("nodeNameToDelete", createdMessageProps.get("node"));
@@ -61,7 +62,7 @@ public void testValidOverseerMessageIsCreated() {
@Test
public void testRequestBodyCanBeOmitted() throws Exception {
- final ZkNodeProps createdMessage = DeleteNodeAPI.createRemoteMessage("nodeNameToDelete", null);
+ final ZkNodeProps createdMessage = DeleteNode.createRemoteMessage("nodeNameToDelete", null);
final Map createdMessageProps = createdMessage.getProperties();
assertEquals(2, createdMessageProps.size());
assertEquals("nodeNameToDelete", createdMessageProps.get("node"));
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
index 3c56f7d189f..d0dca0bce8e 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
@@ -33,7 +33,7 @@
import org.apache.solr.common.SolrException;
import org.junit.Test;
-/** Unit tests for {@link DeleteReplicaAPI} */
+/** Unit tests for {@link DeleteReplica} */
public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
@Test
public void testReportsErrorIfCollectionNameMissing() {
@@ -41,7 +41,7 @@ public void testReportsErrorIfCollectionNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteReplicaAPI(null, null, null);
+ final var api = new DeleteReplica(null, null, null);
api.deleteReplicaByName(
null, "someShard", "someReplica", null, null, null, null, null, null);
});
@@ -56,7 +56,7 @@ public void testReportsErrorIfShardNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteReplicaAPI(null, null, null);
+ final var api = new DeleteReplica(null, null, null);
api.deleteReplicaByName(
"someCollection", null, "someReplica", null, null, null, null, null, null);
});
@@ -71,7 +71,7 @@ public void testReportsErrorIfReplicaNameMissingWhenDeletingByName() {
expectThrows(
SolrException.class,
() -> {
- final var api = new DeleteReplicaAPI(null, null, null);
+ final var api = new DeleteReplica(null, null, null);
api.deleteReplicaByName(
"someCollection", "someShard", null, null, null, null, null, null, null);
});
@@ -83,7 +83,7 @@ public void testReportsErrorIfReplicaNameMissingWhenDeletingByName() {
@Test
public void testCreateRemoteMessageAllProperties() {
final var remoteMessage =
- DeleteReplicaAPI.createRemoteMessage(
+ DeleteReplica.createRemoteMessage(
"someCollName",
"someShardName",
"someReplicaName",
@@ -113,7 +113,7 @@ public void testCreateRemoteMessageAllProperties() {
@Test
public void testMissingValuesExcludedFromRemoteMessage() {
final var remoteMessage =
- DeleteReplicaAPI.createRemoteMessage(
+ DeleteReplica.createRemoteMessage(
"someCollName",
"someShardName",
"someReplicaName",
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
index d03193072e0..19c85b85481 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
@@ -34,7 +34,7 @@
import org.junit.Test;
/**
- * Unit tests for {@link DeleteReplicaPropertyAPI}
+ * Unit tests for {@link DeleteReplicaProperty}
*
* End-to-end functionality is tested implicitly through v1 integration tests, so the unit tests
* here focus primarily on how the v1 code invokes the v2 API and how the v2 API crafts its
@@ -49,7 +49,7 @@ public static void ensureWorkingMockito() {
@Test
public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
- final var api = mock(DeleteReplicaPropertyAPI.class);
+ final var api = mock(DeleteReplicaProperty.class);
final var allParams = new ModifiableSolrParams();
allParams.add(COLLECTION_PROP, "someColl");
allParams.add(SHARD_ID_PROP, "someShard");
@@ -63,7 +63,7 @@ public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
expectThrows(
SolrException.class,
() -> {
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noCollectionParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, noCollectionParams);
});
assertEquals("Missing required parameter: " + COLLECTION_PROP, e.getMessage());
}
@@ -75,7 +75,7 @@ public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
expectThrows(
SolrException.class,
() -> {
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noShardParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, noShardParams);
});
assertEquals("Missing required parameter: " + SHARD_ID_PROP, e.getMessage());
}
@@ -87,7 +87,7 @@ public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
expectThrows(
SolrException.class,
() -> {
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noReplicaParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, noReplicaParams);
});
assertEquals("Missing required parameter: " + REPLICA_PROP, e.getMessage());
}
@@ -99,7 +99,7 @@ public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
expectThrows(
SolrException.class,
() -> {
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noPropertyParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, noPropertyParams);
});
assertEquals("Missing required parameter: " + PROPERTY_PROP, e.getMessage());
}
@@ -107,28 +107,28 @@ public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
@Test
public void testV1InvocationPassesAllProvidedParameters() throws Exception {
- final var api = mock(DeleteReplicaPropertyAPI.class);
+ final var api = mock(DeleteReplicaProperty.class);
final var allParams = new ModifiableSolrParams();
allParams.add(COLLECTION_PROP, "someColl");
allParams.add(SHARD_ID_PROP, "someShard");
allParams.add(REPLICA_PROP, "someReplica");
allParams.add(PROPERTY_PROP, "somePropName");
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, allParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, allParams);
verify(api).deleteReplicaProperty("someColl", "someShard", "someReplica", "somePropName");
}
@Test
public void testV1InvocationTrimsPropertyNamePrefixIfProvided() throws Exception {
- final var api = mock(DeleteReplicaPropertyAPI.class);
+ final var api = mock(DeleteReplicaProperty.class);
final var allParams = new ModifiableSolrParams();
allParams.add(COLLECTION_PROP, "someColl");
allParams.add(SHARD_ID_PROP, "someShard");
allParams.add(REPLICA_PROP, "someReplica");
allParams.add(PROPERTY_PROP, "property.somePropName"); // NOTE THE "property." prefix!
- DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, allParams);
+ DeleteReplicaProperty.invokeUsingV1Inputs(api, allParams);
verify(api).deleteReplicaProperty("someColl", "someShard", "someReplica", "somePropName");
}
@@ -136,7 +136,7 @@ public void testV1InvocationTrimsPropertyNamePrefixIfProvided() throws Exception
@Test
public void testRPCMessageCreation() {
final ZkNodeProps message =
- DeleteReplicaPropertyAPI.createRemoteMessage(
+ DeleteReplicaProperty.createRemoteMessage(
"someColl", "someShard", "someReplica", "somePropName");
final Map props = message.getProperties();
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ForceLeaderAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ForceLeaderAPITest.java
index 09f97a4757f..be1fce44fb8 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/ForceLeaderAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ForceLeaderAPITest.java
@@ -21,7 +21,7 @@
import org.apache.solr.common.SolrException;
import org.junit.Test;
-/** Unit tests for {@link ForceLeaderAPI} */
+/** Unit tests for {@link ForceLeader} */
public class ForceLeaderAPITest extends SolrTestCaseJ4 {
@Test
public void testReportsErrorIfCollectionNameMissing() {
@@ -29,8 +29,8 @@ public void testReportsErrorIfCollectionNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new ForceLeaderAPI(null, null, null);
- api.forceLeader(null, "someShard");
+ final var api = new ForceLeader(null, null, null);
+ api.forceShardLeader(null, "someShard");
});
assertEquals(400, thrown.code());
@@ -43,8 +43,8 @@ public void testReportsErrorIfShardNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new ForceLeaderAPI(null, null, null);
- api.forceLeader("someCollection", null);
+ final var api = new ForceLeader(null, null, null);
+ api.forceShardLeader("someCollection", null);
});
assertEquals(400, thrown.code());
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ListAliasesAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ListAliasesAPITest.java
index 2a6607c40b6..9939217b4b0 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/ListAliasesAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ListAliasesAPITest.java
@@ -23,20 +23,20 @@
import java.util.List;
import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.GetAliasByNameResponse;
+import org.apache.solr.client.api.model.ListAliasesResponse;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.cloud.ZkStateReader.AliasesManager;
import org.apache.solr.core.CoreContainer;
-import org.apache.solr.handler.admin.api.ListAliasesAPI.GetAliasByNameResponse;
-import org.apache.solr.handler.admin.api.ListAliasesAPI.ListAliasesResponse;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
-/** Unit tests for {@link ListAliasesAPI} */
+/** Unit tests for {@link ListAliases} */
public class ListAliasesAPITest extends SolrTestCaseJ4 {
private CoreContainer mockCoreContainer;
@@ -44,7 +44,7 @@ public class ListAliasesAPITest extends SolrTestCaseJ4 {
private SolrQueryRequest mockQueryRequest;
private SolrQueryResponse queryResponse;
- private ListAliasesAPI getAliasesAPI;
+ private ListAliases getAliasesAPI;
@BeforeClass
public static void ensureWorkingMockito() {
@@ -70,7 +70,7 @@ public void setUp() throws Exception {
when(mockQueryRequest.getSpan()).thenReturn(Span.getInvalid());
queryResponse = new SolrQueryResponse();
- getAliasesAPI = new ListAliasesAPI(mockCoreContainer, mockQueryRequest, queryResponse);
+ getAliasesAPI = new ListAliases(mockCoreContainer, mockQueryRequest, queryResponse);
}
@Test
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateReplicasAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateReplicasAPITest.java
index e87a4c67532..f9bf865d4e0 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateReplicasAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateReplicasAPITest.java
@@ -40,7 +40,7 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
-/** Unit tests for {@link ReplaceNodeAPI} */
+/** Unit tests for {@link ReplaceNode} */
public class MigrateReplicasAPITest extends SolrTestCaseJ4 {
private CoreContainer mockCoreContainer;
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ReloadCollectionAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ReloadCollectionAPITest.java
index e7534dc0c1a..5900fece7a4 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/ReloadCollectionAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ReloadCollectionAPITest.java
@@ -22,6 +22,7 @@
import static org.apache.solr.common.params.CoreAdminParams.NAME;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.ReloadCollectionRequestBody;
import org.apache.solr.common.SolrException;
import org.junit.Test;
@@ -34,7 +35,7 @@ public void testReportsErrorIfCollectionNameMissing() {
SolrException.class,
() -> {
final var api = new ReloadCollectionAPI(null, null, null);
- api.reloadCollection(null, new ReloadCollectionAPI.ReloadCollectionRequestBody());
+ api.reloadCollection(null, new ReloadCollectionRequestBody());
});
assertEquals(400, thrown.code());
@@ -44,8 +45,8 @@ public void testReportsErrorIfCollectionNameMissing() {
// TODO message creation
@Test
public void testCreateRemoteMessageAllProperties() {
- final var requestBody = new ReloadCollectionAPI.ReloadCollectionRequestBody();
- requestBody.asyncId = "someAsyncId";
+ final var requestBody = new ReloadCollectionRequestBody();
+ requestBody.async = "someAsyncId";
final var remoteMessage =
ReloadCollectionAPI.createRemoteMessage("someCollName", requestBody).getProperties();
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ReplaceNodeAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ReplaceNodeAPITest.java
index 3a460245775..e49eb56de0d 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/ReplaceNodeAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ReplaceNodeAPITest.java
@@ -25,6 +25,7 @@
import java.util.Map;
import java.util.Optional;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
import org.apache.solr.cloud.OverseerSolrResponse;
import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
import org.apache.solr.common.cloud.ZkNodeProps;
@@ -37,13 +38,13 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
-/** Unit tests for {@link ReplaceNodeAPI} */
+/** Unit tests for {@link ReplaceNode} */
public class ReplaceNodeAPITest extends SolrTestCaseJ4 {
private CoreContainer mockCoreContainer;
private SolrQueryRequest mockQueryRequest;
private SolrQueryResponse queryResponse;
- private ReplaceNodeAPI replaceNodeApi;
+ private ReplaceNode replaceNodeApi;
private DistributedCollectionConfigSetCommandRunner mockCommandRunner;
private ArgumentCaptor messageCapturer;
@@ -65,7 +66,7 @@ public void setUp() throws Exception {
.thenReturn(new OverseerSolrResponse(new NamedList<>()));
mockQueryRequest = mock(SolrQueryRequest.class);
queryResponse = new SolrQueryResponse();
- replaceNodeApi = new ReplaceNodeAPI(mockCoreContainer, mockQueryRequest, queryResponse);
+ replaceNodeApi = new ReplaceNode(mockCoreContainer, mockQueryRequest, queryResponse);
messageCapturer = ArgumentCaptor.forClass(ZkNodeProps.class);
when(mockCoreContainer.isZooKeeperAware()).thenReturn(true);
@@ -73,8 +74,7 @@ public void setUp() throws Exception {
@Test
public void testCreatesValidOverseerMessage() throws Exception {
- ReplaceNodeAPI.ReplaceNodeRequestBody requestBody =
- new ReplaceNodeAPI.ReplaceNodeRequestBody("demoTargetNode", false, "async");
+ final var requestBody = new ReplaceNodeRequestBody("demoTargetNode", false, "async");
replaceNodeApi.replaceNode("demoSourceNode", requestBody);
verify(mockCommandRunner).runCollectionCommand(messageCapturer.capture(), any(), anyLong());
@@ -102,8 +102,7 @@ public void testRequestBodyCanBeOmittedAltogether() throws Exception {
@Test
public void testOptionalValuesNotAddedToRemoteMessageIfNotProvided() throws Exception {
- ReplaceNodeAPI.ReplaceNodeRequestBody requestBody =
- new ReplaceNodeAPI.ReplaceNodeRequestBody("demoTargetNode", null, null);
+ final var requestBody = new ReplaceNodeRequestBody("demoTargetNode", null, null);
replaceNodeApi.replaceNode("demoSourceNode", requestBody);
verify(mockCommandRunner).runCollectionCommand(messageCapturer.capture(), any(), anyLong());
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SyncShardAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SyncShardAPITest.java
index 25ca5bb9cd1..23d3f8982a2 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/SyncShardAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SyncShardAPITest.java
@@ -21,7 +21,7 @@
import org.apache.solr.common.SolrException;
import org.junit.Test;
-/** Unit tests for {@link SyncShardAPI} */
+/** Unit tests for {@link SyncShard} */
public class SyncShardAPITest extends SolrTestCaseJ4 {
@Test
public void testReportsErrorIfCollectionNameMissing() {
@@ -29,7 +29,7 @@ public void testReportsErrorIfCollectionNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new SyncShardAPI(null, null, null);
+ final var api = new SyncShard(null, null, null);
api.syncShard(null, "someShard");
});
@@ -43,7 +43,7 @@ public void testReportsErrorIfShardNameMissing() {
expectThrows(
SolrException.class,
() -> {
- final var api = new SyncShardAPI(null, null, null);
+ final var api = new SyncShard(null, null, null);
api.syncShard("someCollection", null);
});
diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedDebugComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedDebugComponentTest.java
index 58cbe43717d..374cee78888 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/DistributedDebugComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedDebugComponentTest.java
@@ -61,13 +61,13 @@ public static void createThings() throws Exception {
systemSetPropertySolrDisableUrlAllowList("true");
File solrHome = createSolrHome();
createAndStartJetty(solrHome.getAbsolutePath());
- String url = jetty.getBaseUrl().toString();
+ String url = getBaseUrl();
collection1 = getHttpSolrClient(url + "/collection1");
collection2 = getHttpSolrClient(url + "/collection2");
- String urlCollection1 = jetty.getBaseUrl().toString() + "/" + "collection1";
- String urlCollection2 = jetty.getBaseUrl().toString() + "/" + "collection2";
+ String urlCollection1 = getBaseUrl() + "/" + "collection1";
+ String urlCollection2 = getBaseUrl() + "/" + "collection2";
shard1 = urlCollection1.replaceAll("https?://", "");
shard2 = urlCollection2.replaceAll("https?://", "");
@@ -101,10 +101,6 @@ public static void destroyThings() throws Exception {
collection2.close();
collection2 = null;
}
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
resetExceptionIgnores();
systemClearPropertySolrDisableUrlAllowList();
}
diff --git a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
index e26d4d2a73e..a0cf41622cd 100644
--- a/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/configsets/ListConfigSetsAPITest.java
@@ -29,6 +29,7 @@
import javax.inject.Singleton;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
+import org.apache.solr.client.api.model.ListConfigsetsResponse;
import org.apache.solr.client.solrj.response.ConfigSetAdminResponse;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.ConfigSetService;
@@ -44,7 +45,7 @@
import org.junit.Test;
/**
- * Unit tests for {@link ListConfigSetsAPI}.
+ * Unit tests for {@link ListConfigSets}.
*
* Serves primarily as a model and example of how to write unit tests using Jersey's test
* framework.
@@ -63,7 +64,7 @@ protected Application configure() {
forceSet(TestProperties.CONTAINER_PORT, "0");
resetMocks();
final ResourceConfig config = new ResourceConfig();
- config.register(ListConfigSetsAPI.class);
+ config.register(ListConfigSets.class);
config.register(SolrJacksonMapper.class);
config.register(
new AbstractBinder() {
@@ -90,7 +91,7 @@ public void testSuccessfulListConfigsetsRaw() throws Exception {
when(mockCoreContainer.getConfigSetService()).thenReturn(configSetService);
when(configSetService.listConfigs()).thenReturn(List.of("cs1", "cs2"));
- final Response response = target("/cluster/configs").request().get();
+ final Response response = target("/cluster/configs").request("application/json").get();
final String jsonBody = response.readEntity(String.class);
assertEquals(200, response.getStatus());
@@ -107,8 +108,8 @@ public void testSuccessfulListConfigsetsTyped() throws Exception {
when(mockCoreContainer.getConfigSetService()).thenReturn(configSetService);
when(configSetService.listConfigs()).thenReturn(List.of("cs1", "cs2"));
- final ListConfigSetsAPI.ListConfigsetsResponse response =
- target("/cluster/configs").request().get(ListConfigSetsAPI.ListConfigsetsResponse.class);
+ final var response =
+ target("/cluster/configs").request("application/json").get(ListConfigsetsResponse.class);
assertNotNull(response.configSets);
assertNull(response.error);
@@ -120,11 +121,11 @@ public void testSuccessfulListConfigsetsTyped() throws Exception {
/**
* Test the v2 to v1 response mapping for /cluster/configs
*
- *
{@link org.apache.solr.handler.admin.ConfigSetsHandler} uses {@link ListConfigSetsAPI} (and
- * its response class {@link ListConfigSetsAPI.ListConfigsetsResponse}) internally to serve the v1
- * version of this functionality. So it's important to make sure that the v2 response stays
- * compatible with SolrJ - both because that's important in its own right and because that ensures
- * we haven't accidentally changed the v1 response format.
+ *
{@link org.apache.solr.handler.admin.ConfigSetsHandler} uses {@link ListConfigSets} (and its
+ * response class {@link ListConfigsetsResponse}) internally to serve the v1 version of this
+ * functionality. So it's important to make sure that the v2 response stays compatible with SolrJ
+ * - both because that's important in its own right and because that ensures we haven't
+ * accidentally changed the v1 response format.
*/
@Test
public void testListConfigsetsV1Compatibility() throws Exception {
@@ -132,8 +133,8 @@ public void testListConfigsetsV1Compatibility() throws Exception {
when(mockCoreContainer.getConfigSetService()).thenReturn(configSetService);
when(configSetService.listConfigs()).thenReturn(List.of("cs1", "cs2"));
- final ListConfigSetsAPI.ListConfigsetsResponse response =
- target("/cluster/configs").request().get(ListConfigSetsAPI.ListConfigsetsResponse.class);
+ final var response =
+ target("/cluster/configs").request("application/json").get(ListConfigsetsResponse.class);
final NamedList squashedResponse = new NamedList<>();
V2ApiUtils.squashIntoNamedList(squashedResponse, response);
final ConfigSetAdminResponse.List solrjResponse = new ConfigSetAdminResponse.List();
diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
index 970818a27d4..b7dbd43a611 100644
--- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
@@ -89,7 +89,7 @@ public void testSystemProperties() {
// make sure it's set
System.setProperty("basicauth", "foo:bar");
}
- SolrMetricManager metricManager = jetty.getCoreContainer().getMetricManager();
+ SolrMetricManager metricManager = getJetty().getCoreContainer().getMetricManager();
Map metrics = metricManager.registry("solr.jvm").getMetrics();
Metric metric = metrics.get("system.properties");
assertNotNull(metrics.toString(), metric);
@@ -125,7 +125,7 @@ public void testHiddenSysProps() throws Exception {
@Test
public void testSetupJvmMetrics() {
- SolrMetricManager metricManager = jetty.getCoreContainer().getMetricManager();
+ SolrMetricManager metricManager = getJetty().getCoreContainer().getMetricManager();
Map metrics = metricManager.registry("solr.jvm").getMetrics();
assertTrue(metrics.size() > 0);
assertTrue(
diff --git a/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java b/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
index e36fbb1969c..486b3396afd 100644
--- a/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
+++ b/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
@@ -34,7 +34,6 @@
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.embedded.JettySolrRunner;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -43,8 +42,6 @@
@SuppressSSL // does not yet work with ssl yet - uses raw java.net.URL API rather than HttpClient
public class TestRemoteStreaming extends SolrJettyTestBase {
- private static JettySolrRunner jettySolrRunner;
-
@BeforeClass
public static void beforeTest() throws Exception {
System.setProperty("solr.enableRemoteStreaming", "true");
@@ -52,7 +49,7 @@ public static void beforeTest() throws Exception {
// this one has handleSelect=true which a test here needs
File solrHomeDirectory = createTempDir(LuceneTestCase.getTestClass().getSimpleName()).toFile();
setupJettyTestHome(solrHomeDirectory, "collection1");
- jettySolrRunner = createAndStartJetty(solrHomeDirectory.getAbsolutePath());
+ createAndStartJetty(solrHomeDirectory.getAbsolutePath());
}
@Before
@@ -75,16 +72,10 @@ public void testMakeDeleteAllUrl() throws Exception {
@Test
public void testStreamUrl() throws Exception {
- String streamUrl =
- jettySolrRunner.getBaseUrl().toString()
- + "/"
- + DEFAULT_TEST_COLLECTION_NAME
- + "/select?q=*:*&fl=id&wt=csv";
+ String streamUrl = getCoreUrl() + "/select?q=*:*&fl=id&wt=csv";
String getUrl =
- jettySolrRunner.getBaseUrl().toString()
- + "/"
- + DEFAULT_TEST_COLLECTION_NAME
+ getCoreUrl()
+ "/debug/dump?wt=xml&stream.url="
+ URLEncoder.encode(streamUrl, StandardCharsets.UTF_8);
String content = attemptHttpGet(getUrl);
@@ -109,18 +100,14 @@ public void testNoUrlAccess() throws Exception {
SolrQuery query = new SolrQuery();
query.setQuery("*:*"); // for anything
query.add("stream.url", makeDeleteAllUrl());
- try (SolrClient solrClient = createNewSolrClient()) {
- SolrException se = expectThrows(SolrException.class, () -> solrClient.query(query));
- assertSame(ErrorCode.BAD_REQUEST, ErrorCode.getErrorCode(se.code()));
- }
+ SolrException se = expectThrows(SolrException.class, () -> getSolrClient().query(query));
+ assertSame(ErrorCode.BAD_REQUEST, ErrorCode.getErrorCode(se.code()));
}
/** Compose an HTTP GET url that will delete all the data. */
private String makeDeleteAllUrl() {
String deleteQuery = "*:* ";
- return jettySolrRunner.getBaseUrl().toString()
- + "/"
- + DEFAULT_TEST_COLLECTION_NAME
+ return getCoreUrl()
+ "/update?commit=true&stream.body="
+ URLEncoder.encode(deleteQuery, StandardCharsets.UTF_8);
}
diff --git a/solr/core/src/test/org/apache/solr/request/TestStreamBody.java b/solr/core/src/test/org/apache/solr/request/TestStreamBody.java
index e495353b613..197fd2d5986 100644
--- a/solr/core/src/test/org/apache/solr/request/TestStreamBody.java
+++ b/solr/core/src/test/org/apache/solr/request/TestStreamBody.java
@@ -56,20 +56,14 @@ public void startSolr() throws Exception {
if (random().nextBoolean()) {
log.info("These tests are run with V2 API");
restTestHarness.setServerProvider(
- () -> jetty.getBaseUrl().toString() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
+ () -> getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME);
}
}
@After
public void after() throws Exception {
- if (jetty != null) {
- jetty.stop();
- jetty = null;
- }
- if (client != null) {
- client.close();
- client = null;
- }
+ solrClientTestRule.reset();
+
if (restTestHarness != null) {
restTestHarness.close();
restTestHarness = null;
diff --git a/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java b/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java
index 480c2885708..59d2a35d88c 100644
--- a/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java
+++ b/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java
@@ -46,6 +46,9 @@ public static void init() throws Exception {
System.setProperty("coreRootDirectory", coresDir.toString());
System.setProperty("configSetBaseDir", TEST_HOME());
+ System.setProperty("solr.test.sys.prop1", "propone");
+ System.setProperty("solr.test.sys.prop2", "proptwo");
+
final SortedMap extraServlets = new TreeMap<>();
Properties props = new Properties();
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
index e6db67578b0..98a65a3ebae 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
@@ -78,7 +78,7 @@ public void before() throws Exception {
new RESTfulServerProvider() {
@Override
public String getBaseURL() {
- return jetty.getBaseUrl().toString() + "/____v2/cores/" + DEFAULT_TEST_CORENAME;
+ return getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME;
}
});
}
@@ -86,10 +86,7 @@ public String getBaseURL() {
@After
public void after() throws Exception {
- if (jetty != null) {
- jetty.stop();
- jetty = null;
- }
+ solrClientTestRule.reset();
if (restTestHarness != null) {
restTestHarness.close();
}
@@ -1459,7 +1456,7 @@ public static List getDestCopyFields(RestTestHarness harness, String dest) throw
@SuppressWarnings({"unchecked", "varargs"})
private static void assertFieldSimilarity(
String fieldname, Class expected, Consumer... validators) {
- CoreContainer cc = jetty.getCoreContainer();
+ CoreContainer cc = solrClientTestRule.getCoreContainer();
try (SolrCore core = cc.getCore("collection1")) {
SimilarityFactory simfac = core.getLatestSchema().getSimilarityFactory();
assertNotNull(simfac);
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java
index fb6633e2848..a1fff16edc0 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java
@@ -61,10 +61,7 @@ public void before() throws Exception {
@After
public void after() throws Exception {
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
+ solrClientTestRule.reset();
System.clearProperty("managed.schema.mutable");
System.clearProperty("enable.update.log");
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymFilterFactory.java b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymFilterFactory.java
index 87cba4ae404..4738196017c 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymFilterFactory.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymFilterFactory.java
@@ -59,10 +59,7 @@ public void before() throws Exception {
@After
public void after() throws Exception {
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
+ solrClientTestRule.reset();
if (null != tmpSolrHome) {
PathUtils.deleteDirectory(tmpSolrHome);
tmpSolrHome = null;
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java
index f93482a00d0..c5363c7f251 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java
@@ -63,10 +63,7 @@ public void before() throws Exception {
@After
public void after() throws Exception {
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
+ solrClientTestRule.reset();
if (null != tmpSolrHome) {
PathUtils.deleteDirectory(tmpSolrHome.toPath());
}
diff --git a/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java b/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java
index 1f05641a6d1..0a1c6d19dc8 100644
--- a/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java
+++ b/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java
@@ -50,11 +50,8 @@ public void before() throws Exception {
@After
public void after() throws Exception {
- if (jetty != null) {
- jetty.stop();
- jetty = null;
- }
- client = null;
+ solrClientTestRule.reset();
+
if (restTestHarness != null) {
restTestHarness.close();
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestThinCache.java b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
new file mode 100644
index 00000000000..c485b9fe46a
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
@@ -0,0 +1,179 @@
+/*
+ * 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.search;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.lucene.tests.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.EmbeddedSolrServerTestRule;
+import org.apache.solr.util.TestHarness;
+import org.apache.solr.util.stats.MetricUtils;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/** Test for {@link ThinCache}. */
+public class TestThinCache extends SolrTestCaseJ4 {
+
+ @ClassRule public static EmbeddedSolrServerTestRule solrRule = new EmbeddedSolrServerTestRule();
+ public static final String SOLR_NODE_LEVEL_CACHE_XML =
+ "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " ";
+
+ @BeforeClass
+ public static void setupSolrHome() throws Exception {
+ Path home = createTempDir("home");
+ Files.writeString(home.resolve("solr.xml"), SOLR_NODE_LEVEL_CACHE_XML);
+
+ solrRule.startSolr(home);
+
+ Path configSet = createTempDir("configSet");
+ copyMinConf(configSet.toFile());
+ // insert a special filterCache configuration
+ Path solrConfig = configSet.resolve("conf/solrconfig.xml");
+ Files.writeString(
+ solrConfig,
+ Files.readString(solrConfig)
+ .replace(
+ "",
+ "\n"
+ + "\n"
+ + " "));
+
+ solrRule.newCollection().withConfigSet(configSet.toString()).create();
+
+ // legacy; get rid of this someday!
+ h = new TestHarness(solrRule.getCoreContainer());
+ lrf = h.getRequestFactory("/select", 0, 20);
+ }
+
+ SolrMetricManager metricManager = new SolrMetricManager();
+ String registry = TestUtil.randomSimpleString(random(), 2, 10);
+ String scope = TestUtil.randomSimpleString(random(), 2, 10);
+
+ @Test
+ public void testSimple() {
+ Object cacheScope = new Object();
+ ThinCache.NodeLevelCache backing = new ThinCache.NodeLevelCache<>();
+ ThinCache lfuCache = new ThinCache<>();
+ lfuCache.setBacking(cacheScope, backing);
+ SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
+ lfuCache.initializeMetrics(solrMetricsContext, scope + "-1");
+
+ Object cacheScope2 = new Object();
+ ThinCache newLFUCache = new ThinCache<>();
+ newLFUCache.setBacking(cacheScope2, backing);
+ newLFUCache.initializeMetrics(solrMetricsContext, scope + "-2");
+
+ Map params = new HashMap<>();
+ params.put("size", "100");
+ params.put("initialSize", "10");
+
+ NoOpRegenerator regenerator = new NoOpRegenerator();
+ backing.init(params, null, null);
+ Object initObj =
+ lfuCache.init(Collections.singletonMap("autowarmCount", "25"), null, regenerator);
+ lfuCache.setState(SolrCache.State.LIVE);
+ for (int i = 0; i < 101; i++) {
+ lfuCache.put(i + 1, Integer.toString(i + 1));
+ }
+ assertEquals("15", lfuCache.get(15));
+ assertEquals("75", lfuCache.get(75));
+ assertNull(lfuCache.get(110));
+ Map nl = lfuCache.getMetricsMap().getValue();
+ assertEquals(3L, nl.get("lookups"));
+ assertEquals(2L, nl.get("hits"));
+ assertEquals(101L, nl.get("inserts"));
+
+ assertNull(lfuCache.get(1)); // first item put in should be the first out
+
+ // Test autowarming
+ newLFUCache.init(Collections.singletonMap("autowarmCount", "25"), initObj, regenerator);
+ newLFUCache.warm(null, lfuCache);
+ newLFUCache.setState(SolrCache.State.LIVE);
+
+ newLFUCache.put(103, "103");
+ assertEquals("15", newLFUCache.get(15));
+ assertEquals("75", newLFUCache.get(75));
+ assertNull(newLFUCache.get(50));
+ nl = newLFUCache.getMetricsMap().getValue();
+ assertEquals(3L, nl.get("lookups"));
+ assertEquals(2L, nl.get("hits"));
+ assertEquals(1L, nl.get("inserts"));
+ assertEquals(0L, nl.get("evictions"));
+
+ assertEquals(7L, nl.get("cumulative_lookups"));
+ assertEquals(4L, nl.get("cumulative_hits"));
+ assertEquals(102L, nl.get("cumulative_inserts"));
+ }
+
+ @Test
+ public void testInitCore() throws Exception {
+ for (int i = 0; i < 20; i++) {
+ assertU(adoc("id", Integer.toString(i)));
+ }
+ assertU(commit());
+ assertQ(req("q", "*:*", "fq", "id:0"));
+ assertQ(req("q", "*:*", "fq", "id:0"));
+ assertQ(req("q", "*:*", "fq", "id:1"));
+ Map nodeMetricsSnapshot =
+ MetricUtils.convertMetrics(
+ h.getCoreContainer().getMetricManager().registry("solr.node"),
+ List.of(
+ "CACHE.nodeLevelCache/myNodeLevelCacheThin",
+ "CACHE.nodeLevelCache/myNodeLevelCache"));
+ Map coreMetricsSnapshot =
+ MetricUtils.convertMetrics(
+ h.getCore().getCoreMetricManager().getRegistry(),
+ List.of("CACHE.searcher.filterCache"));
+
+ // check that metrics are accessible, and the core cache writes through to the node-level cache
+ Map assertions = Map.of("lookups", 3L, "hits", 1L, "inserts", 2L, "size", 2);
+ for (Map.Entry e : assertions.entrySet()) {
+ String key = e.getKey();
+ Number val = e.getValue();
+ assertEquals(
+ val, nodeMetricsSnapshot.get("CACHE.nodeLevelCache/myNodeLevelCacheThin.".concat(key)));
+ assertEquals(val, coreMetricsSnapshot.get("CACHE.searcher.filterCache.".concat(key)));
+ }
+
+ // for the other node-level cache, simply check that metrics are accessible
+ assertEquals(0, nodeMetricsSnapshot.get("CACHE.nodeLevelCache/myNodeLevelCache.size"));
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/search/ThinCache.java b/solr/core/src/test/org/apache/solr/search/ThinCache.java
new file mode 100644
index 00000000000..75910b94bb9
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/ThinCache.java
@@ -0,0 +1,472 @@
+/*
+ * 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.search;
+
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import org.apache.lucene.util.Accountable;
+import org.apache.solr.metrics.MetricsMap;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.util.IOFunction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DISCLAIMER: This class is initially included in the test codebase as a proof-of-concept for
+ * demonstrating/validating node-level cache configuration. Although the implementation is
+ * relatively naive, it should be usable as-is as a plugin, or as a template for developing more
+ * robust implementations.
+ *
+ * A "thin" cache that does not hold strong direct references to the values that it stores and
+ * supplies. Strong references to values are held by a backing {@link NodeLevelCache}. Local
+ * references to keys (and weak references to values) are held by this ThinCache only as an
+ * approximation of the contents of the cache.
+ *
+ *
There are no strong guarantees regarding the consistency of local bookkeeping in the ThinCache
+ * (wrt the "source of truth" backing cache). Such guarantees are not necessary, because the local
+ * bookkeeping only exists to support functionality (such as auto-warming and metrics reporting)
+ * where strict correctness is not essential.
+ *
+ *
There is however a guarantee that any inconsistency will only be in a safe direction --
+ * i.e., that although there may be entries in the backing cache that are not represented locally in
+ * the ThinCache bookkeeping, the reverse is not true (to protect against memory leak resulting from
+ * the accumulation of stale local references with no corresponding entry in the backing cache).
+ *
+ *
NOTE REGARDING AUTOWARMING: because both the warming cache and the cache associated with the
+ * active searcher are backed by the same underlying node-level cache, some extra consideration must
+ * be taken in configuring autowarming. Crosstalk between thin caches is an unavoidable consequence
+ * of the node-level cache approach. Indeed, in the sense that "dynamic resource allocation" is a
+ * type of crosstalk, crosstalk could be considered to be the distinguishing feature of the
+ * node-level cache approach! But in order to prevent competition between active searchers and
+ * corresponding warming searchers, it is advisable to autowarm by percentage -- generally <= 50%
+ * -- or set a relatively low autowarm count (wrt the anticipated overall size of the backing
+ * cache).
+ */
+public class ThinCache extends SolrCacheBase
+ implements SolrCache, Accountable, RemovalListener {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final class ScopedKey {
+ public final S scope;
+ public final K key;
+
+ private ScopedKey(S scope, K key) {
+ this.scope = scope;
+ this.key = key;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ScopedKey, ?> scopedKey = (ScopedKey, ?>) o;
+ return scope.equals(scopedKey.scope) && key.equals(scopedKey.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scope, key);
+ }
+ }
+
+ private interface RemovalListenerRegistry extends RemovalListener, V> {
+
+ void register(S scope, RemovalListener listener);
+
+ void unregister(S scope);
+ }
+
+ private static class RemovalListenerRegistryImpl
+ implements RemovalListenerRegistry {
+
+ private final Map> listeners = new ConcurrentHashMap<>();
+
+ @Override
+ public void register(S scope, RemovalListener listener) {
+ if (listeners.put(scope, listener) != null) {
+ throw new IllegalStateException("removal listener already added for scope " + scope);
+ }
+ }
+
+ @Override
+ public void unregister(S scope) {
+ if (listeners.remove(scope) == null) {
+ log.warn("no removal listener found for scope {}", scope);
+ }
+ }
+
+ @Override
+ public void onRemoval(ScopedKey key, V value, RemovalCause cause) {
+ RemovalListener listener;
+ if (key != null && (listener = listeners.get(key.scope)) != null) {
+ listener.onRemoval(key.key, value, cause);
+ }
+ }
+ }
+
+ public static final class NodeLevelCache extends CaffeineCache, V>
+ implements RemovalListenerRegistry {
+
+ private final RemovalListenerRegistry removalListenerRegistry =
+ new RemovalListenerRegistryImpl<>();
+
+ @Override
+ public void onRemoval(ScopedKey key, V value, RemovalCause cause) {
+ super.onRemoval(key, value, cause);
+ removalListenerRegistry.onRemoval(key, value, cause);
+ }
+
+ @Override
+ public void register(S scope, RemovalListener listener) {
+ removalListenerRegistry.register(scope, listener);
+ }
+
+ @Override
+ public void unregister(S scope) {
+ removalListenerRegistry.unregister(scope);
+ }
+
+ @Override
+ public String getName() {
+ return NodeLevelCache.class.getName();
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format(Locale.ROOT, "Node Level Cache(impl=%s)", super.getDescription());
+ }
+ }
+
+ private String description = "Thin Cache";
+
+ private NodeLevelCache backing;
+
+ private S scope;
+
+ private String parentCacheName;
+
+ private final ConcurrentMap> local = new ConcurrentHashMap<>();
+
+ private static final class ValEntry {
+ private final LongAdder ct = new LongAdder();
+ private final WeakReference ref;
+
+ private ValEntry(V val) {
+ this.ref = new WeakReference<>(val);
+ }
+ }
+
+ private static final class HitCountEntry {
+ private final long ct;
+ private final K key;
+ private final V val;
+
+ private HitCountEntry(long ct, K key, V val) {
+ this.ct = ct;
+ this.key = key;
+ this.val = val;
+ }
+ }
+
+ @VisibleForTesting
+ void setBacking(S scope, NodeLevelCache backing) {
+ this.scope = scope;
+ this.backing = backing;
+ }
+
+ private void initForSearcher(SolrIndexSearcher searcher) {
+ if (searcher != null) {
+ // `searcher` may be null for tests, in which case we assume that `this.backing` will
+ // have been set manually via `setBacking()`. In normal use, we expect `searcher != null`.
+ @SuppressWarnings("unchecked")
+ S scope = (S) searcher.getTopReaderContext().reader().getReaderCacheHelper().getKey();
+ this.scope = scope;
+ @SuppressWarnings("unchecked")
+ NodeLevelCache backing =
+ (NodeLevelCache) searcher.getCore().getCoreContainer().getCache(parentCacheName);
+ this.backing = backing;
+ }
+ description = generateDescription();
+ backing.register(scope, this);
+ }
+
+ @Override
+ public void initialSearcher(SolrIndexSearcher initialSearcher) {
+ initForSearcher(initialSearcher);
+ }
+
+ @Override
+ public void warm(SolrIndexSearcher searcher, SolrCache old) {
+ initForSearcher(searcher);
+ @SuppressWarnings("unchecked")
+ ThinCache other = (ThinCache) old;
+ long warmingStartTimeNanos = System.nanoTime();
+ List> orderedEntries = Collections.emptyList();
+ // warm entries
+ if (isAutowarmingOn()) {
+ orderedEntries = new ArrayList<>(other.local.size() << 1); // oversize
+ for (Entry> e : other.local.entrySet()) {
+ ValEntry valEntry = e.getValue();
+ V val = valEntry.ref.get();
+ if (val != null) {
+ orderedEntries.add(new HitCountEntry<>(valEntry.ct.sum(), e.getKey(), val));
+ }
+ }
+ orderedEntries.sort((a, b) -> Long.compare(b.ct, a.ct));
+ }
+
+ int size = autowarm.getWarmCount(orderedEntries.size());
+ int ct = 0;
+ for (HitCountEntry entry : orderedEntries) {
+ try {
+ boolean continueRegen =
+ regenerator.regenerateItem(searcher, this, old, entry.key, entry.val);
+ if (!continueRegen || ++ct >= size) {
+ break;
+ }
+ } catch (Exception e) {
+ log.error("Error during auto-warming of key: {}", entry.key, e);
+ }
+ }
+
+ backing.adjustMetrics(hits.sumThenReset(), inserts.sumThenReset(), lookups.sumThenReset());
+ evictions.reset();
+ priorHits = other.hits.sum() + other.priorHits;
+ priorInserts = other.inserts.sum() + other.priorInserts;
+ priorLookups = other.lookups.sum() + other.priorLookups;
+ priorEvictions = other.evictions.sum() + other.priorEvictions;
+ warmupTimeMillis =
+ TimeUnit.MILLISECONDS.convert(
+ System.nanoTime() - warmingStartTimeNanos, TimeUnit.NANOSECONDS);
+ }
+
+ @Override
+ public Object init(Map args, Object persistence, CacheRegenerator regenerator) {
+ super.init(args, regenerator);
+ parentCacheName = args.get("parentCacheName");
+ return persistence;
+ }
+
+ private MetricsMap cacheMap;
+ private SolrMetricsContext solrMetricsContext;
+
+ private final LongAdder hits = new LongAdder();
+ private final LongAdder inserts = new LongAdder();
+ private final LongAdder lookups = new LongAdder();
+ private final LongAdder evictions = new LongAdder();
+ private long warmupTimeMillis;
+
+ private long priorHits;
+ private long priorInserts;
+ private long priorLookups;
+ private long priorEvictions;
+
+ @Override
+ public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
+ solrMetricsContext = parentContext.getChildContext(this);
+ cacheMap =
+ new MetricsMap(
+ map -> {
+ long hitCount = hits.sum();
+ long insertCount = inserts.sum();
+ long lookupCount = lookups.sum();
+ long evictionCount = evictions.sum();
+
+ map.put(LOOKUPS_PARAM, lookupCount);
+ map.put(HITS_PARAM, hitCount);
+ map.put(HIT_RATIO_PARAM, hitRate(hitCount, lookupCount));
+ map.put(INSERTS_PARAM, insertCount);
+ map.put(EVICTIONS_PARAM, evictionCount);
+ map.put(SIZE_PARAM, local.size());
+ map.put("warmupTime", warmupTimeMillis);
+ map.put(RAM_BYTES_USED_PARAM, ramBytesUsed());
+ map.put(MAX_RAM_MB_PARAM, getMaxRamMB());
+
+ long cumLookups = priorLookups + lookupCount;
+ long cumHits = priorHits + hitCount;
+ map.put("cumulative_lookups", cumLookups);
+ map.put("cumulative_hits", cumHits);
+ map.put("cumulative_hitratio", hitRate(cumHits, cumLookups));
+ map.put("cumulative_inserts", priorInserts + insertCount);
+ map.put("cumulative_evictions", priorEvictions + evictionCount);
+ });
+ solrMetricsContext.gauge(cacheMap, true, scope, getCategory().toString());
+ }
+
+ @VisibleForTesting
+ MetricsMap getMetricsMap() {
+ return cacheMap;
+ }
+
+ // TODO: refactor this common method out of here and `CaffeineCache`
+ private static double hitRate(long hitCount, long lookupCount) {
+ return lookupCount == 0 ? 1.0 : (double) hitCount / lookupCount;
+ }
+
+ @Override
+ public SolrMetricsContext getSolrMetricsContext() {
+ return solrMetricsContext;
+ }
+
+ @Override
+ public void close() throws IOException {
+ backing.unregister(scope);
+ SolrCache.super.close();
+ }
+
+ @Override
+ public void onRemoval(K key, V value, RemovalCause cause) {
+ if (cause.wasEvicted()) {
+ evictions.increment();
+ }
+ local.remove(key);
+ }
+
+ @Override
+ public String toString() {
+ return name() + (cacheMap != null ? cacheMap.getValue().toString() : "");
+ }
+
+ @Override
+ public int size() {
+ return local.size();
+ }
+
+ @Override
+ public V put(K key, V value) {
+ inserts.increment();
+ ValEntry valEntry = new ValEntry<>(value);
+ valEntry.ct.increment();
+ local.put(key, valEntry);
+ return backing.put(new ScopedKey<>(scope, key), value);
+ }
+
+ @Override
+ public V get(K key) {
+ lookups.increment();
+ V ret = backing.get(new ScopedKey<>(scope, key));
+ if (ret != null) {
+ hits.increment();
+ ValEntry valEntry = local.get(key);
+ if (valEntry != null) {
+ valEntry.ct.increment();
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public V remove(K key) {
+ // NOTE: rely on `onRemoval()` to remove entry from `local`
+ return backing.remove(new ScopedKey<>(scope, key));
+ }
+
+ @Override
+ public V computeIfAbsent(K key, IOFunction super K, ? extends V> mappingFunction)
+ throws IOException {
+ lookups.increment();
+ boolean[] hit = new boolean[] {true};
+ V ret =
+ backing.computeIfAbsent(
+ new ScopedKey<>(scope, key),
+ (k) -> {
+ hit[0] = false;
+ inserts.increment();
+ V innerRet = mappingFunction.apply(k.key);
+ ValEntry valEntry = new ValEntry<>(innerRet);
+ valEntry.ct.increment();
+ local.put(key, valEntry);
+ return innerRet;
+ });
+ if (hit[0]) {
+ hits.increment();
+ }
+ return ret;
+ }
+
+ @Override
+ public void clear() {
+ for (K key : local.keySet()) {
+ backing.remove(new ScopedKey<>(scope, key));
+ }
+ // NOTE: rely on `onRemoval()` to remove entries from `local`
+ }
+
+ @Override
+ public int getMaxSize() {
+ return backing == null ? 0 : backing.getMaxSize();
+ }
+
+ @Override
+ public void setMaxSize(int maxSize) {
+ throw new UnsupportedOperationException(
+ "limits cannot be configured directly on " + getClass());
+ }
+
+ @Override
+ public int getMaxRamMB() {
+ return backing == null ? 0 : backing.getMaxRamMB();
+ }
+
+ @Override
+ public void setMaxRamMB(int maxRamMB) {
+ throw new UnsupportedOperationException(
+ "limits cannot be configured directly on " + getClass());
+ }
+
+ @Override
+ public long ramBytesUsed() {
+ // TODO: this has yet to be implemented.
+ // the actual implementation should be straightforward, but there are questions about what's
+ // in and out of scope for calculating ramBytesUsed.
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return ThinCache.class.getName();
+ }
+
+ /** Returns the description of this cache. */
+ private String generateDescription() {
+ return String.format(
+ Locale.ROOT,
+ "Thin Cache(backing=%s%s)",
+ backing.getDescription(),
+ isAutowarmingOn() ? (", " + getAutowarmDescription()) : "");
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/search/json/TestJsonRequestWithEdismaxDefType.java b/solr/core/src/test/org/apache/solr/search/json/TestJsonRequestWithEdismaxDefType.java
new file mode 100644
index 00000000000..3d3ec25a878
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/json/TestJsonRequestWithEdismaxDefType.java
@@ -0,0 +1,74 @@
+/*
+ * 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.search.json;
+
+import java.nio.file.Path;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest.METHOD;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.cloud.ConfigRequest;
+import org.apache.solr.util.EmbeddedSolrServerTestRule;
+import org.apache.solr.util.SolrClientTestRule;
+import org.junit.ClassRule;
+
+public class TestJsonRequestWithEdismaxDefType extends SolrTestCaseJ4 {
+
+ @ClassRule
+ public static final SolrClientTestRule solrClientTestRule = new EmbeddedSolrServerTestRule();
+
+ public void test() throws Exception {
+ solrClientTestRule.startSolr(LuceneTestCase.createTempDir());
+
+ Path configSet = LuceneTestCase.createTempDir();
+ SolrTestCaseJ4.copyMinConf(configSet.toFile());
+
+ solrClientTestRule.newCollection().withConfigSet(configSet.toString()).create();
+
+ SolrClient client = solrClientTestRule.getSolrClient();
+
+ client.request(
+ new ConfigRequest(
+ "{"
+ + " 'create-requesthandler':{"
+ + " 'name':'/query',"
+ + " 'class':'solr.SearchHandler',"
+ + " 'defaults' : {'defType':'edismax'}" // the critical part
+ + " }"
+ + "}"));
+
+ addDocs(client);
+
+ doQuery(client);
+ }
+
+ private static void addDocs(SolrClient client) throws Exception {
+ client.add(sdoc("id", "1", "cat_s", "A", "where_s", "NY"));
+ client.add(sdoc("id", "2", "cat_s", "B", "where_s", "NJ"));
+ client.add(sdoc("id", "3"));
+ client.commit();
+ }
+
+ private static void doQuery(SolrClient client) throws Exception {
+ final var jsonQuery =
+ "{\"query\":{\"bool\":{\"should\":[{\"lucene\":{\"query\":\"id:1\"}}, \"id:2\"]}}}";
+ final var req = new QueryRequest(params("json", jsonQuery, "qt", "/query"), METHOD.POST);
+ final var rsp = req.process(client);
+ assertEquals(2, rsp.getResults().getNumFound());
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/security/PublicKeyAPITest.java b/solr/core/src/test/org/apache/solr/security/GetPublicKeyTest.java
similarity index 84%
rename from solr/core/src/test/org/apache/solr/security/PublicKeyAPITest.java
rename to solr/core/src/test/org/apache/solr/security/GetPublicKeyTest.java
index f75b32b85d9..77bf4646a74 100644
--- a/solr/core/src/test/org/apache/solr/security/PublicKeyAPITest.java
+++ b/solr/core/src/test/org/apache/solr/security/GetPublicKeyTest.java
@@ -20,14 +20,14 @@
import org.apache.solr.SolrTestCaseJ4;
import org.junit.Test;
-/** Unit test for {@link PublicKeyAPI} */
-public class PublicKeyAPITest extends SolrTestCaseJ4 {
+/** Unit test for {@link GetPublicKey} */
+public class GetPublicKeyTest extends SolrTestCaseJ4 {
@Test
public void testRetrievesPublicKey() {
final SolrNodeKeyPair nodeKeyPair = new SolrNodeKeyPair(null);
- final PublicKeyAPI.PublicKeyResponse response = new PublicKeyAPI(nodeKeyPair).getPublicKey();
+ final var response = new GetPublicKey(nodeKeyPair).getPublicKey();
assertEquals(nodeKeyPair.getKeyPair().getPublicKeyStr(), response.key);
}
diff --git a/solr/core/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java b/solr/core/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
index b372b4b3c8f..5f25e8140d8 100644
--- a/solr/core/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
+++ b/solr/core/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
@@ -46,7 +46,7 @@ protected HttpRequestBase getSelectMethod(String method, String... params) {
URI uri =
URI.create(
- jetty.getBaseUrl().toString()
+ getBaseUrl()
+ "/"
+ DEFAULT_TEST_COLLECTION_NAME
+ "/select?"
@@ -73,7 +73,7 @@ HttpRequestBase getUpdateMethod(String method, String... params) {
URI uri =
URI.create(
- jetty.getBaseUrl()
+ getBaseUrl()
+ "/"
+ DEFAULT_TEST_COLLECTION_NAME
+ "/update?"
diff --git a/solr/core/src/test/org/apache/solr/servlet/LoadAdminUiServletTest.java b/solr/core/src/test/org/apache/solr/servlet/LoadAdminUiServletTest.java
new file mode 100644
index 00000000000..299029f86f7
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/servlet/LoadAdminUiServletTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.servlet;
+
+import static org.apache.solr.servlet.LoadAdminUiServlet.SYSPROP_CSP_CONNECT_SRC_URLS;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.CoreContainer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class LoadAdminUiServletTest extends SolrTestCaseJ4 {
+
+ @InjectMocks private LoadAdminUiServlet servlet;
+ @Mock private HttpServletRequest mockRequest;
+ @Mock private HttpServletResponse mockResponse;
+ @Mock private CoreContainer coreContainer;
+ @Mock private ServletConfig servletConfig;
+ @Mock private ServletContext mockServletContext;
+ @Mock private ServletOutputStream mockOutputStream;
+
+ private static final Set CSP_URLS =
+ Set.of(
+ "http://example1.com/token",
+ "https://example2.com/path/uri1",
+ "http://example3.com/oauth2/uri2");
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.openMocks(this);
+ when(mockRequest.getRequestURI()).thenReturn("/path/URI");
+ when(mockRequest.getContextPath()).thenReturn("/path");
+ when(mockRequest.getAttribute("org.apache.solr.CoreContainer")).thenReturn(coreContainer);
+ when(servletConfig.getServletContext()).thenReturn(mockServletContext);
+ when(mockResponse.getOutputStream()).thenReturn(mockOutputStream);
+ InputStream mockInputStream =
+ new ByteArrayInputStream("mock content".getBytes(StandardCharsets.UTF_8));
+ when(mockServletContext.getResourceAsStream(anyString())).thenReturn(mockInputStream);
+ }
+
+ @BeforeClass
+ public static void ensureWorkingMockito() {
+ assumeWorkingMockito();
+ }
+
+ @Test
+ public void testDefaultCSPHeaderSet() throws IOException {
+ System.setProperty(SYSPROP_CSP_CONNECT_SRC_URLS, String.join(",", CSP_URLS));
+ ArgumentCaptor headerNameCaptor = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor headerValueCaptor = ArgumentCaptor.forClass(String.class);
+ servlet.doGet(mockRequest, mockResponse);
+
+ verify(mockResponse).setHeader(headerNameCaptor.capture(), headerValueCaptor.capture());
+ assertEquals("Content-Security-Policy", headerNameCaptor.getValue());
+ String cspValue = headerValueCaptor.getValue();
+ for (String endpoint : CSP_URLS) {
+ assertTrue("Expected CSP value to contain " + endpoint, cspValue.contains(endpoint));
+ }
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/servlet/ResponseHeaderTest.java b/solr/core/src/test/org/apache/solr/servlet/ResponseHeaderTest.java
index b5d312eb021..b9e821c3acb 100644
--- a/solr/core/src/test/org/apache/solr/servlet/ResponseHeaderTest.java
+++ b/solr/core/src/test/org/apache/solr/servlet/ResponseHeaderTest.java
@@ -58,7 +58,7 @@ public static void afterTest() throws Exception {
@Test
public void testHttpResponse() throws IOException {
- URI uri = URI.create(jetty.getBaseUrl() + "/collection1/withHeaders?q=*:*");
+ URI uri = URI.create(getBaseUrl() + "/collection1/withHeaders?q=*:*");
HttpGet httpGet = new HttpGet(uri);
HttpResponse response = getHttpClient().execute(httpGet);
Header[] headers = response.getAllHeaders();
diff --git a/solr/core/src/test/org/apache/solr/util/BaseTestCircuitBreaker.java b/solr/core/src/test/org/apache/solr/util/BaseTestCircuitBreaker.java
new file mode 100644
index 00000000000..025db34ed76
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/BaseTestCircuitBreaker.java
@@ -0,0 +1,342 @@
+/*
+ * 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.util;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.common.util.SolrNamedThreadFactory;
+import org.apache.solr.util.circuitbreaker.CPUCircuitBreaker;
+import org.apache.solr.util.circuitbreaker.CircuitBreaker;
+import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
+import org.apache.solr.util.circuitbreaker.MemoryCircuitBreaker;
+import org.hamcrest.MatcherAssert;
+import org.junit.After;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BaseTestCircuitBreaker extends SolrTestCaseJ4 {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ protected static void indexDocs() {
+ for (int i = 0; i < 20; i++) {
+ assertU(adoc("name", "john smith", "id", "1"));
+ assertU(adoc("name", "johathon smith", "id", "2"));
+ assertU(adoc("name", "john percival smith", "id", "3"));
+ assertU(adoc("id", "1", "title", "this is a title.", "inStock_b1", "true"));
+ assertU(adoc("id", "2", "title", "this is another title.", "inStock_b1", "true"));
+ assertU(adoc("id", "3", "title", "Mary had a little lamb.", "inStock_b1", "false"));
+
+ // commit inside the loop to get multiple segments to make search as realistic as possible
+ assertU(commit());
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @After
+ public void after() {
+ h.getCore().getCircuitBreakerRegistry().deregisterAll();
+ }
+
+ public void testCBAlwaysTrips() {
+ removeAllExistingCircuitBreakers();
+
+ CircuitBreaker circuitBreaker = new MockCircuitBreaker(true);
+
+ h.getCore().getCircuitBreakerRegistry().register(circuitBreaker);
+
+ expectThrows(
+ SolrException.class,
+ () -> {
+ h.query(req("name:\"john smith\""));
+ });
+ }
+
+ public void testCBFakeMemoryPressure() throws Exception {
+ removeAllExistingCircuitBreakers();
+
+ // Update and query will not trip
+ h.update(
+ "1 john smith ");
+ h.query(req("name:\"john smith\""));
+
+ MemoryCircuitBreaker searchBreaker = new FakeMemoryPressureCircuitBreaker();
+ searchBreaker.setThreshold(80);
+ // Default request type is "query"
+ // searchBreaker.setRequestTypes(List.of("query"));
+ h.getCore().getCircuitBreakerRegistry().register(searchBreaker);
+
+ // Query will trip, but not update due to defaults
+ expectThrows(SolrException.class, () -> h.query(req("name:\"john smith\"")));
+ h.update(
+ "2 john smith ");
+
+ MemoryCircuitBreaker updateBreaker = new FakeMemoryPressureCircuitBreaker();
+ updateBreaker.setThreshold(75);
+ updateBreaker.setRequestTypes(List.of("update"));
+ h.getCore().getCircuitBreakerRegistry().register(updateBreaker);
+
+ // Now also update will trip
+ expectThrows(
+ SolrException.class,
+ () ->
+ h.update(
+ "1 john smith "));
+ }
+
+ public void testBadRequestType() {
+ expectThrows(
+ IllegalArgumentException.class,
+ () -> new MemoryCircuitBreaker().setRequestTypes(List.of("badRequestType")));
+ }
+
+ public void testBuildingMemoryPressure() {
+ ExecutorService executor =
+ ExecutorUtil.newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("TestCircuitBreaker"));
+
+ AtomicInteger failureCount = new AtomicInteger();
+
+ try {
+ removeAllExistingCircuitBreakers();
+
+ CircuitBreaker circuitBreaker = new BuildingUpMemoryPressureCircuitBreaker();
+ MemoryCircuitBreaker memoryCircuitBreaker = (MemoryCircuitBreaker) circuitBreaker;
+
+ memoryCircuitBreaker.setThreshold(75);
+
+ h.getCore().getCircuitBreakerRegistry().register(circuitBreaker);
+
+ List> futures = new ArrayList<>();
+
+ for (int i = 0; i < 5; i++) {
+ Future> future =
+ executor.submit(
+ () -> {
+ try {
+ h.query(req("name:\"john smith\""));
+ } catch (SolrException e) {
+ MatcherAssert.assertThat(
+ e.getMessage(), containsString("Circuit Breakers tripped"));
+ failureCount.incrementAndGet();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ });
+
+ futures.add(future);
+ }
+
+ for (Future> future : futures) {
+ try {
+ future.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ } finally {
+ ExecutorUtil.shutdownAndAwaitTermination(executor);
+ assertEquals("Number of failed queries is not correct", 1, failureCount.get());
+ }
+ }
+
+ public void testFakeCPUCircuitBreaker() {
+ removeAllExistingCircuitBreakers();
+
+ CircuitBreaker circuitBreaker = new FakeCPUCircuitBreaker();
+ CPUCircuitBreaker cpuCircuitBreaker = (CPUCircuitBreaker) circuitBreaker;
+
+ cpuCircuitBreaker.setThreshold(75);
+
+ h.getCore().getCircuitBreakerRegistry().register(circuitBreaker);
+
+ AtomicInteger failureCount = new AtomicInteger();
+
+ ExecutorService executor =
+ ExecutorUtil.newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("TestCircuitBreaker"));
+ try {
+ List> futures = new ArrayList<>();
+
+ for (int i = 0; i < 5; i++) {
+ Future> future =
+ executor.submit(
+ () -> {
+ try {
+ h.query(req("name:\"john smith\""));
+ } catch (SolrException e) {
+ MatcherAssert.assertThat(
+ e.getMessage(), containsString("Circuit Breakers tripped"));
+ failureCount.incrementAndGet();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ });
+
+ futures.add(future);
+ }
+
+ for (Future> future : futures) {
+ try {
+ future.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ } finally {
+ ExecutorUtil.shutdownAndAwaitTermination(executor);
+ assertEquals("Number of failed queries is not correct", 5, failureCount.get());
+ }
+ }
+
+ public void testResponseWithCBTiming() {
+ removeAllExistingCircuitBreakers();
+
+ assertQ(
+ req("q", "*:*", CommonParams.DEBUG_QUERY, "true"),
+ "//str[@name='rawquerystring']='*:*'",
+ "//str[@name='querystring']='*:*'",
+ "//str[@name='parsedquery']='MatchAllDocsQuery(*:*)'",
+ "//str[@name='parsedquery_toString']='*:*'",
+ "count(//lst[@name='explain']/*)=3",
+ "//lst[@name='explain']/str[@name='1']",
+ "//lst[@name='explain']/str[@name='2']",
+ "//lst[@name='explain']/str[@name='3']",
+ "//str[@name='QParser']",
+ "count(//lst[@name='timing']/*)=3",
+ "//lst[@name='timing']/double[@name='time']",
+ "count(//lst[@name='prepare']/*)>0",
+ "//lst[@name='prepare']/double[@name='time']",
+ "count(//lst[@name='process']/*)>0",
+ "//lst[@name='process']/double[@name='time']");
+
+ CircuitBreaker circuitBreaker = new MockCircuitBreaker(false);
+ h.getCore().getCircuitBreakerRegistry().register(circuitBreaker);
+
+ assertQ(
+ req("q", "*:*", CommonParams.DEBUG_QUERY, "true"),
+ "//str[@name='rawquerystring']='*:*'",
+ "//str[@name='querystring']='*:*'",
+ "//str[@name='parsedquery']='MatchAllDocsQuery(*:*)'",
+ "//str[@name='parsedquery_toString']='*:*'",
+ "count(//lst[@name='explain']/*)=3",
+ "//lst[@name='explain']/str[@name='1']",
+ "//lst[@name='explain']/str[@name='2']",
+ "//lst[@name='explain']/str[@name='3']",
+ "//str[@name='QParser']",
+ "count(//lst[@name='timing']/*)=4",
+ "//lst[@name='timing']/double[@name='time']",
+ "count(//lst[@name='circuitbreaker']/*)>0",
+ "//lst[@name='circuitbreaker']/double[@name='time']",
+ "count(//lst[@name='prepare']/*)>0",
+ "//lst[@name='prepare']/double[@name='time']",
+ "count(//lst[@name='process']/*)>0",
+ "//lst[@name='process']/double[@name='time']");
+ }
+
+ public void testErrorCode() {
+ assertEquals(
+ SolrException.ErrorCode.SERVICE_UNAVAILABLE,
+ CircuitBreaker.getErrorCode(List.of(new CircuitBreakerManager())));
+ assertEquals(
+ SolrException.ErrorCode.TOO_MANY_REQUESTS,
+ CircuitBreaker.getErrorCode(List.of(new MemoryCircuitBreaker())));
+ }
+
+ private void removeAllExistingCircuitBreakers() {
+ h.getCore().getCircuitBreakerRegistry().deregisterAll();
+ }
+
+ private static class MockCircuitBreaker extends MemoryCircuitBreaker {
+
+ private final boolean tripped;
+
+ public MockCircuitBreaker(boolean tripped) {
+ this.tripped = tripped;
+ }
+
+ @Override
+ public boolean isTripped() {
+ return this.tripped;
+ }
+
+ @Override
+ public String getDebugInfo() {
+ return "MockCircuitBreaker";
+ }
+ }
+
+ private static class FakeMemoryPressureCircuitBreaker extends MemoryCircuitBreaker {
+
+ @Override
+ protected long calculateLiveMemoryUsage() {
+ // Return a number large enough to trigger a pushback from the circuit breaker
+ return Long.MAX_VALUE;
+ }
+ }
+
+ private static class BuildingUpMemoryPressureCircuitBreaker extends MemoryCircuitBreaker {
+ private AtomicInteger count;
+
+ public BuildingUpMemoryPressureCircuitBreaker() {
+ this.count = new AtomicInteger(0);
+ }
+
+ @Override
+ protected long calculateLiveMemoryUsage() {
+ int localCount = count.getAndIncrement();
+
+ if (localCount >= 4) {
+ // TODO: To be removed
+ if (log.isInfoEnabled()) {
+ String logMessage =
+ "Blocking query from BuildingUpMemoryPressureCircuitBreaker for count " + localCount;
+ log.info(logMessage);
+ }
+ return Long.MAX_VALUE;
+ }
+
+ // TODO: To be removed
+ if (log.isInfoEnabled()) {
+ String logMessage =
+ "BuildingUpMemoryPressureCircuitBreaker: Returning unblocking value for count "
+ + localCount;
+ log.info(logMessage);
+ }
+ return Long.MIN_VALUE; // Random number guaranteed to not trip the circuit breaker
+ }
+ }
+
+ private static class FakeCPUCircuitBreaker extends CPUCircuitBreaker {
+ @Override
+ protected double calculateLiveCPUUsage() {
+ return 92; // Return a value large enough to trigger the circuit breaker
+ }
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/util/TestCircuitBreaker.java b/solr/core/src/test/org/apache/solr/util/TestCircuitBreaker.java
index 47c8d417c07..37500b4126f 100644
--- a/solr/core/src/test/org/apache/solr/util/TestCircuitBreaker.java
+++ b/solr/core/src/test/org/apache/solr/util/TestCircuitBreaker.java
@@ -17,284 +17,17 @@
package org.apache.solr.util;
-import static org.hamcrest.CoreMatchers.containsString;
-
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.common.util.SolrNamedThreadFactory;
-import org.apache.solr.core.PluginInfo;
-import org.apache.solr.util.circuitbreaker.CPUCircuitBreaker;
-import org.apache.solr.util.circuitbreaker.CircuitBreaker;
-import org.apache.solr.util.circuitbreaker.CircuitBreakerManager;
-import org.apache.solr.util.circuitbreaker.MemoryCircuitBreaker;
-import org.hamcrest.MatcherAssert;
-import org.junit.After;
import org.junit.BeforeClass;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class TestCircuitBreaker extends SolrTestCaseJ4 {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private static final int NUM_DOCS = 20;
+/** Tests the pluggable circuit breaker implementation. The actual tests are in base class. */
+public class TestCircuitBreaker extends BaseTestCircuitBreaker {
@BeforeClass
public static void setUpClass() throws Exception {
System.setProperty("filterCache.enabled", "false");
System.setProperty("queryResultCache.enabled", "false");
System.setProperty("documentCache.enabled", "true");
- initCore("solrconfig-memory-circuitbreaker.xml", "schema.xml");
- for (int i = 0; i < NUM_DOCS; i++) {
- assertU(adoc("name", "john smith", "id", "1"));
- assertU(adoc("name", "johathon smith", "id", "2"));
- assertU(adoc("name", "john percival smith", "id", "3"));
- assertU(adoc("id", "1", "title", "this is a title.", "inStock_b1", "true"));
- assertU(adoc("id", "2", "title", "this is another title.", "inStock_b1", "true"));
- assertU(adoc("id", "3", "title", "Mary had a little lamb.", "inStock_b1", "false"));
-
- // commit inside the loop to get multiple segments to make search as realistic as possible
- assertU(commit());
- }
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- @After
- public void after() {
- h.getCore().getCircuitBreakerManager().deregisterAll();
- }
-
- public void testCBAlwaysTrips() {
- removeAllExistingCircuitBreakers();
-
- PluginInfo pluginInfo =
- h.getCore().getSolrConfig().getPluginInfo(CircuitBreakerManager.class.getName());
-
- CircuitBreaker.CircuitBreakerConfig circuitBreakerConfig =
- CircuitBreakerManager.buildCBConfig(pluginInfo);
-
- CircuitBreaker circuitBreaker = new MockCircuitBreaker(circuitBreakerConfig);
-
- h.getCore().getCircuitBreakerManager().register(circuitBreaker);
-
- expectThrows(
- SolrException.class,
- () -> {
- h.query(req("name:\"john smith\""));
- });
- }
-
- public void testCBFakeMemoryPressure() {
- removeAllExistingCircuitBreakers();
-
- PluginInfo pluginInfo =
- h.getCore().getSolrConfig().getPluginInfo(CircuitBreakerManager.class.getName());
-
- CircuitBreaker.CircuitBreakerConfig circuitBreakerConfig =
- CircuitBreakerManager.buildCBConfig(pluginInfo);
- CircuitBreaker circuitBreaker = new FakeMemoryPressureCircuitBreaker(circuitBreakerConfig);
-
- h.getCore().getCircuitBreakerManager().register(circuitBreaker);
-
- expectThrows(
- SolrException.class,
- () -> {
- h.query(req("name:\"john smith\""));
- });
- }
-
- public void testBuildingMemoryPressure() {
- ExecutorService executor =
- ExecutorUtil.newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("TestCircuitBreaker"));
-
- AtomicInteger failureCount = new AtomicInteger();
-
- try {
- removeAllExistingCircuitBreakers();
-
- PluginInfo pluginInfo =
- h.getCore().getSolrConfig().getPluginInfo(CircuitBreakerManager.class.getName());
-
- CircuitBreaker.CircuitBreakerConfig circuitBreakerConfig =
- CircuitBreakerManager.buildCBConfig(pluginInfo);
- CircuitBreaker circuitBreaker =
- new BuildingUpMemoryPressureCircuitBreaker(circuitBreakerConfig);
-
- h.getCore().getCircuitBreakerManager().register(circuitBreaker);
-
- List> futures = new ArrayList<>();
-
- for (int i = 0; i < 5; i++) {
- Future> future =
- executor.submit(
- () -> {
- try {
- h.query(req("name:\"john smith\""));
- } catch (SolrException e) {
- MatcherAssert.assertThat(
- e.getMessage(), containsString("Circuit Breakers tripped"));
- failureCount.incrementAndGet();
- } catch (Exception e) {
- throw new RuntimeException(e.getMessage());
- }
- });
-
- futures.add(future);
- }
-
- for (Future> future : futures) {
- try {
- future.get();
- } catch (Exception e) {
- throw new RuntimeException(e.getMessage());
- }
- }
- } finally {
- ExecutorUtil.shutdownAndAwaitTermination(executor);
- assertEquals("Number of failed queries is not correct", 1, failureCount.get());
- }
- }
-
- public void testFakeCPUCircuitBreaker() {
- AtomicInteger failureCount = new AtomicInteger();
-
- ExecutorService executor =
- ExecutorUtil.newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("TestCircuitBreaker"));
- try {
- removeAllExistingCircuitBreakers();
-
- PluginInfo pluginInfo =
- h.getCore().getSolrConfig().getPluginInfo(CircuitBreakerManager.class.getName());
-
- CircuitBreaker.CircuitBreakerConfig circuitBreakerConfig =
- CircuitBreakerManager.buildCBConfig(pluginInfo);
- CircuitBreaker circuitBreaker = new FakeCPUCircuitBreaker(circuitBreakerConfig);
-
- h.getCore().getCircuitBreakerManager().register(circuitBreaker);
-
- List> futures = new ArrayList<>();
-
- for (int i = 0; i < 5; i++) {
- Future> future =
- executor.submit(
- () -> {
- try {
- h.query(req("name:\"john smith\""));
- } catch (SolrException e) {
- MatcherAssert.assertThat(
- e.getMessage(), containsString("Circuit Breakers tripped"));
- failureCount.incrementAndGet();
- } catch (Exception e) {
- throw new RuntimeException(e.getMessage());
- }
- });
-
- futures.add(future);
- }
-
- for (Future> future : futures) {
- try {
- future.get();
- } catch (Exception e) {
- throw new RuntimeException(e.getMessage());
- }
- }
- } finally {
- ExecutorUtil.shutdownAndAwaitTermination(executor);
- assertEquals("Number of failed queries is not correct", 5, failureCount.get());
- }
- }
-
- private void removeAllExistingCircuitBreakers() {
- List registeredCircuitBreakers =
- h.getCore().getCircuitBreakerManager().getRegisteredCircuitBreakers();
-
- registeredCircuitBreakers.clear();
- }
-
- private static class MockCircuitBreaker extends MemoryCircuitBreaker {
-
- public MockCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
- }
-
- @Override
- public boolean isTripped() {
- // Always return true
- return true;
- }
-
- @Override
- public String getDebugInfo() {
- return "MockCircuitBreaker";
- }
- }
-
- private static class FakeMemoryPressureCircuitBreaker extends MemoryCircuitBreaker {
-
- public FakeMemoryPressureCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
- }
-
- @Override
- protected long calculateLiveMemoryUsage() {
- // Return a number large enough to trigger a pushback from the circuit breaker
- return Long.MAX_VALUE;
- }
- }
-
- private static class BuildingUpMemoryPressureCircuitBreaker extends MemoryCircuitBreaker {
- private AtomicInteger count;
-
- public BuildingUpMemoryPressureCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
-
- this.count = new AtomicInteger(0);
- }
-
- @Override
- protected long calculateLiveMemoryUsage() {
- int localCount = count.getAndIncrement();
-
- if (localCount >= 4) {
- // TODO: To be removed
- if (log.isInfoEnabled()) {
- String logMessage =
- "Blocking query from BuildingUpMemoryPressureCircuitBreaker for count " + localCount;
- log.info(logMessage);
- }
- return Long.MAX_VALUE;
- }
-
- // TODO: To be removed
- if (log.isInfoEnabled()) {
- String logMessage =
- "BuildingUpMemoryPressureCircuitBreaker: Returning unblocking value for count "
- + localCount;
- log.info(logMessage);
- }
- return Long.MIN_VALUE; // Random number guaranteed to not trip the circuit breaker
- }
- }
-
- private static class FakeCPUCircuitBreaker extends CPUCircuitBreaker {
- public FakeCPUCircuitBreaker(CircuitBreakerConfig config) {
- super(config);
- }
-
- @Override
- protected double calculateLiveCPUUsage() {
- return 92; // Return a value large enough to trigger the circuit breaker
- }
+ initCore("solrconfig-pluggable-circuitbreaker.xml", "schema.xml");
+ BaseTestCircuitBreaker.indexDocs();
}
}
diff --git a/solr/core/src/test/org/apache/solr/util/TestLegacyCircuitBreaker.java b/solr/core/src/test/org/apache/solr/util/TestLegacyCircuitBreaker.java
new file mode 100644
index 00000000000..cd4cc499a36
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/TestLegacyCircuitBreaker.java
@@ -0,0 +1,38 @@
+/*
+ * 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.util;
+
+import org.junit.BeforeClass;
+
+/**
+ * Tests the original circuit breaker configuration format, which was not configurable.
+ *
+ * @deprecated Remove in 10.0
+ */
+@Deprecated(since = "9.4")
+public class TestLegacyCircuitBreaker extends BaseTestCircuitBreaker {
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ System.setProperty("filterCache.enabled", "false");
+ System.setProperty("queryResultCache.enabled", "false");
+ System.setProperty("documentCache.enabled", "true");
+
+ initCore("solrconfig-legacy-circuitbreaker.xml", "schema.xml");
+ BaseTestCircuitBreaker.indexDocs();
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
new file mode 100644
index 00000000000..0213fce7b9a
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
@@ -0,0 +1,117 @@
+/*
+ * 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.util.tracing;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.TracerProvider;
+import java.io.IOException;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.update.processor.LogUpdateProcessorFactory;
+import org.apache.solr.util.LogListener;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+@SuppressForbidden(reason = "We need to use log4J2 classes directly to test MDC impacts")
+public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
+
+ private static final String COLLECTION = "collection1";
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(4).addConfig("conf", configset("cloud-minimal")).configure();
+
+ // tracer should be disabled
+ assertEquals(
+ "Expecting noop otel (propagating only)",
+ TracerProvider.noop(),
+ GlobalOpenTelemetry.get().getTracerProvider());
+
+ CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
+ .process(cluster.getSolrClient());
+ cluster.waitForActiveCollection(COLLECTION, 2, 4);
+ }
+
+ @Test
+ public void test() throws IOException, SolrServerException {
+ CloudSolrClient cloudClient = cluster.getSolrClient();
+
+ // Indexing has trace ids
+ try (LogListener reqLog = LogListener.info(LogUpdateProcessorFactory.class.getName())) {
+ // verify all indexing events have trace id present
+ cloudClient.add(COLLECTION, sdoc("id", "1"));
+ cloudClient.add(COLLECTION, sdoc("id", "2"));
+ cloudClient.add(COLLECTION, sdoc("id", "3"));
+ var queue = reqLog.getQueue();
+ assertFalse(queue.isEmpty());
+ while (!queue.isEmpty()) {
+ var reqEvent = queue.poll();
+ String evTraceId = reqEvent.getContextData().getValue(MDCLoggingContext.TRACE_ID);
+ assertNotNull(evTraceId);
+ }
+
+ // TODO this doesn't work due to solr client creating the UpdateRequest without headers
+ // // verify all events have the same 'custom' traceid
+ // String traceId = "tidTestSimplePropagatorDistributedTracing0";
+ // var doc = sdoc("id", "4");
+ // UpdateRequest u = new UpdateRequest();
+ // u.add(doc);
+ // u.addHeader(SimplePropagator.TRACE_ID, traceId);
+ // var r1 = u.process(cloudClient, COLLECTION);
+ // assertEquals(0, r1.getStatus());
+ // assertSameTraceId(reqLog, traceId);
+ }
+
+ // Searching has trace ids
+ try (LogListener reqLog = LogListener.info(SolrCore.class.getName() + ".Request")) {
+ // verify all query events have the same auto-generated traceid
+ var r1 = cloudClient.query(COLLECTION, new SolrQuery("*:*"));
+ assertEquals(0, r1.getStatus());
+ assertSameTraceId(reqLog, null);
+
+ // verify all query events have the same 'custom' traceid
+ String traceId = "tidTestSimplePropagatorDistributedTracing1";
+ var q = new QueryRequest(new SolrQuery("*:*"));
+ q.addHeader(SimplePropagator.TRACE_ID, traceId);
+ var r2 = q.process(cloudClient, COLLECTION);
+ assertEquals(0, r2.getStatus());
+ assertSameTraceId(reqLog, traceId);
+ }
+ }
+
+ private void assertSameTraceId(LogListener reqLog, String traceId) {
+ var queue = reqLog.getQueue();
+ assertFalse(queue.isEmpty());
+ if (traceId == null) {
+ traceId = queue.poll().getContextData().getValue(MDCLoggingContext.TRACE_ID);
+ assertNotNull(traceId);
+ }
+ while (!queue.isEmpty()) {
+ var reqEvent = queue.poll();
+ String evTraceId = reqEvent.getContextData().getValue(MDCLoggingContext.TRACE_ID);
+ assertEquals(traceId, evTraceId);
+ }
+ }
+}
diff --git a/solr/licenses/annotations-2.20.128.jar.sha1 b/solr/licenses/annotations-2.20.128.jar.sha1
new file mode 100644
index 00000000000..f669d174c70
--- /dev/null
+++ b/solr/licenses/annotations-2.20.128.jar.sha1
@@ -0,0 +1 @@
+4eba3312fad5b1b621fde4cfbf1b9780591589d5
diff --git a/solr/licenses/annotations-2.20.97.jar.sha1 b/solr/licenses/annotations-2.20.97.jar.sha1
deleted file mode 100644
index 074e5ee3d44..00000000000
--- a/solr/licenses/annotations-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-41037256f5c45c951ce61e96370b1e59ed571e17
diff --git a/solr/licenses/apache-client-2.20.128.jar.sha1 b/solr/licenses/apache-client-2.20.128.jar.sha1
new file mode 100644
index 00000000000..0ef9ff55ec0
--- /dev/null
+++ b/solr/licenses/apache-client-2.20.128.jar.sha1
@@ -0,0 +1 @@
+5b82f09fd66fbd3c0b27ff88f7eb2e9a78e9a080
diff --git a/solr/licenses/apache-client-2.20.97.jar.sha1 b/solr/licenses/apache-client-2.20.97.jar.sha1
deleted file mode 100644
index e96404f979c..00000000000
--- a/solr/licenses/apache-client-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-366e2a5a9824eb750ea77ea1c7581055dafd6dac
diff --git a/solr/licenses/arns-2.20.128.jar.sha1 b/solr/licenses/arns-2.20.128.jar.sha1
new file mode 100644
index 00000000000..395694a27e7
--- /dev/null
+++ b/solr/licenses/arns-2.20.128.jar.sha1
@@ -0,0 +1 @@
+5e809b3b87e6f2806e0b67a390d3fe07fcaf7c5e
diff --git a/solr/licenses/arns-2.20.97.jar.sha1 b/solr/licenses/arns-2.20.97.jar.sha1
deleted file mode 100644
index aa4c743d9b7..00000000000
--- a/solr/licenses/arns-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-3e187cb36e2ba023823f839cb6a1e2eb175b02e6
diff --git a/solr/licenses/auth-2.20.128.jar.sha1 b/solr/licenses/auth-2.20.128.jar.sha1
new file mode 100644
index 00000000000..a7481505d04
--- /dev/null
+++ b/solr/licenses/auth-2.20.128.jar.sha1
@@ -0,0 +1 @@
+a27499eb27835bb21f0a8f6d6df9b7dcd2725529
diff --git a/solr/licenses/auth-2.20.97.jar.sha1 b/solr/licenses/auth-2.20.97.jar.sha1
deleted file mode 100644
index 97b18333ff8..00000000000
--- a/solr/licenses/auth-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e04a9881737fdaf84e1d11e27de64e78eb2d0f25
diff --git a/solr/licenses/aws-core-2.20.128.jar.sha1 b/solr/licenses/aws-core-2.20.128.jar.sha1
new file mode 100644
index 00000000000..7d0e6e344c4
--- /dev/null
+++ b/solr/licenses/aws-core-2.20.128.jar.sha1
@@ -0,0 +1 @@
+ca2c9d71fe86609a6e69c7b770cb282f365ca054
diff --git a/solr/licenses/aws-core-2.20.97.jar.sha1 b/solr/licenses/aws-core-2.20.97.jar.sha1
deleted file mode 100644
index 8459bca4d97..00000000000
--- a/solr/licenses/aws-core-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-378334b0a5f20cce0cca4d85f5855de1e0b072cf
diff --git a/solr/licenses/aws-query-protocol-2.20.128.jar.sha1 b/solr/licenses/aws-query-protocol-2.20.128.jar.sha1
new file mode 100644
index 00000000000..6c657e007e0
--- /dev/null
+++ b/solr/licenses/aws-query-protocol-2.20.128.jar.sha1
@@ -0,0 +1 @@
+ab36db110c976df57e6bf81b95a24b7736ddac43
diff --git a/solr/licenses/aws-query-protocol-2.20.97.jar.sha1 b/solr/licenses/aws-query-protocol-2.20.97.jar.sha1
deleted file mode 100644
index de87d5585fb..00000000000
--- a/solr/licenses/aws-query-protocol-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0fef3e8eadb282ff3b4de1ee1fdc9dd6624fd975
diff --git a/solr/licenses/aws-xml-protocol-2.20.128.jar.sha1 b/solr/licenses/aws-xml-protocol-2.20.128.jar.sha1
new file mode 100644
index 00000000000..4c0615d9097
--- /dev/null
+++ b/solr/licenses/aws-xml-protocol-2.20.128.jar.sha1
@@ -0,0 +1 @@
+ca6a04c0b1d5b492612e5b8816aedfc5895733cf
diff --git a/solr/licenses/aws-xml-protocol-2.20.97.jar.sha1 b/solr/licenses/aws-xml-protocol-2.20.97.jar.sha1
deleted file mode 100644
index e68e59378aa..00000000000
--- a/solr/licenses/aws-xml-protocol-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d441f5ee009122b7294923933492aede55acaad0
diff --git a/solr/licenses/crt-core-2.20.128.jar.sha1 b/solr/licenses/crt-core-2.20.128.jar.sha1
new file mode 100644
index 00000000000..8fd66294823
--- /dev/null
+++ b/solr/licenses/crt-core-2.20.128.jar.sha1
@@ -0,0 +1 @@
+fa7c6f4f1037479c021d90d14ec58c55c72f7c4f
diff --git a/solr/licenses/crt-core-2.20.97.jar.sha1 b/solr/licenses/crt-core-2.20.97.jar.sha1
deleted file mode 100644
index 37b54404abf..00000000000
--- a/solr/licenses/crt-core-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4cdd43cda3bbcf3e36162853cfa60acb5bf844e6
diff --git a/solr/licenses/endpoints-spi-2.20.128.jar.sha1 b/solr/licenses/endpoints-spi-2.20.128.jar.sha1
new file mode 100644
index 00000000000..04050c7defd
--- /dev/null
+++ b/solr/licenses/endpoints-spi-2.20.128.jar.sha1
@@ -0,0 +1 @@
+2761b9d660eb3db814d4078eb1e7ee53515effd0
diff --git a/solr/licenses/endpoints-spi-2.20.97.jar.sha1 b/solr/licenses/endpoints-spi-2.20.97.jar.sha1
deleted file mode 100644
index 5126f671412..00000000000
--- a/solr/licenses/endpoints-spi-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2a2763c7ae68ddaaee0513af4bfda4cab936033c
diff --git a/solr/licenses/http-client-spi-2.20.128.jar.sha1 b/solr/licenses/http-client-spi-2.20.128.jar.sha1
new file mode 100644
index 00000000000..b7986560ff0
--- /dev/null
+++ b/solr/licenses/http-client-spi-2.20.128.jar.sha1
@@ -0,0 +1 @@
+d614e5750362fabb312ccb4f2db440d22ed95c72
diff --git a/solr/licenses/http-client-spi-2.20.97.jar.sha1 b/solr/licenses/http-client-spi-2.20.97.jar.sha1
deleted file mode 100644
index bbd40cf57cb..00000000000
--- a/solr/licenses/http-client-spi-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2835a2c7c10225d563ade9c15bce3bff5809b7b8
diff --git a/solr/licenses/http2-client-10.0.15.jar.sha1 b/solr/licenses/http2-client-10.0.15.jar.sha1
deleted file mode 100644
index 1fb6b95c441..00000000000
--- a/solr/licenses/http2-client-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-70ba44dc67e6c4ff3c9d0fddf0b530ff1718a5b7
diff --git a/solr/licenses/http2-client-10.0.16.jar.sha1 b/solr/licenses/http2-client-10.0.16.jar.sha1
new file mode 100644
index 00000000000..9c0d69e62e6
--- /dev/null
+++ b/solr/licenses/http2-client-10.0.16.jar.sha1
@@ -0,0 +1 @@
+1d5ada7bfa44f016ec5089d71e83c7d2596bb993
diff --git a/solr/licenses/http2-common-10.0.15.jar.sha1 b/solr/licenses/http2-common-10.0.15.jar.sha1
deleted file mode 100644
index a1cdb73ed15..00000000000
--- a/solr/licenses/http2-common-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-997745dc17547fbf7d2ab945ef1ecc875a1d0872
diff --git a/solr/licenses/http2-common-10.0.16.jar.sha1 b/solr/licenses/http2-common-10.0.16.jar.sha1
new file mode 100644
index 00000000000..69ff610174b
--- /dev/null
+++ b/solr/licenses/http2-common-10.0.16.jar.sha1
@@ -0,0 +1 @@
+c510d982936eb1ee44f6d8f78816f59ca06c0c2f
diff --git a/solr/licenses/http2-hpack-10.0.15.jar.sha1 b/solr/licenses/http2-hpack-10.0.15.jar.sha1
deleted file mode 100644
index 1d138302c34..00000000000
--- a/solr/licenses/http2-hpack-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f83ed0b45e29af41e7b078e30e844f07bb9c10d5
diff --git a/solr/licenses/http2-hpack-10.0.16.jar.sha1 b/solr/licenses/http2-hpack-10.0.16.jar.sha1
new file mode 100644
index 00000000000..c7fcc824692
--- /dev/null
+++ b/solr/licenses/http2-hpack-10.0.16.jar.sha1
@@ -0,0 +1 @@
+71d71f1ac93bb5e0b1539b2a9c257018b9969022
diff --git a/solr/licenses/http2-http-client-transport-10.0.15.jar.sha1 b/solr/licenses/http2-http-client-transport-10.0.15.jar.sha1
deleted file mode 100644
index bdc15087200..00000000000
--- a/solr/licenses/http2-http-client-transport-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d06da4f9927d78db8d4e905d156af77ebdef7109
diff --git a/solr/licenses/http2-http-client-transport-10.0.16.jar.sha1 b/solr/licenses/http2-http-client-transport-10.0.16.jar.sha1
new file mode 100644
index 00000000000..5b440b9d6e6
--- /dev/null
+++ b/solr/licenses/http2-http-client-transport-10.0.16.jar.sha1
@@ -0,0 +1 @@
+780b6092e64ffb46ff490290add3ed2a2df2f646
diff --git a/solr/licenses/http2-server-10.0.15.jar.sha1 b/solr/licenses/http2-server-10.0.15.jar.sha1
deleted file mode 100644
index 6bdc00a0465..00000000000
--- a/solr/licenses/http2-server-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-508fcf41cefc997bab33b9f7722dff231086f8cd
diff --git a/solr/licenses/http2-server-10.0.16.jar.sha1 b/solr/licenses/http2-server-10.0.16.jar.sha1
new file mode 100644
index 00000000000..6b7996abcf2
--- /dev/null
+++ b/solr/licenses/http2-server-10.0.16.jar.sha1
@@ -0,0 +1 @@
+2117c48664430a2a32bca2239b75ee57b8084e8b
diff --git a/solr/licenses/jetty-alpn-client-10.0.15.jar.sha1 b/solr/licenses/jetty-alpn-client-10.0.15.jar.sha1
deleted file mode 100644
index 11ac116c904..00000000000
--- a/solr/licenses/jetty-alpn-client-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-24fd9dc56ce6bcd8fab03c202edc248b77f56bd3
diff --git a/solr/licenses/jetty-alpn-client-10.0.16.jar.sha1 b/solr/licenses/jetty-alpn-client-10.0.16.jar.sha1
new file mode 100644
index 00000000000..31a82a8316c
--- /dev/null
+++ b/solr/licenses/jetty-alpn-client-10.0.16.jar.sha1
@@ -0,0 +1 @@
+6df5cc027975573d8994e4d9a89d76d5a9c5121a
diff --git a/solr/licenses/jetty-alpn-java-client-10.0.15.jar.sha1 b/solr/licenses/jetty-alpn-java-client-10.0.15.jar.sha1
deleted file mode 100644
index ce016268d5e..00000000000
--- a/solr/licenses/jetty-alpn-java-client-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4d67e504e06c4a1fb2fb3a1d5bf06dda2b254121
diff --git a/solr/licenses/jetty-alpn-java-client-10.0.16.jar.sha1 b/solr/licenses/jetty-alpn-java-client-10.0.16.jar.sha1
new file mode 100644
index 00000000000..ea52966c985
--- /dev/null
+++ b/solr/licenses/jetty-alpn-java-client-10.0.16.jar.sha1
@@ -0,0 +1 @@
+df847a02e1092e4255858f216b1f0df43a98be04
diff --git a/solr/licenses/jetty-alpn-java-server-10.0.15.jar.sha1 b/solr/licenses/jetty-alpn-java-server-10.0.15.jar.sha1
deleted file mode 100644
index 1b9112b6447..00000000000
--- a/solr/licenses/jetty-alpn-java-server-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-13994a7ce7c3fa6f23fcf51204b1afc3c6f89c68
diff --git a/solr/licenses/jetty-alpn-java-server-10.0.16.jar.sha1 b/solr/licenses/jetty-alpn-java-server-10.0.16.jar.sha1
new file mode 100644
index 00000000000..3fb471965be
--- /dev/null
+++ b/solr/licenses/jetty-alpn-java-server-10.0.16.jar.sha1
@@ -0,0 +1 @@
+5cbcaffb0b45e923862d8bbdac53556642c8edca
diff --git a/solr/licenses/jetty-alpn-server-10.0.15.jar.sha1 b/solr/licenses/jetty-alpn-server-10.0.15.jar.sha1
deleted file mode 100644
index 963dfd8eb97..00000000000
--- a/solr/licenses/jetty-alpn-server-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4f6d594d09d5e302013bf74820d48b11f47dde75
diff --git a/solr/licenses/jetty-alpn-server-10.0.16.jar.sha1 b/solr/licenses/jetty-alpn-server-10.0.16.jar.sha1
new file mode 100644
index 00000000000..19aeb175a23
--- /dev/null
+++ b/solr/licenses/jetty-alpn-server-10.0.16.jar.sha1
@@ -0,0 +1 @@
+6af901179b5270fafa8c82e89bd23f37b57f66e9
diff --git a/solr/licenses/jetty-client-10.0.15.jar.sha1 b/solr/licenses/jetty-client-10.0.15.jar.sha1
deleted file mode 100644
index b81cd44cbe8..00000000000
--- a/solr/licenses/jetty-client-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5b1834dc03744e1201adafc04c5f6f564d38f1d5
diff --git a/solr/licenses/jetty-client-10.0.16.jar.sha1 b/solr/licenses/jetty-client-10.0.16.jar.sha1
new file mode 100644
index 00000000000..acf38316f62
--- /dev/null
+++ b/solr/licenses/jetty-client-10.0.16.jar.sha1
@@ -0,0 +1 @@
+62b5879f0530efa5296d0b296e588b79f3e4f0e5
diff --git a/solr/licenses/jetty-deploy-10.0.15.jar.sha1 b/solr/licenses/jetty-deploy-10.0.15.jar.sha1
deleted file mode 100644
index b609e60a63b..00000000000
--- a/solr/licenses/jetty-deploy-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4c3c9641cc7216249a6abf4b124a44fe8d60be8e
diff --git a/solr/licenses/jetty-deploy-10.0.16.jar.sha1 b/solr/licenses/jetty-deploy-10.0.16.jar.sha1
new file mode 100644
index 00000000000..ce800f8a663
--- /dev/null
+++ b/solr/licenses/jetty-deploy-10.0.16.jar.sha1
@@ -0,0 +1 @@
+2f1690ae512b2149550114b15cbdc0bf22069873
diff --git a/solr/licenses/jetty-http-10.0.15.jar.sha1 b/solr/licenses/jetty-http-10.0.15.jar.sha1
deleted file mode 100644
index 55ebe6f33dd..00000000000
--- a/solr/licenses/jetty-http-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-53c4702201c33501bc37a982e5325b5f11084a4e
diff --git a/solr/licenses/jetty-http-10.0.16.jar.sha1 b/solr/licenses/jetty-http-10.0.16.jar.sha1
new file mode 100644
index 00000000000..f292b913874
--- /dev/null
+++ b/solr/licenses/jetty-http-10.0.16.jar.sha1
@@ -0,0 +1 @@
+0bf1c1db5bb6e97c1040ee5c1053ec46b9895c5a
diff --git a/solr/licenses/jetty-io-10.0.15.jar.sha1 b/solr/licenses/jetty-io-10.0.15.jar.sha1
deleted file mode 100644
index 78badcd9857..00000000000
--- a/solr/licenses/jetty-io-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4481d9593bb89c4da016e49463b0d477faca06dc
diff --git a/solr/licenses/jetty-io-10.0.16.jar.sha1 b/solr/licenses/jetty-io-10.0.16.jar.sha1
new file mode 100644
index 00000000000..8db002cd026
--- /dev/null
+++ b/solr/licenses/jetty-io-10.0.16.jar.sha1
@@ -0,0 +1 @@
+a96c25a805a64a39a7c6e81dbd779bd228ca13aa
diff --git a/solr/licenses/jetty-jmx-10.0.15.jar.sha1 b/solr/licenses/jetty-jmx-10.0.15.jar.sha1
deleted file mode 100644
index bfeb8684899..00000000000
--- a/solr/licenses/jetty-jmx-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e42b10a3442639747dd2d925d6b8becd51e3cf52
diff --git a/solr/licenses/jetty-jmx-10.0.16.jar.sha1 b/solr/licenses/jetty-jmx-10.0.16.jar.sha1
new file mode 100644
index 00000000000..031e28e1069
--- /dev/null
+++ b/solr/licenses/jetty-jmx-10.0.16.jar.sha1
@@ -0,0 +1 @@
+aa728ec15d722b6114436521db044750d815b92b
diff --git a/solr/licenses/jetty-rewrite-10.0.15.jar.sha1 b/solr/licenses/jetty-rewrite-10.0.15.jar.sha1
deleted file mode 100644
index ec99309f2f7..00000000000
--- a/solr/licenses/jetty-rewrite-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1a06287e5475ad32397213d5b261d9ae357feea7
diff --git a/solr/licenses/jetty-rewrite-10.0.16.jar.sha1 b/solr/licenses/jetty-rewrite-10.0.16.jar.sha1
new file mode 100644
index 00000000000..9bb5bc57d25
--- /dev/null
+++ b/solr/licenses/jetty-rewrite-10.0.16.jar.sha1
@@ -0,0 +1 @@
+6d482b3d7928d9fcdd00a3f5fd702e7a96d4727f
diff --git a/solr/licenses/jetty-security-10.0.15.jar.sha1 b/solr/licenses/jetty-security-10.0.15.jar.sha1
deleted file mode 100644
index 44ed78bcdbe..00000000000
--- a/solr/licenses/jetty-security-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ae9c2fd327090fc749a6656109adf88f84f05854
diff --git a/solr/licenses/jetty-security-10.0.16.jar.sha1 b/solr/licenses/jetty-security-10.0.16.jar.sha1
new file mode 100644
index 00000000000..fbdc2b91405
--- /dev/null
+++ b/solr/licenses/jetty-security-10.0.16.jar.sha1
@@ -0,0 +1 @@
+7735aba773abefb79f0f68e058bf7b303df9364e
diff --git a/solr/licenses/jetty-server-10.0.15.jar.sha1 b/solr/licenses/jetty-server-10.0.15.jar.sha1
deleted file mode 100644
index d3cf9fc738c..00000000000
--- a/solr/licenses/jetty-server-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d1e941f30300d64b122d5346f1599ecaa8e270ba
diff --git a/solr/licenses/jetty-server-10.0.16.jar.sha1 b/solr/licenses/jetty-server-10.0.16.jar.sha1
new file mode 100644
index 00000000000..0220349e506
--- /dev/null
+++ b/solr/licenses/jetty-server-10.0.16.jar.sha1
@@ -0,0 +1 @@
+fefaa98e95b9737562d196d24f7846734ce99e17
diff --git a/solr/licenses/jetty-servlet-10.0.15.jar.sha1 b/solr/licenses/jetty-servlet-10.0.15.jar.sha1
deleted file mode 100644
index 44ed0b1ece6..00000000000
--- a/solr/licenses/jetty-servlet-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-17e21100d9eabae2c0f560ab2c1d5f0edfc4a57b
diff --git a/solr/licenses/jetty-servlet-10.0.16.jar.sha1 b/solr/licenses/jetty-servlet-10.0.16.jar.sha1
new file mode 100644
index 00000000000..4fe3c7337b4
--- /dev/null
+++ b/solr/licenses/jetty-servlet-10.0.16.jar.sha1
@@ -0,0 +1 @@
+3b995aca89a81109672587f81d411efbd0530d2a
diff --git a/solr/licenses/jetty-servlets-10.0.15.jar.sha1 b/solr/licenses/jetty-servlets-10.0.15.jar.sha1
deleted file mode 100644
index 8b083d602eb..00000000000
--- a/solr/licenses/jetty-servlets-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-73b1391880e636d2e804154704dbf3bfe429e622
diff --git a/solr/licenses/jetty-servlets-10.0.16.jar.sha1 b/solr/licenses/jetty-servlets-10.0.16.jar.sha1
new file mode 100644
index 00000000000..4527090c936
--- /dev/null
+++ b/solr/licenses/jetty-servlets-10.0.16.jar.sha1
@@ -0,0 +1 @@
+dcb14edd4b5453f38f7dc084bb43003090c6f8fa
diff --git a/solr/licenses/jetty-start-10.0.15-shaded.jar.sha1 b/solr/licenses/jetty-start-10.0.15-shaded.jar.sha1
deleted file mode 100644
index ff9a3675dbc..00000000000
--- a/solr/licenses/jetty-start-10.0.15-shaded.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5e0ca1e20769d4189e68b8c42457de75cca24c08
diff --git a/solr/licenses/jetty-start-10.0.16-shaded.jar.sha1 b/solr/licenses/jetty-start-10.0.16-shaded.jar.sha1
new file mode 100644
index 00000000000..2a43ac78457
--- /dev/null
+++ b/solr/licenses/jetty-start-10.0.16-shaded.jar.sha1
@@ -0,0 +1 @@
+d9960222bae7b11846d919557dbef0296d157a3e
diff --git a/solr/licenses/jetty-util-10.0.15.jar.sha1 b/solr/licenses/jetty-util-10.0.15.jar.sha1
deleted file mode 100644
index 5418cf4190e..00000000000
--- a/solr/licenses/jetty-util-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-eb8901d419e83f2f06809e0cdceaf38b06426f01
diff --git a/solr/licenses/jetty-util-10.0.16.jar.sha1 b/solr/licenses/jetty-util-10.0.16.jar.sha1
new file mode 100644
index 00000000000..a2c6113afe7
--- /dev/null
+++ b/solr/licenses/jetty-util-10.0.16.jar.sha1
@@ -0,0 +1 @@
+1513b0c4d8ce627835a5511950d4c40be6450258
diff --git a/solr/licenses/jetty-webapp-10.0.15.jar.sha1 b/solr/licenses/jetty-webapp-10.0.15.jar.sha1
deleted file mode 100644
index c04a11ad334..00000000000
--- a/solr/licenses/jetty-webapp-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ecda5b8297bc963cf411542a1143ad224532508a
diff --git a/solr/licenses/jetty-webapp-10.0.16.jar.sha1 b/solr/licenses/jetty-webapp-10.0.16.jar.sha1
new file mode 100644
index 00000000000..77c5f35abf2
--- /dev/null
+++ b/solr/licenses/jetty-webapp-10.0.16.jar.sha1
@@ -0,0 +1 @@
+5e84843afea5f39fda05c301abd0b7cfe929ec8c
diff --git a/solr/licenses/jetty-xml-10.0.15.jar.sha1 b/solr/licenses/jetty-xml-10.0.15.jar.sha1
deleted file mode 100644
index 2eb14f63413..00000000000
--- a/solr/licenses/jetty-xml-10.0.15.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d30d139b35bf183757b7ef5372dec5096bc39a60
diff --git a/solr/licenses/jetty-xml-10.0.16.jar.sha1 b/solr/licenses/jetty-xml-10.0.16.jar.sha1
new file mode 100644
index 00000000000..2369cf061df
--- /dev/null
+++ b/solr/licenses/jetty-xml-10.0.16.jar.sha1
@@ -0,0 +1 @@
+66dc24cea5422d14e9482dc4273d7ccea4b1def7
diff --git a/solr/licenses/json-utils-2.20.128.jar.sha1 b/solr/licenses/json-utils-2.20.128.jar.sha1
new file mode 100644
index 00000000000..3eb1f4fc2e3
--- /dev/null
+++ b/solr/licenses/json-utils-2.20.128.jar.sha1
@@ -0,0 +1 @@
+0f00a80b03dde32f1d7aca472516b6e45e4a8959
diff --git a/solr/licenses/json-utils-2.20.97.jar.sha1 b/solr/licenses/json-utils-2.20.97.jar.sha1
deleted file mode 100644
index db43070cb42..00000000000
--- a/solr/licenses/json-utils-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f3cdc0572c1db2ac78c5feede45a5e266ec13084
diff --git a/solr/licenses/metrics-spi-2.20.128.jar.sha1 b/solr/licenses/metrics-spi-2.20.128.jar.sha1
new file mode 100644
index 00000000000..83880b2a032
--- /dev/null
+++ b/solr/licenses/metrics-spi-2.20.128.jar.sha1
@@ -0,0 +1 @@
+d59e5e2264e03fe27cd1ab1843ccc2984636bc3c
diff --git a/solr/licenses/metrics-spi-2.20.97.jar.sha1 b/solr/licenses/metrics-spi-2.20.97.jar.sha1
deleted file mode 100644
index 820a98fc66a..00000000000
--- a/solr/licenses/metrics-spi-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-3845268eaaf2269cb83fb0b420ecfaec0736639a
diff --git a/solr/licenses/profiles-2.20.128.jar.sha1 b/solr/licenses/profiles-2.20.128.jar.sha1
new file mode 100644
index 00000000000..1d15205ac83
--- /dev/null
+++ b/solr/licenses/profiles-2.20.128.jar.sha1
@@ -0,0 +1 @@
+cbc0b7b6e98b9e168b96ef92c41f3d274b3d403b
diff --git a/solr/licenses/profiles-2.20.97.jar.sha1 b/solr/licenses/profiles-2.20.97.jar.sha1
deleted file mode 100644
index 21bcd31d075..00000000000
--- a/solr/licenses/profiles-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a26bb7ce18958e71040c3bf9cf6371ab022771c6
diff --git a/solr/licenses/protocol-core-2.20.128.jar.sha1 b/solr/licenses/protocol-core-2.20.128.jar.sha1
new file mode 100644
index 00000000000..42e13ee9ab0
--- /dev/null
+++ b/solr/licenses/protocol-core-2.20.128.jar.sha1
@@ -0,0 +1 @@
+a79cb2c8d2d4399ccbc1f432d23d2b6413c09396
diff --git a/solr/licenses/protocol-core-2.20.97.jar.sha1 b/solr/licenses/protocol-core-2.20.97.jar.sha1
deleted file mode 100644
index ef95d4ef40a..00000000000
--- a/solr/licenses/protocol-core-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0df4613072a99b366974e3f67be0aa955624655e
diff --git a/solr/licenses/regions-2.20.128.jar.sha1 b/solr/licenses/regions-2.20.128.jar.sha1
new file mode 100644
index 00000000000..ea8ed851217
--- /dev/null
+++ b/solr/licenses/regions-2.20.128.jar.sha1
@@ -0,0 +1 @@
+f9cdaff8fc49e3b3473fb741d70f6458bdd1d108
diff --git a/solr/licenses/regions-2.20.97.jar.sha1 b/solr/licenses/regions-2.20.97.jar.sha1
deleted file mode 100644
index 54e30673925..00000000000
--- a/solr/licenses/regions-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-8d5e815ff83afb34666277cf1c40a1a77ee88763
diff --git a/solr/licenses/s3-2.20.128.jar.sha1 b/solr/licenses/s3-2.20.128.jar.sha1
new file mode 100644
index 00000000000..c233b710661
--- /dev/null
+++ b/solr/licenses/s3-2.20.128.jar.sha1
@@ -0,0 +1 @@
+0eae09026984386b3b657c76952f1c33980cc60b
diff --git a/solr/licenses/s3-2.20.97.jar.sha1 b/solr/licenses/s3-2.20.97.jar.sha1
deleted file mode 100644
index 89367f80a09..00000000000
--- a/solr/licenses/s3-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7a9f0859a8a22b4a0a77bd929746bde6d222884d
diff --git a/solr/licenses/sdk-core-2.20.128.jar.sha1 b/solr/licenses/sdk-core-2.20.128.jar.sha1
new file mode 100644
index 00000000000..a7feb66351b
--- /dev/null
+++ b/solr/licenses/sdk-core-2.20.128.jar.sha1
@@ -0,0 +1 @@
+4d2681298b8364b10e8e448c7cfb38a0c57ea137
diff --git a/solr/licenses/sdk-core-2.20.97.jar.sha1 b/solr/licenses/sdk-core-2.20.97.jar.sha1
deleted file mode 100644
index 0a9f23dd634..00000000000
--- a/solr/licenses/sdk-core-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d2a5ab879bd75d84a582212bf04fc06474064810
diff --git a/solr/licenses/semver4j-4.3.0.jar.sha1 b/solr/licenses/semver4j-4.3.0.jar.sha1
deleted file mode 100644
index abf80df034a..00000000000
--- a/solr/licenses/semver4j-4.3.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-71760ca3e4136264c17107659d9df540594340a6
diff --git a/solr/licenses/semver4j-5.1.0.jar.sha1 b/solr/licenses/semver4j-5.1.0.jar.sha1
new file mode 100644
index 00000000000..69c3649226d
--- /dev/null
+++ b/solr/licenses/semver4j-5.1.0.jar.sha1
@@ -0,0 +1 @@
+e4afaf5d7297c8fbb67517ba6f1150b50e034b84
diff --git a/solr/licenses/sts-2.20.128.jar.sha1 b/solr/licenses/sts-2.20.128.jar.sha1
new file mode 100644
index 00000000000..e42ac3530a8
--- /dev/null
+++ b/solr/licenses/sts-2.20.128.jar.sha1
@@ -0,0 +1 @@
+5091ddfe486a878fbca7f7068e4d781109129d47
diff --git a/solr/licenses/sts-2.20.97.jar.sha1 b/solr/licenses/sts-2.20.97.jar.sha1
deleted file mode 100644
index 8675ad9c4f8..00000000000
--- a/solr/licenses/sts-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-717eb0636175d4274758390b306f7344974ddfc6
diff --git a/solr/licenses/third-party-jackson-core-2.20.128.jar.sha1 b/solr/licenses/third-party-jackson-core-2.20.128.jar.sha1
new file mode 100644
index 00000000000..886d413afca
--- /dev/null
+++ b/solr/licenses/third-party-jackson-core-2.20.128.jar.sha1
@@ -0,0 +1 @@
+9714093f164eac041f3f3af506a32ad61fc26e3f
diff --git a/solr/licenses/third-party-jackson-core-2.20.97.jar.sha1 b/solr/licenses/third-party-jackson-core-2.20.97.jar.sha1
deleted file mode 100644
index 7af03a3d0a4..00000000000
--- a/solr/licenses/third-party-jackson-core-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b5d4cb0064b567d65f82d867ea209b2638674d34
diff --git a/solr/licenses/url-connection-client-2.20.128.jar.sha1 b/solr/licenses/url-connection-client-2.20.128.jar.sha1
new file mode 100644
index 00000000000..89ad177e745
--- /dev/null
+++ b/solr/licenses/url-connection-client-2.20.128.jar.sha1
@@ -0,0 +1 @@
+f6fb7d2001efce42580c16b4ce4e55ebe556ab0b
diff --git a/solr/licenses/url-connection-client-2.20.97.jar.sha1 b/solr/licenses/url-connection-client-2.20.97.jar.sha1
deleted file mode 100644
index 11f2b2b3775..00000000000
--- a/solr/licenses/url-connection-client-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-37955fd70059ebbc23673d6e66e2e0c015ea94d7
diff --git a/solr/licenses/utils-2.20.128.jar.sha1 b/solr/licenses/utils-2.20.128.jar.sha1
new file mode 100644
index 00000000000..70c3f94382d
--- /dev/null
+++ b/solr/licenses/utils-2.20.128.jar.sha1
@@ -0,0 +1 @@
+a63a7b5cefd69dc24fddc200d31d2f6ec613b513
diff --git a/solr/licenses/utils-2.20.97.jar.sha1 b/solr/licenses/utils-2.20.97.jar.sha1
deleted file mode 100644
index eddd78f9dec..00000000000
--- a/solr/licenses/utils-2.20.97.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-3a84bf8c7dbf73700740f2f0d8e4babb7bbbdf0b
diff --git a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
index ae20606f7e7..3e3f7578fc9 100644
--- a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
+++ b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
@@ -63,6 +63,7 @@
import org.apache.solr.security.ConfigEditablePlugin;
import org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode;
import org.apache.solr.security.jwt.api.ModifyJWTAuthPluginConfigAPI;
+import org.apache.solr.servlet.LoadAdminUiServlet;
import org.apache.solr.util.CryptoKeys;
import org.eclipse.jetty.client.api.Request;
import org.jose4j.jwa.AlgorithmConstraints;
@@ -130,7 +131,9 @@ public class JWTAuthPlugin extends AuthenticationPlugin
JWTIssuerConfig.PARAM_CLIENT_ID,
JWTIssuerConfig.PARAM_WELL_KNOWN_URL,
JWTIssuerConfig.PARAM_AUDIENCE,
- JWTIssuerConfig.PARAM_AUTHORIZATION_ENDPOINT);
+ JWTIssuerConfig.PARAM_AUTHORIZATION_ENDPOINT,
+ JWTIssuerConfig.PARAM_TOKEN_ENDPOINT,
+ JWTIssuerConfig.PARAM_AUTHORIZATION_FLOW);
private JwtConsumer jwtConsumer;
private boolean requireExpirationTime;
@@ -280,10 +283,24 @@ public void init(Map pluginConfig) {
}
initConsumer();
+ registerTokenEndpointForCsp();
lastInitTime = Instant.now();
}
+ /**
+ * Record Issuer token URL as a system property so it can be picked up and sent to Admin UI as CSP
+ */
+ protected void registerTokenEndpointForCsp() {
+ final String syspropName = LoadAdminUiServlet.SYSPROP_CSP_CONNECT_SRC_URLS;
+ String url = !issuerConfigs.isEmpty() ? getPrimaryIssuer().getTokenEndpoint() : null;
+ if (url != null) {
+ System.setProperty(syspropName, url);
+ } else {
+ System.clearProperty(syspropName);
+ }
+ }
+
/**
* Given a configuration object of a file name or list of file names, read X509 certificates from
* each file
@@ -336,6 +353,8 @@ private Optional parseIssuerFromTopLevelConfig(Map data = new HashMap<>();
data.put(
JWTIssuerConfig.PARAM_AUTHORIZATION_ENDPOINT, primaryIssuer.getAuthorizationEndpoint());
+ data.put(JWTIssuerConfig.PARAM_TOKEN_ENDPOINT, primaryIssuer.getTokenEndpoint());
data.put("client_id", primaryIssuer.getClientId());
data.put("scope", adminUiScope);
data.put("redirect_uris", redirectUris);
+ data.put("authorization_flow", primaryIssuer.getAuthorizationFlow());
String headerJson = Utils.toJSONString(data);
return Base64.getEncoder().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
}
diff --git a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
index 55ccbcc956a..947d040da8b 100644
--- a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
+++ b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
@@ -21,6 +21,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
@@ -32,8 +33,10 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.jose4j.http.Get;
import org.jose4j.http.SimpleResponse;
@@ -41,9 +44,12 @@
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.lang.JoseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Holds information about an IdP (issuer), such as issuer ID, JWK url(s), keys etc */
public class JWTIssuerConfig {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
static final String PARAM_ISS_NAME = "name";
static final String PARAM_JWKS_URL = "jwksUrl";
static final String PARAM_JWK = "jwk";
@@ -51,7 +57,9 @@ public class JWTIssuerConfig {
static final String PARAM_AUDIENCE = "aud";
static final String PARAM_WELL_KNOWN_URL = "wellKnownUrl";
static final String PARAM_AUTHORIZATION_ENDPOINT = "authorizationEndpoint";
+ static final String PARAM_TOKEN_ENDPOINT = "tokenEndpoint";
static final String PARAM_CLIENT_ID = "clientId";
+ static final String PARAM_AUTHORIZATION_FLOW = "authorizationFlow";
private static HttpsJwksFactory httpsJwksFactory = new HttpsJwksFactory(3600, 5000);
private String iss;
@@ -64,12 +72,18 @@ public class JWTIssuerConfig {
private WellKnownDiscoveryConfig wellKnownDiscoveryConfig;
private String clientId;
private String authorizationEndpoint;
+ private String tokenEndpoint;
+ private String authorizationFlow;
private Collection trustedCerts;
public static boolean ALLOW_OUTBOUND_HTTP =
Boolean.parseBoolean(System.getProperty("solr.auth.jwt.allowOutboundHttp", "false"));
public static final String ALLOW_OUTBOUND_HTTP_ERR_MSG =
"HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.";
+ private static final String DEFAULT_AUTHORIZATION_FLOW =
+ "implicit"; // 'implicit' to be deprecated
+ private static final Set VALID_AUTHORIZATION_FLOWS =
+ Set.of(DEFAULT_AUTHORIZATION_FLOW, "code_pkce");
/**
* Create config for further configuration with setters, builder style. Once all values are set,
@@ -117,6 +131,10 @@ public void init() {
if (authorizationEndpoint == null) {
authorizationEndpoint = wellKnownDiscoveryConfig.getAuthorizationEndpoint();
}
+
+ if (tokenEndpoint == null) {
+ tokenEndpoint = wellKnownDiscoveryConfig.getTokenEndpoint();
+ }
}
if (iss == null && usesHttpsJwk() && !JWTAuthPlugin.PRIMARY_ISSUER.equals(name)) {
throw new SolrException(
@@ -141,6 +159,8 @@ protected void parseConfigMap(Map configMap) {
setJwksUrl(confJwksUrl);
setJsonWebKeySet(conf.get(PARAM_JWK));
setAuthorizationEndpoint((String) conf.get(PARAM_AUTHORIZATION_ENDPOINT));
+ setTokenEndpoint((String) conf.get(PARAM_TOKEN_ENDPOINT));
+ setAuthorizationFlow((String) conf.get(PARAM_AUTHORIZATION_FLOW));
conf.remove(PARAM_WELL_KNOWN_URL);
conf.remove(PARAM_ISSUER);
@@ -150,6 +170,8 @@ protected void parseConfigMap(Map configMap) {
conf.remove(PARAM_JWKS_URL);
conf.remove(PARAM_JWK);
conf.remove(PARAM_AUTHORIZATION_ENDPOINT);
+ conf.remove(PARAM_TOKEN_ENDPOINT);
+ conf.remove(PARAM_AUTHORIZATION_FLOW);
if (!conf.isEmpty()) {
throw new SolrException(
@@ -315,6 +337,41 @@ public JWTIssuerConfig setAuthorizationEndpoint(String authorizationEndpoint) {
return this;
}
+ public String getTokenEndpoint() {
+ return tokenEndpoint;
+ }
+
+ public JWTIssuerConfig setTokenEndpoint(String tokenEndpoint) {
+ this.tokenEndpoint = tokenEndpoint;
+ return this;
+ }
+
+ public String getAuthorizationFlow() {
+ return authorizationFlow;
+ }
+
+ public JWTIssuerConfig setAuthorizationFlow(String authorizationFlow) {
+ this.authorizationFlow =
+ StrUtils.isNullOrEmpty(authorizationFlow)
+ ? DEFAULT_AUTHORIZATION_FLOW
+ : authorizationFlow.trim();
+ if (!VALID_AUTHORIZATION_FLOWS.contains(this.authorizationFlow)) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ "Invalid value for "
+ + PARAM_AUTHORIZATION_FLOW
+ + ". Expected one of "
+ + VALID_AUTHORIZATION_FLOWS
+ + " but found "
+ + authorizationFlow);
+ }
+ if (this.authorizationFlow.equals("implicit")) {
+ log.warn(
+ "JWT authentication plugin is using 'implicit flow' which is deprecated and less secure. It's recommended to switch to 'code_pkce'");
+ }
+ return this;
+ }
+
public Map asConfig() {
HashMap config = new HashMap<>();
putIfNotNull(config, PARAM_ISS_NAME, name);
@@ -324,6 +381,8 @@ public Map asConfig() {
putIfNotNull(config, PARAM_WELL_KNOWN_URL, wellKnownUrl);
putIfNotNull(config, PARAM_CLIENT_ID, clientId);
putIfNotNull(config, PARAM_AUTHORIZATION_ENDPOINT, authorizationEndpoint);
+ putIfNotNull(config, PARAM_TOKEN_ENDPOINT, tokenEndpoint);
+ putIfNotNull(config, PARAM_AUTHORIZATION_FLOW, authorizationFlow);
if (jsonWebKeySet != null) {
putIfNotNull(config, PARAM_JWK, jsonWebKeySet.getJsonWebKeys());
}
diff --git a/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security.json b/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security.json
index 772089e3819..313b409a230 100644
--- a/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security.json
+++ b/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security.json
@@ -13,6 +13,8 @@
"realm": "my-solr-jwt",
"adminUiScope": "solr:admin",
"authorizationEndpoint": "http://acmepaymentscorp/oauth/auz/authorize",
+ "tokenEndpoint": "http://acmepaymentscorp/oauth/oauth20/token",
+ "authorizationFlow": "code_pkce",
"clientId": "solr-cluster"
}
}
\ No newline at end of file
diff --git a/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security_blockUnknownFalse.json b/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security_blockUnknownFalse.json
index ef5bcdeea4a..e9dafff1c2f 100644
--- a/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security_blockUnknownFalse.json
+++ b/solr/modules/jwt-auth/src/test-files/solr/security/jwt_plugin_jwk_security_blockUnknownFalse.json
@@ -13,6 +13,8 @@
"realm": "my-solr-jwt-blockunknown-false",
"adminUiScope": "solr:admin",
"authorizationEndpoint": "http://acmepaymentscorp/oauth/auz/authorize",
+ "tokenEndpoint": "http://acmepaymentscorp/oauth/oauth20/token",
+ "authorizationFlow": "code_pkce",
"clientId": "solr-cluster"
},
"authorization": {
diff --git a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
index a7dad973d86..23cefdbca51 100644
--- a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
+++ b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
@@ -162,6 +162,8 @@ public void testStaticJwtKeys() throws Exception {
String authData = new String(Base64.getDecoder().decode(headers.get("X-Solr-AuthData")), UTF_8);
assertEquals(
"{\n"
+ + " \"tokenEndpoint\":\"http://acmepaymentscorp/oauth/oauth20/token\",\n"
+ + " \"authorization_flow\":\"code_pkce\",\n"
+ " \"scope\":\"solr:admin\",\n"
+ " \"redirect_uris\":[],\n"
+ " \"authorizationEndpoint\":\"http://acmepaymentscorp/oauth/auz/authorize\",\n"
@@ -184,6 +186,8 @@ public void infoRequestValidateXSolrAuthHeadersBlockUnknownFalse() throws Except
String authData = new String(Base64.getDecoder().decode(headers.get("X-Solr-AuthData")), UTF_8);
assertEquals(
"{\n"
+ + " \"tokenEndpoint\":\"http://acmepaymentscorp/oauth/oauth20/token\",\n"
+ + " \"authorization_flow\":\"code_pkce\",\n"
+ " \"scope\":\"solr:admin\",\n"
+ " \"redirect_uris\":[],\n"
+ " \"authorizationEndpoint\":\"http://acmepaymentscorp/oauth/auz/authorize\",\n"
diff --git a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
index f40ffee9be9..9e04865c6c3 100644
--- a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
+++ b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
@@ -45,6 +45,7 @@
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.Utils;
import org.apache.solr.security.VerifiedUserRoles;
+import org.apache.solr.servlet.LoadAdminUiServlet;
import org.apache.solr.util.CryptoKeys;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
@@ -519,6 +520,8 @@ public void bothJwksUrlAndJwkFails() {
public void xSolrAuthDataHeader() {
testConfig.put("adminUiScope", "solr:admin");
testConfig.put("authorizationEndpoint", "http://acmepaymentscorp/oauth/auz/authorize");
+ testConfig.put("tokenEndpoint", "http://acmepaymentscorp/oauth/oauth20/token");
+ testConfig.put("authorizationFlow", "code_pkce");
testConfig.put("clientId", "solr-cluster");
plugin.init(testConfig);
String headerBase64 = plugin.generateAuthDataHeader();
@@ -528,6 +531,8 @@ public void xSolrAuthDataHeader() {
assertEquals("solr:admin", parsed.get("scope"));
assertEquals(
"http://acmepaymentscorp/oauth/auz/authorize", parsed.get("authorizationEndpoint"));
+ assertEquals("http://acmepaymentscorp/oauth/oauth20/token", parsed.get("tokenEndpoint"));
+ assertEquals("code_pkce", parsed.get("authorization_flow"));
assertEquals("solr-cluster", parsed.get("client_id"));
}
@@ -703,4 +708,13 @@ public void parseCertsFromFile() throws IOException {
.toString();
assertEquals(2, plugin.parseCertsFromFile(pemFilePath).size());
}
+
+ @Test
+ public void testRegisterTokenEndpointForCsp() {
+ testConfig.put("tokenEndpoint", "http://acmepaymentscorp/oauth/oauth20/token");
+ plugin.init(testConfig);
+ assertEquals(
+ "http://acmepaymentscorp/oauth/oauth20/token",
+ System.getProperty(LoadAdminUiServlet.SYSPROP_CSP_CONNECT_SRC_URLS));
+ }
}
diff --git a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTIssuerConfigTest.java b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTIssuerConfigTest.java
index 3355486001a..57c0261b897 100644
--- a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTIssuerConfigTest.java
+++ b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTIssuerConfigTest.java
@@ -53,15 +53,19 @@ public void setUp() throws Exception {
.setAud("audience")
.setClientId("clientid")
.setWellKnownUrl("wellknown")
- .setAuthorizationEndpoint("https://issuer/authz");
+ .setAuthorizationEndpoint("https://issuer/authz")
+ .setTokenEndpoint("https://issuer/token")
+ .setAuthorizationFlow("code_pkce");
testIssuerConfigMap = testIssuer.asConfig();
testIssuerJson =
"{\n"
+ " \"aud\":\"audience\",\n"
+ + " \"tokenEndpoint\":\"https://issuer/token\",\n"
+ " \"wellKnownUrl\":\"wellknown\",\n"
+ " \"clientId\":\"clientid\",\n"
+ + " \"authorizationFlow\":\"code_pkce\",\n"
+ " \"jwksUrl\":[\"https://issuer/path\"],\n"
+ " \"name\":\"name\",\n"
+ " \"iss\":\"issuer\",\n"
@@ -89,6 +93,11 @@ public void parseConfigMapNoName() {
new JWTIssuerConfig(testIssuerConfigMap).isValid();
}
+ @Test(expected = SolrException.class)
+ public void setInvalidAuthorizationFlow() {
+ new JWTIssuerConfig("name").setAuthorizationFlow("invalid_flow");
+ }
+
@Test
public void parseJwkSet() throws Exception {
HashMap testJwks = new HashMap<>();
@@ -173,6 +182,7 @@ public void wellKnownConfigFromString() throws IOException {
assertEquals("https://acmepaymentscorp/oauth/jwks", config.getJwksUrl());
assertEquals("http://acmepaymentscorp", config.getIssuer());
assertEquals("http://acmepaymentscorp/oauth/auz/authorize", config.getAuthorizationEndpoint());
+ assertEquals("http://acmepaymentscorp/oauth/oauth20/token", config.getTokenEndpoint());
assertEquals(
Arrays.asList(
"READ", "WRITE", "DELETE", "openid", "scope", "profile", "email", "address", "phone"),
diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
index 3089263c036..88a60efea59 100644
--- a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
+++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
@@ -121,13 +121,13 @@ protected static void setupPersistenttest(boolean bulkIndex) throws Exception {
}
public static ManagedFeatureStore getManagedFeatureStore() {
- try (SolrCore core = jetty.getCoreContainer().getCore(DEFAULT_TEST_CORENAME)) {
+ try (SolrCore core = solrClientTestRule.getCoreContainer().getCore(DEFAULT_TEST_CORENAME)) {
return ManagedFeatureStore.getManagedFeatureStore(core);
}
}
public static ManagedModelStore getManagedModelStore() {
- try (SolrCore core = jetty.getCoreContainer().getCore(DEFAULT_TEST_CORENAME)) {
+ try (SolrCore core = solrClientTestRule.getCoreContainer().getCore(DEFAULT_TEST_CORENAME)) {
return ManagedModelStore.getManagedModelStore(core);
}
}
@@ -195,10 +195,7 @@ protected static void aftertest() throws Exception {
restTestHarness.close();
restTestHarness = null;
}
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
+ solrClientTestRule.reset();
if (null != tmpSolrHome) {
PathUtils.deleteDirectory(tmpSolrHome);
tmpSolrHome = null;
diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
index 8123d8ce382..5f102fb67a1 100644
--- a/solr/modules/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
+++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
@@ -407,8 +407,21 @@ public void testThatDefaultFieldValueScorerIsUsedAndDefaultIsReturned() throws E
@Test
public void testBooleanValue() throws Exception {
+ implTestBooleanValue("isTrendy");
+ }
+
+ @Test
+ public void testBooleanValue_docValues() throws Exception {
+ implTestBooleanValue("dvIsTrendy");
+ }
+
+ private void implTestBooleanValue(String isTrendyFieldName) throws Exception {
final String fstore = "test_boolean_store";
- loadFeature("trendy", FieldValueFeature.class.getName(), fstore, "{\"field\":\"isTrendy\"}");
+ loadFeature(
+ "trendy",
+ FieldValueFeature.class.getName(),
+ fstore,
+ "{\"field\":\"" + isTrendyFieldName + "\"}");
loadModel(
"trendy-model",
diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
index d513cfd9dc7..2adcc1f19f8 100644
--- a/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
+++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
@@ -170,8 +170,8 @@ public void testFilePersistence() throws Exception {
"/features/[1]/name=='description'");
// check persistence after restart
- jetty.stop();
- jetty.start();
+ getJetty().stop();
+ getJetty().start();
assertJQ(ManagedModelStore.REST_END_POINT, "/models/[0]/name=='" + modelName + "'");
assertJQ(
ManagedFeatureStore.REST_END_POINT + "/" + FeatureStore.DEFAULT_FEATURE_STORE_NAME,
@@ -197,8 +197,8 @@ public void testFilePersistence() throws Exception {
"/error/msg=='Missing feature store: " + FeatureStore.DEFAULT_FEATURE_STORE_NAME + "'");
// check persistence after restart
- jetty.stop();
- jetty.start();
+ getJetty().stop();
+ getJetty().start();
assertJQ(ManagedModelStore.REST_END_POINT, "/models/==[]");
assertJQ(
ManagedFeatureStore.REST_END_POINT + "/" + FeatureStore.DEFAULT_FEATURE_STORE_NAME,
@@ -269,8 +269,8 @@ public void testWrapperModelPersistence() throws Exception {
doWrapperModelPersistenceChecks(modelName, FS_NAME, baseModelFile.getFileName().toString());
// check persistence after restart
- jetty.stop();
- jetty.start();
+ getJetty().stop();
+ getJetty().start();
doWrapperModelPersistenceChecks(modelName, FS_NAME, baseModelFile.getFileName().toString());
// delete test settings
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/BasicAuthIntegrationTracingTest.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/BasicAuthIntegrationTracingTest.java
new file mode 100644
index 00000000000..cdbf9f27141
--- /dev/null
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/BasicAuthIntegrationTracingTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.opentelemetry;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.opentelemetry.TestDistributedTracing.getAndClearSpans;
+import static org.apache.solr.security.Sha256AuthenticationProvider.getSaltedHashedValue;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.TracerProvider;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.V2Request;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.security.BasicAuthPlugin;
+import org.apache.solr.security.RuleBasedAuthorizationPlugin;
+import org.apache.solr.util.tracing.TraceUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class BasicAuthIntegrationTracingTest extends SolrCloudTestCase {
+
+ private static final String COLLECTION = "collection1";
+ private static final String USER = "solr";
+ private static final String PASS = "SolrRocksAgain";
+ private static final String SECURITY_JSON =
+ Utils.toJSONString(
+ Map.of(
+ "authorization",
+ Map.of(
+ "class",
+ RuleBasedAuthorizationPlugin.class.getName(),
+ "user-role",
+ singletonMap(USER, "admin"),
+ "permissions",
+ singletonList(Map.of("name", "all", "role", "admin"))),
+ "authentication",
+ Map.of(
+ "class",
+ BasicAuthPlugin.class.getName(),
+ "blockUnknown",
+ true,
+ "credentials",
+ singletonMap(USER, getSaltedHashedValue(PASS)))));
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ // force early init
+ CustomTestOtelTracerConfigurator.prepareForTest();
+
+ configureCluster(4)
+ .addConfig("config", TEST_PATH().resolve("collection1").resolve("conf"))
+ .withSolrXml(TEST_PATH().resolve("solr.xml"))
+ .withTraceIdGenerationDisabled()
+ .withSecurityJson(SECURITY_JSON)
+ .configure();
+
+ assertNotEquals(
+ "Expecting active otel, not noop impl",
+ TracerProvider.noop(),
+ GlobalOpenTelemetry.get().getTracerProvider());
+
+ CollectionAdminRequest.createCollection(COLLECTION, "config", 2, 2)
+ .setBasicAuthCredentials(USER, PASS)
+ .process(cluster.getSolrClient());
+ cluster.waitForActiveCollection(COLLECTION, 2, 4);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ CustomTestOtelTracerConfigurator.resetForTest();
+ }
+
+ /** See SOLR-16955 */
+ @Test
+ public void testSetupBasicAuth() throws Exception {
+ getAndClearSpans(); // reset
+
+ CloudSolrClient cloudClient = cluster.getSolrClient();
+ Map ops =
+ Map.of(
+ "set-user", Map.of("harry", "HarryIsCool"),
+ "set-property", Map.of("blockUnknown", true));
+ V2Request req =
+ new V2Request.Builder("/cluster/security/authentication")
+ .withMethod(SolrRequest.METHOD.POST)
+ .withPayload(Utils.toJSONString(ops))
+ .build();
+ req.setBasicAuthCredentials(USER, PASS);
+ assertEquals(0, req.process(cloudClient, COLLECTION).getStatus());
+
+ var finishedSpans = getAndClearSpans();
+ assertEquals(1, finishedSpans.size());
+ var span = finishedSpans.get(0);
+ assertEquals("post:/cluster/security/authentication", span.getName());
+ assertEquals("solr", span.getAttributes().get(TraceUtils.TAG_USER));
+ assertEquals(
+ BasicAuthPlugin.class.getSimpleName(), span.getAttributes().get(TraceUtils.TAG_CLASS));
+ assertEquals(List.copyOf(ops.keySet()), span.getAttributes().get(TraceUtils.TAG_OPS));
+ }
+}
diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
index dea832e87f6..23cf76f1cbf 100644
--- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
+++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
@@ -54,6 +54,7 @@ public static void setupCluster() throws Exception {
configureCluster(4)
.addConfig("config", TEST_PATH().resolve("collection1").resolve("conf"))
.withSolrXml(TEST_PATH().resolve("solr.xml"))
+ .withTraceIdGenerationDisabled()
.configure();
assertNotEquals(
@@ -200,7 +201,7 @@ private void assertOneSpanIsChildOfAnother(List finishedSpans) {
assertEquals(child.getTraceId(), parent.getTraceId());
}
- private List getAndClearSpans() {
+ static List getAndClearSpans() {
InMemorySpanExporter exporter = CustomTestOtelTracerConfigurator.getInMemorySpanExporter();
List result = new ArrayList<>(exporter.getFinishedSpanItems());
Collections.reverse(result); // nicer to see spans chronologically
diff --git a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
index a0af9117b24..7dd50fcb5e7 100644
--- a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
+++ b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
@@ -49,7 +49,7 @@ public static void beforeClass() throws Exception {
public void testSQLHandler() throws Exception {
String sql = "select id, field_i, str_s from " + DEFAULT_TEST_COLLECTION_NAME + " limit 10";
SolrParams sParams = params(CommonParams.QT, "/sql", "stmt", sql);
- String url = jetty.getBaseUrl() + "/" + DEFAULT_TEST_COLLECTION_NAME;
+ String url = getBaseUrl() + "/" + DEFAULT_TEST_COLLECTION_NAME;
SolrStream solrStream = new SolrStream(url, sParams);
IOException ex = expectThrows(IOException.class, () -> getTuples(solrStream));
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
index 310aa45049c..aa366eaafd6 100644
--- a/solr/packaging/build.gradle
+++ b/solr/packaging/build.gradle
@@ -256,6 +256,7 @@ task integrationTests(type: BatsTask) {
environment SOLR_TIP: distDir.toString()
environment SOLR_HOME: solrHome
environment SOLR_LOGS_DIR: "$solrHome/logs"
+ environment TEST_OUTPUT_DIR: integrationTestOutput
environment TEST_FAILURE_DIR: solrTestFailuresDir
environment BATS_LIB_PREFIX: "$nodeProjectDir/node_modules"
}
diff --git a/solr/packaging/test/test_ssl.bats b/solr/packaging/test/test_ssl.bats
index 9f3fdfe806c..f3aae8d6edc 100644
--- a/solr/packaging/test/test_ssl.bats
+++ b/solr/packaging/test/test_ssl.bats
@@ -57,10 +57,53 @@ teardown() {
run solr create -c test -s 2
assert_output --partial "Created collection 'test'"
- run curl --http2 --cacert "$ssl_dir/solr-ssl.pem" 'https://localhost:8983/solr/test/select?q=*:*'
+ run curl --http2 --cacert "$ssl_dir/solr-ssl.pem" 'https://127.0.0.1:8983/solr/test/select?q=*:*'
assert_output --partial '"numFound":0'
}
+@test "use different hostname when not checking peer-name" {
+ # Create a keystore
+ export ssl_dir="${BATS_TEST_TMPDIR}/ssl"
+ mkdir -p "$ssl_dir"
+ (
+ cd "$ssl_dir"
+ rm -f solr-ssl.keystore.p12 solr-ssl.pem
+ # Using a CN that is not localhost, as we will not be checking peer-name
+ keytool -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass secret -storepass secret -validity 9999 -keystore solr-ssl.keystore.p12 -storetype PKCS12 -ext "SAN=DNS:test.solr.apache.org,IP:127.0.0.1" -dname "CN=test.solr.apache.org, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country"
+ openssl pkcs12 -in solr-ssl.keystore.p12 -out solr-ssl.pem -passin pass:secret -passout pass:secret
+ )
+
+ # Set ENV_VARs so that Solr uses this keystore
+ export SOLR_SSL_ENABLED=true
+ export SOLR_SSL_KEY_STORE=$ssl_dir/solr-ssl.keystore.p12
+ export SOLR_SSL_KEY_STORE_PASSWORD=secret
+ export SOLR_SSL_TRUST_STORE=$ssl_dir/solr-ssl.keystore.p12
+ export SOLR_SSL_TRUST_STORE_PASSWORD=secret
+ export SOLR_SSL_NEED_CLIENT_AUTH=false
+ export SOLR_SSL_WANT_CLIENT_AUTH=false
+ export SOLR_SSL_CHECK_PEER_NAME=false
+ # Remove later when SOLR-16963 is resolved
+ export SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=false
+ export SOLR_HOST=localhost
+
+ solr start -c
+ solr assert --started https://localhost:8983/solr --timeout 5000
+
+ run solr create -c test -s 2
+ assert_output --partial "Created collection 'test'"
+
+ run curl --http2 --cacert "$ssl_dir/solr-ssl.pem" -k 'https://localhost:8983/solr/test/select?q=*:*'
+ assert_output --partial '"numFound":0'
+
+ export SOLR_SSL_CHECK_PEER_NAME=true
+ # Remove later when SOLR-16963 is resolved
+ export SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=true
+
+ # This should fail the peername check
+ run ! solr api -get 'https://localhost:8983/solr/test/select?q=*:*'
+ assert_output --partial 'Server refused connection'
+}
+
@test "start solr with ssl and auth" {
# Create a keystore
export ssl_dir="${BATS_TEST_TMPDIR}/ssl"
@@ -97,3 +140,305 @@ teardown() {
run curl --http2 --cacert "$ssl_dir/solr-ssl.pem" 'https://localhost:8983/solr/test/select?q=*:*'
assert_output --partial '401 require authentication'
}
+
+@test "start solr with client truststore and security manager" {
+ # Make a test tmp dir, as the security policy includes TMP, so that might already contain the BATS_TEST_TMPDIR
+ test_tmp_dir="${BATS_TEST_TMPDIR}/tmp"
+ mkdir -p "${test_tmp_dir}"
+ test_tmp_dir="$(cd -P "${test_tmp_dir}" && pwd)"
+
+ export SOLR_SECURITY_MANAGER_ENABLED=true
+ export SOLR_OPTS="-Djava.io.tmpdir=${test_tmp_dir}"
+ export SOLR_TOOL_OPTS="-Djava.io.tmpdir=${test_tmp_dir}"
+
+ # Create a keystore
+ export ssl_dir="${BATS_TEST_TMPDIR}/ssl"
+ export client_ssl_dir="${ssl_dir}-client"
+ mkdir -p "$ssl_dir"
+ (
+ cd "$ssl_dir"
+ rm -f solr-ssl.keystore.p12
+ keytool -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass secret -storepass secret -validity 9999 -keystore solr-ssl.keystore.p12 -storetype PKCS12 -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country"
+ )
+ mkdir -p "$client_ssl_dir"
+ (
+ cd "$client_ssl_dir"
+ rm -f *
+ keytool -export -alias solr-ssl -file solr-ssl.crt -keystore "$ssl_dir/solr-ssl.keystore.p12" -keypass secret -storepass secret
+ keytool -import -v -trustcacerts -alias solr-ssl -file solr-ssl.crt -storetype PKCS12 -keystore solr-ssl.truststore.p12 -keypass secret -storepass secret -noprompt
+ )
+ cp -R "$ssl_dir" "$client_ssl_dir"
+
+ # Set ENV_VARs so that Solr uses this keystore
+ export SOLR_SSL_ENABLED=true
+ export SOLR_SSL_KEY_STORE=$ssl_dir/solr-ssl.keystore.p12
+ export SOLR_SSL_KEY_STORE_PASSWORD=secret
+ export SOLR_SSL_TRUST_STORE=$ssl_dir/solr-ssl.keystore.p12
+ export SOLR_SSL_TRUST_STORE_PASSWORD=secret
+ export SOLR_SSL_CLIENT_TRUST_STORE=$client_ssl_dir/solr-ssl.truststore.p12
+ export SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD=secret
+ export SOLR_SSL_NEED_CLIENT_AUTH=false
+ export SOLR_SSL_WANT_CLIENT_AUTH=true
+ export SOLR_SSL_CHECK_PEER_NAME=true
+ export SOLR_HOST=localhost
+ export SOLR_SECURITY_MANAGER_ENABLED=true
+
+ run solr start -c
+
+ export SOLR_SSL_KEY_STORE=
+ export SOLR_SSL_KEY_STORE_PASSWORD=
+ export SOLR_SSL_TRUST_STORE=
+ export SOLR_SSL_TRUST_STORE_PASSWORD=
+
+ solr assert --started https://localhost:8983/solr --timeout 5000
+
+ run solr create -c test -s 2
+ assert_output --partial "Created collection 'test'"
+
+ run solr api -get 'https://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS'
+ assert_output --partial '"urlScheme":"https"'
+}
+
+@test "start solr with mTLS needed" {
+ # Make a test tmp dir, as the security policy includes TMP, so that might already contain the BATS_TEST_TMPDIR
+ test_tmp_dir="${BATS_TEST_TMPDIR}/tmp"
+ mkdir -p "${test_tmp_dir}"
+ test_tmp_dir="$(cd -P "${test_tmp_dir}" && pwd)"
+
+ export SOLR_SECURITY_MANAGER_ENABLED=true
+ export SOLR_OPTS="-Djava.io.tmpdir=${test_tmp_dir}"
+ export SOLR_TOOL_OPTS="-Djava.io.tmpdir=${test_tmp_dir} -Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake"
+
+ export ssl_dir="${BATS_TEST_TMPDIR}/ssl"
+ export server_ssl_dir="${ssl_dir}/server"
+ export client_ssl_dir="${ssl_dir}/client"
+
+ # Create a root & intermediary CA
+ echo "${ssl_dir}"
+ mkdir -p "${ssl_dir}"
+ (
+ cd "$ssl_dir"
+ rm -f root.p12 root.pem ca.p12 ca.pem
+
+ keytool -genkeypair -keystore root.p12 -storetype PKCS12 -keypass secret -storepass secret -alias root -ext bc:c -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+ keytool -genkeypair -keystore ca.p12 -storetype PKCS12 -keypass secret -storepass secret -alias ca -ext bc:c -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ keytool -keystore root.p12 -storetype PKCS12 -storepass secret -alias root -exportcert -rfc > root.pem
+
+ keytool -storepass secret -storetype PKCS12 -keystore ca.p12 -certreq -alias ca | \
+ keytool -storepass secret -keystore root.p12 -storetype PKCS12 \
+ -gencert -alias root -ext BC=0 -rfc > ca.pem
+ keytool -keystore ca.p12 -importcert -storetype PKCS12 -storepass secret -alias root -file root.pem -noprompt
+ keytool -keystore ca.p12 -importcert -storetype PKCS12 -storepass secret -alias ca -file ca.pem
+ )
+ # Create a server keystore & truststore
+ mkdir -p "$server_ssl_dir"
+ (
+ cd "$server_ssl_dir"
+ rm -f solr-server.keystore.p12 server.pem solr-server.truststore.p12
+
+ # Create a keystore and certificate
+ keytool -genkeypair -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -alias server -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ # Trust the keystore cert with the CA
+ keytool -storepass server-key -keystore solr-server.keystore.p12 -storetype PKCS12 -certreq -alias server | \
+ keytool -storepass secret -keystore "$ssl_dir/ca.p12" -storetype PKCS12 -gencert -alias ca \
+ -ext "ku:c=nonRepudiation,digitalSignature,keyEncipherment" -ext eku:c=serverAuth -rfc > server.pem
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias server -file server.pem
+
+ # Create a truststore with just the Root CA
+ keytool -keystore solr-server.truststore.p12 -storetype PKCS12 -keypass server-trust -storepass server-trust -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-server.truststore.p12 -storetype PKCS12 -keypass server-trust -storepass server-trust -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ )
+ # Create a client keystore & truststore
+ mkdir -p "$client_ssl_dir"
+ (
+ cd "$client_ssl_dir"
+ rm -f solr-client.keystore.p12 client.pem solr-client.truststore.p12
+
+ # Create a keystore and certificate
+ keytool -genkeypair -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -alias client -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ # Trust the keystore cert with the CA
+ keytool -storepass client-key -keystore solr-client.keystore.p12 -storetype PKCS12 -certreq -alias client | \
+ keytool -storepass secret -keystore "$ssl_dir/ca.p12" -storetype PKCS12 -gencert -alias ca \
+ -ext "ku:c=nonRepudiation,digitalSignature,keyEncipherment" -ext eku:c=clientAuth -rfc > client.pem
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias client -file client.pem
+
+ # Create a truststore with just the Root CA
+ keytool -keystore solr-client.truststore.p12 -storetype PKCS12 -keypass client-trust -storepass client-trust -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-client.truststore.p12 -storetype PKCS12 -keypass client-trust -storepass client-trust -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ )
+
+ # Set ENV_VARs so that Solr uses this keystore
+ export SOLR_SSL_ENABLED=true
+ export SOLR_SSL_KEY_STORE="$server_ssl_dir/solr-server.keystore.p12"
+ export SOLR_SSL_KEY_STORE_PASSWORD=server-key
+ export SOLR_SSL_KEY_STORE_TYPE=PKCS12
+ export SOLR_SSL_TRUST_STORE="$server_ssl_dir/solr-server.truststore.p12"
+ export SOLR_SSL_TRUST_STORE_PASSWORD=server-trust
+ export SOLR_SSL_TRUST_STORE_TYPE=PKCS12
+ export SOLR_SSL_CLIENT_KEY_STORE="$client_ssl_dir/solr-client.keystore.p12"
+ export SOLR_SSL_CLIENT_KEY_STORE_PASSWORD=client-key
+ export SOLR_SSL_CLIENT_KEY_STORE_TYPE=PKCS12
+ export SOLR_SSL_CLIENT_TRUST_STORE="$client_ssl_dir/solr-client.truststore.p12"
+ export SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD=client-trust
+ export SOLR_SSL_CLIENT_TRUST_STORE_TYPE=PKCS12
+ export SOLR_SSL_NEED_CLIENT_AUTH=true
+ export SOLR_SSL_WANT_CLIENT_AUTH=false
+ export SOLR_SSL_CHECK_PEER_NAME=true
+ export SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=true
+ export SOLR_HOST=localhost
+
+ solr start -c
+ solr start -c -z localhost:9983 -p 8984
+
+ export SOLR_SSL_KEY_STORE=
+ export SOLR_SSL_KEY_STORE_PASSWORD=
+ export SOLR_SSL_TRUST_STORE=
+ export SOLR_SSL_TRUST_STORE_PASSWORD=
+
+ solr assert --started https://localhost:8983/solr --timeout 5000
+ solr assert --started https://localhost:8984/solr --timeout 5000
+
+ run solr create -c test -s 2
+ assert_output --partial "Created collection 'test'"
+
+ run solr api -get 'https://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS'
+ assert_output --partial '"urlScheme":"https"'
+
+ run solr api -get 'https://localhost:8984/solr/test/select?q=*:*&rows=0'
+ assert_output --partial '"numFound":0'
+
+ export SOLR_SSL_CLIENT_KEY_STORE=
+ export SOLR_SSL_CLIENT_KEY_STORE_PASSWORD=
+
+ run ! solr api -get 'https://localhost:8983/solr/test/select?q=*:*&rows=0'
+ assert_output --partial 'Server refused connection'
+}
+
+@test "start solr with mTLS wanted" {
+ # Make a test tmp dir, as the security policy includes TMP, so that might already contain the BATS_TEST_TMPDIR
+ test_tmp_dir="${BATS_TEST_TMPDIR}/tmp"
+ mkdir -p "${test_tmp_dir}"
+ test_tmp_dir="$(cd -P "${test_tmp_dir}" && pwd)"
+
+ export SOLR_SECURITY_MANAGER_ENABLED=true
+ export SOLR_OPTS="-Djava.io.tmpdir=${test_tmp_dir}"
+ export SOLR_TOOL_OPTS="-Djava.io.tmpdir=${test_tmp_dir} -Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake"
+
+ export ssl_dir="${BATS_TEST_TMPDIR}/ssl"
+ export server_ssl_dir="${ssl_dir}/server"
+ export client_ssl_dir="${ssl_dir}/client"
+
+ # Create a root & intermediary CA
+ echo "${ssl_dir}"
+ mkdir -p "${ssl_dir}"
+ (
+ cd "$ssl_dir"
+ rm -f root.p12 root.pem ca.p12 ca.pem
+
+ keytool -genkeypair -keystore root.p12 -storetype PKCS12 -keypass secret -storepass secret -alias root -ext bc:c -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+ keytool -genkeypair -keystore ca.p12 -storetype PKCS12 -keypass secret -storepass secret -alias ca -ext bc:c -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ keytool -keystore root.p12 -storetype PKCS12 -storepass secret -alias root -exportcert -rfc > root.pem
+
+ keytool -storepass secret -storetype PKCS12 -keystore ca.p12 -certreq -alias ca | \
+ keytool -storepass secret -keystore root.p12 -storetype PKCS12 \
+ -gencert -alias root -ext BC=0 -rfc > ca.pem
+ keytool -keystore ca.p12 -importcert -storetype PKCS12 -storepass secret -alias root -file root.pem -noprompt
+ keytool -keystore ca.p12 -importcert -storetype PKCS12 -storepass secret -alias ca -file ca.pem
+ )
+ # Create a server keystore & truststore
+ mkdir -p "$server_ssl_dir"
+ (
+ cd "$server_ssl_dir"
+ rm -f solr-server.keystore.p12 server.pem solr-server.truststore.p12
+
+ # Create a keystore and certificate
+ keytool -genkeypair -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -alias server -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ # Trust the keystore cert with the CA
+ keytool -storepass server-key -keystore solr-server.keystore.p12 -storetype PKCS12 -certreq -alias server | \
+ keytool -storepass secret -keystore "$ssl_dir/ca.p12" -storetype PKCS12 -gencert -alias ca \
+ -ext "ku:c=nonRepudiation,digitalSignature,keyEncipherment" -ext eku:c=serverAuth -rfc > server.pem
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ keytool -keystore solr-server.keystore.p12 -storetype PKCS12 -keypass server-key -storepass server-key -importcert -alias server -file server.pem
+
+ # Create a truststore with just the Root CA
+ keytool -keystore solr-server.truststore.p12 -storetype PKCS12 -keypass server-trust -storepass server-trust -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-server.truststore.p12 -storetype PKCS12 -keypass server-trust -storepass server-trust -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ )
+ # Create a client keystore & truststore
+ mkdir -p "$client_ssl_dir"
+ (
+ cd "$client_ssl_dir"
+ rm -f solr-client.keystore.p12 client.pem solr-client.truststore.p12
+
+ # Create a keystore and certificate
+ keytool -genkeypair -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -alias client -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country" -keyalg rsa
+
+ # Trust the keystore cert with the CA
+ keytool -storepass client-key -keystore solr-client.keystore.p12 -storetype PKCS12 -certreq -alias client | \
+ keytool -storepass secret -keystore "$ssl_dir/ca.p12" -storetype PKCS12 -gencert -alias ca \
+ -ext "ku:c=nonRepudiation,digitalSignature,keyEncipherment" -ext eku:c=clientAuth -rfc > client.pem
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ keytool -keystore solr-client.keystore.p12 -storetype PKCS12 -keypass client-key -storepass client-key -importcert -alias client -file client.pem
+
+ # Create a truststore with just the Root CA
+ keytool -keystore solr-client.truststore.p12 -storetype PKCS12 -keypass client-trust -storepass client-trust -importcert -alias root -file "$ssl_dir/root.pem" -noprompt
+ keytool -keystore solr-client.truststore.p12 -storetype PKCS12 -keypass client-trust -storepass client-trust -importcert -alias ca -file "$ssl_dir/ca.pem" -noprompt
+ )
+
+ # Set ENV_VARs so that Solr uses this keystore
+ export SOLR_SSL_ENABLED=true
+ export SOLR_SSL_KEY_STORE="$server_ssl_dir/solr-server.keystore.p12"
+ export SOLR_SSL_KEY_STORE_PASSWORD=server-key
+ export SOLR_SSL_KEY_STORE_TYPE=PKCS12
+ export SOLR_SSL_TRUST_STORE="$server_ssl_dir/solr-server.truststore.p12"
+ export SOLR_SSL_TRUST_STORE_PASSWORD=server-trust
+ export SOLR_SSL_TRUST_STORE_TYPE=PKCS12
+ export SOLR_SSL_CLIENT_KEY_STORE="$client_ssl_dir/solr-client.keystore.p12"
+ export SOLR_SSL_CLIENT_KEY_STORE_PASSWORD=client-key
+ export SOLR_SSL_CLIENT_KEY_STORE_TYPE=PKCS12
+ export SOLR_SSL_CLIENT_TRUST_STORE="$client_ssl_dir/solr-client.truststore.p12"
+ export SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD=client-trust
+ export SOLR_SSL_CLIENT_TRUST_STORE_TYPE=PKCS12
+ export SOLR_SSL_NEED_CLIENT_AUTH=false
+ export SOLR_SSL_WANT_CLIENT_AUTH=true
+ export SOLR_SSL_CHECK_PEER_NAME=true
+ export SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION=true
+ export SOLR_HOST=localhost
+
+ solr start -c
+ solr start -c -z localhost:9983 -p 8984
+
+ export SOLR_SSL_KEY_STORE=
+ export SOLR_SSL_KEY_STORE_PASSWORD=
+ export SOLR_SSL_TRUST_STORE=
+ export SOLR_SSL_TRUST_STORE_PASSWORD=
+
+ solr assert --started https://localhost:8983/solr --timeout 5000
+ solr assert --started https://localhost:8984/solr --timeout 5000
+
+ run solr create -c test -s 2
+ assert_output --partial "Created collection 'test'"
+
+ run solr api -get 'https://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS'
+ assert_output --partial '"urlScheme":"https"'
+
+ run solr api -get 'https://localhost:8984/solr/test/select?q=*:*&rows=0'
+ assert_output --partial '"numFound":0'
+
+ export SOLR_SSL_CLIENT_KEY_STORE=
+ export SOLR_SSL_CLIENT_KEY_STORE_PASSWORD=
+
+ run solr api -get 'https://localhost:8983/solr/test/select?q=*:*&rows=0'
+ assert_output --partial '"numFound":0'
+}
diff --git a/solr/packaging/test/test_start_solr.bats b/solr/packaging/test/test_start_solr.bats
index e0d7e5d981e..3b664759da4 100644
--- a/solr/packaging/test/test_start_solr.bats
+++ b/solr/packaging/test/test_start_solr.bats
@@ -36,3 +36,16 @@ teardown() {
run bash -c 'solr stop -all 2>&1'
refute_output --partial 'forcefully killing'
}
+
+@test "stop command for single port" {
+
+ solr start
+ solr start -p 7574
+ solr assert --started http://localhost:8983/solr --timeout 5000
+ solr assert --started http://localhost:7574/solr --timeout 5000
+
+ run solr stop -p 7574
+ solr assert --not-started http://localhost:7574/solr --timeout 5000
+ solr assert --started http://localhost:8983/solr --timeout 5000
+
+}
diff --git a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
index c254cc1e1e4..1db0142c2ea 100644
--- a/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
+++ b/solr/prometheus-exporter/src/test/org/apache/solr/prometheus/scraper/SolrStandaloneScraperTest.java
@@ -81,10 +81,6 @@ public static void cleanUp() throws Exception {
executor.shutdownNow();
executor = null;
}
- if (null != jetty) {
- jetty.stop();
- jetty = null;
- }
solrScraper = null;
solrClient = null;
}
diff --git a/solr/server/etc/security.policy b/solr/server/etc/security.policy
index 77ac99704c5..aec2e2ddcfe 100644
--- a/solr/server/etc/security.policy
+++ b/solr/server/etc/security.policy
@@ -185,11 +185,13 @@ grant {
permission java.io.FilePermission "${hadoop.security.credential.provider.path}", "read,write,delete,readlink";
permission java.io.FilePermission "${hadoop.security.credential.provider.path}${/}-", "read,write,delete,readlink";
- permission java.io.FilePermission "${solr.jetty.keystore}", "read,write,delete,readlink";
- permission java.io.FilePermission "${solr.jetty.keystore}${/}-", "read,write,delete,readlink";
+ permission java.io.FilePermission "${solr.jetty.keystore}", "read,readlink";
- permission java.io.FilePermission "${solr.jetty.truststore}", "read,write,delete,readlink";
- permission java.io.FilePermission "${solr.jetty.truststore}${/}-", "read,write,delete,readlink";
+ permission java.io.FilePermission "${solr.jetty.truststore}", "read,readlink";
+
+ permission java.io.FilePermission "${javax.net.ssl.keyStore}", "read,readlink";
+
+ permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read,readlink";
permission java.io.FilePermission "${solr.install.dir}", "read,write,delete,readlink";
permission java.io.FilePermission "${solr.install.dir}${/}-", "read,write,delete,readlink";
diff --git a/solr/server/resources/log4j2.xml b/solr/server/resources/log4j2.xml
index 186e86bd8cc..752c740514a 100644
--- a/solr/server/resources/log4j2.xml
+++ b/solr/server/resources/log4j2.xml
@@ -23,7 +23,7 @@