SOLR-13723: JettySolrRunner should support /api/* (the v2 end point)

This commit is contained in:
Noble Paul 2019-08-29 14:01:52 +10:00 committed by Noble Paul
parent 7c101fba4a
commit 7d026f803d
7 changed files with 106 additions and 59 deletions

View File

@ -32,6 +32,9 @@ public class JettyConfig {
public final String context;
public final boolean enableV2;
public final boolean stopAtShutdown;
public final Long waitForLoadingCoresToFinishMs;
@ -46,7 +49,7 @@ public class JettyConfig {
private JettyConfig(boolean onlyHttp1, int port, int portRetryTime , String context, boolean stopAtShutdown,
Long waitForLoadingCoresToFinishMs, Map<ServletHolder, String> extraServlets,
Map<Class<? extends Filter>, String> extraFilters, SSLConfig sslConfig) {
Map<Class<? extends Filter>, String> extraFilters, SSLConfig sslConfig, boolean enableV2) {
this.onlyHttp1 = onlyHttp1;
this.port = port;
this.context = context;
@ -56,6 +59,7 @@ public class JettyConfig {
this.extraFilters = extraFilters;
this.sslConfig = sslConfig;
this.portRetryTime = portRetryTime;
this.enableV2 = enableV2;
}
public static Builder builder() {
@ -78,6 +82,7 @@ public class JettyConfig {
boolean onlyHttp1 = false;
int port = 0;
String context = "/solr";
boolean enableV2 = true;
boolean stopAtShutdown = true;
Long waitForLoadingCoresToFinishMs = 300000L;
Map<ServletHolder, String> extraServlets = new TreeMap<>();
@ -89,6 +94,10 @@ public class JettyConfig {
this.onlyHttp1 = useOnlyHttp1;
return this;
}
public Builder enableV2(boolean flag){
this.enableV2 = flag;
return this;
}
public Builder setPort(int port) {
this.port = port;
@ -144,7 +153,8 @@ public class JettyConfig {
public JettyConfig build() {
return new JettyConfig(onlyHttp1, port, portRetryTime, context, stopAtShutdown, waitForLoadingCoresToFinishMs, extraServlets, extraFilters, sslConfig);
return new JettyConfig(onlyHttp1, port, portRetryTime, context, stopAtShutdown,
waitForLoadingCoresToFinishMs, extraServlets, extraFilters, sslConfig, enableV2);
}
}

View File

@ -59,6 +59,8 @@ import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http2.HTTP2Cipher;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.RewritePatternRule;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -83,7 +85,7 @@ import org.slf4j.MDC;
/**
* Run solr using jetty
*
*
* @since solr 1.3
*/
public class JettySolrRunner {
@ -93,7 +95,7 @@ public class JettySolrRunner {
private static final int THREAD_POOL_MAX_THREADS = 10000;
// NOTE: needs to be larger than SolrHttpClient.threadPoolSweeperMaxIdleTime
private static final int THREAD_POOL_MAX_IDLE_TIME_MS = 260000;
Server server;
volatile FilterHolder dispatchFilter;
@ -128,14 +130,14 @@ public class JettySolrRunner {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private AtomicLong nRequests = new AtomicLong();
List<Delay> delays = new ArrayList<>();
public long getTotalRequests() {
return nRequests.get();
}
/**
* Introduce a delay of specified milliseconds for the specified request.
*
@ -146,7 +148,7 @@ public class JettySolrRunner {
public void addDelay(String reason, int count, int delay) {
delays.add(new Delay(reason, count, delay));
}
/**
* Remove any delay introduced before.
*/
@ -167,14 +169,14 @@ public class JettySolrRunner {
@Override
public void destroy() { }
private void executeDelay() {
int delayMs = 0;
for (Delay delay: delays) {
this.log.info("Delaying "+delay.delayValue+", for reason: "+delay.reason);
if (delay.counter.decrementAndGet() == 0) {
delayMs += delay.delayValue;
}
}
}
if (delayMs > 0) {
@ -215,7 +217,7 @@ public class JettySolrRunner {
public JettySolrRunner(String solrHome, JettyConfig config) {
this(solrHome, new Properties(), config);
}
/**
* Construct a JettySolrRunner
*
@ -244,7 +246,7 @@ public class JettySolrRunner {
this.solrHome = solrHome;
this.config = config;
this.nodeProperties = nodeProperties;
if (enableProxy) {
try {
proxy = new SocketProxy(0, config.sslConfig != null && config.sslConfig.isSSLMode());
@ -256,7 +258,7 @@ public class JettySolrRunner {
this.init(this.config.port);
}
private void init(int port) {
QueuedThreadPool qtp = new QueuedThreadPool();
@ -275,7 +277,7 @@ public class JettySolrRunner {
//
// This means we will use the same truststore, keystore (and keys) for
// the server as well as any client actions taken by this JVM in
// talking to that server, but for the purposes of testing that should
// talking to that server, but for the purposes of testing that should
// be good enough
final SslContextFactory sslcontext = SSLConfig.createContextFactory(config.sslConfig);
@ -382,7 +384,7 @@ public class JettySolrRunner {
dispatchFilter.setHeldClass(SolrDispatchFilter.class);
dispatchFilter.setInitParameter("excludePatterns", excludePatterns);
root.addFilter(dispatchFilter, "*", EnumSet.of(DispatcherType.REQUEST));
synchronized (JettySolrRunner.this) {
waitOnSolr = true;
JettySolrRunner.this.notify();
@ -400,7 +402,16 @@ public class JettySolrRunner {
}
chain = injectJettyHandlers(chain);
if(config.enableV2) {
RewriteHandler rwh = new RewriteHandler();
rwh.setHandler(chain);
rwh.setRewriteRequestURI(true);
rwh.setRewritePathInfo(false);
rwh.setOriginalPathAttribute("requestedPath");
rwh.addRule(new RewritePatternRule("/api/*", "/solr/____v2"));
chain = rwh;
}
GzipHandler gzipHandler = new GzipHandler();
gzipHandler.setHandler(chain);
@ -413,7 +424,7 @@ public class JettySolrRunner {
server.setHandler(gzipHandler);
}
/** descendants may inject own handler chaining it to the given root
/** descendants may inject own handler chaining it to the given root
* and then returning that own one*/
protected HandlerWrapper injectJettyHandlers(HandlerWrapper chain) {
return chain;
@ -445,7 +456,7 @@ public class JettySolrRunner {
public boolean isRunning() {
return server.isRunning() && dispatchFilter != null && dispatchFilter.isRunning();
}
public boolean isStopped() {
return (server.isStopped() && dispatchFilter == null) || (server.isStopped() && dispatchFilter.isStopped()
&& ((QueuedThreadPool) server.getThreadPool()).isStopped());
@ -478,12 +489,12 @@ public class JettySolrRunner {
// Do not let Jetty/Solr pollute the MDC for this thread
Map<String, String> prevContext = MDC.getCopyOfContextMap();
MDC.clear();
log.info("Start Jetty (original configured port={})", this.config.port);
try {
int port = reusePort && jettyPort != -1 ? jettyPort : this.config.port;
// if started before, make a new server
if (startedBefore) {
waitOnSolr = false;
@ -508,21 +519,21 @@ public class JettySolrRunner {
}
}
}
if (config.waitForLoadingCoresToFinishMs != null && config.waitForLoadingCoresToFinishMs > 0L) {
waitForLoadingCoresToFinish(config.waitForLoadingCoresToFinishMs);
}
setProtocolAndHost();
if (enableProxy) {
if (started) {
proxy.reopen();
} else {
proxy.open(getBaseUrl().toURI());
}
}
}
} finally {
started = true;
if (prevContext != null) {
@ -548,7 +559,7 @@ public class JettySolrRunner {
this.protocol = protocol;
this.host = c.getHost();
}
private void retryOnPortBindFailure(int portRetryTime, int port) throws Exception, InterruptedException {
TimeOut timeout = new TimeOut(portRetryTime, TimeUnit.SECONDS, TimeSource.NANO_TIME);
int tryCnt = 1;
@ -567,7 +578,7 @@ public class JettySolrRunner {
continue;
}
}
throw e;
}
}
@ -628,7 +639,7 @@ public class JettySolrRunner {
QueuedThreadPool qtp = (QueuedThreadPool) server.getThreadPool();
ReservedThreadExecutor rte = qtp.getBean(ReservedThreadExecutor.class);
server.stop();
if (server.getState().equals(Server.FAILED)) {
@ -647,18 +658,18 @@ public class JettySolrRunner {
Thread.sleep(50);
}
}
// we tried to kill everything, now we wait for executor to stop
qtp.setStopTimeout(Integer.MAX_VALUE);
qtp.stop();
qtp.join();
if (rte != null) {
// we try and wait for the reserved thread executor, but it doesn't always seem to work
// so we actually set 0 reserved threads at creation
rte.stop();
TimeOut timeout = new TimeOut(30, TimeUnit.SECONDS, TimeSource.NANO_TIME);
timeout.waitFor("Timeout waiting for reserved executor to stop.", ()
-> rte.isStopped());
@ -675,12 +686,12 @@ public class JettySolrRunner {
// ignore
}
} while (!server.isStopped());
} finally {
if (enableProxy) {
proxy.close();
}
if (prevContext != null) {
MDC.setContextMap(prevContext);
} else {
@ -691,7 +702,7 @@ public class JettySolrRunner {
/**
* Returns the Local Port of the jetty Server.
*
*
* @exception RuntimeException if there is no Connector
*/
private int getFirstConnectorPort() {
@ -701,22 +712,22 @@ public class JettySolrRunner {
}
return ((ServerConnector) conns[0]).getLocalPort();
}
/**
* Returns the Local Port of the jetty Server.
*
*
* @exception RuntimeException if there is no Connector
*/
public int getLocalPort() {
return getLocalPort(false);
}
/**
* Returns the Local Port of the jetty Server.
*
*
* @param internalPort pass true to get the true jetty port rather than the proxy port if configured
*
*
* @exception RuntimeException if there is no Connector
*/
public int getLocalPort(boolean internalPort) {
@ -728,7 +739,7 @@ public class JettySolrRunner {
}
return (proxyPort != -1) ? proxyPort : jettyPort;
}
/**
* Sets the port of a local socket proxy that sits infront of this server; if set
* then all client traffic will flow through the proxy, giving us the ability to
@ -737,7 +748,7 @@ public class JettySolrRunner {
public void setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
}
/**
* Returns a base URL consisting of the protocol, host, and port for a
* Connector in use by the Jetty Server contained in this runner.
@ -764,7 +775,7 @@ public class JettySolrRunner {
public SolrClient newClient() {
return new HttpSolrClient.Builder(getBaseUrl().toString()).build();
}
public SolrClient newClient(int connectionTimeoutMillis, int socketTimeoutMillis) {
return new HttpSolrClient.Builder(getBaseUrl().toString())
.withConnectionTimeout(connectionTimeoutMillis)
@ -793,13 +804,9 @@ public class JettySolrRunner {
/**
* A main class that starts jetty+solr This is useful for debugging
*/
public static void main(String[] args) {
try {
JettySolrRunner jetty = new JettySolrRunner(".", "/solr", 8983);
jetty.start();
} catch (Exception ex) {
ex.printStackTrace();
}
public static void main(String[] args) throws Exception {
JettySolrRunner jetty = new JettySolrRunner(".", "/solr", 8983);
jetty.start();
}
/**
@ -829,12 +836,12 @@ public class JettySolrRunner {
throw new IllegalStateException("The dispatchFilter is not set!");
}
}
static class Delay {
final AtomicInteger counter;
final int delayValue;
final String reason;
public Delay(String reason, int counter, int delay) {
this.reason = reason;
this.counter = new AtomicInteger(counter);

View File

@ -364,7 +364,7 @@ public class HttpSolrClient extends BaseHttpSolrClient {
basePath += "/" + collection;
if (request instanceof V2Request) {
if (System.getProperty("solr.v2RealPath") == null) {
if (System.getProperty("solr.v2RealPath") == null || ((V2Request) request).isForceV2()) {
basePath = baseUrl.replace("/solr", "/api");
} else {
basePath = baseUrl + "/____v2";

View File

@ -42,6 +42,7 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
private SolrParams solrParams;
public final boolean useBinary;
private String collection;
private boolean forceV2 = false;
private boolean isPerCollectionRequest = false;
private V2Request(METHOD m, String resource, boolean useBinary) {
@ -55,6 +56,10 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
}
public boolean isForceV2(){
return forceV2;
}
@Override
public SolrParams getParams() {
return solrParams;
@ -113,6 +118,8 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
private SolrParams params;
private boolean useBinary = false;
private boolean forceV2EndPoint = false;
/**
* Create a Builder object based on the provided resource.
* The default method is GET.
@ -129,8 +136,17 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
return this;
}
/**
* Only for testing. It's always true otherwise
*/
public Builder forceV2(boolean flag) {
forceV2EndPoint = flag;
return this;
}
/**
* Set payload for request.
*
* @param payload as UTF-8 String
* @return builder object
*/
@ -161,6 +177,7 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
V2Request v2Request = new V2Request(method, resource, useBinary);
v2Request.solrParams = params;
v2Request.payload = payload;
v2Request.forceV2 = forceV2EndPoint;
return v2Request;
}
}

View File

@ -42,9 +42,20 @@ public class TestV2Request extends SolrCloudTestCase {
@Before
public void setupCluster() throws Exception {
configureCluster(4)
.withJettyConfig(jettyCfg -> jettyCfg.enableV2(true))
.addConfig("config", getFile("solrj/solr/collection1/conf").toPath())
.configure();
}
public void testApiPathAvailability() throws Exception {
V2Response rsp = new V2Request.Builder("/cluster/nodes")
.forceV2(true)
.withMethod(SolrRequest.METHOD.GET).build()
.process(cluster.getSolrClient());
List l = (List) rsp._get("nodes",null);
assertNotNull(l);
assertFalse(l.isEmpty());
}
@After
public void afterTest() throws Exception {

View File

@ -786,7 +786,7 @@ public class MiniSolrCloudCluster {
if (activeReplicas == expectedReplicas) {
return true;
}
return false;
};
}

View File

@ -32,6 +32,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.solr.SolrTestCaseJ4;
@ -106,7 +107,7 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
private final int nodeCount;
private final Path baseDir;
private String solrxml = MiniSolrCloudCluster.DEFAULT_CLOUD_SOLR_XML;
private JettyConfig jettyConfig = buildJettyConfig("/solr");
private JettyConfig.Builder jettyConfigBuilder = JettyConfig.builder().setContext("/solr").withSSLConfig(sslConfig.buildServerSSLConfig());
private Optional<String> securityJson = Optional.empty();
private List<Config> configs = new ArrayList<>();
@ -126,10 +127,10 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
}
/**
* Use a {@link JettyConfig} to configure the cluster's jetty servers
* Use a JettyConfig.Builder to configure the cluster's jetty servers
*/
public Builder withJettyConfig(JettyConfig jettyConfig) {
this.jettyConfig = jettyConfig;
public Builder withJettyConfig(Consumer<JettyConfig.Builder> fun) {
fun.accept(jettyConfigBuilder);
return this;
}
@ -226,6 +227,7 @@ public class SolrCloudTestCase extends SolrTestCaseJ4 {
* @throws Exception if an error occurs on startup
*/
public MiniSolrCloudCluster build() throws Exception {
JettyConfig jettyConfig = jettyConfigBuilder.build();
MiniSolrCloudCluster cluster = new MiniSolrCloudCluster(nodeCount, baseDir, solrxml, jettyConfig,
null, securityJson, trackJettyMetrics);
CloudSolrClient client = cluster.getSolrClient();