This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new e5b2b431828 SOLR-16390: Tweak v2 clusterprop APIs to be more REST-ful
(#2788)
e5b2b431828 is described below
commit e5b2b431828af4f3308ea8ddb102521e5702037d
Author: cugarte <[email protected]>
AuthorDate: Thu Nov 21 16:18:00 2024 -0500
SOLR-16390: Tweak v2 clusterprop APIs to be more REST-ful (#2788)
This commit changes several v2 "clusterprop" APIs to be
more in line with the REST-ful design we're targeting for Solr's
v2 APIs.
It also adds new v2 clusterprop APIs for listing-all and fetching-
single clusterprops.
---------
Co-authored-by: Jason Gerlowski <[email protected]>
---
solr/CHANGES.txt | 6 +
.../client/api/endpoint/ClusterPropertyApis.java | 82 ++++++++++
.../client/api/model/ClusterPropertyDetails.java | 31 ++++
.../api/model/GetClusterPropertyResponse.java | 27 ++++
.../api/model/ListClusterPropertiesResponse.java | 28 ++++
.../api/model/SetClusterPropertyRequestBody.java | 27 ++++
.../java/org/apache/solr/handler/ClusterAPI.java | 22 ---
.../solr/handler/admin/CollectionsHandler.java | 15 +-
.../solr/handler/admin/api/ClusterProperty.java | 156 ++++++++++++++++++
.../apache/solr/cloud/CollectionsAPISolrJTest.java | 30 ++--
.../apache/solr/handler/V2ApiIntegrationTest.java | 11 +-
.../solr/handler/V2ClusterAPIMappingTest.java | 19 ---
.../handler/admin/api/ClusterPropsAPITest.java | 178 +++++++++++++++++++++
.../pages/cluster-node-management.adoc | 162 +++++++++++++++----
.../solrj/src/resources/java-template/api.mustache | 8 +-
15 files changed, 696 insertions(+), 106 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 321a61dbc9d..8530533df59 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -45,6 +45,12 @@ Improvements
* SOLR-17390: EmbeddedSolrServer now considers the ResponseParser (David
Smiley)
+* SOLR-16390: v2 "cluster prop" APIs have been updated to be more REST-ful.
Cluster prop creation/update are now available
+ at `PUT /api/cluster/properties/somePropName`. Deletion is now available at
`DELETE /api/cluster/properties/somePropName`.
+ New APIs for listing-all and fetching-single cluster props are also now
available at `GET /api/cluster/properties` and
+ `GET /api/cluster/properties/somePropName`, respectively. (Carlos Ugarte via
Jason Gerlowski)
+
+
Optimizations
---------------------
* SOLR-14985: Solrj CloudSolrClient with Solr URLs had serious performance
regressions (since the
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterPropertyApis.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterPropertyApis.java
new file mode 100644
index 00000000000..5c75eec0c60
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterPropertyApis.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.api.endpoint;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import java.util.Map;
+import org.apache.solr.client.api.model.ListClusterPropertiesResponse;
+import org.apache.solr.client.api.model.SetClusterPropertyRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/** Definitions for v2 JAX-RS cluster properties APIs. */
+@Path("/cluster/properties")
+public interface ClusterPropertyApis {
+ @GET
+ @Operation(
+ summary = "List all cluster properties in this Solr cluster.",
+ tags = {"cluster-properties"})
+ ListClusterPropertiesResponse listClusterProperties();
+
+ @GET
+ @Path("/{propertyName}")
+ @Operation(
+ summary = "Get a cluster property in this Solr cluster.",
+ tags = {"cluster-properties"})
+ SolrJerseyResponse getClusterProperty(
+ @Parameter(description = "The name of the property being retrieved.",
required = true)
+ @PathParam("propertyName")
+ String propertyName);
+
+ @PUT
+ @Path("/{propertyName}")
+ @Operation(
+ summary = "Set a single new or existing cluster property in this Solr
cluster.",
+ tags = {"cluster-properties"})
+ SolrJerseyResponse createOrUpdateClusterProperty(
+ @Parameter(description = "The name of the property being set.", required
= true)
+ @PathParam("propertyName")
+ String propertyName,
+ @RequestBody(description = "Value to set for the property", required =
true)
+ SetClusterPropertyRequestBody requestBody)
+ throws Exception;
+
+ @PUT
+ @Operation(
+ summary = "Set nested cluster properties in this Solr cluster.",
+ tags = {"cluster-properties"})
+ SolrJerseyResponse createOrUpdateNestedClusterProperty(
+ @RequestBody(description = "Property/ies to be set", required = true)
+ Map<String, Object> propertyValuesByName)
+ throws Exception;
+
+ @DELETE
+ @Path("/{propertyName}")
+ @Operation(
+ summary = "Delete a cluster property in this Solr cluster.",
+ tags = {"cluster-properties"})
+ SolrJerseyResponse deleteClusterProperty(
+ @Parameter(description = "The name of the property being deleted.",
required = true)
+ @PathParam("propertyName")
+ String propertyName);
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/ClusterPropertyDetails.java
b/solr/api/src/java/org/apache/solr/client/api/model/ClusterPropertyDetails.java
new file mode 100644
index 00000000000..9619e96ac1e
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/ClusterPropertyDetails.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 com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class ClusterPropertyDetails {
+ @JsonProperty("name")
+ @Schema(description = "The name of the cluster property.")
+ public String name;
+
+ @JsonProperty("value")
+ @Schema(description = "The value of the cluster property.")
+ public Object value;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/GetClusterPropertyResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterPropertyResponse.java
new file mode 100644
index 00000000000..3ebdd74ef60
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterPropertyResponse.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 GetClusterPropertyResponse extends SolrJerseyResponse {
+ @JsonProperty("clusterProperty")
+ @Schema(description = "The requested cluster property.")
+ public ClusterPropertyDetails clusterProperty;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/ListClusterPropertiesResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterPropertiesResponse.java
new file mode 100644
index 00000000000..46504fb23f1
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterPropertiesResponse.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 io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+
+public class ListClusterPropertiesResponse extends SolrJerseyResponse {
+ @JsonProperty("clusterProperties")
+ @Schema(description = "The list of cluster properties.")
+ public List<String> clusterProperties;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/SetClusterPropertyRequestBody.java
b/solr/api/src/java/org/apache/solr/client/api/model/SetClusterPropertyRequestBody.java
new file mode 100644
index 00000000000..057f4bcb1d5
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/SetClusterPropertyRequestBody.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 SetClusterPropertyRequestBody {
+ @Schema(description = "The value to assign to the property.")
+ @JsonProperty("value")
+ public String value;
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index e161d55e5b3..4807e19aca2 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -23,7 +23,6 @@ import static
org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.common.params.CollectionParams.ACTION;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
-import static
org.apache.solr.common.params.CollectionParams.CollectionAction.CLUSTERPROP;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE;
@@ -43,7 +42,6 @@ import org.apache.solr.api.Command;
import org.apache.solr.api.EndPoint;
import org.apache.solr.api.PayloadObj;
import org.apache.solr.client.solrj.cloud.DistribStateManager;
-import org.apache.solr.client.solrj.request.beans.ClusterPropPayload;
import org.apache.solr.client.solrj.request.beans.RateLimiterPayload;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.annotation.JsonProperty;
@@ -275,26 +273,6 @@ public class ClusterAPI {
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m),
obj.getResponse());
}
- @Command(name = "set-obj-property")
- public void setObjProperty(PayloadObj<ClusterPropPayload> obj) {
- // Not using the object directly here because the API differentiate
between {name:null} and {}
- Map<String, Object> m = obj.getDataMap();
- ClusterProperties clusterProperties =
- new
ClusterProperties(getCoreContainer().getZkController().getZkClient());
- try {
- clusterProperties.setClusterProperties(m);
- } catch (Exception e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error
in API", e);
- }
- }
-
- @Command(name = "set-property")
- public void setProperty(PayloadObj<Map<String, String>> obj) throws
Exception {
- Map<String, Object> m = obj.getDataMap();
- m.put("action", CLUSTERPROP.toString());
- collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m),
obj.getResponse());
- }
-
@Command(name = "set-ratelimiter")
public void setRateLimiters(PayloadObj<RateLimiterPayload> payLoad) {
RateLimiterPayload rateLimiterConfig = payLoad.get();
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 3bcc0ce9c3f..b7d2f2c9280 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
@@ -128,6 +128,7 @@ import
org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
import org.apache.solr.client.api.model.InstallShardDataRequestBody;
import org.apache.solr.client.api.model.ListCollectionSnapshotsResponse;
import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
+import org.apache.solr.client.api.model.SetClusterPropertyRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
@@ -144,7 +145,6 @@ import
org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetComma
import org.apache.solr.cloud.api.collections.ReindexCollectionCmd;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Replica.State;
@@ -174,6 +174,7 @@ import org.apache.solr.handler.admin.api.AdminAPIBase;
import org.apache.solr.handler.admin.api.AliasProperty;
import org.apache.solr.handler.admin.api.BalanceReplicas;
import org.apache.solr.handler.admin.api.BalanceShardUnique;
+import org.apache.solr.handler.admin.api.ClusterProperty;
import org.apache.solr.handler.admin.api.CollectionProperty;
import org.apache.solr.handler.admin.api.CollectionStatusAPI;
import org.apache.solr.handler.admin.api.CreateAlias;
@@ -775,11 +776,12 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
CLUSTERPROP_OP(
CLUSTERPROP,
(req, rsp, h) -> {
+ ClusterProperty clusterProperty = new
ClusterProperty(req.getCoreContainer(), req, rsp);
+ SetClusterPropertyRequestBody setClusterPropertyRequestBody =
+ new SetClusterPropertyRequestBody();
String name = req.getParams().required().get(NAME);
- String val = req.getParams().get(VALUE_LONG);
- ClusterProperties cp =
- new
ClusterProperties(h.coreContainer.getZkController().getZkClient());
- cp.setClusterProperty(name, val);
+ setClusterPropertyRequestBody.value =
req.getParams().get(VALUE_LONG);
+ clusterProperty.createOrUpdateClusterProperty(name,
setClusterPropertyRequestBody);
return null;
}),
COLLECTIONPROP_OP(
@@ -1389,7 +1391,8 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
AliasProperty.class,
ListCollectionSnapshots.class,
CreateCollectionSnapshot.class,
- DeleteCollectionSnapshot.class);
+ DeleteCollectionSnapshot.class,
+ ClusterProperty.class);
}
@Override
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterProperty.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterProperty.java
new file mode 100644
index 00000000000..efce79f7e84
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterProperty.java
@@ -0,0 +1,156 @@
+/*
+ * 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.handler.admin.api;
+
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
+
+import jakarta.inject.Inject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import org.apache.solr.client.api.endpoint.ClusterPropertyApis;
+import org.apache.solr.client.api.model.ClusterPropertyDetails;
+import org.apache.solr.client.api.model.GetClusterPropertyResponse;
+import org.apache.solr.client.api.model.ListClusterPropertiesResponse;
+import org.apache.solr.client.api.model.SetClusterPropertyRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.ClusterProperties;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+public class ClusterProperty extends AdminAPIBase implements
ClusterPropertyApis {
+ protected final ClusterProperties clusterProperties;
+
+ @Inject
+ public ClusterProperty(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ this.clusterProperties =
+ new ClusterProperties(
+
fetchAndValidateZooKeeperAwareCoreContainer().getZkController().getZkClient());
+ }
+
+ /**
+ * V2 API for listing cluster properties.
+ *
+ * <p>This API (GET /api/cluster/properties) has no v1 equivalent.
+ */
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public ListClusterPropertiesResponse listClusterProperties() {
+ ListClusterPropertiesResponse response =
+ instantiateJerseyResponse(ListClusterPropertiesResponse.class);
+
+ try {
+ response.clusterProperties =
+ new ArrayList<>(clusterProperties.getClusterProperties().keySet());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return response;
+ }
+
+ /**
+ * V2 API for returning the value of a cluster property.
+ *
+ * <p>This API (GET /api/cluster/properties/{propertyName}) has no v1
equivalent.
+ */
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public SolrJerseyResponse getClusterProperty(String propertyName) {
+ GetClusterPropertyResponse response =
+ instantiateJerseyResponse(GetClusterPropertyResponse.class);
+
+ try {
+ Object value =
clusterProperties.getClusterProperties().get(propertyName);
+ if (value != null) {
+ response.clusterProperty = new ClusterPropertyDetails();
+ response.clusterProperty.name = propertyName;
+ response.clusterProperty.value = value;
+ } else {
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND, "No such cluster property [" +
propertyName + "]");
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return response;
+ }
+
+ /**
+ * V2 API for setting the value of a single new or existing cluster property.
+ *
+ * <p>This API (PUT /api/cluster/properties/{propertyName} with an object
listing the value) is
+ * equivalent to the v1 GET
+ *
/solr/admin/collections?action=CLUSTERPROP&name={propertyName}&val={propertyValue}
API.
+ */
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public SolrJerseyResponse createOrUpdateClusterProperty(
+ String propertyName, SetClusterPropertyRequestBody requestBody) throws
IOException {
+ SolrJerseyResponse response =
instantiateJerseyResponse(SolrJerseyResponse.class);
+ clusterProperties.setClusterProperty(propertyName, requestBody.value);
+ return response;
+ }
+
+ /**
+ * V2 API for setting the value of nested cluster properties.
+ *
+ * <p>This API (PUT /api/cluster/properties with an object listing those
properties) has no v1
+ * equivalent.
+ */
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public SolrJerseyResponse createOrUpdateNestedClusterProperty(
+ Map<String, Object> propertyValuesByName) {
+ SolrJerseyResponse response =
instantiateJerseyResponse(SolrJerseyResponse.class);
+ try {
+ clusterProperties.setClusterProperties(propertyValuesByName);
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in
API", e);
+ }
+ return response;
+ }
+
+ /**
+ * V2 API for deleting a cluster property.
+ *
+ * <p>This API (DELETE /api/cluster/properties/{propertyName}) is equivalent
to the v1 GET
+ * /solr/admin/collections?action=CLUSTERPROP&name={propertyName} API.
+ */
+ @PermissionName(COLL_EDIT_PERM)
+ @Override
+ public SolrJerseyResponse deleteClusterProperty(String propertyName) {
+ final var response = instantiateJerseyResponse(SolrJerseyResponse.class);
+
+ try {
+ clusterProperties.setClusterProperty(propertyName, null);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return response;
+ }
+}
diff --git
a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
index f49f51a2f04..dc1595a1c8e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
@@ -134,10 +134,10 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
String COLL_NAME = "CollWithDefaultClusterProperties";
try {
V2Response rsp =
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
+ new V2Request.Builder("/cluster/properties")
+ .withMethod(SolrRequest.METHOD.PUT)
.withPayload(
- "{set-obj-property:{defaults : {collection:{numShards : 2 ,
nrtReplicas : 2}}}}")
+ "{\"defaults\": {\"collection\": {\"numShards\": 2,
\"nrtReplicas\": 2}}}")
.build()
.process(cluster.getSolrClient());
@@ -174,15 +174,13 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
// unset only a single value
rsp =
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
+ new V2Request.Builder("/cluster/properties")
+ .withMethod(SolrRequest.METHOD.PUT)
.withPayload(
"{\n"
- + " \"set-obj-property\": {\n"
- + " \"defaults\" : {\n"
- + " \"collection\": {\n"
- + " \"nrtReplicas\": null\n"
- + " }\n"
+ + " \"defaults\" : {\n"
+ + " \"collection\": {\n"
+ + " \"nrtReplicas\": null\n"
+ " }\n"
+ " }\n"
+ "}")
@@ -201,9 +199,9 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
assertNull(clusterProperty);
rsp =
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
- .withPayload("{set-obj-property:{defaults: {collection:null}}}")
+ new V2Request.Builder("/cluster/properties")
+ .withMethod(SolrRequest.METHOD.PUT)
+ .withPayload("{\"defaults\": {\"collection\": null}}")
.build()
.process(cluster.getSolrClient());
// assert that it is really gone in both old and new paths
@@ -218,9 +216,9 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
assertNull(clusterProperty);
} finally {
V2Response rsp =
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
- .withPayload("{set-obj-property:{defaults: null}}")
+ new V2Request.Builder("/cluster/properties")
+ .withMethod(SolrRequest.METHOD.PUT)
+ .withPayload("{\"defaults\": null}")
.build()
.process(cluster.getSolrClient());
}
diff --git
a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
index 284f268acc2..0476aed8fb3 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
@@ -155,18 +155,17 @@ public class V2ApiIntegrationTest extends
SolrCloudTestCase {
cluster
.getSolrClient()
.request(
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
- .withPayload("{set-property: {name: maxCoresPerNode,
val:42}}")
+ new V2Request.Builder("/cluster/properties/maxCoresPerNode")
+ .withMethod(SolrRequest.METHOD.PUT)
+ .withPayload("{\"value\": \"42\"}")
.build());
assertTrue(resp.toString().contains("status=0"));
resp =
cluster
.getSolrClient()
.request(
- new V2Request.Builder("/cluster")
- .withMethod(SolrRequest.METHOD.POST)
- .withPayload("{set-property: {name: maxCoresPerNode,
val:null}}")
+ new V2Request.Builder("/cluster/properties/maxCoresPerNode")
+ .withMethod(SolrRequest.METHOD.DELETE)
.build());
assertTrue(resp.toString().contains("status=0"));
}
diff --git
a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
index 94f091fba2a..6410dc60a47 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -19,7 +19,6 @@ package org.apache.solr.handler;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.common.params.CommonParams.ACTION;
-import static org.apache.solr.common.params.CommonParams.NAME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -128,29 +127,11 @@ public class V2ClusterAPIMappingTest extends
SolrTestCaseJ4 {
assertEquals("some_role", v1Params.get("role"));
}
- @Test
- public void testSetPropertyAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params(
- "/cluster",
- "POST",
- "{'set-property': {" + "'name': 'some_prop_name', " +
"'val':'some_value'}}");
-
- assertEquals(CollectionParams.CollectionAction.CLUSTERPROP.toString(),
v1Params.get(ACTION));
- assertEquals("some_prop_name", v1Params.get(NAME));
- assertEquals("some_value", v1Params.get("val"));
- }
-
private SolrParams captureConvertedV1Params(String path, String method,
String v2RequestBody)
throws Exception {
return doCaptureParams(path, method, v2RequestBody,
mockCollectionsHandler);
}
- private SolrParams captureConvertedConfigsetV1Params(
- String path, String method, String v2RequestBody) throws Exception {
- return doCaptureParams(path, method, v2RequestBody, mockConfigSetHandler);
- }
-
private SolrParams doCaptureParams(
String path, String method, String v2RequestBody, RequestHandlerBase
mockHandler)
throws Exception {
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterPropsAPITest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterPropsAPITest.java
new file mode 100644
index 00000000000..9ae628e71eb
--- /dev/null
+++
b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterPropsAPITest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.handler.admin.api;
+
+import static org.apache.solr.common.util.Utils.getObjectByPath;
+
+import java.net.URL;
+import java.util.List;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.StringEntity;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ClusterPropsAPITest extends SolrCloudTestCase {
+
+ private URL baseUrl;
+ private String baseUrlV2ClusterProps;
+
+ private static final String testClusterProperty = "ext.test";
+ private static final String testClusterPropertyValue = "test value";
+ private static final String testClusterPropertyNestedKeyAndValue =
+ " \"defaults\": {"
+ + " \"collection\": {"
+ + " \"numShards\": 4,"
+ + " \"nrtReplicas\": 2,"
+ + " \"tlogReplicas\": 2,"
+ + " \"pullReplicas\": 2"
+ + " }"
+ + " }";
+ private static final String testClusterPropertyBulkAndNestedValues =
+ "{"
+ + testClusterPropertyNestedKeyAndValue
+ + ","
+ + " \""
+ + testClusterProperty
+ + "\": "
+ + "\""
+ + testClusterPropertyValue
+ + "\""
+ + " }";
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1).addConfig("conf",
configset("cloud-minimal")).configure();
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
+ baseUrlV2ClusterProps =
+ cluster.getJettySolrRunner(0).getBaseURLV2().toString() +
"/cluster/properties";
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testClusterPropertyOpsAllGood() throws Exception {
+ try (HttpSolrClient client = new
HttpSolrClient.Builder(baseUrl.toString()).build()) {
+ // List Properties, confirm there aren't any
+ Object o =
+ Utils.executeGET(client.getHttpClient(), baseUrlV2ClusterProps,
Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals(0, ((List<?>) getObjectByPath(o, true,
"clusterProperties")).size());
+
+ // Create a single cluster property
+ String path = baseUrlV2ClusterProps + "/" + testClusterProperty;
+ HttpPut httpPut = new HttpPut(path);
+ httpPut.setHeader("Content-Type", "application/json");
+ httpPut.setEntity(new StringEntity("{\"value\":\"" +
testClusterPropertyValue + "\"}"));
+ o = Utils.executeHttpMethod(client.getHttpClient(), path,
Utils.JSONCONSUMER, httpPut);
+ assertNotNull(o);
+
+ // List Properties, this time there should be 1
+ o = Utils.executeGET(client.getHttpClient(), baseUrlV2ClusterProps,
Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals(1, ((List<?>) getObjectByPath(o, true,
"clusterProperties")).size());
+ assertEquals(
+ testClusterProperty,
+ (String) ((List<?>) getObjectByPath(o, true,
"clusterProperties")).get(0));
+
+ // Fetch Cluster Property
+ // Same path as used in the Create step above
+ o = Utils.executeGET(client.getHttpClient(), path, Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals(testClusterProperty, (String) getObjectByPath(o, true,
"clusterProperty/name"));
+ assertEquals(
+ testClusterPropertyValue, (String) getObjectByPath(o, true,
"clusterProperty/value"));
+
+ // Delete Cluster Property
+ // Same path as used in the Create step above
+ HttpDelete httpDelete = new HttpDelete(path);
+ o = Utils.executeHttpMethod(client.getHttpClient(), path,
Utils.JSONCONSUMER, httpDelete);
+ assertNotNull(o);
+
+ // List Properties, should be back to 0
+ o = Utils.executeGET(client.getHttpClient(), baseUrlV2ClusterProps,
Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals(0, ((List<?>) getObjectByPath(o, true,
"clusterProperties")).size());
+ }
+ }
+
+ @Test
+ public void testClusterPropertyNestedBulkSet() throws Exception {
+ try (HttpSolrClient client = new
HttpSolrClient.Builder(baseUrl.toString()).build()) {
+ // Create a single cluster property using the Bulk/Nested set
ClusterProp API
+ HttpPut httpPut = new HttpPut(baseUrlV2ClusterProps);
+ httpPut.setHeader("Content-Type", "application/json");
+ httpPut.setEntity(new
StringEntity(testClusterPropertyBulkAndNestedValues));
+ Object o =
+ Utils.executeHttpMethod(
+ client.getHttpClient(), baseUrlV2ClusterProps,
Utils.JSONCONSUMER, httpPut);
+ assertNotNull(o);
+
+ // Fetch Cluster Property checking the not-nested property set above
+ String path = baseUrlV2ClusterProps + "/" + testClusterProperty;
+ o = Utils.executeGET(client.getHttpClient(), path, Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals(testClusterProperty, (String) getObjectByPath(o, true,
"clusterProperty/name"));
+ assertEquals(
+ testClusterPropertyValue, (String) getObjectByPath(o, true,
"clusterProperty/value"));
+
+ // Fetch Cluster Property checking the nested property set above
+ path = baseUrlV2ClusterProps + "/" + "defaults";
+ o = Utils.executeGET(client.getHttpClient(), path, Utils.JSONCONSUMER);
+ assertNotNull(o);
+ assertEquals("defaults", (String) getObjectByPath(o, true,
"clusterProperty/name"));
+ assertEquals(4L, getObjectByPath(o, true,
"clusterProperty/value/collection/numShards"));
+
+ // Clean up to leave the state unchanged
+ HttpDelete httpDelete = new HttpDelete(path);
+ Utils.executeHttpMethod(client.getHttpClient(), path,
Utils.JSONCONSUMER, httpDelete);
+ path = baseUrlV2ClusterProps + "/" + testClusterProperty;
+ httpDelete = new HttpDelete(path);
+ Utils.executeHttpMethod(client.getHttpClient(), path,
Utils.JSONCONSUMER, httpDelete);
+ }
+ }
+
+ @Test
+ public void testClusterPropertyFetchNonExistentProperty() throws Exception {
+ try (HttpSolrClient client = new
HttpSolrClient.Builder(baseUrl.toString()).build()) {
+ // Fetch Cluster Property that doesn't exist
+ String path = baseUrlV2ClusterProps + "/ext.clusterPropThatDoesNotExist";
+ HttpGet fetchClusterPropertyGet = new HttpGet(path);
+ HttpResponse httpResponse =
client.getHttpClient().execute(fetchClusterPropertyGet);
+ assertEquals(404, httpResponse.getStatusLine().getStatusCode());
+ }
+ }
+}
diff --git
a/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
b/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
index cc5085b6a3c..e16bee4477d 100644
---
a/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
+++
b/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
@@ -248,37 +248,41 @@
http://localhost:8983/solr/admin/collections?action=CLUSTERPROP&name=urlScheme&v
V2 API::
+
====
+To create or update a cluster property:
[source,bash]
----
-curl -X POST http://localhost:8983/api/cluster -H 'Content-Type:
application/json' -d '
+curl -X PUT http://localhost:8983/api/cluster/properties/urlScheme -H
'Content-Type: application/json' -d '
{
- "set-property": {
- "name": "urlScheme",
- "val": "https"
- }
+ "value": "https"
}
'
----
+
+To delete an existing cluster property:
+[source,bash]
+----
+curl -X DELETE http://localhost:8983/api/cluster/properties/urlScheme
+----
====
======
=== CLUSTERPROP Parameters
-`name`::
+`name` (v1)::
+
[%autowidth,frame=none]
|===
|Optional |Default: none
|===
+
-The name of the property.
+The name of the property. Appears in the path of v2 requests.
Supported properties names are `location`, `maxCoresPerNode`, `urlScheme`, and
`defaultShardPreferences`.
If the xref:distributed-tracing.adoc[Jaeger tracing module] has been enabled,
the property `samplePercentage` is also available.
+
Other properties can be set (for example, if you need them for custom plugins)
but they must begin with the prefix `ext.`.
Unknown properties that don't begin with `ext.` will be rejected.
-`val`::
+`val` (v1), `value` (v2)::
+
[%autowidth,frame=none]
|===
@@ -332,19 +336,17 @@ V2 API::
====
[source,bash]
----
-curl -X POST -H 'Content-type:application/json' --data-binary '
+curl -X PUT -H 'Content-type:application/json' --data-binary '
{
- "set-obj-property": {
- "defaults" : {
- "collection": {
- "numShards": 2,
- "nrtReplicas": 1,
- "tlogReplicas": 1,
- "pullReplicas": 1
- }
+ "defaults" : {
+ "collection": {
+ "numShards": 2,
+ "nrtReplicas": 1,
+ "tlogReplicas": 1,
+ "pullReplicas": 1
}
}
-}' http://localhost:8983/api/cluster
+}' http://localhost:8983/api/cluster/properties
----
====
======
@@ -353,26 +355,30 @@ curl -X POST -H 'Content-type:application/json'
--data-binary '
[source,bash]
----
-curl -X POST -H 'Content-type:application/json' --data-binary '
+curl -X PUT -H 'Content-type:application/json' --data-binary '
{
- "set-obj-property": {
- "defaults" : {
- "collection": {
- "nrtReplicas": null
- }
+ "defaults" : {
+ "collection": {
+ "nrtReplicas": null
}
}
-}' http://localhost:8983/api/cluster
+}' http://localhost:8983/api/cluster/properties
----
*Unset all values in `defaults`*
[source,bash]
----
-curl -X POST -H 'Content-type:application/json' --data-binary '
-{ "set-obj-property" : {
- "defaults" : null
-}' http://localhost:8983/api/cluster
+curl -X PUT -H 'Content-type:application/json' --data-binary '
+{
+ "defaults" : null
+}' http://localhost:8983/api/cluster/properties
+----
+or
+[source,bash]
----
+curl -X DELETE http://localhost:8983/api/cluster/properties/defaults
+----
+
=== Default Shard Preferences
@@ -382,17 +388,103 @@ Then, set the value of `defaultShardPreferences` to
`node.sysprop:sysprop.YOUR_P
[source,bash]
----
-curl -X POST -H 'Content-type:application/json' --data-binary '
+curl -X PUT -H 'Content-type:application/json' --data-binary '
{
- "set-property" : {
- "name" : "defaultShardPreferences",
- "val" : "node.sysprop:sysprop.rack"
- }
-}' http://localhost:8983/api/cluster
+ "value" : "node.sysprop:sysprop.rack"
+}' http://localhost:8983/api/cluster/properties/defaultShardPreferences
----
At this point, if you run a query on a node having e.g., `rack=rack1`, Solr
will try to hit only replicas from `rack1`.
+
+=== List Cluster Properties
+
+[tabs#setobjproperty-request]
+======
+V1 API::
++
+====
+There is no V1 equivalent of this action.
+
+====
+V2 API::
++
+====
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/cluster/properties
+----
+====
+======
+
+*Input*
+
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/cluster/properties
+----
+
+*Output*
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 2
+ },
+ "clusterProperties": [
+ "urlScheme",
+ "defaultShardPreferences"
+ ]
+}
+----
+
+
+=== Fetch Cluster Property
+
+[tabs#setobjproperty-request]
+======
+V1 API::
++
+====
+There is no V1 equivalent of this action.
+
+====
+V2 API::
++
+====
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/cluster/properties/urlScheme
+----
+====
+======
+
+*Input*
+
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/cluster/properties/urlScheme
+----
+
+*Output*
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 2
+ },
+ "clusterProperty": {
+ "name": "urlScheme",
+ "value": "https"
+ }
+}
+----
+
+
[[balancereplicas]]
== Balance Replicas
diff --git a/solr/solrj/src/resources/java-template/api.mustache
b/solr/solrj/src/resources/java-template/api.mustache
index 757335a2b83..c4ef7f9c59b 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -133,14 +133,18 @@ public class {{classname}} {
{{/requiredParams}}
{{#bodyParam}}
{{^vendorExtensions.x-genericEntity}}
+ {{#isMap}}
+ this.requestBody = new HashMap<>();
+ {{/isMap}}
{{#isArray}}
this.requestBody = new ArrayList<>();
- addHeader("Content-type", "application/json");
{{/isArray}}
+ {{^isMap}}
{{^isArray}}
this.requestBody = new {{{dataType}}}();
- addHeader("Content-type", "application/json");
{{/isArray}}
+ {{/isMap}}
+ addHeader("Content-type", "application/json");
{{/vendorExtensions.x-genericEntity}}
{{/bodyParam}}
}