You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2023/03/16 18:49:28 UTC
[solr] branch main updated: SOLR-16393 Migrate aliasprop CRUD APIs to JAX-RS (#1459)
This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new ef6e362ad80 SOLR-16393 Migrate aliasprop CRUD APIs to JAX-RS (#1459)
ef6e362ad80 is described below
commit ef6e362ad80eb8158de8c34bab45b1bf10cbabd3
Author: Alex <st...@users.noreply.github.com>
AuthorDate: Thu Mar 16 11:49:21 2023 -0700
SOLR-16393 Migrate aliasprop CRUD APIs to JAX-RS (#1459)
This commit makes various cosmetic improvements to Solr's v2 alias
property CRUD APIs, to bring them more into line with the REST-ful v2
design. In the process it also converts the APIs to the JAX-RS
framework. Modified v2 APIs include:
- PUT /api/aliases/aliasName/properties (bulk update)
- PUT /api/aliases/aliasName/properties/propName (single update)
- GET /api/aliases/aliasName/properties (new API: list props)
- GET /api/aliases/aliasName/properties/propName (get single prop)
- DELETE /api/aliases/aliasName/properties/propName (delete prop)
---
solr/CHANGES.txt | 6 +
.../org/apache/solr/handler/CollectionsAPI.java | 18 --
.../solr/handler/admin/CollectionsHandler.java | 37 +--
.../handler/admin/api/AddReplicaPropertyAPI.java | 3 +
.../solr/handler/admin/api/AliasPropertyAPI.java | 264 +++++++++++++++++++++
.../handler/admin/api/CollectionPropertyAPI.java | 4 +
.../apache/solr/cloud/AliasIntegrationTest.java | 105 ++++++--
.../handler/admin/V2CollectionsAPIMappingTest.java | 19 --
.../deployment-guide/pages/alias-management.adoc | 101 +++++++-
.../solrj/request/CollectionAdminRequest.java | 6 +-
.../request/beans/SetAliasPropertyPayload.java | 30 ---
11 files changed, 471 insertions(+), 122 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 8f54153a212..8acdfc9eac7 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -45,6 +45,12 @@ Improvements
* SOLR-16393: The path of the v2 "delete alias" API has been tweaked slightly to be more intuitive, and is now available at
`DELETE /api/aliases/aliasName`. (Jason Gerlowski)
+* SOLR-16393: Solr's v2 "aliasprop" CRUD APIs have been tweaked slightly to be more intuitive. Alias property modification
+ is now available at `PUT /api/aliases/aliasName/properties` (for bulk modification) and `PUT /api/aliases/aliasName/properties/propName`
+ (for single property updates). Additionally new APIs have been added for listing properties (`GET /api/aliases/aliasName/properties`),
+ fetching single property values (`GET /api/aliases/aliasName/properties/propName`), and property deletion
+ (`DELETE /api/aliases/aliasName/properties/propName`). (Alex Deparvu via Jason Gerlowski)
+
Bug Fixes
---------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/CollectionsAPI.java b/solr/core/src/java/org/apache/solr/handler/CollectionsAPI.java
index 1e3aeac45ee..79af6a65d31 100644
--- a/solr/core/src/java/org/apache/solr/handler/CollectionsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/CollectionsAPI.java
@@ -40,7 +40,6 @@ import org.apache.solr.client.solrj.request.beans.BackupCollectionPayload;
import org.apache.solr.client.solrj.request.beans.CreateAliasPayload;
import org.apache.solr.client.solrj.request.beans.CreatePayload;
import org.apache.solr.client.solrj.request.beans.RestoreCollectionPayload;
-import org.apache.solr.client.solrj.request.beans.SetAliasPropertyPayload;
import org.apache.solr.client.solrj.request.beans.V2ApiConstants;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
@@ -53,7 +52,6 @@ public class CollectionsAPI {
public static final String V2_BACKUP_CMD = "backup-collection";
public static final String V2_RESTORE_CMD = "restore-collection";
public static final String V2_CREATE_ALIAS_CMD = "create-alias";
- public static final String V2_SET_ALIAS_PROP_CMD = "set-alias-property";
private final CollectionsHandler collectionsHandler;
@@ -124,22 +122,6 @@ public class CollectionsAPI {
wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
- @Command(name = V2_SET_ALIAS_PROP_CMD)
- @SuppressWarnings("unchecked")
- public void setAliasProperty(PayloadObj<SetAliasPropertyPayload> obj) throws Exception {
- final SetAliasPropertyPayload v2Body = obj.get();
- final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
-
- v1Params.put(ACTION, CollectionAction.ALIASPROP.toLower());
- // Flatten "properties" map into individual prefixed params
- final Map<String, Object> propertiesMap =
- (Map<String, Object>) v1Params.remove(V2ApiConstants.PROPERTIES_KEY);
- flattenMapWithPrefix(propertiesMap, v1Params, PROPERTY_PREFIX);
-
- collectionsHandler.handleRequestBody(
- wrapParams(obj.getRequest(), v1Params), obj.getResponse());
- }
-
@Command(name = V2_CREATE_COLLECTION_CMD)
public void create(PayloadObj<CreatePayload> obj) throws Exception {
final CreatePayload v2Body = obj.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 8371e1041b1..ff5f292495d 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
@@ -206,6 +206,7 @@ import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.admin.api.AddReplicaAPI;
import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
import org.apache.solr.handler.admin.api.AdminAPIBase;
+import org.apache.solr.handler.admin.api.AliasPropertyAPI;
import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
import org.apache.solr.handler.admin.api.CollectionPropertyAPI;
import org.apache.solr.handler.admin.api.CollectionStatusAPI;
@@ -875,11 +876,17 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
ALIASPROP_OP(
ALIASPROP,
(req, rsp, h) -> {
- Map<String, Object> params = copy(req.getParams().required(), null, NAME);
-
- // Note: success/no-op in the event of no properties supplied is intentional. Keeps code
- // simple and one less case for api-callers to check for.
- return convertPrefixToMap(req.getParams(), params, "property");
+ String name = req.getParams().required().get(NAME);
+ Map<String, Object> properties = collectToMap(req.getParams(), "property");
+ AliasPropertyAPI.UpdateAliasPropertiesRequestBody requestBody =
+ new AliasPropertyAPI.UpdateAliasPropertiesRequestBody();
+ requestBody.properties = properties;
+ requestBody.async = req.getParams().get(ASYNC);
+ final AliasPropertyAPI aliasPropertyAPI = new AliasPropertyAPI(h.coreContainer, req, rsp);
+ final SolrJerseyResponse getAliasesResponse =
+ aliasPropertyAPI.updateAliasProperties(name, requestBody);
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
+ return null;
}),
/** List the aliases and associated properties. */
@@ -1838,28 +1845,21 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
});
/**
- * Places all prefixed properties in the sink map (or a new map) using the prefix as the key and
- * a map of all prefixed properties as the value. The sub-map keys have the prefix removed.
+ * Collects all prefixed properties in a new map. The resulting keys have the prefix removed.
*
* @param params The solr params from which to extract prefixed properties.
- * @param sink The map to add the properties too.
* @param prefix The prefix to identify properties to be extracted
- * @return The sink map, or a new map if the sink map was null
+ * @return a map with collected properties
*/
- private static Map<String, Object> convertPrefixToMap(
- SolrParams params, Map<String, Object> sink, String prefix) {
- Map<String, Object> result = new LinkedHashMap<>();
+ private static Map<String, Object> collectToMap(SolrParams params, String prefix) {
+ Map<String, Object> sink = new LinkedHashMap<>();
Iterator<String> iter = params.getParameterNamesIterator();
while (iter.hasNext()) {
String param = iter.next();
if (param.startsWith(prefix)) {
- result.put(param.substring(prefix.length() + 1), params.get(param));
+ sink.put(param.substring(prefix.length() + 1), params.get(param));
}
}
- if (sink == null) {
- sink = new LinkedHashMap<>();
- }
- sink.put(prefix, result);
return sink;
}
@@ -2091,7 +2091,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
ReplaceNodeAPI.class,
CollectionPropertyAPI.class,
DeleteNodeAPI.class,
- ListAliasesAPI.class);
+ ListAliasesAPI.class,
+ AliasPropertyAPI.class);
}
@Override
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
index 5bb8e5395df..30936638ce5 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
@@ -92,6 +92,9 @@ public class AddReplicaPropertyAPI extends AdminAPIBase {
required = true)
AddReplicaPropertyRequestBody requestBody)
throws Exception {
+ if (requestBody == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
+ }
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
recordCollectionForLogAndTracing(collName, solrQueryRequest);
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/AliasPropertyAPI.java
new file mode 100644
index 00000000000..02d2f7024c5
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
@@ -0,0 +1,264 @@
+/*
+ * 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.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;
+import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
+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.solrj.SolrResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.Aliases;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.cloud.ZkStateReader;
+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.jersey.SolrJerseyResponse;
+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 {
+
+ @Inject
+ public AliasPropertyAPI(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ @GET
+ @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 {
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ final GetAllAliasPropertiesResponse response =
+ instantiateJerseyResponse(GetAllAliasPropertiesResponse.class);
+ final Aliases aliases = readAliasesFromZk();
+ if (aliases != null) {
+ response.properties = aliases.getCollectionAliasProperties(aliasName);
+ } else {
+ throw new SolrException(SolrException.ErrorCode.NOT_FOUND, aliasName + " not found");
+ }
+
+ return response;
+ }
+
+ @GET
+ @Path("/{propName}")
+ @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)
+ throws Exception {
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ final GetAliasPropertyResponse response =
+ instantiateJerseyResponse(GetAliasPropertyResponse.class);
+ final Aliases aliases = readAliasesFromZk();
+ if (aliases != null) {
+ String value = aliases.getCollectionAliasProperties(aliasName).get(propName);
+ if (value != null) {
+ response.value = value;
+ } else {
+ throw new SolrException(SolrException.ErrorCode.NOT_FOUND, propName + " not found");
+ }
+ }
+
+ return response;
+ }
+
+ private Aliases readAliasesFromZk() throws Exception {
+ final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
+ final ZkStateReader zkStateReader = coreContainer.getZkController().getZkStateReader();
+ // Make sure we have the latest alias info, since a user has explicitly invoked an alias API
+ zkStateReader.getAliasesManager().update();
+ return zkStateReader.getAliases();
+ }
+
+ @PUT
+ @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 {
+
+ if (requestBody == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
+ }
+
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
+ modifyAliasProperties(aliasName, requestBody.properties, requestBody.async);
+ return response;
+ }
+
+ @PUT
+ @Path("/{propName}")
+ @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)
+ throws Exception {
+ if (requestBody == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
+ }
+
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
+ modifyAliasProperty(aliasName, propName, requestBody.value);
+ return response;
+ }
+
+ @DELETE
+ @Path("/{propName}")
+ @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)
+ throws Exception {
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
+ modifyAliasProperty(aliasName, propName, null);
+ return response;
+ }
+
+ private void modifyAliasProperty(String alias, String proertyName, Object value)
+ throws Exception {
+ Map<String, Object> props = new HashMap<>();
+ // value can be null
+ props.put(proertyName, value);
+ modifyAliasProperties(alias, props, null);
+ }
+
+ /**
+ * @param alias alias
+ */
+ private void modifyAliasProperties(String alias, Map<String, Object> properties, String async)
+ throws Exception {
+ // Note: success/no-op in the event of no properties supplied is intentional. Keeps code
+ // simple and one less case for api-callers to check for.
+ final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
+ final ZkNodeProps remoteMessage = createRemoteMessage(alias, properties, async);
+ final SolrResponse remoteResponse =
+ CollectionsHandler.submitCollectionApiCommand(
+ coreContainer,
+ coreContainer.getDistributedCollectionCommandRunner(),
+ remoteMessage,
+ CollectionParams.CollectionAction.ALIASPROP,
+ DEFAULT_COLLECTION_OP_TIMEOUT);
+ if (remoteResponse.getException() != null) {
+ throw remoteResponse.getException();
+ }
+
+ disableResponseCaching();
+ }
+
+ private static final String PROPERTIES = "property";
+
+ public ZkNodeProps createRemoteMessage(
+ String alias, Map<String, Object> properties, String async) {
+ final Map<String, Object> remoteMessage = new HashMap<>();
+ remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.ALIASPROP.toLower());
+ remoteMessage.put(NAME, alias);
+ remoteMessage.put(PROPERTIES, properties);
+ if (async != null) {
+ remoteMessage.put(ASYNC, async);
+ }
+ 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<String, Object> 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<String, String> 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/CollectionPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
index 72d3c1e8145..5302571dcad 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
@@ -28,6 +28,7 @@ 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.common.SolrException;
import org.apache.solr.common.cloud.CollectionProperties;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.jersey.JacksonReflectMapWriter;
@@ -60,6 +61,9 @@ public class CollectionPropertyAPI extends AdminAPIBase {
@PathParam("propName") String propName,
UpdateCollectionPropertyRequestBody requestBody)
throws Exception {
+ if (requestBody == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
+ }
final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
recordCollectionForLogAndTracing(collName, solrQueryRequest);
modifyCollectionProperty(collName, propName, requestBody.value);
diff --git a/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java b/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
index 3c57c892c8d..0e7be0e6544 100644
--- a/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
@@ -20,13 +20,15 @@ import static org.apache.solr.common.cloud.ZkStateReader.ALIASES;
import java.io.IOException;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@@ -238,28 +240,51 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
public void testModifyPropertiesV2() throws Exception {
final String aliasName = getSaferTestName();
ZkStateReader zkStateReader = createColectionsAndAlias(aliasName);
- final String baseUrl = cluster.getRandomJetty(random()).getBaseUrl().toString();
- // TODO fix Solr test infra so that this /____v2/ becomes /api/
- HttpPost post = new HttpPost(baseUrl + "/____v2/c");
- post.setEntity(
+ final String baseUrl =
+ cluster.getRandomJetty(random()).getBaseUrl().toString().replace("/solr", "");
+ String aliasApi = String.format(Locale.ENGLISH, "/api/aliases/%s/properties", aliasName);
+
+ HttpPut withoutBody = new HttpPut(baseUrl + aliasApi);
+ assertEquals(400, httpClient.execute(withoutBody).getStatusLine().getStatusCode());
+
+ HttpPut update = new HttpPut(baseUrl + aliasApi);
+ update.setEntity(
new StringEntity(
"{\n"
- + "\"set-alias-property\" : {\n"
- + " \"name\": \""
- + aliasName
- + "\",\n"
- + " \"properties\" : {\n"
- + " \"foo\": \"baz\",\n"
- + " \"bar\": \"bam\"\n"
+ + " \"properties\":\n"
+ + " {\n"
+ + " \"foo\": \"baz\",\n"
+ + " \"bar\": \"bam\"\n"
+ + " }\n"
+ + "}",
+ ContentType.APPLICATION_JSON));
+ assertSuccess(update);
+ checkFooAndBarMeta(aliasName, zkStateReader, "baz", "bam");
+
+ String aliasPropertyApi =
+ String.format(Locale.ENGLISH, "/api/aliases/%s/properties/%s", aliasName, "foo");
+ HttpPut updateByProperty = new HttpPut(baseUrl + aliasPropertyApi);
+ updateByProperty.setEntity(
+ new StringEntity("{ \"value\": \"zab\" }", ContentType.APPLICATION_JSON));
+ assertSuccess(updateByProperty);
+ checkFooAndBarMeta(aliasName, zkStateReader, "zab", "bam");
+
+ HttpDelete deleteByProperty = new HttpDelete(baseUrl + aliasPropertyApi);
+ assertSuccess(deleteByProperty);
+ checkFooAndBarMeta(aliasName, zkStateReader, null, "bam");
+
+ HttpPut deleteByEmptyValue = new HttpPut(baseUrl + aliasApi);
+ deleteByEmptyValue.setEntity(
+ new StringEntity(
+ "{\n"
+ + " \"properties\":\n"
+ + " {\n"
+ + " \"bar\": \"\"\n"
+ " }\n"
- +
- // TODO should we use "NOW=" param? Won't work with v2 and is kinda a hack any way
- // since intended for distrib
- " }\n"
+ "}",
ContentType.APPLICATION_JSON));
- assertSuccess(post);
- checkFooAndBarMeta(aliasName, zkStateReader);
+ assertSuccess(deleteByEmptyValue);
+ checkFooAndBarMeta(aliasName, zkStateReader, null, null);
}
@Test
@@ -278,7 +303,19 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
+ "&property.foo=baz"
+ "&property.bar=bam");
assertSuccess(get);
- checkFooAndBarMeta(aliasName, zkStateReader);
+ checkFooAndBarMeta(aliasName, zkStateReader, "baz", "bam");
+
+ HttpGet remove =
+ new HttpGet(
+ baseUrl
+ + "/admin/collections?action=ALIASPROP"
+ + "&wt=xml"
+ + "&name="
+ + aliasName
+ + "&property.foo="
+ + "&property.bar=bar");
+ assertSuccess(remove);
+ checkFooAndBarMeta(aliasName, zkStateReader, null, "bar");
}
@Test
@@ -291,20 +328,24 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
setAliasProperty.addProperty("foo", "baz");
setAliasProperty.addProperty("bar", "bam");
setAliasProperty.process(cluster.getSolrClient());
- checkFooAndBarMeta(aliasName, zkStateReader);
+ checkFooAndBarMeta(aliasName, zkStateReader, "baz", "bam");
// now verify we can delete
setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
setAliasProperty.addProperty("foo", "");
setAliasProperty.process(cluster.getSolrClient());
+ checkFooAndBarMeta(aliasName, zkStateReader, null, "bam");
+
setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
setAliasProperty.addProperty("bar", null);
setAliasProperty.process(cluster.getSolrClient());
- setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
+ checkFooAndBarMeta(aliasName, zkStateReader, null, null);
+ setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
// whitespace value
setAliasProperty.addProperty("foo", " ");
setAliasProperty.process(cluster.getSolrClient());
+ checkFooAndBarMeta(aliasName, zkStateReader, " ", null);
}
@Test
@@ -413,14 +454,26 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
return -1;
}
- private void checkFooAndBarMeta(String aliasName, ZkStateReader zkStateReader) throws Exception {
+ private void checkFooAndBarMeta(
+ String aliasName, ZkStateReader zkStateReader, String fooValue, String barValue)
+ throws Exception {
zkStateReader.aliasesManager.update(); // ensure our view is up-to-date
Map<String, String> meta = zkStateReader.getAliases().getCollectionAliasProperties(aliasName);
assertNotNull(meta);
- assertTrue(meta.containsKey("foo"));
- assertEquals("baz", meta.get("foo"));
- assertTrue(meta.containsKey("bar"));
- assertEquals("bam", meta.get("bar"));
+
+ if (fooValue != null) {
+ assertTrue(meta.containsKey("foo"));
+ assertEquals(fooValue, meta.get("foo"));
+ } else {
+ assertFalse(meta.toString(), meta.containsKey("foo"));
+ }
+
+ if (barValue != null) {
+ assertTrue(meta.containsKey("bar"));
+ assertEquals(barValue, meta.get("bar"));
+ } else {
+ assertFalse(meta.toString(), meta.containsKey("bar"));
+ }
}
private ZkStateReader createColectionsAndAlias(String aliasName)
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
index ae099f24cb7..d37d9146c21 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
@@ -175,25 +175,6 @@ public class V2CollectionsAPIMappingTest extends V2ApiMappingTest<CollectionsHan
RoutedAlias.CREATE_COLLECTION_PREFIX + ZkStateReader.REPLICATION_FACTOR));
}
- @Test
- public void testSetAliasAllProperties() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params(
- "/collections",
- "POST",
- "{'set-alias-property': {"
- + "'name': 'aliasName', "
- + "'async': 'requestTrackingId', "
- + "'properties': {'foo':'bar', 'foo2':'bar2'}"
- + "}}");
-
- assertEquals(CollectionParams.CollectionAction.ALIASPROP.lowerName, v1Params.get(ACTION));
- assertEquals("aliasName", v1Params.get(CommonParams.NAME));
- assertEquals("requestTrackingId", v1Params.get(CommonAdminParams.ASYNC));
- assertEquals("bar", v1Params.get("property.foo"));
- assertEquals("bar2", v1Params.get("property.foo2"));
- }
-
@Test
public void testBackupAllProperties() throws Exception {
final SolrParams v1Params =
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
index b3f5d1d93a1..f8093939515 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
@@ -644,7 +644,7 @@ curl -X GET http://localhost:8983/api/aliases/testalias2
----
[[aliasprop]]
-== ALIASPROP: Modify Alias Properties for a Collection
+== ALIASPROP: Modify Alias Properties
The `ALIASPROP` action modifies the properties (metadata) on an alias.
If a key is set with a value that is empty it will be removed.
@@ -657,7 +657,7 @@ If a key is set with a value that is empty it will be removed.
[source,bash]
----
-http://localhost:8983/admin/collections?action=ALIASPROP&name=techproducts_alias&property.foo=bar
+curl -X POST 'http://localhost:8983/admin/collections?action=ALIASPROP&name=techproducts_alias&property.foo=bar'
----
====
@@ -667,17 +667,39 @@ http://localhost:8983/admin/collections?action=ALIASPROP&name=techproducts_alias
[source,bash]
----
-curl -X POST http://localhost:8983/api/collections -H 'Content-Type: application/json' -d '
+curl -X PUT http://localhost:8983/api/aliases/techproducts_alias/properties -H 'Content-Type: application/json' -d '
{
- "set-alias-property":{
- "name":"techproducts_alias",
- "properties": {"foo":"bar"}
- }
-}
-'
+ "properties": {"foo":"bar"}
+}'
+----
+
+====
+
+[example.tab-pane#v2aliasplevelprop]
+====
+[.tab-label]*V2 API* Update via property level api
+
+[source,bash]
+----
+curl -X PUT http://localhost:8983/api/aliases/techproducts_alias/properties/foo -H 'Content-Type: application/json' -d '
+{
+ "value": "baz"
+}'
----
====
+
+[example.tab-pane#v2deleteplevelprop]
+====
+[.tab-label]*V2 API* Delete via property level api
+
+[source,bash]
+----
+curl -X DELETE http://localhost:8983/api/aliases/techproducts_alias/properties/foo -H 'Content-Type: application/json'
+----
+
+====
+
--
@@ -726,7 +748,66 @@ Request ID to track this action which will be xref:configuration-guide:collectio
=== ALIASPROP Response
The output will simply be a responseHeader with details of the time it took to process the request.
-To confirm the creation of the property or properties, you can look in the Solr Admin UI, under the Cloud section and find the `aliases.json` file or use the LISTALIASES api command.
+Alias property creation can be confirmed using the "List Alias Properties" APIs described below, or by inspecting the `aliases.json` in the "Cloud" section of the Solr Admin UI.
+
+[[aliaspropread]]
+== Listing Alias Properties
+
+Retrieves the metadata properties associated with a specified alias.
+Solr's v2 API supports either listing out these properties in bulk or accessing them individually by name, as necessary.
+
+
+[.dynamic-tabs]
+--
+[example.tab-pane#v2listallprops]
+====
+[.tab-label]*V2 API* Get all properties on an alias
+
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/aliases/techproducts_alias/properties
+----
+
+*Output*
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 1
+ },
+ "properties": {
+ "foo": "bar"
+ }
+}
+----
+====
+
+[example.tab-pane#v2listsingleprop]
+====
+[.tab-label]*V2 API* Get single property value on an alias
+
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/aliases/techproducts_alias/properties/foo
+----
+
+*Output*
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 1
+ },
+ "value": "bar"
+}
+----
+====
+--
+
[[deletealias]]
== DELETEALIAS: Delete a Collection Alias
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
index 0a41b2c782a..fad9264b8d6 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
@@ -1893,7 +1893,11 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
}
public SetAliasProperty addProperty(String key, String value) {
- properties.put(key, value);
+ if (value == null) {
+ properties.put(key, "");
+ } else {
+ properties.put(key, value);
+ }
return this;
}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetAliasPropertyPayload.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetAliasPropertyPayload.java
deleted file mode 100644
index 93133cd2051..00000000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetAliasPropertyPayload.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.request.beans;
-
-import java.util.Map;
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
-
-public class SetAliasPropertyPayload implements ReflectMapWriter {
- @JsonProperty(required = true)
- public String name;
-
- @JsonProperty public String async;
-
- @JsonProperty public Map<String, Object> properties;
-}