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/06 21:32:54 UTC
[solr] branch main updated: SOLR-16393 Cosmetic, REST-fulness improvements to "list aliases" API
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 ef494220e65 SOLR-16393 Cosmetic, REST-fulness improvements to "list aliases" API
ef494220e65 is described below
commit ef494220e65525f4c6df660f0f5643d222fb83a3
Author: Alex <st...@users.noreply.github.com>
AuthorDate: Mon Mar 6 13:32:47 2023 -0800
SOLR-16393 Cosmetic, REST-fulness improvements to "list aliases" API
Co-authored-by: Jason Gerlowski <ge...@apache.org>
---
solr/CHANGES.txt | 4 +
.../java/org/apache/solr/handler/ClusterAPI.java | 8 --
.../solr/handler/admin/CollectionsHandler.java | 25 +---
.../solr/handler/admin/api/ListAliasesAPI.java | 143 +++++++++++++++++++++
.../solr/handler/V2ClusterAPIMappingTest.java | 7 -
.../solr/handler/admin/api/ListAliasesAPITest.java | 116 +++++++++++++++++
.../deployment-guide/pages/alias-management.adoc | 83 ++++++++----
.../apache/solr/common/cloud/ZkStateReader.java | 4 +
.../solr/client/solrj/request/TestV2Request.java | 7 -
9 files changed, 333 insertions(+), 64 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index cc5ea2a07d1..abde885dcee 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -154,6 +154,10 @@ Improvements
at `GET /api/cluster/zookeeper/data/<path>`, and "list node children" is now available at `GET /api/cluster/zookeeper/children/<path>`
(Jason Gerlowski, Joshua Ouma)
+* SOLR-16393: The path of the v2 "list alias" API has been tweaked slightly to be more intuitive, and is now available at
+ `GET /api/aliases`. It is also now possible to request information about a specific alias at `GET /api/aliases/<aliasName>`.
+ (Alex Deparvu via Jason Gerlowski)
+
Optimizations
---------------------
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 1a56f7980d5..83e3ffd006f 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -50,7 +50,6 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.DefaultSolrParams;
@@ -198,13 +197,6 @@ public class ClusterAPI {
}
}
- @EndPoint(method = GET, path = "/cluster/aliases", permission = COLL_READ_PERM)
- public void aliases(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- final Map<String, Object> v1Params = Maps.newHashMap();
- v1Params.put(ACTION, CollectionParams.CollectionAction.LISTALIASES.lowerName);
- collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
- }
-
@EndPoint(method = GET, path = "/cluster/overseer", permission = COLL_READ_PERM)
public void getOverseerStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
collectionsHandler.handleRequestBody(wrapParams(req, "action", OVERSEERSTATUS.lowerName), rsp);
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 3fdba5bbc11..374373f8da6 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
@@ -216,6 +216,7 @@ import org.apache.solr.handler.admin.api.DeleteReplicaAPI;
import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI;
import org.apache.solr.handler.admin.api.DeleteShardAPI;
import org.apache.solr.handler.admin.api.ForceLeaderAPI;
+import org.apache.solr.handler.admin.api.ListAliasesAPI;
import org.apache.solr.handler.admin.api.MigrateDocsAPI;
import org.apache.solr.handler.admin.api.ModifyCollectionAPI;
import org.apache.solr.handler.admin.api.MoveReplicaAPI;
@@ -866,24 +867,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
LISTALIASES_OP(
LISTALIASES,
(req, rsp, h) -> {
- ZkStateReader zkStateReader = h.coreContainer.getZkController().getZkStateReader();
- // if someone calls listAliases, lets ensure we return an up to date response
- zkStateReader.aliasesManager.update();
- Aliases aliases = zkStateReader.getAliases();
- if (aliases != null) {
- // the aliases themselves...
- rsp.getValues().add("aliases", aliases.getCollectionAliasMap());
- // Any properties for the above aliases.
- Map<String, Map<String, String>> meta = new LinkedHashMap<>();
- for (String alias : aliases.getCollectionAliasListMap().keySet()) {
- Map<String, String> collectionAliasProperties =
- aliases.getCollectionAliasProperties(alias);
- if (!collectionAliasProperties.isEmpty()) {
- meta.put(alias, collectionAliasProperties);
- }
- }
- rsp.getValues().add("properties", meta);
- }
+ final ListAliasesAPI getAliasesAPI = new ListAliasesAPI(h.coreContainer, req, rsp);
+ final SolrJerseyResponse getAliasesResponse = getAliasesAPI.getAliases();
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
return null;
}),
SPLITSHARD_OP(
@@ -2083,7 +2069,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
AddReplicaPropertyAPI.class,
DeleteReplicaPropertyAPI.class,
ReplaceNodeAPI.class,
- DeleteNodeAPI.class);
+ DeleteNodeAPI.class,
+ ListAliasesAPI.class);
}
@Override
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/ListAliasesAPI.java
new file mode 100644
index 00000000000..0f7103e5155
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
@@ -0,0 +1,143 @@
+/*
+ * 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.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.common.cloud.Aliases;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.core.CoreContainer;
+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 collection aliases */
+@Path("/aliases")
+public class ListAliasesAPI extends AdminAPIBase {
+
+ @Inject
+ public ListAliasesAPI(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ /**
+ * V2 API for listing all aliases known by Solr.
+ *
+ * <p>This API <code>GET /api/aliases</code> is analogous to the v1 <code>GET /api/cluster/aliases
+ * </code> API.
+ */
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @PermissionName(COLL_READ_PERM)
+ @Operation(
+ summary = "List the existing collection aliases.",
+ tags = {"aliases"})
+ public ListAliasesResponse getAliases() throws Exception {
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ final ListAliasesResponse response = instantiateJerseyResponse(ListAliasesResponse.class);
+ final Aliases aliases = readAliasesFromZk();
+
+ if (aliases != null) {
+ // the aliases themselves...
+ response.aliases = aliases.getCollectionAliasMap();
+ // Any properties for the above aliases.
+ Map<String, Map<String, String>> meta = new LinkedHashMap<>();
+ for (String alias : aliases.getCollectionAliasListMap().keySet()) {
+ Map<String, String> collectionAliasProperties = aliases.getCollectionAliasProperties(alias);
+ if (!collectionAliasProperties.isEmpty()) {
+ meta.put(alias, collectionAliasProperties);
+ }
+ }
+ response.properties = meta;
+ }
+
+ return response;
+ }
+
+ @GET
+ @Path("/{aliasName}")
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, BINARY_CONTENT_TYPE_V2})
+ @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 {
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ final GetAliasByNameResponse response = instantiateJerseyResponse(GetAliasByNameResponse.class);
+ response.alias = aliasName;
+
+ final Aliases aliases = readAliasesFromZk();
+ if (aliases != null) {
+ response.collections = aliases.getCollectionAliasListMap().getOrDefault(aliasName, List.of());
+ response.properties = aliases.getCollectionAliasProperties(aliasName);
+ }
+
+ 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();
+ }
+
+ /** Response for {@link ListAliasesAPI#getAliases()}. */
+ public static class ListAliasesResponse extends SolrJerseyResponse {
+ @JsonProperty("aliases")
+ public Map<String, String> aliases;
+
+ @JsonProperty("properties")
+ public Map<String, Map<String, String>> properties;
+ }
+
+ /** Response for {@link ListAliasesAPI#getAliasByName(String)}. */
+ public static class GetAliasByNameResponse extends SolrJerseyResponse {
+ @JsonProperty("name")
+ public String alias;
+
+ @JsonProperty("collections")
+ public List<String> collections;
+
+ @JsonProperty("properties")
+ public Map<String, String> properties;
+ }
+}
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 9eb5bb845bd..a7723c6e09f 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -80,13 +80,6 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
assertEquals("someId", v1Params.get(REQUESTID));
}
- @Test
- public void testClusterAliasesAllParams() throws Exception {
- final SolrParams v1Params = captureConvertedV1Params("/cluster/aliases", "GET", null);
-
- assertEquals(CollectionParams.CollectionAction.LISTALIASES.lowerName, v1Params.get(ACTION));
- }
-
@Test
public void testClusterOverseerAllParams() throws Exception {
final SolrParams v1Params = captureConvertedV1Params("/cluster/overseer", "GET", null);
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
new file mode 100644
index 00000000000..4cb4d065e36
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ListAliasesAPITest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.opentracing.noop.NoopSpan;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.SolrTestCaseJ4;
+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} */
+public class ListAliasesAPITest extends SolrTestCaseJ4 {
+
+ private CoreContainer mockCoreContainer;
+ private ZkStateReader zkStateReader;
+ private SolrQueryRequest mockQueryRequest;
+ private SolrQueryResponse queryResponse;
+
+ private ListAliasesAPI getAliasesAPI;
+
+ @BeforeClass
+ public static void ensureWorkingMockito() {
+ assumeWorkingMockito();
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ AliasesManager aliasesManager = mock(AliasesManager.class);
+ zkStateReader = mock(ZkStateReader.class);
+ when(zkStateReader.getAliasesManager()).thenReturn(aliasesManager);
+
+ ZkController zkController = mock(ZkController.class);
+ when(zkController.getZkStateReader()).thenReturn(zkStateReader);
+
+ mockCoreContainer = mock(CoreContainer.class);
+ when(mockCoreContainer.getZkController()).thenReturn(zkController);
+
+ mockQueryRequest = mock(SolrQueryRequest.class);
+ when(mockQueryRequest.getSpan()).thenReturn(NoopSpan.INSTANCE);
+ queryResponse = new SolrQueryResponse();
+
+ getAliasesAPI = new ListAliasesAPI(mockCoreContainer, mockQueryRequest, queryResponse);
+ }
+
+ @Test
+ public void testGetAliases() throws Exception {
+ when(mockCoreContainer.isZooKeeperAware()).thenReturn(true);
+
+ Aliases aliases =
+ Aliases.EMPTY
+ .cloneWithCollectionAlias("alias0", "colA")
+ .cloneWithCollectionAlias("alias1", "colB")
+ .cloneWithCollectionAliasProperties("alias1", "pkey1", "pvalA");
+ when(zkStateReader.getAliases()).thenReturn(aliases);
+
+ ListAliasesResponse response = getAliasesAPI.getAliases();
+ assertEquals(aliases.getCollectionAliasMap(), response.aliases);
+ assertEquals(
+ aliases.getCollectionAliasProperties("alias0"),
+ response.properties.getOrDefault("alias0", Map.of()));
+ assertEquals(
+ aliases.getCollectionAliasProperties("alias1"),
+ response.properties.getOrDefault("alias1", Map.of()));
+ assertNull(response.error);
+ }
+
+ @Test
+ public void testGetAliasByName() throws Exception {
+ when(mockCoreContainer.isZooKeeperAware()).thenReturn(true);
+
+ Aliases aliases =
+ Aliases.EMPTY
+ .cloneWithCollectionAlias("alias0", "colA")
+ .cloneWithCollectionAlias("alias1", "colB")
+ .cloneWithCollectionAliasProperties("alias1", "pkey1", "pvalA");
+ when(zkStateReader.getAliases()).thenReturn(aliases);
+
+ GetAliasByNameResponse response = getAliasesAPI.getAliasByName("alias1");
+ assertEquals("alias1", response.alias);
+ assertEquals(List.of("colB"), response.collections);
+ assertEquals(Map.of("pkey1", "pvalA"), response.properties);
+
+ assertNull(response.error);
+ }
+}
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 c01bf492bd3..bb1f98c98f6 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
@@ -552,7 +552,7 @@ curl -X POST http://localhost:8983/api/collections -H 'Content-Type: application
[source,bash]
----
-http://localhost:8982/solr/admin/collections?action=LISTALIASES
+curl -X GET 'http://localhost:8983/solr/admin/collections?action=LISTALIASES'
----
====
@@ -562,12 +562,21 @@ http://localhost:8982/solr/admin/collections?action=LISTALIASES
[source,bash]
----
-curl -X GET http://localhost:8983/api/cluster/aliases
+curl -X GET http://localhost:8983/api/aliases
----
====
--
-The LISTALIASES action does not take any parameters.
+=== LISTALIASES Getting details for a single alias
+[example.tab-pane#v2listsinglealias]
+====
+[.tab-label]*V2 API only*
+
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/aliases/testalias2
+----
+====
=== LISTALIASES Response
@@ -575,35 +584,63 @@ The output will contain a list of aliases with the corresponding collection name
=== Examples using LISTALIASES
+==== List the existing aliases
+
*Input*
-List the existing aliases, requesting information as XML from Solr:
+[source,bash]
+----
+curl -X GET http://localhost:8983/api/aliases
+----
+
+*Output*
-[source,text]
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 1
+ },
+ "aliases": {
+ "testalias1": "collection1",
+ "testalias2": "collection2,collection1"
+ },
+ "properties": {
+ "testalias2": {
+ "someKey": "someValue"
+ }
+ }
+}
+----
+
+==== Getting details for a single alias
+
+*Input*
+
+[source,bash]
----
-http://localhost:8983/solr/admin/collections?action=LISTALIASES&wt=xml
+curl -X GET http://localhost:8983/api/aliases/testalias2
----
*Output*
-[source,xml]
+[source,json]
----
-<response>
- <lst name="responseHeader">
- <int name="status">0</int>
- <int name="QTime">0</int>
- </lst>
- <lst name="aliases">
- <str name="testalias1">collection1</str>
- <str name="testalias2">collection1,collection2</str>
- </lst>
- <lst name="properties">
- <lst name="testalias1"/>
- <lst name="testalias2">
- <str name="someKey">someValue</str>
- </lst>
- </lst>
-</response>
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 1
+ },
+ "name": "testalias2",
+ "collections": [
+ "collection2",
+ "collection1"
+ ],
+ "properties": {
+ "someKey": "someValue"
+ }
+}
----
[[aliasprop]]
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkStateReader.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkStateReader.java
index 739baa254c1..259141d0f54 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkStateReader.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkStateReader.java
@@ -2169,6 +2169,10 @@ public class ZkStateReader implements SolrCloseable {
return aliasesManager.getAliases();
}
+ public AliasesManager getAliasesManager() {
+ return aliasesManager;
+ }
+
// called by createClusterStateWatchersAndUpdate()
private void refreshAliases(AliasesManager watcher) throws KeeperException, InterruptedException {
synchronized (getUpdateLock()) {
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
index 3ff70ae5375..315bced3ee0 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
@@ -58,13 +58,6 @@ public class TestV2Request extends SolrCloudTestCase {
List<?> l = (List<?>) rsp._get("nodes", null);
assertNotNull(l);
assertFalse(l.isEmpty());
- rsp =
- new V2Request.Builder("/cluster/aliases")
- .forceV2(true)
- .withMethod(SolrRequest.METHOD.GET)
- .build()
- .process(cluster.getSolrClient());
- assertTrue(rsp.getResponse().indexOf("aliases", 0) > -1);
}
@After