This is an automated email from the ASF dual-hosted git repository.
jdyer 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 72cc2756a7b SOLR-17516: LBHttpSolrClient: support HttpJdkSolrClient
(Generic Version) (#2828)
72cc2756a7b is described below
commit 72cc2756a7befcdb1e9fff5275261695054bd152
Author: James Dyer <[email protected]>
AuthorDate: Tue Nov 19 09:20:25 2024 -0600
SOLR-17516: LBHttpSolrClient: support HttpJdkSolrClient (Generic Version)
(#2828)
---
solr/CHANGES.txt | 4 +-
.../solr/handler/component/HttpShardHandler.java | 3 +-
.../handler/component/HttpShardHandlerFactory.java | 4 +-
.../client/solrj/impl/CloudHttp2SolrClient.java | 6 +-
.../solr/client/solrj/impl/Http2SolrClient.java | 4 -
.../solr/client/solrj/impl/HttpJdkSolrClient.java | 28 +++--
.../solr/client/solrj/impl/HttpSolrClientBase.java | 4 +
.../solr/client/solrj/impl/LBHttp2SolrClient.java | 33 +++---
.../solr/client/solrj/impl/LBSolrClient.java | 13 ++-
.../client/solrj/impl/HttpJdkSolrClientTest.java | 102 ++++--------------
.../LBHttp2SolrClientIntegrationTest.java} | 112 +++++++++++++-------
.../client/solrj/impl/LBHttp2SolrClientTest.java | 115 +++++++++++++++++++--
.../solr/client/solrj/impl/MockTrustManager.java | 100 ++++++++++++++++++
13 files changed, 363 insertions(+), 165 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 93e737a543d..05574f64371 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -32,6 +32,8 @@ Improvements
* SOLR-17495: Change Solr CLI delete command to not delete configs by default.
Decouple lifecycle of collections from configsets. (Eric Pugh)
+* SOLR-17516: `LBHttp2SolrClient` is now generic, adding support for
`HttpJdkSolrClient`. (James Dyer)
+
Optimizations
---------------------
(No changes)
@@ -71,7 +73,7 @@ Deprecation Removals
in WordBreakSolrSpellChecker (Andrey Bozhko via Eric Pugh)
* SOLR-14763: Remove deprecated asynchronous request methods from
`Http2SolrClient`, `HttpJdkSolrClient` and `LBHttp2SolrClient`
- in favor of the new CompletableFuture based methods. Remove the related
deprecated interfaces `AsyncListener` and ``Cancellable`
+ in favor of the new CompletableFuture based methods. Remove the related
deprecated interfaces `AsyncListener` and `Cancellable`
(James Dyer)
* SOLR-14115: Remove deprecated zkcli script in favour of equivalent bin/solr
sub commmands. (Eric Pugh)
diff --git
a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
index 219197d8cd8..7592eed86fc 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
@@ -31,6 +31,7 @@ import java.util.function.BiConsumer;
import net.jcip.annotations.NotThreadSafe;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
import org.apache.solr.client.solrj.impl.LBSolrClient;
import org.apache.solr.client.solrj.request.QueryRequest;
@@ -113,7 +114,7 @@ public class HttpShardHandler extends ShardHandler {
protected AtomicInteger pending;
private final Map<String, List<String>> shardToURLs;
- protected LBHttp2SolrClient lbClient;
+ protected LBHttp2SolrClient<Http2SolrClient> lbClient;
public HttpShardHandler(HttpShardHandlerFactory httpShardHandlerFactory) {
this.httpShardHandlerFactory = httpShardHandlerFactory;
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 2bfc4cb236a..ac7dc0cf8e0 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
@@ -84,7 +84,7 @@ public class HttpShardHandlerFactory extends
ShardHandlerFactory
protected volatile Http2SolrClient defaultClient;
protected InstrumentedHttpListenerFactory httpListenerFactory;
- protected LBHttp2SolrClient loadbalancer;
+ protected LBHttp2SolrClient<Http2SolrClient> loadbalancer;
int corePoolSize = 0;
int maximumPoolSize = Integer.MAX_VALUE;
@@ -314,7 +314,7 @@ public class HttpShardHandlerFactory extends
ShardHandlerFactory
.withMaxConnectionsPerHost(maxConnectionsPerHost)
.build();
this.defaultClient.addListenerFactory(this.httpListenerFactory);
- this.loadbalancer = new LBHttp2SolrClient.Builder(defaultClient).build();
+ this.loadbalancer = new
LBHttp2SolrClient.Builder<Http2SolrClient>(defaultClient).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 92da218d15c..523c46b844c 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
@@ -40,7 +40,7 @@ import org.apache.solr.common.SolrException;
public class CloudHttp2SolrClient extends CloudSolrClient {
private final ClusterStateProvider stateProvider;
- private final LBHttp2SolrClient lbClient;
+ private final LBHttp2SolrClient<Http2SolrClient> lbClient;
private final Http2SolrClient myClient;
private final boolean clientIsInternal;
@@ -73,7 +73,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
// locks.
this.locks = objectList(builder.parallelCacheRefreshesLocks);
- this.lbClient = new LBHttp2SolrClient.Builder(myClient).build();
+ this.lbClient = new
LBHttp2SolrClient.Builder<Http2SolrClient>(myClient).build();
}
private Http2SolrClient createOrGetHttpClientFromBuilder(Builder builder) {
@@ -149,7 +149,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
}
@Override
- public LBHttp2SolrClient getLbClient() {
+ public LBHttp2SolrClient<Http2SolrClient> getLbClient() {
return lbClient;
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index e3f974e41e3..fb2eb1a123f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -831,10 +831,6 @@ public class Http2SolrClient extends HttpSolrClientBase {
.collect(Collectors.joining(", "));
}
- protected RequestWriter getRequestWriter() {
- return requestWriter;
- }
-
/**
* An Http2SolrClient that doesn't close or cleanup any resources
*
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index d3610f8d544..1127b3fd1a1 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -138,7 +138,7 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
public CompletableFuture<NamedList<Object>> requestAsync(
final SolrRequest<?> solrRequest, String collection) {
try {
- PreparedRequest pReq = prepareRequest(solrRequest, collection);
+ PreparedRequest pReq = prepareRequest(solrRequest, collection, null);
return httpClient
.sendAsync(pReq.reqb.build(),
HttpResponse.BodyHandlers.ofInputStream())
.thenApply(
@@ -157,10 +157,10 @@ public class HttpJdkSolrClient extends HttpSolrClientBase
{
}
}
- @Override
- public NamedList<Object> request(SolrRequest<?> solrRequest, String
collection)
+ protected NamedList<Object> requestWithBaseUrl(
+ String baseUrl, SolrRequest<?> solrRequest, String collection)
throws SolrServerException, IOException {
- PreparedRequest pReq = prepareRequest(solrRequest, collection);
+ PreparedRequest pReq = prepareRequest(solrRequest, collection, baseUrl);
HttpResponse<InputStream> response = null;
try {
response = httpClient.send(pReq.reqb.build(),
HttpResponse.BodyHandlers.ofInputStream());
@@ -173,8 +173,8 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
"Timeout occurred while waiting response from server at: " +
pReq.url, e);
} catch (SolrException se) {
throw se;
- } catch (RuntimeException re) {
- throw new SolrServerException(re);
+ } catch (RuntimeException e) {
+ throw new SolrServerException(e);
} finally {
if (pReq.contentWritingFuture != null) {
pReq.contentWritingFuture.cancel(true);
@@ -192,13 +192,25 @@ public class HttpJdkSolrClient extends HttpSolrClientBase
{
}
}
- private PreparedRequest prepareRequest(SolrRequest<?> solrRequest, String
collection)
+ @Override
+ public NamedList<Object> request(SolrRequest<?> solrRequest, String
collection)
+ throws SolrServerException, IOException {
+ return requestWithBaseUrl(null, solrRequest, collection);
+ }
+
+ private PreparedRequest prepareRequest(
+ SolrRequest<?> solrRequest, String collection, String overrideBaseUrl)
throws SolrServerException, IOException {
checkClosed();
if (ClientUtils.shouldApplyDefaultCollection(collection, solrRequest)) {
collection = defaultCollection;
}
- String url = getRequestUrl(solrRequest, collection);
+ String url;
+ if (overrideBaseUrl != null) {
+ url = ClientUtils.buildRequestUrl(solrRequest, overrideBaseUrl,
collection);
+ } else {
+ url = getRequestUrl(solrRequest, collection);
+ }
ResponseParser parserToUse = responseParser(solrRequest);
ModifiableSolrParams queryParams = initializeSolrParams(solrRequest,
parserToUse);
var reqb = HttpRequest.newBuilder();
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
index 27e56375c2d..8683b965ad0 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
@@ -120,6 +120,10 @@ public abstract class HttpSolrClientBase extends
SolrClient {
return solrRequest.getResponseParser() == null ? this.parser :
solrRequest.getResponseParser();
}
+ protected RequestWriter getRequestWriter() {
+ return requestWriter;
+ }
+
// TODO: Remove this for 10.0, there is a typo in the method name
@Deprecated(since = "9.8", forRemoval = true)
protected ModifiableSolrParams initalizeSolrParams(
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 cb06c9d0c27..2c926a26261 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
@@ -37,9 +37,8 @@ import org.apache.solr.common.util.NamedList;
import org.slf4j.MDC;
/**
- * LBHttp2SolrClient or "LoadBalanced LBHttp2SolrClient" is a load balancing
wrapper around {@link
- * Http2SolrClient}. This is useful when you have multiple Solr endpoints and
requests need to be
- * Load Balanced among them.
+ * This "LoadBalanced Http Solr Client" is a load balancing wrapper around a
Http Solr Client. This
+ * is useful when you have multiple Solr endpoints and requests need to be
Load Balanced among them.
*
* <p>Do <b>NOT</b> use this class for indexing in leader/follower scenarios
since documents must be
* sent to the correct leader; no inter-node routing is done.
@@ -95,12 +94,14 @@ import org.slf4j.MDC;
*
* @since solr 8.0
*/
-public class LBHttp2SolrClient extends LBSolrClient {
- private final Http2SolrClient solrClient;
+public class LBHttp2SolrClient<C extends HttpSolrClientBase> extends
LBSolrClient {
- private LBHttp2SolrClient(Builder builder) {
+ protected final C solrClient;
+
+ @SuppressWarnings("unchecked")
+ private LBHttp2SolrClient(Builder<?> builder) {
super(Arrays.asList(builder.solrEndpoints));
- this.solrClient = builder.http2SolrClient;
+ this.solrClient = (C) builder.solrClient;
this.aliveCheckIntervalMillis = builder.aliveCheckIntervalMillis;
this.defaultCollection = builder.defaultCollection;
}
@@ -289,16 +290,16 @@ public class LBHttp2SolrClient extends LBSolrClient {
}
}
- public static class Builder {
+ public static class Builder<C extends HttpSolrClientBase> {
- private final Http2SolrClient http2SolrClient;
- private final Endpoint[] solrEndpoints;
+ private final C solrClient;
+ private final LBSolrClient.Endpoint[] solrEndpoints;
private long aliveCheckIntervalMillis =
TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); // 1 minute
between checks
protected String defaultCollection;
- public Builder(Http2SolrClient http2Client, Endpoint... endpoints) {
- this.http2SolrClient = http2Client;
+ public Builder(C solrClient, Endpoint... endpoints) {
+ this.solrClient = solrClient;
this.solrEndpoints = endpoints;
}
@@ -308,7 +309,7 @@ public class LBHttp2SolrClient extends LBSolrClient {
*
* @param aliveCheckInterval how often to ping for aliveness
*/
- public LBHttp2SolrClient.Builder setAliveCheckInterval(int
aliveCheckInterval, TimeUnit unit) {
+ public Builder<C> setAliveCheckInterval(int aliveCheckInterval, TimeUnit
unit) {
if (aliveCheckInterval <= 0) {
throw new IllegalArgumentException(
"Alive check interval must be " + "positive, specified value = " +
aliveCheckInterval);
@@ -318,13 +319,13 @@ public class LBHttp2SolrClient extends LBSolrClient {
}
/** Sets a default for core or collection based requests. */
- public LBHttp2SolrClient.Builder withDefaultCollection(String
defaultCoreOrCollection) {
+ public Builder<C> withDefaultCollection(String defaultCoreOrCollection) {
this.defaultCollection = defaultCoreOrCollection;
return this;
}
- public LBHttp2SolrClient build() {
- return new LBHttp2SolrClient(this);
+ public LBHttp2SolrClient<C> build() {
+ return new LBHttp2SolrClient<C>(this);
}
}
}
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 e31f3c08393..6e691b802f0 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
@@ -500,6 +500,8 @@ public abstract class LBSolrClient extends SolrClient {
if (solrClient instanceof Http2SolrClient) {
final var httpSolrClient = (Http2SolrClient) solrClient;
return httpSolrClient.requestWithBaseUrl(baseUrl, (c) ->
c.request(solrRequest, collection));
+ } else if (solrClient instanceof HttpJdkSolrClient) {
+ return ((HttpJdkSolrClient) solrClient).requestWithBaseUrl(baseUrl,
solrRequest, collection);
}
// Assume provided client already uses 'baseUrl'
@@ -730,11 +732,20 @@ public abstract class LBSolrClient extends SolrClient {
if (e.getRootCause() instanceof IOException) {
ex = e;
moveAliveToDead(wrapper);
- if (justFailed == null) justFailed = new HashMap<>();
+ if (justFailed == null) {
+ justFailed = new HashMap<>();
+ }
justFailed.put(endpoint.getUrl(), wrapper);
} else {
throw e;
}
+ } catch (IOException e) {
+ ex = e;
+ moveAliveToDead(wrapper);
+ if (justFailed == null) {
+ justFailed = new HashMap<>();
+ }
+ justFailed.put(endpoint.getUrl(), wrapper);
} catch (Exception e) {
throw new SolrServerException(e);
}
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
index c3fb5609476..b3980ad44bc 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpJdkSolrClientTest.java
@@ -20,10 +20,7 @@ package org.apache.solr.client.solrj.impl;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
-import java.net.Socket;
import java.net.http.HttpClient;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
@@ -31,51 +28,23 @@ import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509ExtendedTrustManager;
import org.apache.lucene.util.NamedThreadFactory;
-import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.RequestWriter;
import org.apache.solr.client.solrj.response.SolrPingResponse;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.util.SSLTestConfig;
import org.junit.After;
-import org.junit.BeforeClass;
import org.junit.Test;
public class HttpJdkSolrClientTest extends HttpSolrClientTestBase {
- private static SSLContext allTrustingSslContext;
-
- @BeforeClass
- public static void beforeClass() {
- try {
- KeyManagerFactory keyManagerFactory =
-
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
- SSLTestConfig stc = SolrTestCaseJ4.sslConfig;
- keyManagerFactory.init(stc.defaultKeyStore(),
stc.defaultKeyStorePassword().toCharArray());
-
- SSLContext sslContext = SSLContext.getInstance("SSL");
- sslContext.init(
- keyManagerFactory.getKeyManagers(),
- new TrustManager[] {MOCK_TRUST_MANAGER},
- stc.notSecureSecureRandom());
- allTrustingSslContext = sslContext;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
@After
public void workaroundToReleaseThreads_noClosableUntilJava21() {
Thread[] threads = new
Thread[Thread.currentThread().getThreadGroup().activeCount()];
@@ -185,6 +154,25 @@ public class HttpJdkSolrClientTest extends
HttpSolrClientTestBase {
}
}
+ @Test
+ public void testRequestWithBaseUrl() throws Exception {
+ DebugServlet.clear();
+ DebugServlet.addResponseHeader("Content-Type", "application/octet-stream");
+ DebugServlet.responseBodyByQueryFragment.put("", javabinResponse());
+ String someOtherUrl = getBaseUrl() + "/some/other/base/url";
+ String intendedUrl = getBaseUrl() + DEBUG_SERVLET_PATH;
+ SolrQuery q = new SolrQuery("foo");
+ q.setParam("a", MUST_ENCODE);
+
+ HttpJdkSolrClient.Builder b =
+ builder(someOtherUrl).withResponseParser(new BinaryResponseParser());
+ try (HttpJdkSolrClient client = b.build()) {
+ client.requestWithBaseUrl(intendedUrl, new QueryRequest(q,
SolrRequest.METHOD.GET), null);
+ assertEquals(
+ client.getParser().getVersion(),
DebugServlet.parameters.get(CommonParams.VERSION)[0]);
+ }
+ }
+
@Test
public void testGetById() throws Exception {
DebugServlet.clear();
@@ -541,7 +529,7 @@ public class HttpJdkSolrClientTest extends
HttpSolrClientTestBase {
.withConnectionTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
.withIdleTimeout(socketTimeout, TimeUnit.MILLISECONDS)
.withDefaultCollection(DEFAULT_CORE)
- .withSSLContext(allTrustingSslContext);
+ .withSSLContext(MockTrustManager.ALL_TRUSTING_SSL_CONTEXT);
return (B) b;
}
@@ -574,52 +562,4 @@ public class HttpJdkSolrClientTest extends
HttpSolrClientTestBase {
+ "6f 6e 21 32 e0 28 72 65 73 "
+ "70 6f 6e 73 65 0c 84 60 60 "
+ "00 01 80";
-
- /**
- * Taken from: https://www.baeldung.com/java-httpclient-ssl sec 4.1,
2024/02/12. This is an
- * all-trusting Trust Manager. Works with self-signed certificates.
- */
- private static final TrustManager MOCK_TRUST_MANAGER =
- new X509ExtendedTrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String
authType, Socket socket)
- throws CertificateException {
- // no-op
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String
authType, Socket socket)
- throws CertificateException {
- // no-op
- }
-
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String
authType, SSLEngine engine)
- throws CertificateException {
- // no-op
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String
authType, SSLEngine engine)
- throws CertificateException {
- // no-op
- }
-
- @Override
- public java.security.cert.X509Certificate[] getAcceptedIssuers() {
- return new java.security.cert.X509Certificate[0];
- }
-
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String
authType)
- throws CertificateException {
- // no-op
- }
-
- @Override
- public void checkServerTrusted(java.security.cert.X509Certificate[]
chain, String authType)
- throws CertificateException {
- // no-op
- }
- };
}
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
similarity index 77%
rename from
solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java
rename to
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
index c3464b73a82..a4cf3292e81 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/TestLBHttp2SolrClient.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
@@ -14,10 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.solr.client.solrj;
+package org.apache.solr.client.solrj.impl;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -29,9 +30,9 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.impl.Http2SolrClient;
-import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
-import org.apache.solr.client.solrj.impl.LBSolrClient;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SolrResponseBase;
import org.apache.solr.common.SolrInputDocument;
@@ -49,12 +50,11 @@ import org.slf4j.LoggerFactory;
*
* @since solr 1.4
*/
-public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
+public class LBHttp2SolrClientIntegrationTest extends SolrTestCaseJ4 {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
SolrInstance[] solr = new SolrInstance[3];
- Http2SolrClient httpClient;
// TODO: fix this test to not require FSDirectory
static String savedFactory;
@@ -79,11 +79,6 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
@Override
public void setUp() throws Exception {
super.setUp();
- httpClient =
- new Http2SolrClient.Builder()
- .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
- .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
- .build();
for (int i = 0; i < solr.length; i++) {
solr[i] =
@@ -119,22 +114,46 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4
{
aSolr.tearDown();
}
}
- httpClient.close();
super.tearDown();
}
+ private LBClientHolder client(LBSolrClient.Endpoint... baseSolrEndpoints) {
+ if (random().nextBoolean()) {
+ var delegateClient =
+ new Http2SolrClient.Builder()
+ .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
+ .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
+ .build();
+ var lbClient =
+ new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+ .withDefaultCollection(solr[0].getDefaultCollection())
+ .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
+ .build();
+ return new LBClientHolder(lbClient, delegateClient);
+ } else {
+ var delegateClient =
+ new HttpJdkSolrClient.Builder()
+ .withConnectionTimeout(1000, TimeUnit.MILLISECONDS)
+ .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
+ .withSSLContext(MockTrustManager.ALL_TRUSTING_SSL_CONTEXT)
+ .build();
+ var lbClient =
+ new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+ .withDefaultCollection(solr[0].getDefaultCollection())
+ .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
+ .build();
+ return new LBClientHolder(lbClient, delegateClient);
+ }
+ }
+
public void testSimple() throws Exception {
final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(solr.length);
- try (LBHttp2SolrClient client =
- new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
- .withDefaultCollection(solr[0].getDefaultCollection())
- .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
- .build()) {
+ try (var h = client(baseSolrEndpoints)) {
SolrQuery solrQuery = new SolrQuery("*:*");
Set<String> names = new HashSet<>();
QueryResponse resp = null;
for (int i = 0; i < solr.length; i++) {
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
assertEquals(10, resp.getResults().getNumFound());
names.add(resp.getResults().get(0).getFieldValue("name").toString());
}
@@ -145,7 +164,7 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
solr[1].jetty = null;
names.clear();
for (int i = 0; i < solr.length; i++) {
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
assertEquals(10, resp.getResults().getNumFound());
names.add(resp.getResults().get(0).getFieldValue("name").toString());
}
@@ -158,7 +177,7 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
Thread.sleep(1200);
names.clear();
for (int i = 0; i < solr.length; i++) {
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
assertEquals(10, resp.getResults().getNumFound());
names.add(resp.getResults().get(0).getFieldValue("name").toString());
}
@@ -168,19 +187,15 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4
{
public void testTwoServers() throws Exception {
final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(2);
- try (LBHttp2SolrClient client =
- new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
- .withDefaultCollection(solr[0].getDefaultCollection())
- .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
- .build()) {
+ try (var h = client(baseSolrEndpoints)) {
SolrQuery solrQuery = new SolrQuery("*:*");
QueryResponse resp = null;
solr[0].jetty.stop();
solr[0].jetty = null;
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
String name = resp.getResults().get(0).getFieldValue("name").toString();
assertEquals("solr/collection11", name);
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
name = resp.getResults().get(0).getFieldValue("name").toString();
assertEquals("solr/collection11", name);
solr[1].jetty.stop();
@@ -188,11 +203,11 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4
{
solr[0].startJetty();
Thread.sleep(1200);
try {
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
} catch (SolrServerException e) {
// try again after a pause in case the error is lack of time to start
server
Thread.sleep(3000);
- resp = client.query(solrQuery);
+ resp = h.lbClient.query(solrQuery);
}
name = resp.getResults().get(0).getFieldValue("name").toString();
assertEquals("solr/collection10", name);
@@ -201,30 +216,28 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4
{
public void testReliability() throws Exception {
final var baseSolrEndpoints = bootstrapBaseSolrEndpoints(solr.length);
-
- try (LBHttp2SolrClient client =
- new LBHttp2SolrClient.Builder(httpClient, baseSolrEndpoints)
- .withDefaultCollection(solr[0].getDefaultCollection())
- .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
- .build()) {
+ try (var h = client(baseSolrEndpoints)) {
// Kill a server and test again
solr[1].jetty.stop();
solr[1].jetty = null;
// query the servers
- for (int i = 0; i < solr.length; i++) client.query(new SolrQuery("*:*"));
+ for (int i = 0; i < solr.length; i++) {
+ h.lbClient.query(new SolrQuery("*:*"));
+ }
// Start the killed server once again
solr[1].startJetty();
// Wait for the alive check to complete
- waitForServer(30, client, 3, solr[1].name);
+ waitForServer(30, h.lbClient, 3, solr[1].name);
}
}
// wait maximum ms for serverName to come back up
private void waitForServer(
- int maxSeconds, LBHttp2SolrClient client, int nServers, String
serverName) throws Exception {
+ int maxSeconds, LBHttp2SolrClient<?> client, int nServers, String
serverName)
+ throws Exception {
final TimeOut timeout = new TimeOut(maxSeconds, TimeUnit.SECONDS,
TimeSource.NANO_TIME);
while (!timeout.hasTimedOut()) {
QueryResponse resp;
@@ -337,8 +350,27 @@ public class TestLBHttp2SolrClient extends SolrTestCaseJ4 {
fail("TESTING FAILURE: could not grab requested port.");
}
this.port = newPort;
- // System.out.println("waiting.........");
- // Thread.sleep(5000);
+ }
+ }
+
+ private static class LBClientHolder implements AutoCloseable {
+
+ final LBHttp2SolrClient<?> lbClient;
+ final HttpSolrClientBase delegate;
+
+ LBClientHolder(LBHttp2SolrClient<?> lbClient, HttpSolrClientBase delegate)
{
+ this.lbClient = lbClient;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void close() {
+ lbClient.close();
+ try {
+ delegate.close();
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
}
}
}
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
index 3a93488e726..9d2019309b0 100644
---
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
@@ -53,8 +53,8 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
try (Http2SolrClient http2SolrClient =
new
Http2SolrClient.Builder(url).withTheseParamNamesInTheUrl(urlParamNames).build();
- LBHttp2SolrClient testClient =
- new LBHttp2SolrClient.Builder(http2SolrClient, new
LBSolrClient.Endpoint(url))
+ LBHttp2SolrClient<Http2SolrClient> testClient =
+ new LBHttp2SolrClient.Builder<>(http2SolrClient, new
LBSolrClient.Endpoint(url))
.build()) {
assertArrayEquals(
@@ -68,6 +68,90 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
}
}
+ @Test
+ public void testSynchronous() throws Exception {
+ LBSolrClient.Endpoint ep1 = new
LBSolrClient.Endpoint("http://endpoint.one");
+ LBSolrClient.Endpoint ep2 = new
LBSolrClient.Endpoint("http://endpoint.two");
+ List<LBSolrClient.Endpoint> endpointList = List.of(ep1, ep2);
+
+ Http2SolrClient.Builder b =
+ new
Http2SolrClient.Builder("http://base.url").withConnectionTimeout(10,
TimeUnit.SECONDS);
+ ;
+ try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url",
b);
+ LBHttp2SolrClient<MockHttpSolrClient> testClient =
+ new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+
+ String lastEndpoint = null;
+ for (int i = 0; i < 10; i++) {
+ String qValue = "Query Number: " + i;
+ QueryRequest queryRequest = new QueryRequest(new
MapSolrParams(Map.of("q", qValue)));
+ LBSolrClient.Req req = new LBSolrClient.Req(queryRequest,
endpointList);
+ LBSolrClient.Rsp response = testClient.request(req);
+
+ String expectedEndpoint =
+ ep1.toString().equals(lastEndpoint) ? ep2.toString() :
ep1.toString();
+ assertEquals(
+ "There should be round-robin load balancing.", expectedEndpoint,
response.server);
+ checkSynchonousResponseContent(response, qValue);
+ }
+ }
+ }
+
+ @Test
+ public void testSynchronousWithFalures() throws Exception {
+ LBSolrClient.Endpoint ep1 = new
LBSolrClient.Endpoint("http://endpoint.one");
+ LBSolrClient.Endpoint ep2 = new
LBSolrClient.Endpoint("http://endpoint.two");
+ List<LBSolrClient.Endpoint> endpointList = List.of(ep1, ep2);
+
+ Http2SolrClient.Builder b =
+ new
Http2SolrClient.Builder("http://base.url").withConnectionTimeout(10,
TimeUnit.SECONDS);
+ ;
+ try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url",
b);
+ LBHttp2SolrClient<MockHttpSolrClient> testClient =
+ new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+
+ client.basePathToFail = ep1.getBaseUrl();
+ String basePathToSucceed = ep2.getBaseUrl();
+ String qValue = "First time";
+
+ for (int i = 0; i < 5; i++) {
+ LBSolrClient.Req req =
+ new LBSolrClient.Req(
+ new QueryRequest(new MapSolrParams(Map.of("q", qValue))),
endpointList);
+ LBSolrClient.Rsp response = testClient.request(req);
+ assertEquals(
+ "The healthy node 'endpoint two' should have served the request: "
+ i,
+ basePathToSucceed,
+ response.server);
+ checkSynchonousResponseContent(response, qValue);
+ }
+
+ client.basePathToFail = ep2.getBaseUrl();
+ basePathToSucceed = ep1.getBaseUrl();
+ qValue = "Second time";
+
+ for (int i = 0; i < 5; i++) {
+ LBSolrClient.Req req =
+ new LBSolrClient.Req(
+ new QueryRequest(new MapSolrParams(Map.of("q", qValue))),
endpointList);
+ LBSolrClient.Rsp response = testClient.request(req);
+ assertEquals(
+ "The healthy node 'endpoint one' should have served the request: "
+ i,
+ basePathToSucceed,
+ response.server);
+ checkSynchonousResponseContent(response, qValue);
+ }
+ }
+ }
+
+ private void checkSynchonousResponseContent(LBSolrClient.Rsp response,
String qValue) {
+ assertEquals("There should be one element in the respnse.", 1,
response.getResponse().size());
+ assertEquals(
+ "The response key 'response' should echo the query.",
+ qValue,
+ response.getResponse().get("response"));
+ }
+
@Test
public void testAsyncWithFailures() {
@@ -81,8 +165,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
Http2SolrClient.Builder b =
new
Http2SolrClient.Builder("http://base.url").withConnectionTimeout(10,
TimeUnit.SECONDS);
;
- try (MockHttp2SolrClient client = new
MockHttp2SolrClient("http://base.url", b);
- LBHttp2SolrClient testClient = new LBHttp2SolrClient.Builder(client,
ep1, ep2).build()) {
+ try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url",
b);
+ LBHttp2SolrClient<MockHttpSolrClient> testClient =
+ new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
for (int j = 0; j < 2; j++) {
// first time Endpoint One will return error code 500.
@@ -144,8 +229,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
Http2SolrClient.Builder b =
new
Http2SolrClient.Builder("http://base.url").withConnectionTimeout(10,
TimeUnit.SECONDS);
- try (MockHttp2SolrClient client = new
MockHttp2SolrClient("http://base.url", b);
- LBHttp2SolrClient testClient = new LBHttp2SolrClient.Builder(client,
ep1, ep2).build()) {
+ try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url",
b);
+ LBHttp2SolrClient<MockHttpSolrClient> testClient =
+ new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
int limit = 10; // For simplicity use an even limit
List<CompletableFuture<LBSolrClient.Rsp>> responses = new ArrayList<>();
@@ -200,7 +286,7 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
}
}
- public static class MockHttp2SolrClient extends Http2SolrClient {
+ public static class MockHttpSolrClient extends Http2SolrClient {
public List<SolrRequest<?>> lastSolrRequests = new ArrayList<>();
@@ -212,13 +298,26 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
public String tmpBaseUrl = null;
- protected MockHttp2SolrClient(String serverBaseUrl, Builder builder) {
+ protected MockHttpSolrClient(String serverBaseUrl, Builder builder) {
+
// TODO: Consider creating an interface for Http*SolrClient
// so mocks can Implement, not Extend, and not actually need to
// build an (unused) client
super(serverBaseUrl, builder);
}
+ @Override
+ public NamedList<Object> request(final SolrRequest<?> request, String
collection)
+ throws SolrServerException, IOException {
+ lastSolrRequests.add(request);
+ lastBasePaths.add(tmpBaseUrl);
+ lastCollections.add(collection);
+ if (tmpBaseUrl.equals(basePathToFail)) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "We
should retry this.");
+ }
+ return generateResponse(request);
+ }
+
@Override
public <R> R requestWithBaseUrl(
String baseUrl, SolrClientFunction<Http2SolrClient, R> clientFunction)
diff --git
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java
new file mode 100644
index 00000000000..c7e1351d01d
--- /dev/null
+++
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/MockTrustManager.java
@@ -0,0 +1,100 @@
+/*
+ * 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.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509ExtendedTrustManager;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.util.SSLTestConfig;
+
+/**
+ * Taken from: https://www.baeldung.com/java-httpclient-ssl sec 4.1,
2024/02/12. This is an
+ * all-trusting Trust Manager. Works with self-signed certificates.
+ */
+public class MockTrustManager extends X509ExtendedTrustManager {
+
+ public static final SSLContext ALL_TRUSTING_SSL_CONTEXT;
+
+ private static final MockTrustManager INSTANCE = new MockTrustManager();
+
+ static {
+ try {
+ KeyManagerFactory keyManagerFactory =
+
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ SSLTestConfig stc = SolrTestCaseJ4.sslConfig;
+ keyManagerFactory.init(stc.defaultKeyStore(),
stc.defaultKeyStorePassword().toCharArray());
+
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(
+ keyManagerFactory.getKeyManagers(),
+ new TrustManager[] {INSTANCE},
+ stc.notSecureSecureRandom());
+ ALL_TRUSTING_SSL_CONTEXT = sslContext;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private MockTrustManager() {}
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType,
Socket socket)
+ throws CertificateException {
+ // no-op
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType,
Socket socket)
+ throws CertificateException {
+ // no-op
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType,
SSLEngine engine)
+ throws CertificateException {
+ // no-op
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType,
SSLEngine engine)
+ throws CertificateException {
+ // no-op
+ }
+
+ @Override
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return new java.security.cert.X509Certificate[0];
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ // no-op
+ }
+
+ @Override
+ public void checkServerTrusted(java.security.cert.X509Certificate[] chain,
String authType)
+ throws CertificateException {
+ // no-op
+ }
+}