SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

SOLR-18390: remove test-framework HTTP client convenience helpers - #4778

Open
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers
Open

SOLR-18390: remove test-framework HTTP client convenience helpers#4778
serhiy-bzhezytskyy wants to merge 28 commits into
apache:mainfrom
serhiy-bzhezytskyy:SOLR-18390-remove-test-http-client-helpers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18390

Removes the four @Deprecated test-framework HTTP client convenience helpers (SolrTestCaseJ4.getHttpSolrClient overloads and JettySolrRunner.newClient()) and inlines every call site to the equivalent HttpJettySolrClient.Builder construction -- matching what the deprecated methods' own bodies did. Test-only change, no changelog.

84 files, 173 call sites migrated. While migrating, found and fixed a subtle bug the mechanical inlining would otherwise have introduced: several sites called Slice.getLeader() twice (once for the URL, once for the collection name). For PRS collections getLeader() recomputes the leader on every call, so calling it twice could return two different replicas if the leader changed in between. Fixed by capturing it once.

Ran the highest call-site-density test classes plus everything touched by the getLeader() fix, all green (0 failures): FullSolrCloudDistribCmdsTest, AliasIntegrationTest, TestCoordinatorRole, LB2SolrClientTest, TestPullReplica (nightly), TestPullReplicaErrorHandling, TestPullReplicaWithAuth, TestTlogReplica, HealthCheckHandlerTest, MultiAuthPluginTest, BasicAuthIntegrationTest, HttpSolrClientBadInputTest, TestJettySolrRunner, BasicDistributedZkTest, BasicDistributedZk2Test.

AI-assisted (Claude Sonnet 5)

Removes the four @deprecated helpers (SolrTestCaseJ4.getHttpSolrClient
overloads and JettySolrRunner.newClient()) and inlines every call site to
the equivalent HttpJettySolrClient.Builder construction. Test-only change,
no behavior change.
The mechanical migration called .getLeader() separately for getBaseUrl()
and getCoreName(). For PRS collections getLeader() recomputes the leader
on every call (its own comment: 'leader may keep changing') -- calling it
twice risked getBaseUrl() and getCoreName() coming from two different
replicas if the leader changed in between. Capture it once instead.
@serhiy-bzhezytskyy
serhiy-bzhezytskyyforce-pushed the SOLR-18390-remove-test-http-client-helpers branch from 9ea76db to 92ed892CompareAugust 22, 2026 05:17
@epugh

Copy link
Copy Markdown
Contributor

@dsmiley I think we need your eyes here. I thought this was going to be a more "oh wow, that is so much nicer" type of impact when I looked at the changelog. More "oh yes, the tests are more readable, more compact" and I didn't get that.
The migration isn't bad per se, i see the removeal of the deprecated convenience helpers. Maybe I evern liek the less obfuscation of what is going on...

However, didn't you have some thoughts about providing the client via TestRule or other ideas that maybe would be a fit?

…newClient() call sites
CI (ecjLintTest / compileTestJava) failed: the solr/webapp module wasn't covered by
the original migration sweep, so its 8 call sites across 5 test files still called
the now-removed no-arg newClient(). Migrated them to the same
new HttpJettySolrClient.Builder(baseUrl).build() pattern used everywhere else in
this PR.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

@epugh could you add the no-changelog label here? Pushed a fix for solr:webapp's 8 missed newClient() call sites (the ecjLintTest/compileTestJava failure). I don't have permission to add labels myself.

@dsmiley
dsmiley self-requested a review August 23, 2026 01:33
@dsmiley

Copy link
Copy Markdown
Contributor

I should have expressed more when I deprecated that method recently; sorry. I deprecated it because I'd guess that most callers should call getSolrClient() and then don't close it either, as it's lifecycle is managed by the runner. That loses a try-finally on the caller (nice), and avoids needless work and additional connections (nice). The clients are immutable (or should be). So why create a new vanilla client when the JettySolrRunner already has one?

Additionally there's a new method: newSolrClient(Collection) that callers might use if they want a vanilla client scoped to one core/collection, inherited via the SolrBackend, which is new. So it was too much to also have the original newClient method, so I deprecated it. I forgot to deprecate newClient that takes the timeouts because there aren't many callers; I'd rather not keep the method. Too many similar ones on this class.

@dsmileydsmiley added this to the 10.x milestone Aug 23, 2026
serhiy-bzhezytskyy added a commit to serhiy-bzhezytskyy/solr that referenced this pull request Aug 23, 2026
…eplicas() call
solr:webapp wasn't covered by this PR's own migration sweep -- same recurring gap as
SOLR-18390/apache#4778 and SOLR-18357/apache#4790. replicaCount() still called the now-removed
getReplicas().size(); migrated to replicaStream().count() (cast to int, matching the
method's return type), the same pattern used at every other call site in this PR.
…a new one
Where a JettySolrRunner reference is directly available, use its own
getSolrClient()/newSolrClient(collection) instead of building a fresh
HttpJettySolrClient. 59 call sites (51 core/benchmark, 8 webapp).
newSolrClient() builds a fresh, uncached client per call (unlike
getSolrClient()), so those sites keep an explicit close -- caught via
ObjectReleaseTracker flagging 9 leaked clients before this fix.
3 sites left as constructed clients for verified reasons: explicit
close ownership elsewhere, a stop()/start() cycle invalidating a cached
client mid-test, or an in-flight request needing to survive a runner
shutdown that closes the cached client first.
Removed the 2-arg timeout overload too, per the reviewer's follow-up:
he'd rather not keep it either, and it has few callers.
Migrated the 4 callers to the shared getSolrClient() rather than
reconstructing a custom-timeout client: their explicit timeouts (15s
conn/60s idle, 30s/300s) are all shorter than the client's own default
(60s/600s), so nothing was relying on them -- the shared client's
default gives at least as much headroom either way.
…tion
No variable-name collision in the enclosing method required scoping
this off from a try(...) block; de-indented instead of wrapping in a
pointless bare {}.
…JettyRunner.jetty
shardToJetty.get(SHARD2).get(0) is a CloudJettyRunner, which carries a
public JettySolrRunner jetty field right next to the Replica-shaped info
map the code was pulling a raw base_url string from. Same fix as the
other 59 sites, just reached through a different field on the same
object instead of a direct JettySolrRunner variable.
…ibZkTestBase
Experiment: this test never used any AbstractFullDistribZkTestBase-specific
machinery (ChaosMonkey, control_collection, shard-consistency checks) --
setupRestTestHarnesses()/randomRestTestHarness() already delegated to
JettySolrRunner.getRestClient() internally, so the only real work was
replicating the implicit cluster/collection setup explicitly on
SolrCloudTestCase. Passed first try. Not a template for a blanket
migration -- most subclasses of the legacy base actually use the
machinery this one didn't.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Reworked to reuse getSolrClient()/newSolrClient() where a runner reference was available. Also removed newClient(int, int) -- its 4 callers all used timeouts shorter than the shared client's own default, so nothing needed them.

Also tried migrating one AbstractFullDistribZkTestBase test (TestCustomStream) to SolrCloudTestCase as an experiment -- worked first try for this trivial case, but required tracing 2 layers of legacy inheritance to find the implicit setup defaults. Worth a separate, incremental effort, not a blanket migration.

…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/TestPullReplica.java
solrCluster.uploadConfigSet(configset("configset-2"), collectionName);
final String baseUrl = solrCluster.getJettySolrRunners().get(0).getBaseUrl().toString();
final SolrClient solrClient = getHttpSolrClient(baseUrl);
final SolrClient solrClient = new HttpJettySolrClient.Builder(baseUrl).build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can get a new client via solrCluster.getJettySolrRunners().get(0).newSolrClient(). Please apply this pattern wherever possible on tests in this PR -- where the test code deliberately needs a new client for some reason, and a JettySolrRunner is in-scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — applied everywhere a runner was in scope. Two helper methods took baseUrl as a bare String param (SolrJMetricTestUtils.getNumNodeRequestErrors, BasicDistributedZk2Test.addAndQueryDocs); changed both to take JettySolrRunner since their only callers already held one. Also removed BasicDistributedZkTest.createSolrCore, a private method with zero callers anywhere in the repo, found during the sweep.

Replaces manual HttpJettySolrClient.Builder(...) construction with
JettySolrRunner.newSolrClient() across this PR's test changes, per
David's review comment. Also removes BasicDistributedZkTest.createSolrCore,
a private method with zero callers found during the sweep.

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a biggie... we've started on this one but I wish we could moderate/influence which issues you take up before you start as it's a large review undertaking. For this one in particular, there is an overarching theme of SolrClient implementations already existing and managed by JettySolrRunner which one can get one way or another usually. Couple that with CollectionScopedSolrClient and we can use it even if we need to talk to a specific core/replica/collection/alias. There ought to be more conveniences in-place to make this a bit easier. I added some review comments hoping you might take some of them up.

// pick an arbitrary node to use for our requests
client = cluster.getRandomJetty(random()).newClient();
client =
new HttpJettySolrClient.Builder(cluster.getRandomJetty(random()).getBaseUrl().toString())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be newSolrClient on the jetty

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cluster.getRandomJetty(random()).newSolrClient(null).


try (SolrClient coreClient =
getHttpSolrClient(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))) {
new HttpJettySolrClient.Builder(cluster.getZkStateReader().getBaseUrlForNodeName(nodeName))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add MiniSolrCloudCluster.getJetty(nodeNameOrUrl)? Then you'd call it here with getSolrClient(); no need to close.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getJetty(nodeNameOrUrl) and switched to it. Turned out nodeName in this test is actually nodeName/coreNodeName (the success-map key, per CollectionHandlingUtils.requestKey), so I split that off before the lookup.


String coreName = replica.getCoreName();
try (SolrClient client = getHttpSolrClient(replica)) {
try (SolrClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I convince you to add a MiniSolrCloudCluster.getSolrClient(Replica) method that lookups up the right Jetty (see getReplicaJetty)... and then calls getSolrClient() on it and then wraps that with CollectionScopedSolrClient for the core?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MiniSolrCloudCluster.getSolrClient(Replica), wrapping getReplicaJetty(replica).getSolrClient() with CollectionScopedSolrClient, and switched the call site to it.

Use newSolrClient() where a JettySolrRunner is already in scope, and
add MiniSolrCloudCluster.getJetty(nodeNameOrUrl) / getSolrClient(Replica)
so the other two tests can reuse a jetty's shared client instead of
opening and closing their own.
…t-http-client-helpers
# Conflicts:
#	solr/core/src/test/org/apache/solr/cloud/DistribDocExpirationUpdateProcessorTest.java
#	solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
#	solr/core/src/test/org/apache/solr/cloud/NestedShardedAtomicUpdateTest.java
…t-http-client-helpers
# Conflicts:
#	solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
#	solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java

@dsmileydsmiley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the new utility methods :-)

In many of the various classes (I didn't point out each case), you can use the new methods you added (especially the Replica one) to get an existing client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several spots here can use the new method you added to get an existing client by Replica on the cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the new method you added to get an existing client by Replica

…scope
Replaces 34 hand-built clients across 14 test classes. Since the returned
client is cluster-owned, try-with-resources and explicit close() calls on
them are dropped.
…ites
getReplicaJetty now also matches a jetty's proxy URL, so it resolves in
tests that front a node with a SocketProxy. The same lookup is shared with
AbstractFullDistribZkTestBase, which gains its own getSolrClient(Replica)
since it has jettys but no MiniSolrCloudCluster.
@serhiy-bzhezytskyy

Copy link
Copy Markdown
ContributorAuthor

Done

Each needed a different route: node-level CoreAdmin calls take the jetty's
own client, a core-scoped query takes getSolrClient(Replica), a node named
by nodeName takes getJetty(name), and the two _stateVer_ cases wrap the
node's client in CollectionScopedSolrClient to keep addressing by
collection.
MigrateReplicasTest/ReplaceNodeTest/TestCoordinatorRole address a node by
name or URL, so they take getJetty(...).getSolrClient(). The two tests that
keep a set of per-node collection-scoped clients now wrap the nodes' own
clients, so their @afterclass no longer closes them.
… one
TestPrometheusResponseWriter's six metrics calls and one of the bad-input
cases are node-level, so they use getAdminClient(); the collection-scoped
bad-input case uses getSolrClient(collection).
Two tests that build one client per runner now wrap the runner's own client,
so their @afterclass no longer closes them. UpdateLogCloudTest keeps its own
because it restarts a node, which the cached client does not survive.
…tty in lookups
AbstractFullDistribZkTestBase keeps controlJetty outside jettys, so a replica
hosted there was unresolvable -- ShardSplitTest only surfaced it under
-Ptests.nightly=true. Both lookups now search every runner.
The wrapper's default collection is unused by CoreAdmin requests, so the
method David asked for works there as well and keeps one route instead of two.
…ica)
The earlier attempt failed only because controlJetty sat outside the lookup;
with that fixed the conversion works. Every converted site runs with the
proxies reopened, or in the ZK-expiry test where no proxy is involved, so
nothing loses the partition it was exercising.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@serhiy-bzhezytskyy@epugh@dsmiley