This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 27b8abbb6eb14f52cd49f055bee61179d342021f Author: David Smiley <[email protected]> AuthorDate: Sun Apr 12 11:37:45 2026 -0400 SOLR-18188: RestTestHarness overhaul, add URLUtil.buildURI (#4270) Refactors RestTestHarness to be immutable and use Jetty HttpClient instead of Apache HttpClient. Introduces URLUtil.buildURI helper, used by RestTestHarness and a backup test. Co-authored-by: Claude Sonnet 4.6 <[email protected]> --- solr/core/build.gradle | 1 - .../apache/solr/core/TestConfigSetImmutable.java | 5 - .../org/apache/solr/core/TestCustomStream.java | 4 - .../solr/core/TestSetPropertyConfigApis.java | 19 +- .../apache/solr/core/TestSolrConfigHandler.java | 295 +++++---------------- .../org/apache/solr/handler/TestReqParamsAPI.java | 76 ++---- .../solr/handler/TestSolrConfigHandlerCloud.java | 48 +--- .../handler/TestSolrConfigHandlerConcurrent.java | 53 ++-- .../test/org/apache/solr/rest/TestRestManager.java | 5 + .../apache/solr/rest/schema/TestBulkSchemaAPI.java | 4 - .../analysis/TestManagedStopFilterFactory.java | 5 - .../TestManagedSynonymGraphFilterFactory.java | 5 - .../solr/schema/TestUseDocValuesAsStored2.java | 5 - .../solr/languagemodels/TestLanguageModelBase.java | 5 +- .../store/rest/TestModelManagerPersistence.java | 6 +- .../org/apache/solr/ltr/TestLTROnSolrCloud.java | 5 +- .../test/org/apache/solr/ltr/TestRerankBase.java | 9 +- .../store/rest/TestModelManagerPersistence.java | 9 +- .../java/org/apache/solr/common/util/URLUtil.java | 106 ++++++++ .../solr/client/solrj/request/SchemaTest.java | 4 - .../org/apache/solr/common/util/URLUtilTest.java | 156 +++++++++++ .../solr/cloud/AbstractFullDistribZkTestBase.java | 10 +- .../org/apache/solr/embedded/JettySolrRunner.java | 33 ++- .../apache/solr/handler/BackupRestoreUtils.java | 52 ++-- .../java/org/apache/solr/util/RestTestBase.java | 33 +-- .../java/org/apache/solr/util/RestTestHarness.java | 206 ++++++++------ 26 files changed, 584 insertions(+), 575 deletions(-) diff --git a/solr/core/build.gradle b/solr/core/build.gradle index f1bb21be4aa..819ad44f645 100644 --- a/solr/core/build.gradle +++ b/solr/core/build.gradle @@ -203,7 +203,6 @@ dependencies { exclude group: "net.bytebuddy", module: "byte-buddy-agent" }) testImplementation libs.apache.httpcomponents.httpclient - testImplementation libs.apache.httpcomponents.httpcore testImplementation libs.opentelemetry.sdk.testing testImplementation libs.dropwizard.metrics.core diff --git a/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java b/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java index 78719fe9fe4..4d456a2f826 100644 --- a/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java +++ b/solr/core/src/test/org/apache/solr/core/TestConfigSetImmutable.java @@ -61,11 +61,6 @@ public class TestConfigSetImmutable extends RestTestBase { @After public void after() throws Exception { solrTestRule.reset(); - - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } @Test diff --git a/solr/core/src/test/org/apache/solr/core/TestCustomStream.java b/solr/core/src/test/org/apache/solr/core/TestCustomStream.java index 96fc61f165f..73c8afc93de 100644 --- a/solr/core/src/test/org/apache/solr/core/TestCustomStream.java +++ b/solr/core/src/test/org/apache/solr/core/TestCustomStream.java @@ -36,18 +36,14 @@ public class TestCustomStream extends AbstractFullDistribZkTestBase { TestSolrConfigHandler.runConfigCommand(client, "/config", payload); TestSolrConfigHandler.testForResponseElement( client, - null, "/config/overlay", - null, Arrays.asList("overlay", "expressible", "hello", "class"), "org.apache.solr.core.HelloStream", 10); TestSolrConfigHandler.testForResponseElement( client, - null, "/stream?expr=hello()", - null, Arrays.asList("result-set", "docs[0]", "msg"), "Hello World!", 10); diff --git a/solr/core/src/test/org/apache/solr/core/TestSetPropertyConfigApis.java b/solr/core/src/test/org/apache/solr/core/TestSetPropertyConfigApis.java index eb60c3f8347..54c30f7d75f 100644 --- a/solr/core/src/test/org/apache/solr/core/TestSetPropertyConfigApis.java +++ b/solr/core/src/test/org/apache/solr/core/TestSetPropertyConfigApis.java @@ -290,15 +290,13 @@ public class TestSetPropertyConfigApis extends SolrCloudTestCase { private static void setUserProperties( final String collectionName, final Map<String, String> props) throws Exception { - try (RestTestHarness harness = makeRestHarness(collectionName)) { - final String cmd = - "{ 'set-user-property' : { " - + props.entrySet().stream() - .map(e -> "'" + e.getKey() + "':'" + e.getValue() + "'") - .collect(Collectors.joining(",")) - + "}} "; - runConfigCommand(harness, cmd); - } + final String cmd = + "{ 'set-user-property' : { " + + props.entrySet().stream() + .map(e -> "'" + e.getKey() + "':'" + e.getValue() + "'") + .collect(Collectors.joining(",")) + + "}} "; + runConfigCommand(makeRestHarness(collectionName), cmd); } /** @@ -345,8 +343,7 @@ public class TestSetPropertyConfigApis extends SolrCloudTestCase { } private static RestTestHarness makeRestHarness(final String collectionName) { - return new RestTestHarness( - () -> cluster.getRandomJetty(random()).getBaseUrl().toString() + "/" + collectionName); + return cluster.getRandomJetty(random()).getRestClient(collectionName); } private static void runConfigCommand(RestTestHarness harness, String json) throws IOException { diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java index 8649327aa60..4cb5b8889b6 100644 --- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java +++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java @@ -33,7 +33,6 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import org.apache.commons.io.file.PathUtils; import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.common.LinkedHashMapWriter; import org.apache.solr.common.MapWriter; import org.apache.solr.common.util.StrUtils; @@ -41,11 +40,9 @@ import org.apache.solr.common.util.TimeSource; import org.apache.solr.common.util.Utils; import org.apache.solr.common.util.ValidatingJsonMap; import org.apache.solr.handler.DumpRequestHandler; -import org.apache.solr.handler.TestSolrConfigHandlerConcurrent; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.search.SolrCache; -import org.apache.solr.util.RESTfulServerProvider; import org.apache.solr.util.RestTestBase; import org.apache.solr.util.RestTestHarness; import org.apache.solr.util.TimeOut; @@ -91,24 +88,19 @@ public class TestSolrConfigHandler extends RestTestBase { createJettyAndHarness(tmpSolrHome, "solrconfig-managed-schema.xml", "schema-rest.xml"); if (random().nextBoolean()) { log.info("These tests are run with V2 API"); - restTestHarness.setServerProvider( - () -> getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME); + restTestHarness = + restTestHarness.newWithUrl(getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME); } } @After public void after() throws Exception { solrTestRule.reset(); - - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } public void testProperty() throws Exception { RestTestHarness harness = restTestHarness; - MapWriter confMap = getRespMap("/config", harness); + MapWriter confMap = getRespMap(harness, "/config"); assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/luke"), null)); assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/info"), null)); assertNotNull(confMap._get(asList("config", "requestHandler", "/admin/file"), null)); @@ -120,16 +112,16 @@ public class TestSolrConfigHandler extends RestTestBase { + " }"; runConfigCommand(harness, "/config", payload); - MapWriter m = getRespMap("/config/overlay", harness); + MapWriter m = getRespMap(harness, "/config/overlay"); assertEquals("100", m._getStr("overlay/props/updateHandler/autoCommit/maxDocs")); assertEquals("10", m._getStr("overlay/props/updateHandler/autoCommit/maxTime")); - m = getRespMap("/config/updateHandler", harness); + m = getRespMap(harness, "/config/updateHandler"); assertNotNull(m._get("config/updateHandler/commitWithin/softCommit")); assertNotNull(m._get("config/updateHandler/autoCommit/maxDocs")); assertNotNull(m._get("config/updateHandler/autoCommit/maxTime")); - m = getRespMap("/config", harness); + m = getRespMap(harness, "/config"); assertNotNull(m); assertEquals("100", m._getStr("config/updateHandler/autoCommit/maxDocs")); @@ -138,7 +130,7 @@ public class TestSolrConfigHandler extends RestTestBase { payload = "{\n" + " 'unset-property' : 'updateHandler.autoCommit.maxDocs' \n" + " }"; runConfigCommand(harness, "/config", payload); - m = getRespMap("/config/overlay", harness); + m = getRespMap(harness, "/config/overlay"); assertNull(m._get("overlay/props/updateHandler/autoCommit/maxDocs")); assertEquals("10", m._getStr("overlay/props/updateHandler/autoCommit/maxTime")); } @@ -152,18 +144,18 @@ public class TestSolrConfigHandler extends RestTestBase { + " }"; runConfigCommand(harness, "/config", payload); - MapWriter m = getRespMap("/config/overlay", harness); // .get("overlay"); + MapWriter m = getRespMap(harness, "/config/overlay"); // .get("overlay"); assertEquals(m._get("overlay/userProps/my.custom.variable.a"), "MODIFIEDA"); assertEquals(m._get("overlay/userProps/my.custom.variable.b"), "MODIFIEDB"); - m = getRespMap("/dump?json.nl=map&initArgs=true", harness); // .get("initArgs"); + m = getRespMap(harness, "/dump?json.nl=map&initArgs=true"); // .get("initArgs"); assertEquals("MODIFIEDA", m._get("initArgs/defaults/a")); assertEquals("MODIFIEDB", m._get("initArgs/defaults/b")); } public void testReqHandlerAPIs() throws Exception { - reqhandlertests(restTestHarness, null, null); + reqhandlertests(restTestHarness); } public static void runConfigCommand(RestTestHarness harness, String uri, String payload) @@ -195,9 +187,7 @@ public class TestSolrConfigHandler extends RestTestBase { errorMessages.get(0).toString().contains(expectedErrorMessage)); } - public static void reqhandlertests( - RestTestHarness writeHarness, String testServerBaseUrl, CloudSolrClient cloudSolrClient) - throws Exception { + public static void reqhandlertests(RestTestHarness writeHarness) throws Exception { String payload = "{\n" + "'create-requesthandler' : { 'name' : '/x', 'class': 'org.apache.solr.handler.DumpRequestHandler' , 'startup' : 'lazy'}\n" @@ -206,9 +196,7 @@ public class TestSolrConfigHandler extends RestTestBase { testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudSolrClient, asList("overlay", "requestHandler", "/x", "startup"), "lazy", TIMEOUT_S); @@ -222,9 +210,7 @@ public class TestSolrConfigHandler extends RestTestBase { testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudSolrClient, asList("overlay", "requestHandler", "/x", "a"), "b", TIMEOUT_S); @@ -240,27 +226,21 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudSolrClient, asList("overlay", "requestHandler", "/dump", "defaults", "c"), "C", TIMEOUT_S); testForResponseElement( writeHarness, - testServerBaseUrl, "/x?getdefaults=true&json.nl=map", - cloudSolrClient, asList("getdefaults", "def_a"), "def A val", TIMEOUT_S); testForResponseElement( writeHarness, - testServerBaseUrl, "/x?param=multival&json.nl=map", - cloudSolrClient, asList("params", "multival"), asList("a", "b", "c"), TIMEOUT_S); @@ -273,10 +253,7 @@ public class TestSolrConfigHandler extends RestTestBase { while (TimeUnit.SECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS) < maxTimeoutSeconds) { String uri = "/config/overlay"; - Map<?, ?> m = - testServerBaseUrl == null - ? getRespMap(uri, writeHarness) - : TestSolrConfigHandlerConcurrent.getAsMap(testServerBaseUrl + uri, cloudSolrClient); + Map<?, ?> m = getRespMap(writeHarness, uri); if (null == Utils.getObjectByPath(m, true, asList("overlay", "requestHandler", "/x", "a"))) { success = true; break; @@ -292,9 +269,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "queryConverter", "qc", "class"), "org.apache.solr.spelling.SpellingQueryConverter", TIMEOUT_S); @@ -305,9 +280,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "queryConverter", "qc", "class"), "org.apache.solr.spelling.SuggestQueryConverter", TIMEOUT_S); @@ -315,13 +288,7 @@ public class TestSolrConfigHandler extends RestTestBase { payload = "{\n" + "'delete-queryconverter' : 'qc'" + "}"; runConfigCommand(writeHarness, "/config", payload); testForResponseElement( - writeHarness, - testServerBaseUrl, - "/config", - cloudSolrClient, - asList("config", "queryConverter", "qc"), - null, - TIMEOUT_S); + writeHarness, "/config", asList("config", "queryConverter", "qc"), null, TIMEOUT_S); payload = "{\n" @@ -333,9 +300,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "listener[0]", "class"), "solr.QuerySenderListener", TIMEOUT_S); @@ -350,23 +315,14 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "listener[0]", "class"), "org.apache.solr.core.QuerySenderListener", TIMEOUT_S); payload = "{\n" + "'delete-listener' : 'f7fb2d87bea44464af2401cf33f42b69'" + "}"; runConfigCommand(writeHarness, "/config", payload); - testForResponseElement( - writeHarness, - testServerBaseUrl, - "/config", - cloudSolrClient, - asList("config", "listener"), - null, - TIMEOUT_S); + testForResponseElement(writeHarness, "/config", asList("config", "listener"), null, TIMEOUT_S); payload = "{\n" @@ -375,9 +331,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "searchComponent", "tc", "class"), "org.apache.solr.handler.component.TermsComponent", TIMEOUT_S); @@ -388,9 +342,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "searchComponent", "tc", "class"), "org.apache.solr.handler.component.TermVectorComponent", TIMEOUT_S); @@ -398,13 +350,7 @@ public class TestSolrConfigHandler extends RestTestBase { payload = "{\n" + "'delete-searchcomponent' : 'tc'" + "}"; runConfigCommand(writeHarness, "/config", payload); testForResponseElement( - writeHarness, - testServerBaseUrl, - "/config", - cloudSolrClient, - asList("config", "searchComponent", "tc"), - null, - TIMEOUT_S); + writeHarness, "/config", asList("config", "searchComponent", "tc"), null, TIMEOUT_S); // <valueSourceParser name="countUsage" // class="org.apache.solr.core.CountUsageValueSourceParser"/> payload = @@ -414,9 +360,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "valueSourceParser", "cu", "class"), "org.apache.solr.core.CountUsageValueSourceParser", TIMEOUT_S); @@ -430,9 +374,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "valueSourceParser", "cu", "class"), "org.apache.solr.search.function.NvlValueSourceParser", TIMEOUT_S); @@ -440,13 +382,7 @@ public class TestSolrConfigHandler extends RestTestBase { payload = "{\n" + "'delete-valuesourceparser' : 'cu'" + "}"; runConfigCommand(writeHarness, "/config", payload); testForResponseElement( - writeHarness, - testServerBaseUrl, - "/config", - cloudSolrClient, - asList("config", "valueSourceParser", "cu"), - null, - TIMEOUT_S); + writeHarness, "/config", asList("config", "valueSourceParser", "cu"), null, TIMEOUT_S); // <transformer name="mytrans2" // class="org.apache.solr.response.transform.ValueAugmenterFactory" > // <int name="value">5</int> @@ -458,9 +394,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "transformer", "mytrans", "class"), "org.apache.solr.response.transform.ValueAugmenterFactory", TIMEOUT_S); @@ -472,9 +406,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "transformer", "mytrans", "value"), "6", TIMEOUT_S); @@ -487,13 +419,7 @@ public class TestSolrConfigHandler extends RestTestBase { runConfigCommand(writeHarness, "/config", payload); Map<?, ?> map = testForResponseElement( - writeHarness, - testServerBaseUrl, - "/config", - cloudSolrClient, - asList("config", "transformer", "mytrans"), - null, - TIMEOUT_S); + writeHarness, "/config", asList("config", "transformer", "mytrans"), null, TIMEOUT_S); List<?> l = (List<?>) Utils.getObjectByPath(map, false, asList("config", "initParams")); assertNotNull("no object /config/initParams : " + map, l); @@ -517,9 +443,7 @@ public class TestSolrConfigHandler extends RestTestBase { map = testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "searchComponent", "myspellcheck", "spellchecker", "class"), "solr.DirectSolrSpellChecker", TIMEOUT_S); @@ -538,14 +462,12 @@ public class TestSolrConfigHandler extends RestTestBase { map = testForResponseElement( writeHarness, - testServerBaseUrl, "/config", - cloudSolrClient, asList("config", "requestHandler", "/dump100", "class"), "org.apache.solr.handler.DumpRequestHandler", TIMEOUT_S); - map = getRespMap("/dump100?json.nl=arrmap&initArgs=true", writeHarness); + map = getRespMap(writeHarness, "/dump100?json.nl=arrmap&initArgs=true"); List<?> initArgs = (List<?>) map.get("initArgs"); assertNotNull(initArgs); assertTrue(initArgs.size() >= 2); @@ -565,9 +487,7 @@ public class TestSolrConfigHandler extends RestTestBase { testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudSolrClient, asList("overlay", "requestHandler", "/dump101", "startup"), "lazy", TIMEOUT_S); @@ -581,9 +501,7 @@ public class TestSolrConfigHandler extends RestTestBase { map = testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudSolrClient, asList("overlay", "cache", "lfuCacheDecayFalse", "class"), "solr.search.CaffeineCache", TIMEOUT_S); @@ -592,7 +510,7 @@ public class TestSolrConfigHandler extends RestTestBase { getObjectByPath(map, true, List.of("overlay", "cache", "perSegFilter", "class"))); map = - getRespMap("/dump101?cacheNames=lfuCacheDecayFalse&cacheNames=perSegFilter", writeHarness); + getRespMap(writeHarness, "/dump101?cacheNames=lfuCacheDecayFalse&cacheNames=perSegFilter"); assertEquals( "Actual output " + Utils.toJSONString(map), "org.apache.solr.search.CaffeineCache", @@ -663,9 +581,7 @@ public class TestSolrConfigHandler extends RestTestBase { @SuppressWarnings({"unchecked", "rawtypes"}) public static LinkedHashMapWriter testForResponseElement( RestTestHarness harness, - String testServerBaseUrl, String uri, - CloudSolrClient cloudSolrClient, List<String> jsonPath, Object expected, long maxTimeoutSeconds) @@ -677,11 +593,7 @@ public class TestSolrConfigHandler extends RestTestBase { TimeOut timeOut = new TimeOut(maxTimeoutSeconds, TimeUnit.SECONDS, TimeSource.NANO_TIME); while (!timeOut.hasTimedOut()) { try { - m = - testServerBaseUrl == null - ? getRespMap(uri, harness) - : TestSolrConfigHandlerConcurrent.getAsMap( - testServerBaseUrl + uri, cloudSolrClient); + m = getRespMap(harness, uri); } catch (Exception e) { Thread.sleep(100); continue; @@ -705,7 +617,7 @@ public class TestSolrConfigHandler extends RestTestBase { assertTrue( StrUtils.formatString( "Could not get expected value ''{0}'' for path ''{1}'' full output: {2}, from server: {3}", - expected, StrUtils.join(jsonPath, '/'), m.toString(), testServerBaseUrl), + expected, StrUtils.join(jsonPath, '/'), m.toString(), harness.getBaseURL()), success); return m; @@ -724,22 +636,10 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "x", "a"), - "A val", - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "x", "a"), "A val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "x", "b"), - "B val", - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "x", "b"), "B val", TIMEOUT_S); payload = "{\n" @@ -750,23 +650,15 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.testForResponseElement( harness, - null, "/config/overlay", - null, asList("overlay", "requestHandler", "/d", "name"), "/d", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, null, "/d?useParams=x", null, asList("params", "a"), "A val", TIMEOUT_S); + harness, "/d?useParams=x", asList("params", "a"), "A val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/d?useParams=x&a=fomrequest", - null, - asList("params", "a"), - "fomrequest", - TIMEOUT_S); + harness, "/d?useParams=x&a=fomrequest", asList("params", "a"), "fomrequest", TIMEOUT_S); payload = "{\n" @@ -777,15 +669,13 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.testForResponseElement( harness, - null, "/config/overlay", - null, asList("overlay", "requestHandler", "/dump1", "name"), "/dump1", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, null, "/dump1", null, asList("params", "a"), "A val", TIMEOUT_S); + harness, "/dump1", asList("params", "a"), "A val", TIMEOUT_S); payload = " {\n" @@ -799,31 +689,19 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "y", "c"), - "CY val", - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "y", "c"), "CY val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, null, "/dump1?useParams=y", null, asList("params", "c"), "CY val", TIMEOUT_S); + harness, "/dump1?useParams=y", asList("params", "c"), "CY val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, null, "/dump1?useParams=y", null, asList("params", "b"), "BY val", TIMEOUT_S); + harness, "/dump1?useParams=y", asList("params", "b"), "BY val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, null, "/dump1?useParams=y", null, asList("params", "a"), "A val", TIMEOUT_S); + harness, "/dump1?useParams=y", asList("params", "a"), "A val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/dump1?useParams=y", - null, - asList("params", "d"), - asList("val 1", "val 2"), - TIMEOUT_S); + harness, "/dump1?useParams=y", asList("params", "d"), asList("val 1", "val 2"), TIMEOUT_S); payload = " {\n" @@ -839,21 +717,13 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.testForResponseElement( harness, - null, "/config/params", - null, asList("response", "params", "y", "c"), "CY val modified", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "y", "e"), - "EY val", - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "y", "e"), "EY val", TIMEOUT_S); payload = " {\n" @@ -866,32 +736,14 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "y", "p"), - "P val", - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "y", "p"), "P val", TIMEOUT_S); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "y", "c"), - null, - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "y", "c"), null, TIMEOUT_S); payload = " {'delete' : 'y'}"; TestSolrConfigHandler.runConfigCommand(harness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/config/params", - null, - asList("response", "params", "y", "p"), - null, - TIMEOUT_S); + harness, "/config/params", asList("response", "params", "y", "p"), null, TIMEOUT_S); payload = "{\n" @@ -916,44 +768,35 @@ public class TestSolrConfigHandler extends RestTestBase { TestSolrConfigHandler.runConfigCommand(harness, "/config", payload); TestSolrConfigHandler.testForResponseElement( harness, - null, "/config/overlay", - null, asList("overlay", "requestHandler", "aRequestHandler", "class"), "org.apache.solr.handler.DumpRequestHandler", TIMEOUT_S); - RESTfulServerProvider oldProvider = restTestHarness.getServerProvider(); - restTestHarness.setServerProvider( - () -> getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME); - - Map<?, ?> rsp = - TestSolrConfigHandler.testForResponseElement( - harness, - null, - "/something/part1_Value/fixed/part2_Value?urlTemplateValues=part1&urlTemplateValues=part2", - null, - asList("urlTemplateValues"), - new ValidatingJsonMap.PredicateWithErrMsg<>() { - @Override - public String test(Object o) { - if (o instanceof Map<?, ?> m) { - if ("part1_Value".equals(m.get("part1")) && "part2_Value".equals(m.get("part2"))) - return null; - } - return "no match"; - } - - @Override - public String toString() { - return "{part1:part1_Value, part2 : part2_Value]"; - } - }, - TIMEOUT_S); - restTestHarness.setServerProvider(oldProvider); + + TestSolrConfigHandler.testForResponseElement( + harness.newWithUrl(getBaseUrl() + "/____v2/cores/" + DEFAULT_TEST_CORENAME), + "/something/part1_Value/fixed/part2_Value?urlTemplateValues=part1&urlTemplateValues=part2", + asList("urlTemplateValues"), + new ValidatingJsonMap.PredicateWithErrMsg<>() { + @Override + public String test(Object o) { + if (o instanceof Map<?, ?> m) { + if ("part1_Value".equals(m.get("part1")) && "part2_Value".equals(m.get("part2"))) + return null; + } + return "no match"; + } + + @Override + public String toString() { + return "{part1:part1_Value, part2 : part2_Value]"; + } + }, + TIMEOUT_S); } @SuppressWarnings({"rawtypes"}) - public static LinkedHashMapWriter getRespMap(String path, RestTestHarness restHarness) + public static LinkedHashMapWriter getRespMap(RestTestHarness restHarness, String path) throws Exception { String response = restHarness.query(path); try { @@ -966,8 +809,8 @@ public class TestSolrConfigHandler extends RestTestBase { } public void testCacheDisableSolrConfig() throws Exception { - RESTfulServerProvider oldProvider = restTestHarness.getServerProvider(); - restTestHarness.setServerProvider(RestTestBase::getBaseUrl); + RestTestHarness oldRth = restTestHarness; + restTestHarness = restTestHarness.newWithUrl(getBaseUrl()); String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus"); assertTrue( "fieldValueCache metrics should be present", @@ -977,45 +820,45 @@ public class TestSolrConfigHandler extends RestTestBase { "documentCache metrics should be absent", prometheusMetrics.contains("name=\"documentCache\"")); - restTestHarness.setServerProvider(oldProvider); + restTestHarness = oldRth; } public void testSetPropertyCacheSize() throws Exception { - RESTfulServerProvider oldProvider = restTestHarness.getServerProvider(); + RestTestHarness oldRth = restTestHarness; // Changing cache size String payload = "{'set-property' : { 'query.documentCache.size': 399} }"; runConfigCommand(restTestHarness, "/config", payload); - MapWriter overlay = getRespMap("/config/overlay", restTestHarness); + MapWriter overlay = getRespMap(restTestHarness, "/config/overlay"); assertEquals("399", overlay._getStr("overlay/props/query/documentCache/size")); // Setting size only will not enable the cache - restTestHarness.setServerProvider(RestTestBase::getBaseUrl); + restTestHarness = restTestHarness.newWithUrl(getBaseUrl()); String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus"); assertFalse(prometheusMetrics.contains("cache_name=\"documentCache\"")); - restTestHarness.setServerProvider(oldProvider); + restTestHarness = oldRth; } public void testSetPropertyEnableAndDisableCache() throws Exception { - RESTfulServerProvider oldProvider = restTestHarness.getServerProvider(); + RestTestHarness oldRth = restTestHarness; // Enabling Cache String payload = "{'set-property' : { 'query.documentCache.enabled': true} }"; runConfigCommand(restTestHarness, "/config", payload); - restTestHarness.setServerProvider(RestTestBase::getBaseUrl); + restTestHarness = restTestHarness.newWithUrl(getBaseUrl()); String prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus"); assertTrue(prometheusMetrics.contains("name=\"documentCache\"")); // Disabling Cache payload = "{ 'set-property' : { 'query.documentCache.enabled': false } }"; - restTestHarness.setServerProvider(oldProvider); + restTestHarness = oldRth; runConfigCommand(restTestHarness, "/config", payload); - restTestHarness.setServerProvider(RestTestBase::getBaseUrl); + restTestHarness = restTestHarness.newWithUrl(getBaseUrl()); prometheusMetrics = restTestHarness.query("/admin/metrics?wt=prometheus"); assertFalse(prometheusMetrics.contains("name=\"documentCache\"")); - restTestHarness.setServerProvider(oldProvider); + restTestHarness = oldRth; } } diff --git a/solr/core/src/test/org/apache/solr/handler/TestReqParamsAPI.java b/solr/core/src/test/org/apache/solr/handler/TestReqParamsAPI.java index ac3d82b1956..47eb83e7e9d 100644 --- a/solr/core/src/test/org/apache/solr/handler/TestReqParamsAPI.java +++ b/solr/core/src/test/org/apache/solr/handler/TestReqParamsAPI.java @@ -46,13 +46,9 @@ public class TestReqParamsAPI extends SolrCloudTestCase { private void setupHarnesses() { for (final JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) { - RestTestHarness harness = - new RestTestHarness(() -> jettySolrRunner.getBaseUrl().toString() + "/" + COLL_NAME); - if (random().nextBoolean()) { - harness.setServerProvider( - () -> jettySolrRunner.getBaseUrl().toString() + "/____v2/c/" + COLL_NAME); - } - restTestHarnesses.add(harness); + // Randomly pick v1 or v2 API + String path = random().nextBoolean() ? COLL_NAME : "____v2/c/" + COLL_NAME; + restTestHarnesses.add(jettySolrRunner.getRestClient(path)); } } @@ -70,14 +66,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { @Test public void test() throws Exception { - try { - setupHarnesses(); - testReqParams(); - } finally { - for (RestTestHarness r : restTestHarnesses) { - r.close(); - } - } + setupHarnesses(); + testReqParams(); } private void testReqParams() throws Exception { @@ -120,60 +110,48 @@ public class TestReqParamsAPI extends SolrCloudTestCase { Map<?, ?> result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "x", "a"), "A val", 10); compareValues(result, "B val", asList("response", "params", "x", "b")); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/overlay", - cloudClient, asList("overlay", "requestHandler", "/dump0", "name"), "/dump0", 10); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump0?useParams=x", - cloudClient, asList("params", "a"), "A val", 5); compareValues(result, "", asList("params", RequestParams.USEPARAM)); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump0?useParams=x&a=fomrequest", - cloudClient, asList("params", "a"), "fomrequest", 5); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/overlay", - cloudClient, asList("overlay", "requestHandler", "/dump1", "name"), "/dump1", 10); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump1", - cloudClient, asList("params", "a"), "A val", 5); @@ -193,10 +171,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "c"), "CY val", 10); @@ -205,10 +181,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump1?useParams=y", - cloudClient, asList("params", "c"), "CY val", 5); @@ -219,10 +193,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/requestHandler?componentName=/dump1&expandParams=true&useParams=y&c=CC", - cloudClient, asList("config", "requestHandler", "/dump1", "_useParamsExpanded_", "x", "a"), "A val", 5); @@ -259,10 +231,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "c"), "CY val modified", 10); @@ -280,10 +250,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params", payload); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "p"), "P val", 10); @@ -296,10 +264,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "x", "_appends_", "add"), "first", 10); @@ -307,10 +273,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump1?fixed=changeit&add=second", - cloudClient, asList("params", "fixed"), "f", 5); @@ -328,10 +292,8 @@ public class TestReqParamsAPI extends SolrCloudTestCase { payload = " {'delete' : 'y'}"; TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "p"), null, 10); diff --git a/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerCloud.java b/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerCloud.java index 87a31f226c1..3370a5c5f77 100644 --- a/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerCloud.java +++ b/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerCloud.java @@ -63,9 +63,7 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { TestSolrConfigHandler.testForResponseElement( writeHarness, - testServerBaseUrl, "/config/overlay", - cloudClient, Arrays.asList("overlay", "requestHandler", "/admin/luke", "class"), "org.apache.solr.handler.DumpRequestHandler", TIMEOUT_S); @@ -77,7 +75,7 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { private void testReqHandlerAPIs() throws Exception { String testServerBaseUrl = getRandomServer(cloudClient, "collection1"); RestTestHarness writeHarness = randomRestTestHarness(); - TestSolrConfigHandler.reqhandlertests(writeHarness, testServerBaseUrl, cloudClient); + TestSolrConfigHandler.reqhandlertests(writeHarness.newWithUrl(testServerBaseUrl)); } public static String getRandomServer(CloudSolrClient cloudClient, String collName) { @@ -111,10 +109,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { Map<?, ?> result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "x", "a"), "A val", TIMEOUT_S); @@ -128,30 +124,24 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { TestSolrConfigHandler.runConfigCommand(writeHarness, "/config", payload); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/overlay", - cloudClient, asList("overlay", "requestHandler", "/dump", "name"), "/dump", TIMEOUT_S); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump?useParams=x", - cloudClient, asList("params", "a"), "A val", TIMEOUT_S); compareValues(result, "", asList("params", RequestParams.USEPARAM)); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump?useParams=x&a=fomrequest", - cloudClient, asList("params", "a"), "fomrequest", TIMEOUT_S); @@ -165,20 +155,16 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/overlay", - cloudClient, asList("overlay", "requestHandler", "/dump1", "name"), "/dump1", TIMEOUT_S); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump1", - cloudClient, asList("params", "a"), "A val", TIMEOUT_S); @@ -198,10 +184,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "c"), "CY val", TIMEOUT_S); @@ -209,10 +193,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/dump?useParams=y", - cloudClient, asList("params", "c"), "CY val", TIMEOUT_S); @@ -234,10 +216,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "c"), "CY val modified", TIMEOUT_S); @@ -255,10 +235,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params", payload); result = TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "p"), "P val", TIMEOUT_S); @@ -267,10 +245,8 @@ public class TestSolrConfigHandlerCloud extends AbstractFullDistribZkTestBase { payload = " {'delete' : 'y'}"; TestSolrConfigHandler.runConfigCommand(writeHarness, "/config/params", payload); TestSolrConfigHandler.testForResponseElement( - null, - urls.get(random().nextInt(urls.size())), + writeHarness.newWithUrl(urls.get(random().nextInt(urls.size()))), "/config/params", - cloudClient, asList("response", "params", "y", "p"), null, TIMEOUT_S); diff --git a/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerConcurrent.java b/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerConcurrent.java index 6730e580298..8a2a40aeacb 100644 --- a/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerConcurrent.java +++ b/solr/core/src/test/org/apache/solr/handler/TestSolrConfigHandlerConcurrent.java @@ -27,12 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.util.EntityUtils; import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.client.solrj.apache.CloudLegacySolrClient; -import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.cloud.AbstractFullDistribZkTestBase; import org.apache.solr.common.LinkedHashMapWriter; import org.apache.solr.common.MapWriter; @@ -112,19 +107,15 @@ public class TestSolrConfigHandlerConcurrent extends AbstractFullDistribZkTestBa String val1; String val2; String val3; - try { - payload = payload.replace("CACHENAME", cacheName); - val1 = String.valueOf(10 * i + 1); - payload = payload.replace("CACHEVAL1", val1); - val2 = String.valueOf(10 * i + 2); - payload = payload.replace("CACHEVAL2", val2); - val3 = String.valueOf(10 * i + 3); - payload = payload.replace("CACHEVAL3", val3); - - response = publisher.post("/config", SolrTestCaseJ4.json(payload)); - } finally { - publisher.close(); - } + payload = payload.replace("CACHENAME", cacheName); + val1 = String.valueOf(10 * i + 1); + payload = payload.replace("CACHEVAL1", val1); + val2 = String.valueOf(10 * i + 2); + payload = payload.replace("CACHEVAL2", val2); + val3 = String.valueOf(10 * i + 3); + payload = payload.replace("CACHEVAL3", val3); + + response = publisher.post("/config", SolrTestCaseJ4.json(payload)); Map<?, ?> map = (Map<?, ?>) Utils.fromJSONString(response); Object errors = map.get("errors"); @@ -142,6 +133,7 @@ public class TestSolrConfigHandlerConcurrent extends AbstractFullDistribZkTestBa // get another node String url = urls.get(urls.size() - 1); + RestTestHarness urlHarness = publisher.newWithUrl(url); long startTime = System.nanoTime(); long maxTimeoutSeconds = 20; @@ -149,7 +141,7 @@ public class TestSolrConfigHandlerConcurrent extends AbstractFullDistribZkTestBa < maxTimeoutSeconds) { Thread.sleep(100); errmessages.clear(); - MapWriter respMap = getAsMap(url + "/config/overlay", cloudClient); + MapWriter respMap = getAsMap(urlHarness, "/config/overlay"); MapWriter m = (MapWriter) respMap._get("overlay/props"); if (m == null) { errmessages.add( @@ -186,23 +178,14 @@ public class TestSolrConfigHandlerConcurrent extends AbstractFullDistribZkTestBa } @SuppressWarnings({"rawtypes"}) - public static LinkedHashMapWriter getAsMap(String uri, CloudSolrClient cloudClient) - throws Exception { - HttpGet get = new HttpGet(uri); - HttpEntity entity = null; + public static LinkedHashMapWriter getAsMap(RestTestHarness harness, String uri) throws Exception { + String response = harness.query(uri); try { - entity = ((CloudLegacySolrClient) cloudClient).getHttpClient().execute(get).getEntity(); - String response = EntityUtils.toString(entity, StandardCharsets.UTF_8); - try { - return (LinkedHashMapWriter) - Utils.MAPWRITEROBJBUILDER.apply(new JSONParser(new StringReader(response))).getVal(); - } catch (JSONParser.ParseException e) { - log.error(response, e); - throw e; - } - } finally { - EntityUtils.consumeQuietly(entity); - get.releaseConnection(); + return (LinkedHashMapWriter) + Utils.MAPWRITEROBJBUILDER.apply(new JSONParser(new StringReader(response))).getVal(); + } catch (JSONParser.ParseException e) { + log.error(response, e); + throw e; } } } diff --git a/solr/core/src/test/org/apache/solr/rest/TestRestManager.java b/solr/core/src/test/org/apache/solr/rest/TestRestManager.java index 3d7f6707bcd..698e388657c 100644 --- a/solr/core/src/test/org/apache/solr/rest/TestRestManager.java +++ b/solr/core/src/test/org/apache/solr/rest/TestRestManager.java @@ -120,6 +120,11 @@ public class TestRestManager extends SolrRestletTestBase { // assertJQ("/config/managed", "/managedResources==[]"); } + /** Helper method to verify HEAD request returns expected status code */ + private void assertHead(String request, int expectedStatusCode) throws Exception { + assertEquals(expectedStatusCode, restTestHarness.head(request)); + } + @Test public void testReloadFromPersistentStorage() throws IOException { SolrResourceLoader loader = new SolrResourceLoader(Path.of("./")); diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java index 14be7ddde90..def647fe426 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java @@ -70,10 +70,6 @@ public class TestBulkSchemaAPI extends RestTestBase { @After public void after() throws Exception { solrTestRule.reset(); - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } public void testMultipleAddFieldWithErrors() throws Exception { diff --git a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java index 9acb4da9ad0..2a0bcc385e3 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedStopFilterFactory.java @@ -51,11 +51,6 @@ public class TestManagedStopFilterFactory extends RestTestBase { @After public void after() throws Exception { solrTestRule.reset(); - - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } /** diff --git a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java index 9fc2a2fcfd9..9bb95fc52a5 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/analysis/TestManagedSynonymGraphFilterFactory.java @@ -55,11 +55,6 @@ public class TestManagedSynonymGraphFilterFactory extends RestTestBase { if (null != tmpSolrHome) { PathUtils.deleteDirectory(tmpSolrHome); } - - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } @Test diff --git a/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java b/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java index 3ee29f9f9ed..0f34a9be823 100644 --- a/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java +++ b/solr/core/src/test/org/apache/solr/schema/TestUseDocValuesAsStored2.java @@ -45,11 +45,6 @@ public class TestUseDocValuesAsStored2 extends RestTestBase { @After public void after() throws Exception { solrTestRule.reset(); - - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } public void testSchemaAPI() throws Exception { diff --git a/solr/modules/language-models/src/test/org/apache/solr/languagemodels/TestLanguageModelBase.java b/solr/modules/language-models/src/test/org/apache/solr/languagemodels/TestLanguageModelBase.java index b5de672f6e8..a54e8e1875d 100644 --- a/solr/modules/language-models/src/test/org/apache/solr/languagemodels/TestLanguageModelBase.java +++ b/solr/modules/language-models/src/test/org/apache/solr/languagemodels/TestLanguageModelBase.java @@ -78,10 +78,7 @@ public class TestLanguageModelBase extends RestTestBase { } protected static void afterTest() throws Exception { - if (null != restTestHarness) { - restTestHarness.close(); - restTestHarness = null; - } + restTestHarness = null; solrTestRule.reset(); if (null != tmpSolrHome) { PathUtils.deleteDirectory(tmpSolrHome); diff --git a/solr/modules/language-models/src/test/org/apache/solr/languagemodels/textvectorisation/store/rest/TestModelManagerPersistence.java b/solr/modules/language-models/src/test/org/apache/solr/languagemodels/textvectorisation/store/rest/TestModelManagerPersistence.java index ece13f4e1b7..92e8b68244e 100644 --- a/solr/modules/language-models/src/test/org/apache/solr/languagemodels/textvectorisation/store/rest/TestModelManagerPersistence.java +++ b/solr/modules/language-models/src/test/org/apache/solr/languagemodels/textvectorisation/store/rest/TestModelManagerPersistence.java @@ -91,8 +91,7 @@ public class TestModelManagerPersistence extends TestLanguageModelBase { assertJQ(ManagedTextToVectorModelStore.REST_END_POINT, "/models/[0]/params/logResponses==true"); // check persistence after restart - getJetty().stop(); - getJetty().start(); + restartJetty(); assertJQ(ManagedTextToVectorModelStore.REST_END_POINT, "/models/[0]/name=='" + modelName + "'"); assertJQ( ManagedTextToVectorModelStore.REST_END_POINT, @@ -118,8 +117,7 @@ public class TestModelManagerPersistence extends TestLanguageModelBase { assertJQ(ManagedTextToVectorModelStore.REST_END_POINT, "/models/==[]"); // check persistence after restart - getJetty().stop(); - getJetty().start(); + restartJetty(); assertJQ(ManagedTextToVectorModelStore.REST_END_POINT, "/models/==[]"); } } diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java index ed3a0c492c2..31a70b9652f 100644 --- a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java +++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java @@ -36,7 +36,6 @@ import org.apache.solr.ltr.feature.OriginalScoreFeature; import org.apache.solr.ltr.feature.SolrFeature; import org.apache.solr.ltr.feature.ValueFeature; import org.apache.solr.ltr.model.LinearModel; -import org.apache.solr.util.RestTestHarness; import org.junit.AfterClass; import org.junit.Test; @@ -63,7 +62,6 @@ public class TestLTROnSolrCloud extends TestRerankBase { @Override public void tearDown() throws Exception { - restTestHarness.close(); restTestHarness = null; solrCluster.shutdown(); super.tearDown(); @@ -332,8 +330,7 @@ public class TestLTROnSolrCloud extends TestRerankBase { for (JettySolrRunner solrRunner : solrCluster.getJettySolrRunners()) { if (!solrRunner.getCoreContainer().getCores().isEmpty()) { String coreName = solrRunner.getCoreContainer().getCores().iterator().next().getName(); - restTestHarness = - new RestTestHarness(() -> solrRunner.getBaseUrl().toString() + "/" + coreName); + restTestHarness = solrRunner.getRestClient(coreName); break; } } diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java index a632080d8f3..acd3eeb3e59 100644 --- a/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java +++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java @@ -190,10 +190,7 @@ public class TestRerankBase extends RestTestBase { } protected static void aftertest() throws Exception { - if (null != restTestHarness) { - restTestHarness.close(); - restTestHarness = null; - } + restTestHarness = null; solrTestRule.reset(); if (null != tmpSolrHome) { PathUtils.deleteDirectory(tmpSolrHome); @@ -201,10 +198,6 @@ public class TestRerankBase extends RestTestBase { } } - public static void makeRestTestHarnessNull() { - restTestHarness = null; - } - /** produces a model encoded in json * */ public static String getModelInJson( String name, String type, String[] features, String fstore, String params) { diff --git a/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java b/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java index 7b159e0e650..7fb717765c6 100644 --- a/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java +++ b/solr/modules/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java @@ -190,8 +190,7 @@ public class TestModelManagerPersistence extends TestRerankBase { "/features/[1]/name=='description'"); // check persistence after restart - getJetty().stop(); - getJetty().start(); + restartJetty(); assertJQ(ManagedModelStore.REST_END_POINT, "/models/[0]/name=='" + modelName + "'"); assertJQ( ManagedFeatureStore.REST_END_POINT + "/" + FeatureStore.DEFAULT_FEATURE_STORE_NAME, @@ -217,8 +216,7 @@ public class TestModelManagerPersistence extends TestRerankBase { "/error/msg=='Missing feature store: " + FeatureStore.DEFAULT_FEATURE_STORE_NAME + "'"); // check persistence after restart - getJetty().stop(); - getJetty().start(); + restartJetty(); assertJQ(ManagedModelStore.REST_END_POINT, "/models/==[]"); assertJQ( ManagedFeatureStore.REST_END_POINT + "/" + FeatureStore.DEFAULT_FEATURE_STORE_NAME, @@ -289,8 +287,7 @@ public class TestModelManagerPersistence extends TestRerankBase { doWrapperModelPersistenceChecks(modelName, FS_NAME, baseModelFile.getFileName().toString()); // check persistence after restart - getJetty().stop(); - getJetty().start(); + restartJetty(); doWrapperModelPersistenceChecks(modelName, FS_NAME, baseModelFile.getFileName().toString()); // delete test settings diff --git a/solr/solrj/src/java/org/apache/solr/common/util/URLUtil.java b/solr/solrj/src/java/org/apache/solr/common/util/URLUtil.java index 2cc577d5abe..e4e97e355ac 100644 --- a/solr/solrj/src/java/org/apache/solr/common/util/URLUtil.java +++ b/solr/solrj/src/java/org/apache/solr/common/util/URLUtil.java @@ -23,6 +23,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.solr.common.params.SolrParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -157,4 +158,109 @@ public class URLUtil { URL url = new URI(solrUrl).toURL(); return url.getAuthority() + url.getPath().replace('/', '_'); } + + /** + * Constructs a properly encoded URI by combining a base URI with a request path. This ensures + * special characters (like umlauts) are properly percent-encoded. + * + * @param baseUri the base URI (e.g., URI for "http://localhost:8983/solr") + * @param path the path to append (e.g., "/config/overlay" or "admin/cores") + * @return a properly encoded URI + * @throws IllegalArgumentException if the baseUri or path are invalid + */ + public static URI buildURI(URI baseUri, String path) { + return buildURI(baseUri, path, null); + } + + /** + * Constructs a properly encoded URI by combining a base URI with a request path and optional + * query parameters. This ensures special characters are properly percent-encoded. + * + * @param baseUri the base URI (e.g., URI for "http://localhost:8983/solr") + * @param path the path to append (e.g., "/config/overlay" or "admin/cores"). May include a query + * string (e.g., "/select?q=test") + * @param queryParams optional query parameters to append (may be null). If the path already + * contains a query string, these params will be added to it. + * @return a properly encoded URI + * @throws IllegalArgumentException if the baseUri or path are invalid + */ + public static URI buildURI(URI baseUri, String path, SolrParams queryParams) { + if (baseUri == null) { + throw new IllegalArgumentException("baseUri cannot be null"); + } + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("path cannot be null or empty"); + } + + try { + // The path may contain a query string (e.g., "/path?param=value") + // Split on the first '?' to separate path from query + String pathOnly; + String queryFromPath = null; + int queryIndex = path.indexOf('?'); + if (queryIndex != -1) { + pathOnly = path.substring(0, queryIndex); + queryFromPath = path.substring(queryIndex + 1); + } else { + pathOnly = path; + } + + // Normalize path: remove leading slash if present + String normalizedPath = pathOnly.startsWith("/") ? pathOnly.substring(1) : pathOnly; + + // Construct the full path by combining base path with the additional path + String basePath = baseUri.getPath(); + if (basePath == null) { + basePath = ""; + } + // Ensure base path ends with '/' for proper concatenation + if (!basePath.isEmpty() && !basePath.endsWith("/")) { + basePath += "/"; + } + String fullPath = basePath + normalizedPath; + + // Combine query strings before building final URI + // Both queryFromPath and queryParams.toQueryString() are already percent-encoded + String combinedEncodedQuery = null; + if (queryFromPath != null || queryParams != null) { + StringBuilder queryBuilder = new StringBuilder(); + if (queryFromPath != null) { + queryBuilder.append(queryFromPath); + } + if (queryParams != null) { + String encodedParams = + queryParams.toQueryString(); // returns "?name1=value1&name2=value2" + if (encodedParams.length() > 1) { // More than just "?" + if (!queryBuilder.isEmpty()) { + queryBuilder.append('&'); + } + queryBuilder.append(encodedParams.substring(1)); // Skip leading '?' + } + } + if (!queryBuilder.isEmpty()) { + combinedEncodedQuery = queryBuilder.toString(); + } + } + + // Build URI with path only (no query) + URI uri = + new URI( + baseUri.getScheme(), + baseUri.getUserInfo(), + baseUri.getHost(), + baseUri.getPort(), + fullPath, + null, // no query - we'll add it to the string + baseUri.getFragment()); + + // If we have a combined query, append it to the URI string + if (combinedEncodedQuery != null) { + return URI.create(uri.toASCIIString() + '?' + combinedEncodedQuery); + } + + return uri; + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Failed to construct URI", e); + } + } } diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/SchemaTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/SchemaTest.java index 6bb6ba96ab6..72f6640a3a2 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/SchemaTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/SchemaTest.java @@ -107,10 +107,6 @@ public class SchemaTest extends RestTestBase { @After public void cleanup() throws Exception { solrTestRule.reset(); - if (restTestHarness != null) { - restTestHarness.close(); - } - restTestHarness = null; } @Test diff --git a/solr/solrj/src/test/org/apache/solr/common/util/URLUtilTest.java b/solr/solrj/src/test/org/apache/solr/common/util/URLUtilTest.java index 1ddb06cc8cc..946bddc8980 100644 --- a/solr/solrj/src/test/org/apache/solr/common/util/URLUtilTest.java +++ b/solr/solrj/src/test/org/apache/solr/common/util/URLUtilTest.java @@ -20,8 +20,10 @@ import static org.apache.solr.common.util.URLUtil.getBaseUrlForNodeName; import static org.apache.solr.common.util.URLUtil.getNodeNameForBaseUrl; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import org.apache.solr.SolrTestCase; +import org.apache.solr.common.params.ModifiableSolrParams; import org.junit.Test; public class URLUtilTest extends SolrTestCase { @@ -125,4 +127,158 @@ public class URLUtilTest extends SolrTestCase { "https://app-node-1:8983/api", getBaseUrlForNodeName("app-node-1:8983_solr", "https", true)); } + + @Test + public void testBuildURIBasic() { + URI baseUri = URI.create("http://localhost:8983/solr"); + // Test with and without trailing/leading slashes + URI uri1 = URLUtil.buildURI(baseUri, "/config/overlay"); + assertEquals("http://localhost:8983/solr/config/overlay", uri1.toString()); + + URI uri2 = URLUtil.buildURI(URI.create("http://localhost:8983/solr/"), "config/overlay"); + assertEquals("http://localhost:8983/solr/config/overlay", uri2.toString()); + + URI uri3 = URLUtil.buildURI(baseUri, "admin/cores"); + assertEquals("http://localhost:8983/solr/admin/cores", uri3.toString()); + + URI uri4 = URLUtil.buildURI(URI.create("http://localhost:8983/solr/"), "/admin/cores"); + assertEquals("http://localhost:8983/solr/admin/cores", uri4.toString()); + } + + @Test + public void testBuildURIWithSpecialCharactersInPath() { + URI baseUri = URI.create("http://localhost:8983/solr"); + // Test with umlaut in path + URI uri1 = URLUtil.buildURI(baseUri, "config/überlay"); + assertEquals("http://localhost:8983/solr/config/%C3%BCberlay", uri1.toASCIIString()); + + // Test with multiple special characters + URI uri2 = URLUtil.buildURI(baseUri, "cöllection/döc"); + assertEquals("http://localhost:8983/solr/c%C3%B6llection/d%C3%B6c", uri2.toASCIIString()); + + // Test with spaces (should be encoded) + URI uri3 = URLUtil.buildURI(baseUri, "admin/my core"); + assertEquals("http://localhost:8983/solr/admin/my%20core", uri3.toASCIIString()); + } + + @Test + public void testBuildURIWithQueryParams() { + URI baseUri = URI.create("http://localhost:8983/solr"); + ModifiableSolrParams params = new ModifiableSolrParams(); + params.add("action", "STATUS"); + params.add("wt", "json"); + + URI uri = URLUtil.buildURI(baseUri, "admin/cores", params); + String uriString = uri.toString(); + assertTrue(uriString.startsWith("http://localhost:8983/solr/admin/cores?")); + assertTrue(uriString.contains("action=STATUS")); + assertTrue(uriString.contains("wt=json")); + assertFalse(uriString.contains("??")); // Should NOT have double '?' + + // Verify exactly one '?' character + int questionMarkCount = 0; + for (int i = 0; i < uriString.length(); i++) { + if (uriString.charAt(i) == '?') questionMarkCount++; + } + assertEquals("Should have exactly one '?' in the URI", 1, questionMarkCount); + } + + @Test + public void testBuildURIWithSpecialCharactersInQueryParams() { + URI baseUri = URI.create("http://localhost:8983/solr"); + ModifiableSolrParams params = new ModifiableSolrParams(); + params.add("q", "naïve"); + params.add("fq", "field:übung"); + + URI uri = URLUtil.buildURI(baseUri, "select", params); + String uriString = uri.toASCIIString(); + assertTrue(uriString.startsWith("http://localhost:8983/solr/select?")); + // Query params should be percent-encoded by SolrParams.toQueryString() + assertTrue(uriString.contains("na%C3%AFve")); + assertTrue(uriString.contains("%C3%BCbung")); + } + + @Test + public void testBuildURIWithPathAndQueryParamsWithSpecialChars() { + URI baseUri = URI.create("http://localhost:8983/solr"); + ModifiableSolrParams params = new ModifiableSolrParams(); + params.add("name", "Müller"); + params.add("city", "Zürich"); + + URI uri = URLUtil.buildURI(baseUri, "cöre/select", params); + String uriString = uri.toASCIIString(); + assertTrue(uriString.startsWith("http://localhost:8983/solr/c%C3%B6re/select?")); + // Check that both path and query params are encoded + assertTrue(uriString.contains("M%C3%BCller")); + assertTrue(uriString.contains("Z%C3%BCrich")); + } + + @Test + public void testBuildURIWithSpecialCharsInParamValues() { + URI baseUri = URI.create("http://localhost:8983/solr"); + ModifiableSolrParams params = new ModifiableSolrParams(); + // Test ampersand and equals sign as literal values in parameters + params.add("q", "foo=bar&baz"); + params.add("fq", "field:a=b"); + + URI uri = URLUtil.buildURI(baseUri, "select", params); + String uriString = uri.toASCIIString(); + + // These characters should be percent-encoded in values + // URLEncoder also encodes ':' to %3A + assertTrue( + "Expected encoded = and & in value, got: " + uriString, + uriString.contains("foo%3Dbar%26baz")); + assertTrue( + "Expected encoded = and : in value, got: " + uriString, + uriString.contains("field%3Aa%3Db")); + } + + @Test + public void testBuildURIValidation() { + URI baseUri = URI.create("http://localhost:8983/solr"); + // Test null baseUri + assertThrows(IllegalArgumentException.class, () -> URLUtil.buildURI(null, "path")); + + // Test null/empty path + assertThrows(IllegalArgumentException.class, () -> URLUtil.buildURI(baseUri, null)); + assertThrows(IllegalArgumentException.class, () -> URLUtil.buildURI(baseUri, "")); + } + + @Test + public void testBuildURIWithQueryStringInPath() { + URI baseUri = URI.create("http://localhost:8983/solr"); + // Test with query string in path parameter + URI uri1 = URLUtil.buildURI(baseUri, "/select?q=test"); + assertEquals("http://localhost:8983/solr/select?q=test", uri1.toString()); + + // Test with query string with special characters + URI uri2 = URLUtil.buildURI(baseUri, "/select?q=naïve"); + String uriString = uri2.toASCIIString(); + assertTrue(uriString.startsWith("http://localhost:8983/solr/select?")); + assertTrue(uriString.contains("na%C3%AFve")); + } + + @Test + public void testBuildURIWithQueryStringInPathAndParams() { + URI baseUri = URI.create("http://localhost:8983/solr"); + ModifiableSolrParams params = new ModifiableSolrParams(); + params.add("wt", "json"); + + // Test combining query string from path with additional params + URI uri = URLUtil.buildURI(baseUri, "/select?q=test", params); + String uriString = uri.toString(); + assertTrue(uriString.startsWith("http://localhost:8983/solr/select?")); + assertTrue(uriString.contains("q=test")); + assertTrue(uriString.contains("wt=json")); + assertTrue(uriString.contains("&")); // Should have & separator + assertFalse(uriString.contains("??")); // Should NOT have double '?' + + // Verify exactly one '?' character + int questionMarkCount = 0; + for (int i = 0; i < uriString.length(); i++) { + if (uriString.charAt(i) == '?') questionMarkCount++; + } + assertEquals("Should have exactly one '?' in the URI", 1, questionMarkCount); + } } diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java index 0bb4f08edb2..7f452aa4e61 100644 --- a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java +++ b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java @@ -3107,17 +3107,13 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc protected void setupRestTestHarnesses() { for (final JettySolrRunner jetty : jettys) { - RestTestHarness harness = - new RestTestHarness( - () -> jetty.getBaseUrl().toString() + "/" + DEFAULT_TEST_COLLECTION_NAME); - restTestHarnesses.add(harness); + restTestHarnesses.add(jetty.getRestClient(DEFAULT_TEST_COLLECTION_NAME)); } } protected void closeRestTestHarnesses() throws IOException { - for (RestTestHarness h : restTestHarnesses) { - h.close(); - } + // note: prior to Solr 10.1, there was actual closing to do. + restTestHarnesses.clear(); } protected RestTestHarness randomRestTestHarness() { diff --git a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java index 1c6405675ee..9428b6878c4 100644 --- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java +++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java @@ -69,6 +69,7 @@ import org.apache.solr.servlet.RateLimitFilter; import org.apache.solr.servlet.RequiredSolrRequestFilter; import org.apache.solr.servlet.SolrServlet; import org.apache.solr.servlet.TracingFilter; +import org.apache.solr.util.RestTestHarness; import org.apache.solr.util.SocketProxy; import org.apache.solr.util.TimeOut; import org.apache.solr.util.configuration.SSLConfigurationsFactory; @@ -808,19 +809,27 @@ public class JettySolrRunner implements SolrBackend { this.proxyPort = proxyPort; } + private URI getBaseUri(int jettyPort, String path) { + try { + return new URI(protocol, null, host, jettyPort, path, null, null); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + /** Returns a base URL like {@code http://localhost:8983/solr} */ public URL getBaseUrl() { try { - return new URI(protocol, null, host, jettyPort, "/solr", null, null).toURL(); - } catch (URISyntaxException | MalformedURLException e) { + return getBaseUri(jettyPort, "/solr").toURL(); + } catch (MalformedURLException e) { throw new RuntimeException(e); } } public URL getBaseURLV2() { try { - return new URI(protocol, null, host, jettyPort, "/api", null, null).toURL(); - } catch (MalformedURLException | URISyntaxException e) { + return getBaseUri(jettyPort, "/api").toURL(); + } catch (MalformedURLException e) { throw new RuntimeException(e); } } @@ -831,8 +840,8 @@ public class JettySolrRunner implements SolrBackend { */ public URL getProxyBaseUrl() { try { - return new URI(protocol, null, host, getLocalPort(), "/solr", null, null).toURL(); - } catch (MalformedURLException | URISyntaxException e) { + return getBaseUri(getLocalPort(), "/solr").toURL(); + } catch (MalformedURLException e) { throw new RuntimeException(e); } } @@ -907,6 +916,18 @@ public class JettySolrRunner implements SolrBackend { return proxy; } + /** + * Creates a REST client useful for HTTP operations. It closes when this {@link JettySolrRunner} + * is stopped. + */ + public RestTestHarness getRestClient(String collection) { + String path = "/solr"; + if (collection != null) { + path += "/" + collection; + } + return new RestTestHarness(getSolrClient().getHttpClient(), getBaseUri(jettyPort, path)); + } + // ---- SolrBackend implementation ---- @Override diff --git a/solr/test-framework/src/java/org/apache/solr/handler/BackupRestoreUtils.java b/solr/test-framework/src/java/org/apache/solr/handler/BackupRestoreUtils.java index bd610200895..7bd4fe12ee7 100644 --- a/solr/test-framework/src/java/org/apache/solr/handler/BackupRestoreUtils.java +++ b/solr/test-framework/src/java/org/apache/solr/handler/BackupRestoreUtils.java @@ -21,20 +21,21 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; -import org.apache.http.client.utils.URIBuilder; import org.apache.lucene.tests.util.TestUtil; import org.apache.solr.SolrTestCase; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.URLUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,42 +78,25 @@ public class BackupRestoreUtils extends SolrTestCase { public static void runCoreAdminCommand( String baseUrl, String coreName, String action, Map<String, String> params) - throws IOException, URISyntaxException { - final URI uri = new URI(baseUrl); - final var oldPath = uri.getPath() != null ? uri.getPath().substring(1) : ""; - final var newPath = "admin/cores"; - final var finalPath = oldPath.isEmpty() ? newPath : oldPath + "/" + newPath; - - final URIBuilder builder = - new URIBuilder(uri) - .setPath(finalPath) - .addParameter("action", action) - .addParameter("core", coreName); - - // Add additional parameters using loop - for (Map.Entry<String, String> entry : params.entrySet()) { - builder.addParameter(entry.getKey(), entry.getValue()); - } - - executeHttpRequest(builder.build()); + throws IOException { + executeHttpRequest( + URLUtil.buildURI( + URI.create(baseUrl), + "admin/cores", + SolrParams.wrapDefaults( + new MapSolrParams(params), + new MapSolrParams(Map.of("action", action, "core", coreName))))); } public static void runReplicationHandlerCommand( String baseUrl, String coreName, String action, String repoName, String backupName) - throws IOException, URISyntaxException { - final URI uri = new URI(baseUrl); - final var oldPath = uri.getPath() != null ? uri.getPath().substring(1) : ""; - final var newPath = coreName + ReplicationHandler.PATH; - final var finalPath = oldPath.isEmpty() ? newPath : oldPath + "/" + newPath; - - final URI finalURI = - new URIBuilder(uri) - .setPath(finalPath) - .addParameter("command", action) - .addParameter("repository", repoName) - .addParameter("name", backupName) - .build(); - executeHttpRequest(finalURI); + throws IOException { + executeHttpRequest( + URLUtil.buildURI( + URI.create(baseUrl), + coreName + ReplicationHandler.PATH, + new MapSolrParams( + Map.of("command", action, "repository", repoName, "name", backupName)))); } private static void executeHttpRequest(URI uri) throws IOException { diff --git a/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java b/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java index a8b18812e32..83b2d302c0c 100644 --- a/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java +++ b/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java @@ -22,11 +22,9 @@ import java.nio.file.Path; import java.util.Map; import java.util.Properties; import javax.xml.xpath.XPathExpressionException; -import org.apache.http.client.HttpClient; import org.apache.solr.JSONTestUtil; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.apache.HttpSolrClient; import org.apache.solr.common.params.MultiMapSolrParams; import org.apache.solr.common.util.StrUtils; import org.apache.solr.embedded.JettyConfig; @@ -47,12 +45,8 @@ public abstract class RestTestBase extends SolrTestCaseJ4 { protected static RestTestHarness restTestHarness; @AfterClass - public static void cleanUpHarness() throws IOException { - RestTestHarness localHarness = restTestHarness; - if (localHarness != null) { - localHarness.close(); - restTestHarness = null; - } + public static void cleanUpHarness() { + restTestHarness = null; } /** @@ -78,13 +72,23 @@ public abstract class RestTestBase extends SolrTestCaseJ4 { .withSchemaFile(schemaFile) .create(); - restTestHarness = new RestTestHarness(RestTestBase::getCoreUrl); + restTestHarness = getJetty().getRestClient(DEFAULT_TEST_CORENAME); } protected static JettySolrRunner getJetty() { return solrTestRule.getJetty(); } + /** + * Restarts Jetty and recreates the RestTestHarness with a new HttpClient. Use this instead of + * calling getJetty().stop()/start() directly. + */ + protected static void restartJetty() throws Exception { + getJetty().stop(); + getJetty().start(); + restTestHarness = getJetty().getRestClient(DEFAULT_TEST_CORENAME); + } + /** URL to Solr */ protected static String getBaseUrl() { return solrTestRule.getBaseUrl(); @@ -99,11 +103,6 @@ public abstract class RestTestBase extends SolrTestCaseJ4 { return solrTestRule.getSolrClient(); } - protected static HttpClient getHttpClient() { - HttpSolrClient client = (HttpSolrClient) getSolrClient(); - return client.getHttpClient(); - } - /** Validates an update XML String is successful */ public static void assertU(String update) { assertU(null, update); @@ -215,12 +214,6 @@ public abstract class RestTestBase extends SolrTestCaseJ4 { } } - public static void assertHead(String request, int expectedStatusCode) throws IOException { - String response = restTestHarness.head(request); - assertTrue(response.contains("HTTP/1.1 " + expectedStatusCode)); - assertTrue(response.contains("Content-Length: 0")); - } - /** * Makes a query request and returns the JSON string response * diff --git a/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java index 18c1f3355ce..910d5d76a24 100644 --- a/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java +++ b/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java @@ -16,50 +16,74 @@ */ package org.apache.solr.util; -import java.io.Closeable; import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.util.EntityUtils; -import org.apache.solr.client.solrj.apache.HttpClientUtil; -import org.apache.solr.common.params.ModifiableSolrParams; - -/** Facilitates testing Solr's REST API via a provided embedded Jetty */ -public class RestTestHarness extends BaseTestHarness implements Closeable { - private RESTfulServerProvider serverProvider; - private CloseableHttpClient httpClient = HttpClientUtil.createClient(new ModifiableSolrParams()); - - public RestTestHarness(RESTfulServerProvider serverProvider) { - this.serverProvider = serverProvider; +import org.apache.solr.common.util.URLUtil; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.StringRequestContent; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Facilitates testing Solr's REST API via Jetty HttpClient. Immutable and cheap to construct. */ +public class RestTestHarness extends BaseTestHarness { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final HttpClient httpClient; + private final URI baseUri; + + /** + * Creates a RestTestHarness for the given base URL using the provided HttpClient. + * + * @param httpClient The HttpClient to use for requests (not closed by this harness) + * @param baseUri The base URI (e.g., "http://localhost:8983/solr/collection1") + */ + public RestTestHarness(HttpClient httpClient, URI baseUri) { + if (httpClient == null) { + throw new IllegalArgumentException("httpClient cannot be null"); + } + if (baseUri == null || !baseUri.isAbsolute()) { + throw new IllegalArgumentException("baseUrl cannot be null or non-absolute"); + } + this.httpClient = httpClient; + this.baseUri = baseUri; } - public String getBaseURL() { - return serverProvider.getBaseURL(); + /** Creates a new instance for the specified URL, sharing the underlying HTTP client. */ + public RestTestHarness newWithUrl(String baseUrl) { + return new RestTestHarness(httpClient, URI.create(baseUrl)); } - public void setServerProvider(RESTfulServerProvider serverProvider) { - this.serverProvider = serverProvider; + @Override + public String toString() { + return getBaseURL(); } - public RESTfulServerProvider getServerProvider() { - return this.serverProvider; + public String getBaseURL() { + return getBaseURI().toASCIIString(); + } + + public URI getBaseURI() { + return baseUri; } public String getAdminURL() { return getBaseURL().replace("/collection1", ""); } + private URI getAdminURI() { + return URI.create(getAdminURL()); + } + /** * Processes a "query" using a URL path (with no context path) + optional query params, e.g. * "/schema/fields?indent=off" @@ -69,25 +93,29 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { * @exception IOException any exception in the response. */ public String query(String request) throws IOException { - return getResponse(new HttpGet(getBaseURL() + request)); + return sendAndReturnString( + getHttpClient().newRequest(buildUri(request)).method(HttpMethod.GET)); } public String adminQuery(String request) throws IOException { - return getResponse(new HttpGet(getAdminURL() + request)); + return sendAndReturnString( + getHttpClient() + .newRequest(URLUtil.buildURI(getAdminURI(), request)) + .method(HttpMethod.GET)); } /** * Processes a HEAD request using a URL path (with no context path) + optional query params and - * returns the response content. + * returns the status code. * * @param request The URL path and optional query params - * @return The response to the HEAD request + * @return The HTTP status code */ - public String head(String request) throws IOException { - HttpHead httpHead = new HttpHead(getBaseURL() + request); - return httpClient - .execute(httpHead, HttpClientUtil.createNewHttpClientRequestContext()) - .toString(); + public int head(String request) throws IOException { + ContentResponse rsp = + send(getHttpClient().newRequest(buildUri(request)).method(HttpMethod.HEAD)); + assert rsp.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH) <= 0 : "Expected no content"; + return rsp.getStatus(); } /** @@ -95,15 +123,15 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { * "/schema/fields/newfield", PUTs the given content, and returns the response content. * * @param request The URL path and optional query params - * @param content The content to include with the PUT request + * @param json The content to include with the PUT request * @return The response to the PUT request */ - public String put(String request, String content) throws IOException { - HttpPut httpPut = new HttpPut(getBaseURL() + request); - httpPut.setEntity( - new StringEntity(content, ContentType.create("application/json", StandardCharsets.UTF_8))); - - return getResponse(httpPut); + public String put(String request, String json) throws IOException { + return sendAndReturnString( + getHttpClient() + .newRequest(buildUri(request)) + .method(HttpMethod.PUT) + .body(new StringRequestContent("application/json", json, StandardCharsets.UTF_8))); } /** @@ -114,8 +142,8 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { * @return The response to the DELETE request */ public String delete(String request) throws IOException { - HttpDelete httpDelete = new HttpDelete(getBaseURL() + request); - return getResponse(httpDelete); + return sendAndReturnString( + getHttpClient().newRequest(buildUri(request)).method(HttpMethod.DELETE)); } /** @@ -123,30 +151,20 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { * "/schema/fields/newfield", PUTs the given content, and returns the response content. * * @param request The URL path and optional query params - * @param content The content to include with the POST request + * @param json The content to include with the POST request * @return The response to the POST request */ - public String post(String request, String content) throws IOException { - HttpPost httpPost = new HttpPost(getBaseURL() + request); - httpPost.setEntity( - new StringEntity(content, ContentType.create("application/json", StandardCharsets.UTF_8))); - - return getResponse(httpPost); + public String post(String request, String json) throws IOException { + return sendAndReturnString( + getHttpClient() + .newRequest(buildUri(request)) + .method(HttpMethod.POST) + .body(new StringRequestContent("application/json", json, StandardCharsets.UTF_8))); } - public String checkResponseStatus(String xml, String code) throws Exception { + public String checkAdminResponseStatus(String request, int code) throws Exception { try { - String response = query(xml); - String valid = validateXPath(response, "//int[@name='status']=" + code); - return (null == valid) ? null : response; - } catch (XPathExpressionException e) { - throw new RuntimeException("?!? static xpath has bug?", e); - } - } - - public String checkAdminResponseStatus(String xml, String code) throws Exception { - try { - String response = adminQuery(xml); + String response = adminQuery(request); String valid = validateXPath(response, "//int[@name='status']=" + code); return (null == valid) ? null : response; } catch (XPathExpressionException e) { @@ -163,8 +181,7 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { adminQuery("/admin/cores?wt=xml&action=STATUS"), "//lst[@name='status']/lst[1]/str[@name='name']", XPathConstants.STRING); - String xml = - checkAdminResponseStatus("/admin/cores?wt=xml&action=RELOAD&core=" + coreName, "0"); + String xml = checkAdminResponseStatus("/admin/cores?wt=xml&action=RELOAD&core=" + coreName, 0); if (null != xml) { throw new RuntimeException("RELOAD failed:\n" + xml); } @@ -179,31 +196,52 @@ public class RestTestHarness extends BaseTestHarness implements Closeable { @Override public String update(String xml) { try { - HttpPost httpPost = new HttpPost(getBaseURL() + "/update"); - httpPost.setEntity( - new StringEntity(xml, ContentType.create("application/xml", StandardCharsets.UTF_8))); - return getResponse(httpPost); + return sendAndReturnString( + getHttpClient() + .POST(buildUri("/update")) + .body(new StringRequestContent("application/xml", xml, StandardCharsets.UTF_8))); + } catch (RuntimeException e) { + throw e; } catch (Exception e) { throw new RuntimeException(e); } } - /** Executes the given request and returns the response. */ - private String getResponse(HttpUriRequest request) throws IOException { - HttpEntity entity = null; + private URI buildUri(String request) { + return request == null ? getBaseURI() : URLUtil.buildURI(getBaseURI(), request); + } + + /** Executes the given request and returns the response as a String. */ + private String sendAndReturnString(Request request) throws IOException { + ContentResponse rsp = send(request); + return rsp.getContentAsString(); + } + + private static ContentResponse send(Request request) throws IOException { + ContentResponse rsp; try { - entity = - httpClient - .execute(request, HttpClientUtil.createNewHttpClientRequestContext()) - .getEntity(); - return EntityUtils.toString(entity, StandardCharsets.UTF_8); - } finally { - EntityUtils.consumeQuietly(entity); + rsp = request.send(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // reset + throw new RuntimeException(e); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + log.warn("Propagating cause of executionException"); + try { + throw e.getCause(); + } catch (RuntimeException | IOException | Error cause) { + throw cause; + } catch (Exception cause) { + throw new IOException(cause); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } } + return rsp; } - @Override - public void close() throws IOException { - HttpClientUtil.close(httpClient); + public HttpClient getHttpClient() { + return httpClient; } }
