SOLR-9604,SOLR-9608: Ensure SSL connections are re-used

Fix ConnectionReuseTest. Add coverage for all SolrClients.
Remove explicit cacheKey for HttpRequestContext, make it singleton.
This commit is contained in:
Alan Woodward 2016-10-05 10:22:22 +01:00 committed by Mikhail Khludnev
parent 0414570348
commit f22b1da261
13 changed files with 290 additions and 41 deletions

View File

@ -160,6 +160,9 @@ Bug Fixes
* SOLR-9278: Index replication interactions with IndexWriter can cause deadlock. (Xunlong via Mark Miller)
* SOLR-9604: Pooled SSL connections were not being re-used (Alan Woodward,
Mikhail Khludnev, hossman)
Optimizations
----------------------

View File

@ -16,8 +16,11 @@
*/
package org.apache.solr.security;
import static java.nio.charset.StandardCharsets.UTF_8;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
@ -27,12 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
@ -58,6 +55,8 @@ import org.apache.solr.util.CryptoKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
public class PKIAuthenticationPlugin extends AuthenticationPlugin implements HttpClientBuilderPlugin {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@ -198,7 +197,8 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
try {
String uri = url + PATH + "?wt=json&omitHeader=true";
log.debug("Fetching fresh public key from : {}",uri);
HttpResponse rsp = cores.getUpdateShardHandler().getHttpClient().execute(new HttpGet(uri), HttpClientUtil.createNewHttpClientRequestContext());
HttpResponse rsp = cores.getUpdateShardHandler().getHttpClient()
.execute(new HttpGet(uri), HttpClientUtil.createNewHttpClientRequestContext());
byte[] bytes = EntityUtils.toByteArray(rsp.getEntity());
Map m = (Map) Utils.fromJSON(bytes);
String key = (String) m.get("key");

View File

@ -558,7 +558,8 @@ public class HttpSolrCall {
method.removeHeaders(CONTENT_LENGTH_HEADER);
}
final HttpResponse response = solrDispatchFilter.httpClient.execute(method, HttpClientUtil.createNewHttpClientRequestContext());
final HttpResponse response
= solrDispatchFilter.httpClient.execute(method, HttpClientUtil.createNewHttpClientRequestContext());
int httpStatus = response.getStatusLine().getStatusCode();
httpEntity = response.getEntity();

View File

@ -14,7 +14,7 @@
* 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.IOException;
import java.net.URL;
@ -36,12 +36,10 @@ import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHttpRequest;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.util.TestInjection;
@ -134,7 +132,7 @@ public class ConnectionReuseTest extends SolrCloudTestCase {
route = new HttpRoute(new HttpHost(url.getHost(), url.getPort(), isSSLMode() ? "https" : "http"));
mConn = cm.requestConnection(route, null);
mConn = cm.requestConnection(route, HttpSolrClient.cacheKey);
HttpClientConnection conn2 = getConn(mConn);
@ -190,7 +188,7 @@ public class ConnectionReuseTest extends SolrCloudTestCase {
}
public ConnectionRequest getClientConnectionRequest(HttpClient httpClient, HttpRoute route, PoolingHttpClientConnectionManager cm) {
ConnectionRequest mConn = cm.requestConnection(route, null);
ConnectionRequest mConn = cm.requestConnection(route, HttpSolrClient.cacheKey);
return mConn;
}

View File

@ -320,7 +320,8 @@ public class ConcurrentUpdateSolrClient extends SolrClient {
method.addHeader("User-Agent", HttpSolrClient.AGENT);
method.addHeader("Content-Type", contentType);
response = client.getHttpClient().execute(method, HttpClientUtil.createNewHttpClientRequestContext());
response = client.getHttpClient()
.execute(method, HttpClientUtil.createNewHttpClientRequestContext());
rspBody = response.getEntity().getContent();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {

View File

@ -198,7 +198,12 @@ public class HttpClientUtil {
* configuration (no additional configuration) is created.
*/
public static CloseableHttpClient createClient(SolrParams params) {
return createClient(params, new PoolingHttpClientConnectionManager(schemaRegistryProvider.getSchemaRegistry()));
return createClient(params, createPoolingConnectionManager());
}
/** test usage subject to change @lucene.experimental */
static PoolingHttpClientConnectionManager createPoolingConnectionManager() {
return new PoolingHttpClientConnectionManager(schemaRegistryProvider.getSchemaRegistry());
}
public static CloseableHttpClient createClient(SolrParams params, PoolingHttpClientConnectionManager cm) {
@ -396,10 +401,15 @@ public class HttpClientUtil {
}
/**
*
* Create a HttpClientContext object and {@link HttpClientContext#setUserToken(Object)}
* to an internal singleton. It allows to reuse underneath {@link HttpClient}
* in connection pools if client authentication is enabled.
*/
public static HttpClientContext createNewHttpClientRequestContext() {
return httpClientRequestContextBuilder.createContext();
HttpClientContext context = new HttpClientContext();
context.setUserToken(HttpSolrClient.cacheKey);
return context;
}
public static Builder createDefaultRequestConfigBuilder() {

View File

@ -47,6 +47,7 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
@ -111,6 +112,8 @@ public class HttpSolrClient extends SolrClient {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
static final Class<HttpSolrClient> cacheKey = HttpSolrClient.class;
/**
* The URL of the Solr server.
*/
@ -508,7 +511,8 @@ public class HttpSolrClient extends SolrClient {
boolean shouldClose = true;
try {
// Execute the method.
final HttpResponse response = httpClient.execute(method, HttpClientUtil.createNewHttpClientRequestContext());
HttpClientContext httpClientRequestContext = HttpClientUtil.createNewHttpClientRequestContext();
final HttpResponse response = httpClient.execute(method, httpClientRequestContext);
int httpStatus = response.getStatusLine().getStatusCode();
// Read the contents

View File

@ -76,8 +76,16 @@ public class SolrHttpClientContextBuilder {
public CredentialsProviderProvider getCredentialsProviderProvider() {
return credentialsProviderProvider;
}
/**
* @deprecated use {@link #createContext(Object)}
*/
@Deprecated
public HttpClientContext createContext() {
return createContext(null);
}
public HttpClientContext createContext(Object userToken) {
HttpClientContext context = new HttpClientContext();
if (getCredentialsProviderProvider() != null) {
context.setCredentialsProvider(getCredentialsProviderProvider().getCredentialsProvider());
@ -89,6 +97,8 @@ public class SolrHttpClientContextBuilder {
if (getCookieSpecRegistryProvider() != null) {
context.setCookieSpecRegistry(getCookieSpecRegistryProvider().getCookieSpecRegistry());
}
context.setUserToken(userToken);
return context;
}

View File

@ -16,6 +16,14 @@
*/
package org.apache.solr.client.solrj;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@ -32,14 +40,6 @@ import org.apache.solr.util.ExternalPaths;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
public class SolrSchemalessExampleTest extends SolrExampleTestsBase {
@BeforeClass

View File

@ -16,6 +16,9 @@
*/
package org.apache.solr.client.solrj.embedded;
import java.io.ByteArrayInputStream;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
@ -32,9 +35,6 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.noggit.ObjectBuilder;
import java.io.ByteArrayInputStream;
import java.util.Map;
/**
* TODO? perhaps use:
* http://docs.codehaus.org/display/JETTY/ServletTester

View File

@ -0,0 +1,182 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.SolrjNamedThreadFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
public class HttpSolrClientConPoolTest extends SolrJettyTestBase {
protected static JettySolrRunner yetty;
private static String fooUrl;
private static String barUrl;
@BeforeClass
public static void beforeTest() throws Exception {
createJetty(legacyExampleCollection1SolrHome());
// stealing the first made jetty
yetty = jetty;
barUrl = yetty.getBaseUrl().toString() + "/" + "collection1";
createJetty(legacyExampleCollection1SolrHome());
fooUrl = jetty.getBaseUrl().toString() + "/" + "collection1";
}
@AfterClass
public static void stopYetty() throws Exception {
yetty.stop();
yetty = null;
}
public void testPoolSize() throws SolrServerException, IOException {
PoolingHttpClientConnectionManager pool = HttpClientUtil.createPoolingConnectionManager();
final HttpSolrClient client1 ;
final String fooUrl;
{
fooUrl = jetty.getBaseUrl().toString() + "/" + "collection1";
CloseableHttpClient httpClient = HttpClientUtil.createClient(new ModifiableSolrParams(), pool,
false /* let client shutdown it*/);
client1 = getHttpSolrClient(fooUrl, httpClient);
client1.setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
}
final String barUrl = yetty.getBaseUrl().toString() + "/" + "collection1";
List<String> urls = new ArrayList<>();
for(int i=0; i<17; i++) {
urls.add(fooUrl);
}
for(int i=0; i<31; i++) {
urls.add(barUrl);
}
Collections.shuffle(urls, random());
try {
int i=0;
for (String url : urls) {
if (!client1.getBaseURL().equals(url)) {
client1.setBaseURL(url);
}
client1.add(new SolrInputDocument("id", ""+(i++)));
}
client1.setBaseURL(fooUrl);
client1.commit();
assertEquals(17, client1.query(new SolrQuery("*:*")).getResults().getNumFound());
client1.setBaseURL(barUrl);
client1.commit();
assertEquals(31, client1.query(new SolrQuery("*:*")).getResults().getNumFound());
PoolStats stats = pool.getTotalStats();
assertEquals("oh "+stats, 2, stats.getAvailable());
} finally {
for (HttpSolrClient c : new HttpSolrClient []{ client1}) {
HttpClientUtil.close(c.getHttpClient());
c.close();
}
}
}
public void testLBClient() throws IOException, SolrServerException {
PoolingHttpClientConnectionManager pool = HttpClientUtil.createPoolingConnectionManager();
final HttpSolrClient client1 ;
int threadCount = atLeast(2);
final ExecutorService threads = ExecutorUtil.newMDCAwareFixedThreadPool(threadCount,
new SolrjNamedThreadFactory(getClass().getSimpleName()+"TestScheduler"));
CloseableHttpClient httpClient = HttpClientUtil.createClient(new ModifiableSolrParams(), pool);
try{
final LBHttpSolrClient roundRobin = new LBHttpSolrClient.Builder().
withBaseSolrUrl(fooUrl).
withBaseSolrUrl(barUrl).
withHttpClient(httpClient)
.build();
List<ConcurrentUpdateSolrClient> concurrentClients = Arrays.asList(
new ConcurrentUpdateSolrClient.Builder(fooUrl)
.withHttpClient(httpClient).withThreadCount(threadCount)
.withQueueSize(10)
.withExecutorService(threads).build(),
new ConcurrentUpdateSolrClient.Builder(barUrl)
.withHttpClient(httpClient).withThreadCount(threadCount)
.withQueueSize(10)
.withExecutorService(threads).build());
for (int i=0; i<2; i++) {
roundRobin.deleteByQuery("*:*");
}
for (int i=0; i<57; i++) {
final SolrInputDocument doc = new SolrInputDocument("id", ""+i);
if (random().nextBoolean()) {
final ConcurrentUpdateSolrClient concurrentClient = concurrentClients.get(random().nextInt(concurrentClients.size()));
concurrentClient.add(doc); // here we are testing that CUSC and plain clients reuse pool
concurrentClient.blockUntilFinished();
} else {
if (random().nextBoolean()) {
roundRobin.add(doc);
} else {
final UpdateRequest updateRequest = new UpdateRequest();
updateRequest.add(doc); // here we mimic CloudSolrClient impl
final List<String> urls = Arrays.asList(fooUrl, barUrl);
Collections.shuffle(urls, random());
LBHttpSolrClient.Req req = new LBHttpSolrClient.Req(updateRequest,
urls);
roundRobin.request(req);
}
}
}
for (int i=0; i<2; i++) {
roundRobin.commit();
}
int total=0;
for (int i=0; i<2; i++) {
total += roundRobin.query(new SolrQuery("*:*")).getResults().getNumFound();
}
assertEquals(57, total);
PoolStats stats = pool.getTotalStats();
//System.out.println("\n"+stats);
assertEquals("expected number of connections shouldn't exceed number of endpoints" + stats,
2, stats.getAvailable());
}finally {
threads.shutdown();
HttpClientUtil.close(httpClient);
}
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.URL;
import java.util.Arrays;
import org.apache.solr.util.RandomizeSSL;
import org.junit.BeforeClass;
@RandomizeSSL(1.0)
public class HttpSolrClientSSLAuthConPoolTest extends HttpSolrClientConPoolTest {
@BeforeClass
public static void checkUrls() throws Exception {
URL[] urls = new URL[] {
jetty.getBaseUrl(), yetty.getBaseUrl()
};
for (URL u : urls) {
assertEquals("expect https urls ","https", u.getProtocol());
}
assertFalse("expect different urls "+Arrays.toString(urls),
urls[0].equals(urls[1]));
}
}

View File

@ -15,6 +15,13 @@
* limitations under the License.
*/
package org.apache.solr.util;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import java.io.Closeable;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
@ -28,13 +35,6 @@ import org.apache.http.util.EntityUtils;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.common.params.ModifiableSolrParams;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import java.io.Closeable;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* Facilitates testing Solr's REST API via a provided embedded Jetty
*/