You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by is...@apache.org on 2017/04/17 04:43:47 UTC
lucene-solr:master: SOLR-10447, SOLR-10447: LISTALIASES Collections
API command; CloudSolrClient can be initialized using Solr URL
Repository: lucene-solr
Updated Branches:
refs/heads/master 00f0c3022 -> 4df4c52c0
SOLR-10447, SOLR-10447: LISTALIASES Collections API command; CloudSolrClient can be initialized using Solr URL
SOLR-10447: Collections API now supports a LISTALIASES command to return a list of all collection aliases.
SOLR-10446: CloudSolrClient can now be initialized using the base URL of a Solr instance instead of
ZooKeeper hosts. This is possible through the use of newly introduced HttpClusterStateProvider.
To fetch a list of collection aliases, this depends on LISTALIASES command, and hence this way of
initializing CloudSolrClient would not work with older versions of Solr that doesn't support LISTALIASES.
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/4df4c52c
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/4df4c52c
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/4df4c52c
Branch: refs/heads/master
Commit: 4df4c52c0cfb8b47a066a0495bd164f6a4c973de
Parents: 00f0c30
Author: Ishan Chattopadhyaya <is...@apache.org>
Authored: Mon Apr 17 10:11:18 2017 +0530
Committer: Ishan Chattopadhyaya <is...@apache.org>
Committed: Mon Apr 17 10:11:18 2017 +0530
----------------------------------------------------------------------
solr/CHANGES.txt | 9 +
.../handler/admin/CollectionHandlerApi.java | 2 +
.../solr/handler/admin/CollectionsHandler.java | 14 ++
.../src/resources/apispec/cluster.aliases.json | 12 +
.../apache/solr/cloud/AliasIntegrationTest.java | 4 +
.../solr/client/solrj/impl/CloudSolrClient.java | 84 ++++++-
.../solrj/impl/HttpClusterStateProvider.java | 252 +++++++++++++++++++
.../impl/ZkClientClusterStateProvider.java | 14 +-
.../solrj/request/CollectionAdminRequest.java | 14 ++
.../solrj/response/CollectionAdminResponse.java | 11 +
.../apache/solr/common/cloud/ClusterState.java | 5 +-
.../solr/common/params/CollectionParams.java | 1 +
.../solrj/impl/CloudSolrClientCacheTest.java | 15 +-
.../client/solrj/impl/CloudSolrClientTest.java | 119 ++++++---
14 files changed, 513 insertions(+), 43 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index c06eaa4..7d0933d 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -158,6 +158,15 @@ New Features
* SOLR-9936: Allow configuration for recoveryExecutor thread pool size. (Tim Owen via Mark Miller)
+* SOLR-10447: Collections API now supports a LISTALIASES command to return a list of all collection aliases.
+ (Ishan Chattopadhyaya)
+
+* SOLR-10446: CloudSolrClient can now be initialized using the base URL of a Solr instance instead of
+ ZooKeeper hosts. This is possible through the use of newly introduced HttpClusterStateProvider.
+ To fetch a list of collection aliases, this depends on LISTALIASES command, and hence this way of
+ initializing CloudSolrClient would not work with older versions of Solr that doesn't support LISTALIASES.
+ (Ishan Chattopadhyaya, Noble Paul)
+
Optimizations
----------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
index 581fe46..3cb21ab 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -65,6 +65,7 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
GET_CLUSTER_STATUS_CMD(EndPoint.CLUSTER_CMD_STATUS, GET, REQUESTSTATUS_OP),
DELETE_CLUSTER_STATUS(EndPoint.CLUSTER_CMD_STATUS_DELETE, DELETE, DELETESTATUS_OP),
GET_A_COLLECTION(EndPoint.COLLECTION_STATE, GET, CLUSTERSTATUS_OP),
+ LIST_ALIASES(EndPoint.CLUSTER_ALIASES, GET, LISTALIASES_OP),
CREATE_COLLECTION(EndPoint.COLLECTIONS_COMMANDS,
POST,
CREATE_OP,
@@ -290,6 +291,7 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
enum EndPoint implements V2EndPoint {
CLUSTER("cluster"),
+ CLUSTER_ALIASES("cluster.aliases"),
CLUSTER_CMD("cluster.Commands"),
CLUSTER_NODES("cluster.nodes"),
CLUSTER_CMD_STATUS("cluster.commandstatus"),
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
----------------------------------------------------------------------
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 bb06190..d5c4927 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
@@ -52,6 +52,7 @@ import org.apache.solr.cloud.rule.ReplicaAssigner;
import org.apache.solr.cloud.rule.Rule;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
@@ -460,6 +461,19 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
return req.getParams().required().getAll(null, NAME, "collections");
}),
DELETEALIAS_OP(DELETEALIAS, (req, rsp, h) -> req.getParams().required().getAll(null, NAME)),
+
+ /**
+ * Handle cluster status request.
+ * Can return status per specific collection/shard or per all collections.
+ */
+ LISTALIASES_OP(LISTALIASES, (req, rsp, h) -> {
+ ZkStateReader zkStateReader = h.coreContainer.getZkController().getZkStateReader();
+ Aliases aliases = zkStateReader.getAliases();
+ if (aliases != null) {
+ rsp.getValues().add("aliases", aliases.getCollectionAliasMap());
+ }
+ return null;
+ }),
SPLITSHARD_OP(SPLITSHARD, DEFAULT_COLLECTION_OP_TIMEOUT * 5, true, (req, rsp, h) -> {
String name = req.getParams().required().get(COLLECTION_PROP);
// TODO : add support for multiple shards
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/core/src/resources/apispec/cluster.aliases.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cluster.aliases.json b/solr/core/src/resources/apispec/cluster.aliases.json
new file mode 100644
index 0000000..9cffb71
--- /dev/null
+++ b/solr/core/src/resources/apispec/cluster.aliases.json
@@ -0,0 +1,12 @@
+{
+ "documentation": "https://cwiki.apache.org/confluence/display/solr/Collections+API",
+ "description": "Provides list of collection alises.",
+ "methods": [
+ "GET"
+ ],
+ "url": {
+ "paths": [
+ "/cluster/aliases"
+ ]
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
----------------------------------------------------------------------
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 6ca072b..869650df 100644
--- a/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
@@ -57,6 +57,10 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
CollectionAdminRequest.createAlias("testalias", "collection1").process(cluster.getSolrClient());
+ // ensure that the alias has been registered
+ assertEquals("collection1",
+ new CollectionAdminRequest.ListAliases().process(cluster.getSolrClient()).getAliases().get("testalias"));
+
// search for alias
QueryResponse res = cluster.getSolrClient().query("testalias", new SolrQuery("*:*"));
assertEquals(3, res.getResults().getNumFound());
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
index 4c6dd51..183896f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
@@ -393,7 +393,7 @@ public class CloudSolrClient extends SolrClient {
*/
@Deprecated
public CloudSolrClient(Collection<String> zkHosts, String chroot, HttpClient httpClient, LBHttpSolrClient lbSolrClient, boolean updatesToLeaders) {
- this(zkHosts, chroot, httpClient, lbSolrClient, null, updatesToLeaders, false, null);
+ this(zkHosts, chroot, null, httpClient, lbSolrClient, null, updatesToLeaders, false, null);
}
/**
@@ -407,9 +407,14 @@ public class CloudSolrClient extends SolrClient {
* each host in the zookeeper ensemble. Note that with certain
* Collection types like HashSet, the order of hosts in the final
* connect string may not be in the same order you added them.
+ * Provide only one of solrUrls or zkHosts.
* @param chroot
* A chroot value for zookeeper, starting with a forward slash. If no
* chroot is required, use null.
+ * @param solrUrls
+ * A list of Solr URLs to configure the underlying {@link HttpClusterStateProvider}, which will
+ * use of the these URLs to fetch the list of live nodes for this Solr cluster. Provide only
+ * one of solrUrls or zkHosts.
* @param httpClient
* the {@link HttpClient} instance to be used for all requests. The provided httpClient should use a
* multi-threaded connection manager. If null, a default HttpClient will be used.
@@ -424,6 +429,7 @@ public class CloudSolrClient extends SolrClient {
*/
private CloudSolrClient(Collection<String> zkHosts,
String chroot,
+ List<String> solrUrls,
HttpClient httpClient,
LBHttpSolrClient lbSolrClient,
LBHttpSolrClient.Builder lbHttpSolrClientBuilder,
@@ -433,7 +439,21 @@ public class CloudSolrClient extends SolrClient {
) {
if (stateProvider == null) {
- this.stateProvider = new ZkClientClusterStateProvider(zkHosts, chroot);
+ if (zkHosts != null && solrUrls != null) {
+ throw new IllegalArgumentException("Both zkHost(s) & solrUrl(s) have been specified. Only specify one.");
+ }
+ if (zkHosts != null) {
+ this.stateProvider = new ZkClientClusterStateProvider(zkHosts, chroot);
+ } else if (solrUrls != null && !solrUrls.isEmpty()) {
+ try {
+ this.stateProvider = new HttpClusterStateProvider(solrUrls, httpClient);
+ } catch (Exception e) {
+ throw new RuntimeException("Couldn't initialize a HttpClusterStateProvider (is/are the "
+ + "Solr server(s), " + solrUrls + ", down?)", e);
+ }
+ } else {
+ throw new IllegalArgumentException("Both zkHosts and solrUrl cannot be null.");
+ }
} else {
this.stateProvider = stateProvider;
}
@@ -1259,7 +1279,7 @@ public class CloudSolrClient extends SolrClient {
Set<String> liveNodes = stateProvider.liveNodes();
for (String liveNode : liveNodes) {
theUrlList.add(ZkStateReader.getBaseUrlForNodeName(liveNode,
- (String) stateProvider.getClusterProperties().getOrDefault(ZkStateReader.URL_SCHEME,"http")));
+ (String) stateProvider.getClusterProperty(ZkStateReader.URL_SCHEME,"http")));
}
} else {
@@ -1365,7 +1385,7 @@ public class CloudSolrClient extends SolrClient {
return rsp.getResponse();
}
- Set<String> getCollectionNames(String collection) {
+ private Set<String> getCollectionNames(String collection) {
// Extract each comma separated collection name and store in a List.
List<String> rawCollectionsList = StrUtils.splitSmart(collection, ",", true);
Set<String> collectionNames = new HashSet<>();
@@ -1602,6 +1622,7 @@ public class CloudSolrClient extends SolrClient {
*/
public static class Builder {
private Collection<String> zkHosts;
+ private List<String> solrUrls;
private HttpClient httpClient;
private String zkChroot;
private LBHttpSolrClient loadBalancedSolrClient;
@@ -1613,6 +1634,7 @@ public class CloudSolrClient extends SolrClient {
public Builder() {
this.zkHosts = new ArrayList();
+ this.solrUrls = new ArrayList();
this.shardLeadersOnly = true;
}
@@ -1629,8 +1651,29 @@ public class CloudSolrClient extends SolrClient {
this.zkHosts.add(zkHost);
return this;
}
+
+ /**
+ * Provide a Solr URL to be used when configuring {@link CloudSolrClient} instances.
+ *
+ * Method may be called multiple times. One of the provided values will be used to fetch
+ * the list of live Solr nodes that the underlying {@link HttpClusterStateProvider} would be maintaining.
+ */
+ public Builder withSolrUrl(String solrUrl) {
+ this.solrUrls.add(solrUrl);
+ return this;
+ }
/**
+ * Provide a list of Solr URL to be used when configuring {@link CloudSolrClient} instances.
+ * One of the provided values will be used to fetch the list of live Solr
+ * nodes that the underlying {@link HttpClusterStateProvider} would be maintaining.
+ */
+ public Builder withSolrUrl(Collection<String> solrUrls) {
+ this.solrUrls.addAll(solrUrls);
+ return this;
+ }
+
+ /**
* Provides a {@link HttpClient} for the builder to use when creating clients.
*/
public Builder withLBHttpSolrClientBuilder(LBHttpSolrClient.Builder lbHttpSolrClientBuilder) {
@@ -1722,24 +1765,51 @@ public class CloudSolrClient extends SolrClient {
*/
public CloudSolrClient build() {
if (stateProvider == null) {
- stateProvider = new ZkClientClusterStateProvider(zkHosts, zkChroot);
+ if (!zkHosts.isEmpty()) {
+ stateProvider = new ZkClientClusterStateProvider(zkHosts, zkChroot);
+ }
+ else if (!this.solrUrls.isEmpty()) {
+ try {
+ stateProvider = new HttpClusterStateProvider(solrUrls, httpClient);
+ } catch (Exception e) {
+ throw new RuntimeException("Couldn't initialize a HttpClusterStateProvider (is/are the "
+ + "Solr server(s), " + solrUrls + ", down?)", e);
+ }
+ } else {
+ throw new IllegalArgumentException("Both zkHosts and solrUrl cannot be null.");
+ }
}
- return new CloudSolrClient(zkHosts, zkChroot, httpClient, loadBalancedSolrClient, lbClientBuilder,
+ return new CloudSolrClient(zkHosts, zkChroot, solrUrls, httpClient, loadBalancedSolrClient, lbClientBuilder,
shardLeadersOnly, directUpdatesToLeadersOnly, stateProvider);
}
}
interface ClusterStateProvider extends Closeable {
+ /**
+ * Obtain the state of the collection (cluster status).
+ * @return the collection state, or null is collection doesn't exist
+ */
ClusterState.CollectionRef getState(String collection);
+ /**
+ * Obtain set of live_nodes for the cluster.
+ */
Set<String> liveNodes();
String getAlias(String collection);
String getCollectionName(String name);
- Map<String, Object> getClusterProperties();
+ /**
+ * Obtain a cluster property, or null if it doesn't exist.
+ */
+ Object getClusterProperty(String propertyName);
+
+ /**
+ * Obtain a cluster property, or the default value if it doesn't exist.
+ */
+ Object getClusterProperty(String propertyName, String def);
void connect();
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..b5cf414
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpClusterStateProvider.java
@@ -0,0 +1,252 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.client.HttpClient;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.HttpSolrClient.RemoteSolrException;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.ClusterState.CollectionRef;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HttpClusterStateProvider implements CloudSolrClient.ClusterStateProvider {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private String urlScheme;
+ volatile Set<String> liveNodes;
+ long liveNodesTimestamp = 0;
+ volatile Map<String, String> aliases;
+ long aliasesTimestamp = 0;
+
+ private int cacheTimeout = 5; // the liveNodes and aliases cache will be invalidated after 5 secs
+ final HttpClient httpClient;
+ final boolean clientIsInternal;
+
+ public HttpClusterStateProvider(List<String> solrUrls, HttpClient httpClient) throws Exception {
+ this.httpClient = httpClient == null? HttpClientUtil.createClient(null): httpClient;
+ this.clientIsInternal = httpClient == null;
+ for (String solrUrl: solrUrls) {
+ urlScheme = solrUrl.startsWith("https")? "https": "http";
+ try (SolrClient initialClient = new HttpSolrClient.Builder().withBaseSolrUrl(solrUrl).withHttpClient(httpClient).build()) {
+ Set<String> liveNodes = fetchLiveNodes(initialClient); // throws exception if unable to fetch
+ this.liveNodes = liveNodes;
+ liveNodesTimestamp = System.nanoTime();
+ break;
+ } catch (IOException e) {
+ log.warn("Attempt to fetch live_nodes from " + solrUrl + " failed.", e);
+ }
+ }
+
+ if (this.liveNodes == null || this.liveNodes.isEmpty()) {
+ throw new RuntimeException("Tried fetching live_nodes using Solr URLs provided, i.e. " + solrUrls + ". 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 a working"
+ + " solrUrl or zkUrl.");
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.clientIsInternal && this.httpClient != null) {
+ HttpClientUtil.close(httpClient);
+ }
+ }
+
+ @Override
+ public CollectionRef getState(String collection) {
+ for (String nodeName: liveNodes) {
+ try (HttpSolrClient client = new HttpSolrClient.Builder().
+ withBaseSolrUrl(ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme)).
+ withHttpClient(httpClient).build()) {
+ ClusterState cs = fetchClusterState(client, collection);
+ 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 " +
+ ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e);
+ }
+ }
+ 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 a working"
+ + " solrUrl or zkUrl.");
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private ClusterState fetchClusterState(SolrClient client, String collection) throws SolrServerException, IOException {
+ ModifiableSolrParams params = new ModifiableSolrParams();
+ params.set("collection", collection);
+ params.set("action", "CLUSTERSTATUS");
+ QueryRequest request = new QueryRequest(params);
+ request.setPath("/admin/collections");
+ NamedList cluster = (SimpleOrderedMap) client.request(request).get("cluster");
+ Map<String, Object> collectionsMap = ((NamedList) cluster.get("collections")).asShallowMap();
+ int znodeVersion = (int)((Map<String, Object>)(collectionsMap).get(collection)).get("znodeVersion");
+ Set<String> liveNodes = new HashSet((List<String>)(cluster.get("live_nodes")));
+ this.liveNodes = liveNodes;
+ liveNodesTimestamp = System.nanoTime();
+ ClusterState cs = ClusterState.load(znodeVersion, collectionsMap, liveNodes, ZkStateReader.CLUSTER_STATE);
+ return cs;
+ }
+
+ @Override
+ public Set<String> liveNodes() {
+ if (liveNodes == null) {
+ throw new RuntimeException("We don't know of any live_nodes to fetch the"
+ + " latest live_nodes information from. "
+ + "If you think your Solr cluster is up and is accessible,"
+ + " you could try re-creating a new CloudSolrClient using a working"
+ + " solrUrl or zkUrl.");
+ }
+ if (TimeUnit.SECONDS.convert((System.nanoTime() - liveNodesTimestamp), TimeUnit.NANOSECONDS) > getCacheTimeout()) {
+ for (String nodeName: liveNodes) {
+ try (HttpSolrClient client = new HttpSolrClient.Builder().
+ withBaseSolrUrl(ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme)).
+ withHttpClient(httpClient).build()) {
+ Set<String> liveNodes = fetchLiveNodes(client);
+ this.liveNodes = (liveNodes);
+ liveNodesTimestamp = System.nanoTime();
+ return liveNodes;
+ } catch (Exception e) {
+ log.warn("Attempt to fetch live_nodes from " +
+ ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e);
+ }
+ }
+ throw new RuntimeException("Tried fetching live_nodes using all 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 a working"
+ + " solrUrl or zkUrl.");
+ } else {
+ return liveNodes; // cached copy is fresh enough
+ }
+ }
+
+ private static Set<String> fetchLiveNodes(SolrClient client) throws Exception {
+ ModifiableSolrParams params = new ModifiableSolrParams();
+ params.set("action", "CLUSTERSTATUS");
+ QueryRequest request = new QueryRequest(params);
+ request.setPath("/admin/collections");
+ NamedList cluster = (SimpleOrderedMap) client.request(request).get("cluster");
+ Set<String> liveNodes = new HashSet((List<String>)(cluster.get("live_nodes")));
+ return liveNodes;
+ }
+
+ @Override
+ public String getAlias(String collection) {
+ Map<String, String> aliases = getAliases(false);
+ return aliases.get(collection);
+ }
+
+ private Map<String, String> getAliases(boolean forceFetch) {
+ if (this.liveNodes == null) {
+ throw new RuntimeException("We don't know of any live_nodes to fetch the"
+ + " latest aliases information from. "
+ + "If you think your Solr cluster is up and is accessible,"
+ + " you could try re-creating a new CloudSolrClient using a working"
+ + " solrUrl or zkUrl.");
+ }
+
+ if (forceFetch || this.aliases == null ||
+ TimeUnit.SECONDS.convert((System.nanoTime() - aliasesTimestamp), TimeUnit.NANOSECONDS) > getCacheTimeout()) {
+ for (String nodeName: liveNodes) {
+ try (HttpSolrClient client = new HttpSolrClient.Builder().
+ withBaseSolrUrl(ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme)).
+ withHttpClient(httpClient).build()) {
+
+ Map<String, String> aliases = new CollectionAdminRequest.ListAliases().process(client).getAliases();
+ this.aliases = aliases;
+ this.aliasesTimestamp = System.nanoTime();
+ return Collections.unmodifiableMap(aliases);
+ } catch (SolrServerException | RemoteSolrException | IOException e) {
+ log.warn("Attempt to fetch cluster state from " +
+ ZkStateReader.getBaseUrlForNodeName(nodeName, urlScheme) + " failed.", e);
+ }
+ }
+
+ throw new RuntimeException("Tried fetching aliases using all 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 a working"
+ + " solrUrl or zkUrl.");
+ } else {
+ return Collections.unmodifiableMap(this.aliases); // cached copy is fresh enough
+ }
+ }
+
+ @Override
+ public String getCollectionName(String name) {
+ Map<String, String> aliases = getAliases(false);
+ return aliases.containsKey(name) ? aliases.get(name): name;
+ }
+
+ @Override
+ public Object getClusterProperty(String propertyName) {
+ if (propertyName.equals(ZkStateReader.URL_SCHEME)) {
+ return this.urlScheme;
+ }
+ throw new UnsupportedOperationException("Fetching cluster properties not supported"
+ + " using the HttpClusterStateProvider. "
+ + "ZkClientClusterStateProvider can be used for this."); // TODO
+ }
+
+ @Override
+ public Object getClusterProperty(String propertyName, String def) {
+ if (propertyName.equals(ZkStateReader.URL_SCHEME)) {
+ return this.urlScheme;
+ }
+ throw new UnsupportedOperationException("Fetching cluster properties not supported"
+ + " using the HttpClusterStateProvider. "
+ + "ZkClientClusterStateProvider can be used for this."); // TODO
+ }
+
+ @Override
+ public void connect() {}
+
+ public int getCacheTimeout() {
+ return cacheTimeout;
+ }
+
+ public void setCacheTimeout(int cacheTimeout) {
+ this.cacheTimeout = cacheTimeout;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
index 8ed1b5c..6b37eb7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
@@ -69,8 +69,18 @@ public class ZkClientClusterStateProvider implements CloudSolrClient.ClusterStat
}
@Override
- public Map<String, Object> getClusterProperties() {
- return zkStateReader.getClusterProperties();
+ public Object getClusterProperty(String propertyName) {
+ Map<String, Object> props = zkStateReader.getClusterProperties();
+ return props.get(propertyName);
+ }
+
+ @Override
+ public Object getClusterProperty(String propertyName, String def) {
+ Map<String, Object> props = zkStateReader.getClusterProperties();
+ if (props.containsKey(propertyName)) {
+ return props.get(propertyName);
+ }
+ return def;
}
@Override
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
----------------------------------------------------------------------
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 f87f149..ec43e11 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
@@ -2246,6 +2246,20 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
}
+ // LISTALIASES request
+ public static class ListAliases extends CollectionAdminRequest<CollectionAdminResponse> {
+
+ public ListAliases() {
+ super(CollectionAction.LISTALIASES);
+ }
+
+ @Override
+ protected CollectionAdminResponse createResponse(SolrClient client) {
+ return new CollectionAdminResponse();
+ }
+
+ }
+
/**
* Returns a SolrRequest to get a list of collections in the cluster
*/
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/client/solrj/response/CollectionAdminResponse.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/CollectionAdminResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/CollectionAdminResponse.java
index 82d4d6f..6821075 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/CollectionAdminResponse.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/CollectionAdminResponse.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.client.solrj.response;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -61,6 +62,16 @@ public class CollectionAdminResponse extends SolrResponseBase
}
@SuppressWarnings("unchecked")
+ public Map<String, String> getAliases()
+ {
+ NamedList<Object> response = getResponse();
+ if (response.get("aliases") != null) {
+ return ((Map<String, String>)response.get("aliases"));
+ }
+ return Collections.emptyMap();
+ }
+
+ @SuppressWarnings("unchecked")
public Map<String, NamedList<Integer>> getCollectionNodesStatus()
{
Map<String, NamedList<Integer>> res = new HashMap<>();
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
index 302ee62..65bd81b 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
@@ -322,6 +322,10 @@ public class ClusterState implements JSONWriter.Writable {
return new ClusterState(version, liveNodes, Collections.<String, DocCollection>emptyMap());
}
Map<String, Object> stateMap = (Map<String, Object>) Utils.fromJSON(bytes);
+ return load(version, stateMap, liveNodes, znode);
+ }
+
+ public static ClusterState load(Integer version, Map<String, Object> stateMap, Set<String> liveNodes, String znode) {
Map<String,CollectionRef> collections = new LinkedHashMap<>(stateMap.size());
for (Entry<String, Object> entry : stateMap.entrySet()) {
String collectionName = entry.getKey();
@@ -332,7 +336,6 @@ public class ClusterState implements JSONWriter.Writable {
return new ClusterState( liveNodes, collections,version);
}
-
public static Aliases load(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return new Aliases();
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
index 51db039..d79fafa 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
@@ -68,6 +68,7 @@ public interface CollectionParams {
SYNCSHARD(true, LockLevel.SHARD),
CREATEALIAS(true, LockLevel.COLLECTION),
DELETEALIAS(true, LockLevel.COLLECTION),
+ LISTALIASES(false, LockLevel.NONE),
SPLITSHARD(true, LockLevel.SHARD),
DELETESHARD(true, LockLevel.SHARD),
CREATESHARD(true, LockLevel.COLLECTION),
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
index d260b02..d94d7e4 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
@@ -131,11 +131,6 @@ public class CloudSolrClientCacheTest extends SolrTestCaseJ4 {
}
@Override
- public Map<String, Object> getClusterProperties() {
- return Collections.EMPTY_MAP;
- }
-
- @Override
public String getAlias(String collection) {
return collection;
}
@@ -152,6 +147,16 @@ public class CloudSolrClientCacheTest extends SolrTestCaseJ4 {
public void close() throws IOException {
}
+
+ @Override
+ public Object getClusterProperty(String propertyName) {
+ return null;
+ }
+
+ @Override
+ public Object getClusterProperty(String propertyName, String def) {
+ return def;
+ }
};
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4df4c52c/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
index 5ebb650..c91cb67 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
@@ -68,6 +68,7 @@ import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -90,6 +91,8 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
private static final int TIMEOUT = 30;
private static final int NODE_COUNT = 3;
+ private static CloudSolrClient httpBasedCloudSolrClient = null;
+
@BeforeClass
public static void setupCluster() throws Exception {
configureCluster(NODE_COUNT)
@@ -99,8 +102,21 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 1).process(cluster.getSolrClient());
AbstractDistribZkTestBase.waitForRecoveriesToFinish(COLLECTION, cluster.getSolrClient().getZkStateReader(),
false, true, TIMEOUT);
+
+ httpBasedCloudSolrClient = new CloudSolrClient.Builder().withSolrUrl(
+ cluster.getJettySolrRunner(0).getBaseUrl().toString()).build();
}
+ @AfterClass
+ public static void afterClass() {
+ if (httpBasedCloudSolrClient != null) {
+ try {
+ httpBasedCloudSolrClient.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Before
@@ -110,6 +126,13 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.commit(cluster.getSolrClient(), COLLECTION);
}
+ /**
+ * Randomly return the cluster's ZK based CSC, or HttpClusterProvider based CSC.
+ */
+ private CloudSolrClient getRandomClient() {
+ return random().nextBoolean()? cluster.getSolrClient(): httpBasedCloudSolrClient;
+ }
+
@Test
public void testParallelUpdateQTime() throws Exception {
UpdateRequest req = new UpdateRequest();
@@ -118,7 +141,7 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
doc.addField("id", String.valueOf(TestUtil.nextInt(random(), 1000, 1100)));
req.add(doc);
}
- UpdateResponse response = req.process(cluster.getSolrClient(), COLLECTION);
+ UpdateResponse response = req.process(getRandomClient(), COLLECTION);
// See SOLR-6547, we just need to ensure that no exception is thrown here
assertTrue(response.getQTime() >= 0);
}
@@ -143,33 +166,48 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.add(new SolrInputDocument(id, "1", "a_t", "hello2"), false)
.commit(cluster.getSolrClient(), "overwrite");
- resp = cluster.getSolrClient().query("overwrite", new SolrQuery("*:*"));
+ resp = getRandomClient().query("overwrite", new SolrQuery("*:*"));
assertEquals("There should be 3 documents because there should be two id=1 docs due to overwrite=false", 3, resp.getResults().getNumFound());
}
@Test
+ public void testAliasHandling() throws Exception {
+ CloudSolrClient client = getRandomClient();
+ SolrInputDocument doc = new SolrInputDocument("id", "1", "title_s", "my doc");
+ client.add(COLLECTION, doc);
+ client.commit(COLLECTION);
+
+ CollectionAdminRequest.createAlias("testalias", COLLECTION).process(cluster.getSolrClient());
+
+ // ensure that the alias has been registered
+ assertEquals(COLLECTION,
+ new CollectionAdminRequest.ListAliases().process(cluster.getSolrClient()).getAliases().get("testalias"));
+
+ assertEquals(1, client.query(COLLECTION, params("q", "*:*")).getResults().getNumFound());
+ assertEquals(1, client.query("testalias", params("q", "*:*")).getResults().getNumFound());
+ }
+
+ @Test
public void testHandlingOfStaleAlias() throws Exception {
- try (CloudSolrClient client = getCloudSolrClient(cluster.getZkServer().getZkAddress())) {
- client.setDefaultCollection("misconfigured-alias");
+ CloudSolrClient client = getRandomClient();
- CollectionAdminRequest.createCollection("nemesis", "conf", 2, 1).process(client);
- CollectionAdminRequest.createAlias("misconfigured-alias", "nemesis").process(client);
- CollectionAdminRequest.deleteCollection("nemesis").process(client);
+ CollectionAdminRequest.createCollection("nemesis", "conf", 2, 1).process(client);
+ CollectionAdminRequest.createAlias("misconfigured-alias", "nemesis").process(client);
+ CollectionAdminRequest.deleteCollection("nemesis").process(client);
- List<SolrInputDocument> docs = new ArrayList<>();
+ List<SolrInputDocument> docs = new ArrayList<>();
- SolrInputDocument doc = new SolrInputDocument();
- doc.addField(id, Integer.toString(1));
- docs.add(doc);
+ SolrInputDocument doc = new SolrInputDocument();
+ doc.addField(id, Integer.toString(1));
+ docs.add(doc);
- try {
- client.add(docs);
- fail("Alias points to non-existing collection, add should fail");
- } catch (SolrException e) {
- assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
- assertTrue("Unexpected exception", e.getMessage().contains("Collection not found"));
- }
+ try {
+ client.add("misconfigured-alias", docs);
+ fail("Alias points to non-existing collection, add should fail");
+ } catch (SolrException e) {
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+ assertTrue("Unexpected exception", e.getMessage().contains("Collection not found"));
}
}
@@ -182,8 +220,8 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
// Test single threaded routed updates for UpdateRequest
- NamedList<Object> response = cluster.getSolrClient().request(request, COLLECTION);
- if (cluster.getSolrClient().isDirectUpdatesToLeadersOnly()) {
+ NamedList<Object> response = getRandomClient().request(request, COLLECTION);
+ if (getRandomClient().isDirectUpdatesToLeadersOnly()) {
checkSingleServer(response);
}
CloudSolrClient.RouteResponse rr = (CloudSolrClient.RouteResponse) response;
@@ -214,11 +252,11 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.deleteById("0")
.deleteById("2")
.commit(cluster.getSolrClient(), COLLECTION);
- if (cluster.getSolrClient().isDirectUpdatesToLeadersOnly()) {
+ if (getRandomClient().isDirectUpdatesToLeadersOnly()) {
checkSingleServer(uResponse.getResponse());
}
- QueryResponse qResponse = cluster.getSolrClient().query(COLLECTION, new SolrQuery("*:*"));
+ QueryResponse qResponse = getRandomClient().query(COLLECTION, new SolrQuery("*:*"));
SolrDocumentList docs = qResponse.getResults();
assertEquals(0, docs.getNumFound());
@@ -307,7 +345,7 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set(CommonParams.Q, "*:*");
solrParams.set(ShardParams._ROUTE_, sameShardRoutes.get(random().nextInt(sameShardRoutes.size())));
- log.info("output: {}", cluster.getSolrClient().query(COLLECTION, solrParams));
+ log.info("output: {}", getRandomClient().query(COLLECTION, solrParams));
}
// Request counts increase from expected nodes should aggregate to 1000, while there should be
@@ -362,10 +400,10 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.add(id, "0", "a_t", "hello1")
.add(id, "2", "a_t", "hello2")
.add(id, "3", "a_t", "hello2")
- .commit(cluster.getSolrClient(), collectionName);
+ .commit(getRandomClient(), collectionName);
// Run the actual test for 'preferLocalShards'
- queryWithPreferLocalShards(cluster.getSolrClient(), true, collectionName);
+ queryWithPreferLocalShards(getRandomClient(), true, collectionName);
}
private void queryWithPreferLocalShards(CloudSolrClient cloudClient,
@@ -658,7 +696,7 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
.add("id", "2", "a_t", "hello2");
updateRequest.setParam(UpdateParams.VERSIONS, Boolean.TRUE.toString());
- NamedList<Object> response = updateRequest.commit(cluster.getSolrClient(), COLLECTION).getResponse();
+ NamedList<Object> response = updateRequest.commit(getRandomClient(), COLLECTION).getResponse();
Object addsObject = response.get("adds");
assertNotNull("There must be a adds parameter", addsObject);
@@ -677,7 +715,7 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
assertTrue("Version for id 2 must be a long", object instanceof Long);
versions.put("2", (Long) object);
- QueryResponse resp = cluster.getSolrClient().query(COLLECTION, new SolrQuery("*:*"));
+ QueryResponse resp = getRandomClient().query(COLLECTION, new SolrQuery("*:*"));
assertEquals("There should be one document because overwrite=true", 2, resp.getResults().getNumFound());
for (SolrDocument doc : resp.getResults()) {
@@ -688,13 +726,38 @@ public class CloudSolrClientTest extends SolrCloudTestCase {
// assert that "deletes" are returned
UpdateRequest deleteRequest = new UpdateRequest().deleteById("1");
deleteRequest.setParam(UpdateParams.VERSIONS, Boolean.TRUE.toString());
- response = deleteRequest.commit(cluster.getSolrClient(), COLLECTION).getResponse();
+ response = deleteRequest.commit(getRandomClient(), COLLECTION).getResponse();
Object deletesObject = response.get("deletes");
assertNotNull("There must be a deletes parameter", deletesObject);
NamedList deletes = (NamedList) deletesObject;
assertEquals("There must be 1 version", 1, deletes.size());
}
+ @Test
+ public void testInitializationWithSolrUrls() throws Exception {
+ CloudSolrClient client = httpBasedCloudSolrClient;
+ SolrInputDocument doc = new SolrInputDocument("id", "1", "title_s", "my doc");
+ client.add(COLLECTION, doc);
+ client.commit(COLLECTION);
+ assertEquals(1, client.query(COLLECTION, params("q", "*:*")).getResults().getNumFound());
+ }
+
+ @Test
+ public void testCollectionDoesntExist() throws Exception {
+ CloudSolrClient client = getRandomClient();
+ SolrInputDocument doc = new SolrInputDocument("id", "1", "title_s", "my doc");
+ try {
+ client.add("boguscollectionname", doc);
+ fail();
+ } catch (SolrException ex) {
+ if (ex.getMessage().equals("Collection not found: boguscollectionname")) {
+ // pass
+ } else {
+ throw ex;
+ }
+ }
+ }
+
private static void checkSingleServer(NamedList<Object> response) {
final CloudSolrClient.RouteResponse rr = (CloudSolrClient.RouteResponse) response;
final Map<String,LBHttpSolrClient.Req> routes = rr.getRoutes();