diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index be31fe7f479..a5616bf13e9 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -90,6 +90,8 @@ New Features * SOLR-12862: Add log10 Stream Evaluator and allow the pow Stream Evaluator to accept a vector of exponents (Joel Bernstein) +* SOLR-12938: Cluster Status returns results for aliases, instead of throwing exceptions (Gus Heck) + Other Changes ---------------------- diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java index 9ebac776cdc..4daae31d800 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.Aliases; @@ -92,6 +93,17 @@ public class ClusterStatus { collectionsMap = Collections.singletonMap(collection, clusterState.getCollectionOrNull(collection)); } + boolean isAlias = aliasVsCollections.containsKey(collection); + boolean didNotFindCollection = collectionsMap.get(collection) == null; + + if (didNotFindCollection && isAlias) { + // In this case this.collection is an alias name not a collection + // get all collections and filter out collections not in the alias + collectionsMap = clusterState.getCollectionsMap().entrySet().stream() + .filter((entry) -> aliasVsCollections.get(collection).contains(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + NamedList collectionProps = new SimpleOrderedMap<>(); for (Map.Entry entry : collectionsMap.entrySet()) { diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java index 574ae37c835..531e154fd2a 100644 --- a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java +++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java @@ -418,13 +418,17 @@ public class TestCollectionAPI extends ReplicaPropertiesBase { private void clusterStatusAliasTest() throws Exception { try (CloudSolrClient client = createCloudClient(null)) { + // create an alias named myalias ModifiableSolrParams params = new ModifiableSolrParams(); params.set("action", CollectionParams.CollectionAction.CREATEALIAS.toString()); params.set("name", "myalias"); params.set("collections", DEFAULT_COLLECTION + "," + COLLECTION_NAME); SolrRequest request = new QueryRequest(params); request.setPath("/admin/collections"); + client.request(request); + + // request a collection that's part of an alias params = new ModifiableSolrParams(); params.set("action", CollectionParams.CollectionAction.CLUSTERSTATUS.toString()); params.set("collection", DEFAULT_COLLECTION); @@ -433,7 +437,6 @@ public class TestCollectionAPI extends ReplicaPropertiesBase { NamedList rsp = client.request(request); - NamedList cluster = (NamedList) rsp.get("cluster"); assertNotNull("Cluster state should not be null", cluster); Map aliases = (Map) cluster.get("aliases"); @@ -448,6 +451,38 @@ public class TestCollectionAPI extends ReplicaPropertiesBase { assertEquals("conf1", collection.get("configName")); List collAlias = (List) collection.get("aliases"); assertEquals("Aliases not found", Lists.newArrayList("myalias"), collAlias); + + // status request on the alias itself + params = new ModifiableSolrParams(); + params.set("action", CollectionParams.CollectionAction.CLUSTERSTATUS.toString()); + params.set("collection", "myalias"); + request = new QueryRequest(params); + request.setPath("/admin/collections"); + + // SOLR-12938 - this should NOT cause an exception + rsp = client.request(request); + + cluster = (NamedList) rsp.get("cluster"); + assertNotNull("Cluster state should not be null", cluster); + collections = (NamedList) cluster.get("collections"); + assertNotNull("Collections should not be null in cluster state", collections); + assertNotNull(collections.get(DEFAULT_COLLECTION)); + assertNotNull(collections.get(COLLECTION_NAME)); + + // status request on something neither an alias nor a collection itself + params = new ModifiableSolrParams(); + params.set("action", CollectionParams.CollectionAction.CLUSTERSTATUS.toString()); + params.set("collection", "notAnAliasOrCollection"); + request = new QueryRequest(params); + request.setPath("/admin/collections"); + + // SOLR-12938 - this should still cause an exception + try { + client.request(request); + fail("requesting status for 'notAnAliasOrCollection' should cause an exception from CLUSTERSTATUS" ); + } catch (RuntimeException e) { + // success + } } } diff --git a/solr/solr-ref-guide/src/collections-api.adoc b/solr/solr-ref-guide/src/collections-api.adoc index ec8517d9e55..b5dda3edc55 100644 --- a/solr/solr-ref-guide/src/collections-api.adoc +++ b/solr/solr-ref-guide/src/collections-api.adoc @@ -1663,7 +1663,7 @@ Fetch the cluster status including collections, shards, replicas, configuration === CLUSTERSTATUS Parameters `collection`:: -The collection name for which information is requested. If omitted, information on all collections in the cluster will be returned. +The collection or alias name for which information is requested. If omitted, information on all collections in the cluster will be returned. If an alias is supplied, information on the collections in the alias will be returned. `shard`:: The shard(s) for which information is requested. Multiple shard names can be specified as a comma-separated list. diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java index deb8fbc123c..484eaada2b9 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java @@ -96,14 +96,13 @@ public class HttpClusterStateProvider implements ClusterStateProvider { ClusterState cs = fetchClusterState(client, collection, null); return cs.getCollectionRef(collection); } catch (SolrServerException | RemoteSolrException | IOException e) { - if (e.getMessage().contains(collection + " not found")) { - // Cluster state for the given collection was not found. - // Lets fetch/update our aliases: - getAliases(true); - return null; - } log.warn("Attempt to fetch cluster state from " + Utils.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e); + } catch (NotACollectionException e) { + // Cluster state for the given collection was not found, could be an alias. + // Lets fetch/update our aliases: + getAliases(true); + return null; } } throw new RuntimeException("Tried fetching cluster state using the node names we knew of, i.e. " + liveNodes +". However, " @@ -114,7 +113,7 @@ public class HttpClusterStateProvider implements ClusterStateProvider { } @SuppressWarnings({"rawtypes", "unchecked"}) - private ClusterState fetchClusterState(SolrClient client, String collection, Map clusterProperties) throws SolrServerException, IOException { + private ClusterState fetchClusterState(SolrClient client, String collection, Map clusterProperties) throws SolrServerException, IOException, NotACollectionException { ModifiableSolrParams params = new ModifiableSolrParams(); if (collection != null) { params.set("collection", collection); @@ -131,8 +130,12 @@ public class HttpClusterStateProvider implements ClusterStateProvider { collectionsMap = ((NamedList)cluster.get("collections")).asMap(10); } int znodeVersion; - if (collection != null) { - znodeVersion = (int)((Map)(collectionsMap).get(collection)).get("znodeVersion"); + Map collFromStatus = (Map) (collectionsMap).get(collection); + if (collection != null && collFromStatus == null) { + throw new NotACollectionException(); // probably an alias + } + if (collection != null) { // can be null if alias + znodeVersion = (int) collFromStatus.get("znodeVersion"); } else { znodeVersion = -1; } @@ -253,6 +256,10 @@ public class HttpClusterStateProvider implements ClusterStateProvider { } catch (SolrServerException | RemoteSolrException | IOException e) { log.warn("Attempt to fetch cluster state from " + Utils.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e); + } catch (NotACollectionException e) { + // Cluster state for the given collection was not found, could be an alias. + // Lets fetch/update our aliases: + getAliases(true); } } throw new RuntimeException("Tried fetching cluster state using the node names we knew of, i.e. " + liveNodes +". However, " @@ -264,7 +271,7 @@ public class HttpClusterStateProvider implements ClusterStateProvider { @Override public Map getClusterProperties() { - for (String nodeName: liveNodes) { + for (String nodeName : liveNodes) { try (HttpSolrClient client = new HttpSolrClient.Builder(). withBaseSolrUrl(Utils.getBaseUrlForNodeName(nodeName, urlScheme)). withHttpClient(httpClient).build()) { @@ -274,9 +281,11 @@ public class HttpClusterStateProvider implements ClusterStateProvider { } catch (SolrServerException | RemoteSolrException | IOException e) { log.warn("Attempt to fetch cluster state from " + Utils.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e); + } catch (NotACollectionException e) { + // should be an an alias, don't care } } - throw new RuntimeException("Tried fetching cluster state using the node names we knew of, i.e. " + liveNodes +". However, " + throw new RuntimeException("Tried fetching cluster state using the node names we knew of, i.e. " + liveNodes + ". However, " + "succeeded in obtaining the cluster state from none of them." + "If you think your Solr cluster is up and is accessible," + " you could try re-creating a new CloudSolrClient using working" @@ -309,4 +318,7 @@ public class HttpClusterStateProvider implements ClusterStateProvider { this.cacheTimeout = cacheTimeout; } + // This exception is not meant to escape this class it should be caught and wrapped. + private class NotACollectionException extends Exception { + } }