diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java index 5b227359701..79e0ad48681 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java +++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java @@ -287,7 +287,7 @@ public void init(PluginInfo info) { .withMaxConnectionsPerHost(maxConnectionsPerHost) .build(); this.defaultClient.addListenerFactory(this.httpListenerFactory); - this.loadbalancer = new LBHttp2SolrClient.Builder(defaultClient).build(); + this.loadbalancer = new LBHttp2SolrClient.Builder(defaultClient, new String[0]).build(); initReplicaListTransformers(getParameter(args, "replicaRouting", null, sb)); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java index 5eab2dea39b..6838a575755 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java @@ -83,7 +83,7 @@ protected CloudHttp2SolrClient(Builder builder) { // locks. this.locks = objectList(builder.parallelCacheRefreshesLocks); - this.lbClient = new LBHttp2SolrClient.Builder(myClient).build(); + this.lbClient = new LBHttp2SolrClient.Builder(myClient, new String[0]).build(); } @Override diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java index 6cfada08541..f971e21fcd7 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java @@ -101,11 +101,20 @@ private LBHttp2SolrClient(Builder builder) { this.defaultCollection = builder.defaultCollection; } + /** + * @deprecated Use {@link #getClient(Endpoint)} instead. + */ + @Deprecated @Override protected SolrClient getClient(String baseUrl) { return solrClient; } + @Override + protected SolrClient getClient(Endpoint endpoint) { + return solrClient; + } + /** * Note: This setter method is not thread-safe. * @@ -352,12 +361,55 @@ public static class Builder { * In this case the client is more flexible and can be used to send requests to any cores. Users * can still provide a "default" collection if desired through use of {@link * #withDefaultCollection(String)}. + * + * @deprecated use {@link #Builder(Http2SolrClient, Endpoint...)} instead */ + @Deprecated public Builder(Http2SolrClient http2Client, String... baseSolrUrls) { this.http2SolrClient = http2Client; this.baseSolrUrls = baseSolrUrls; } + /** + * Create a Builder object, based on the provided solrClient and endpoint objects. + * + *

Endpoint instances come in two main flavors: + * + *

1) Endpoints representing a particular core or collection + * + *

+     *   SolrClient client = new LBHttp2SolrClient.Builder(
+     *           client, new LBSolrClient.Endpoint("http://my-solr-server:8983/solr", "core1"))
+     *       .build();
+     *   QueryResponse resp = client.query(new SolrQuery("*:*"));
+     * 
+ * + * Note that when a core is provided in the endpoint, queries and other requests can be made + * without mentioning the core explicitly. However, the client can only send requests to that + * core. Attempts to make core-agnostic requests, or requests for other cores will fail. + * + *

2) Endpoints representing the root Solr path (i.e. "/solr") + * + *

+     *   SolrClient client = new LBHttp2SolrClient.Builder(
+     *           client, new LBSolrClient.Endpoint("http://my-solr-server:8983/solr"))
+     *       .build();
+     *   QueryResponse resp = client.query("core1", new SolrQuery("*:*"));
+     * 
+ * + * In this case the client is more flexible and can be used to send requests to any cores. Users + * can still provide a "default" collection if desired through use of {@link + * #withDefaultCollection(String)}. + */ + public Builder(Http2SolrClient http2Client, Endpoint... endpoints) { + this.http2SolrClient = http2Client; + + this.baseSolrUrls = new String[endpoints.length]; + for (int i = 0; i < endpoints.length; i++) { + this.baseSolrUrls[i] = endpoints[i].getUrl(); + } + } + /** * LBHttpSolrServer keeps pinging the dead servers at fixed interval to find if it is alive. Use * this to set that interval diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java index b595701aec5..dd86f170669 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java @@ -25,6 +25,7 @@ import org.apache.http.client.HttpClient; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.URLUtil; /** * LBHttpSolrClient or "LoadBalanced HttpSolrClient" is a load balancing wrapper around {@link @@ -117,6 +118,14 @@ private HttpClient constructClient(String[] solrServerUrl) { return HttpClientUtil.createClient(params); } + protected HttpSolrClient makeSolrClient(Endpoint endpoint) { + return makeSolrClient(endpoint.getUrl()); + } + + /** + * @deprecated use {@link #makeSolrClient(Endpoint)} instead + */ + @Deprecated protected HttpSolrClient makeSolrClient(String server) { HttpSolrClient client; if (httpSolrClientBuilder != null) { @@ -159,6 +168,10 @@ protected HttpSolrClient makeSolrClient(String server) { return client; } + /** + * @deprecated use {@link #getClient(Endpoint)} instead + */ + @Deprecated @Override protected SolrClient getClient(String baseUrl) { SolrClient client = urlToClient.get(baseUrl); @@ -169,12 +182,26 @@ protected SolrClient getClient(String baseUrl) { } } + @Override + protected SolrClient getClient(Endpoint endpoint) { + return getClient(endpoint.getUrl()); + } + + /** + * @deprecated use {@link #removeSolrEndpoint(Endpoint)} instead + */ + @Deprecated @Override public String removeSolrServer(String server) { urlToClient.remove(server); return super.removeSolrServer(server); } + @Override + public String removeSolrEndpoint(Endpoint endpoint) { + return removeSolrServer(endpoint.getUrl()); + } + @Override public void close() { super.close(); @@ -256,12 +283,91 @@ public HttpSolrClient.Builder getHttpSolrClientBuilder() { * * In this case the client is more flexible and can be used to send requests to any cores. This * flexibility though requires that the core is specified on all requests. + * + * @deprecated use {@link #withBaseEndpoint(String)} or {@link #withCollectionEndpoint(String, + * String)} instead, based on the type of URL string currently being supplied */ + @Deprecated public Builder withBaseSolrUrl(String baseSolrUrl) { this.baseSolrUrls.add(baseSolrUrl); return this; } + /** + * Provide a "base" Solr URL to be used when configuring {@link LBHttpSolrClient} instances. + * + *

Method may be called multiple times. All provided values will be used. However, all + * endpoints must be of the same type: providing a mix of "base" endpoints via this method and + * core/collection endpoints via {@link #withCollectionEndpoint(String, String)} is prohibited. + * + *

Users who use this method to provide base Solr URLs may specify a "default collection" for + * their requests using {@link #withDefaultCollection(String)} if they wish to avoid needing to + * specify a collection or core on relevant requests. + * + * @param rootUrl the base URL for a Solr node, in the form "http[s]://hostname:port/solr" + */ + public Builder withBaseEndpoint(String rootUrl) { + this.baseSolrUrls.add(rootUrl); + return this; + } + + /** + * Provide multiple "base" Solr URLs to be used when configuring {@link LBHttpSolrClient} + * instances. + * + *

Method may be called multiple times. All provided values will be used. However, all + * endpoints must be of the same type: providing a mix of"base" endpoints via this method and + * core/collection endpoints via {@link #withCollectionEndpoint(String, String)} is prohibited. + * + *

Users who use this method to provide base Solr URLs may specify a "default collection" for + * their requests using {@link #withDefaultCollection(String)} if they wish to avoid needing to + * specify a collection or core on relevant requests. + * + * @param baseSolrUrls Solr base URLs, in the form "http[s]://hostname:port/solr" + */ + public Builder withBaseEndpoints(String... baseSolrUrls) { + for (String baseSolrUrl : baseSolrUrls) { + this.baseSolrUrls.add(baseSolrUrl); + } + return this; + } + + /** + * Provide a core/collection Solr endpoint to be used when configuring {@link LBHttpSolrClient} + * instances. + * + *

Method may be called multiple times. All provided values will be used. However, all + * endpoints must be of the same type: providing a mix of "core" endpoints via this method and + * base endpoints via {@link #withBaseEndpoint(String)} is prohibited. + * + * @param rootUrl the base URL for a Solr node, in the form "http[s]://hostname:port/solr" + * @param collection the Solr core or collection to target + */ + public Builder withCollectionEndpoint(String rootUrl, String collection) { + this.baseSolrUrls.add(URLUtil.buildCoreUrl(rootUrl, collection)); + return this; + } + + /** + * Provide multiple core/collection endpoints to be used when configuring {@link + * LBHttpSolrClient} instances. + * + *

Method may be called multiple times. All provided values will be used. However, all + * endpoints must be of the same type: providing a mix of "core" endpoints via this method and + * base endpoints via {@link #withBaseEndpoint(String)} is prohibited. + * + * @param endpoints endpoint instances pointing to distinct cores/collections + */ + public Builder withCollectionEndpoints(Endpoint... endpoints) { + if (endpoints != null) { + for (Endpoint e : endpoints) { + this.baseSolrUrls.add(URLUtil.buildCoreUrl(e.getBaseUrl(), e.getCore())); + } + } + + return this; + } + /** * Provide Solr endpoints to be used when configuring {@link LBHttpSolrClient} instances. * @@ -294,7 +400,11 @@ public Builder withBaseSolrUrl(String baseSolrUrl) { * In this case the client is more flexible and can be used to send requests to any cores. Users * can still provide a "default" collection if desired through use of {@link * #withDefaultCollection(String)}. + * + * @deprecated use either {@link #withBaseEndpoints(String...)} or {@link + * #withCollectionEndpoints(Endpoint...)}, based on the type of URL strings currently used. */ + @Deprecated public Builder withBaseSolrUrls(String... solrUrls) { for (String baseSolrUrl : solrUrls) { this.baseSolrUrls.add(baseSolrUrl); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java index 9202768bcf6..87e203b1cab 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java @@ -28,18 +28,21 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.apache.solr.client.solrj.ResponseParser; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; @@ -100,6 +103,82 @@ public abstract class LBSolrClient extends SolrClient { solrQuery.setDistrib(false); } + /** + * A Solr endpoint for {@link LBSolrClient} to include in its load-balancing + * + *

Used in many places instead of the more common String URL to allow {@link LBSolrClient} to + * more easily determine whether a URL is a "base" or "core-aware" URL. + */ + public static class Endpoint { + private final String baseUrl; + private final String core; + + /** + * Creates an {@link Endpoint} representing a "base" URL of a Solr node + * + * @param baseUrl a base Solr URL, in the form "http[s]://host:port/solr" + */ + public Endpoint(String baseUrl) { + this(baseUrl, null); + } + + /** + * Create an {@link Endpoint} representing a Solr core or collection + * + * @param baseUrl a base Solr URL, in the form "http[s]://host:port/solr" + * @param core the name of a Solr core or collection + */ + public Endpoint(String baseUrl, String core) { + this.baseUrl = normalize(baseUrl); + this.core = core; + } + + /** + * Return the base URL of the Solr node this endpoint represents + * + * @return a base Solr URL, in the form "http[s]://host:port/solr" + */ + public String getBaseUrl() { + return baseUrl; + } + + /** + * The core or collection this endpoint represents + * + * @return a core/collection name, or null if this endpoint doesn't represent a particular core. + */ + public String getCore() { + return core; + } + + /** Get the full URL, possibly including the collection/core if one was provided */ + public String getUrl() { + if (core == null) { + return baseUrl; + } + return baseUrl + "/" + core; + } + + @Override + public String toString() { + return getUrl(); + } + + @Override + public int hashCode() { + return Objects.hash(baseUrl, core); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Endpoint)) return false; + final Endpoint rhs = (Endpoint) obj; + + return Objects.equals(baseUrl, rhs.baseUrl) && Objects.equals(core, rhs.core); + } + } + protected static class ServerWrapper { final String baseUrl; @@ -245,10 +324,24 @@ public static class Req { protected int numDeadServersToTry; private final Integer numServersToTry; + /** + * @deprecated use {@link #Req(SolrRequest, Collection)} instead + */ + @Deprecated public Req(SolrRequest request, List servers) { this(request, servers, null); } + public Req(SolrRequest request, Collection servers) { + this( + request, + servers.stream().map(endpoint -> endpoint.getUrl()).collect(Collectors.toList())); + } + + /** + * @deprecated use {@link #Req(SolrRequest, Collection, Integer)} instead + */ + @Deprecated public Req(SolrRequest request, List servers, Integer numServersToTry) { this.request = request; this.servers = servers; @@ -256,10 +349,22 @@ public Req(SolrRequest request, List servers, Integer numServersToTry this.numServersToTry = numServersToTry; } + public Req(SolrRequest request, Collection servers, Integer numServersToTry) { + this( + request, + servers.stream().map(endpoint -> endpoint.getUrl()).collect(Collectors.toList()), + numServersToTry); + } + public SolrRequest getRequest() { return request; } + /** + * @deprecated will be replaced with a similar method in 10.0 that returns {@link Endpoint} + * instances instead. + */ + @Deprecated public List getServers() { return servers; } @@ -435,6 +540,8 @@ protected Exception doRequest( protected abstract SolrClient getClient(String baseUrl); + protected abstract SolrClient getClient(Endpoint endpoint); + protected Exception addZombie(String serverStr, Exception e) { ServerWrapper wrapper = createServerWrapper(serverStr); wrapper.standard = false; @@ -571,10 +678,22 @@ private void addToAlive(ServerWrapper wrapper) { } } + /** + * @deprecated use {@link #addSolrServer(Endpoint)} instead + */ + @Deprecated public void addSolrServer(String server) throws MalformedURLException { addToAlive(createServerWrapper(server)); } + public void addSolrServer(Endpoint server) throws MalformedURLException { + addSolrServer(server.getUrl()); + } + + /** + * @deprecated use {@link #removeSolrEndpoint(Endpoint)} instead + */ + @Deprecated public String removeSolrServer(String server) { try { server = new URL(server).toExternalForm(); @@ -592,6 +711,10 @@ public String removeSolrServer(String server) { return null; } + public String removeSolrEndpoint(Endpoint endpoint) { + return removeSolrServer(endpoint.getUrl()); + } + /** * Tries to query a live server. A SolrServerException is thrown if all servers are dead. If the * request failed due to IOException then the live server is moved to dead pool and the request is