This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new dae9a938241 SOLR-17582: Stream CLUSTERSTATUS API for SolrJ version >= 
9.9 (#3156)
dae9a938241 is described below

commit dae9a93824124805b6b96700dab6037a737f40eb
Author: Matthew Biscocho <54160956+mlbis...@users.noreply.github.com>
AuthorDate: Tue Feb 4 21:18:07 2025 -0500

    SOLR-17582: Stream CLUSTERSTATUS API for SolrJ version >= 9.9 (#3156)
    
    Or if unknown (e.g. some misc. HTTP/JSON).
    
    Co-authored-by: Matthew Biscocho <mbisco...@bloomberg.net>
    (cherry picked from commit 5e328ee77e5eeee858f4112e02652ddd4c8e14b6)
---
 .../src/java/org/apache/solr/cli/StatusTool.java   |  2 +-
 .../apache/solr/handler/admin/ClusterStatus.java   | 35 +++++------
 .../solr/handler/admin/CollectionsHandler.java     |  2 +-
 .../cloud/api/collections/TestCollectionAPI.java   | 32 +++++-----
 .../solrj/impl/BaseHttpClusterStateProvider.java   | 15 ++---
 .../solrj/impl/ClusterStateProviderTest.java       | 70 +++++++++++++++++++++-
 6 files changed, 110 insertions(+), 46 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java 
b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
index 3e2368a8d6a..582143f713b 100644
--- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
@@ -372,7 +372,7 @@ public class StatusTool extends ToolBase {
     cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
 
     // TODO get this as a metric from the metrics API instead, or something 
else.
-    var collections = (NamedList<Object>) json.findRecursive("cluster", 
"collections");
+    var collections = (Map<String, Object>) json.findRecursive("cluster", 
"collections");
     cloudStatus.put("collections", String.valueOf(collections.size()));
 
     return cloudStatus;
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
index 7a8ecf9c850..6ef549698d3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Stream;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Aliases;
@@ -34,7 +35,6 @@ import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.PerReplicaStates;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
@@ -101,7 +101,7 @@ public class ClusterStatus {
     collection = params.get(ZkStateReader.COLLECTION_PROP);
   }
 
-  public void getClusterStatus(NamedList<Object> results)
+  public void getClusterStatus(NamedList<Object> results, SolrVersion 
solrVersion)
       throws KeeperException, InterruptedException {
     NamedList<Object> clusterStatus = new SimpleOrderedMap<>();
 
@@ -127,7 +127,7 @@ public class ClusterStatus {
 
     if (withCollection) {
       assert liveNodes != null;
-      fetchClusterStatusForCollOrAlias(clusterStatus, liveNodes, aliases);
+      fetchClusterStatusForCollOrAlias(clusterStatus, liveNodes, aliases, 
solrVersion);
     }
 
     if (withAliases) {
@@ -158,7 +158,10 @@ public class ClusterStatus {
   }
 
   private void fetchClusterStatusForCollOrAlias(
-      NamedList<Object> clusterStatus, List<String> liveNodes, Aliases 
aliases) {
+      NamedList<Object> clusterStatus,
+      List<String> liveNodes,
+      Aliases aliases,
+      SolrVersion solrVersion) {
 
     // read aliases
     Map<String, List<String>> collectionVsAliases = new HashMap<>();
@@ -206,19 +209,7 @@ public class ClusterStatus {
       }
     }
 
-    // Because of back-compat for SolrJ, create the whole response into a 
NamedList
-    // Otherwise stream with MapWriter to save memory
-    if (CommonParams.JAVABIN.equals(solrParams.get(CommonParams.WT))) {
-      NamedList<Object> collectionProps = new SimpleOrderedMap<>();
-      collectionStream.forEach(
-          collectionState -> {
-            collectionProps.add(
-                collectionState.getName(),
-                buildResponseForCollection(
-                    collectionState, collectionVsAliases, routeKey, liveNodes, 
requestedShards));
-          });
-      clusterStatus.add("collections", collectionProps);
-    } else {
+    if (solrVersion == null || 
solrVersion.greaterThanOrEqualTo(SolrVersion.valueOf("9.9.0"))) {
       MapWriter collectionPropsWriter =
           ew -> {
             collectionStream.forEach(
@@ -234,6 +225,16 @@ public class ClusterStatus {
                 });
           };
       clusterStatus.add("collections", collectionPropsWriter);
+    } else {
+      NamedList<Object> collectionProps = new SimpleOrderedMap<>();
+      collectionStream.forEach(
+          collectionState -> {
+            collectionProps.add(
+                collectionState.getName(),
+                buildResponseForCollection(
+                    collectionState, collectionVsAliases, routeKey, liveNodes, 
requestedShards));
+          });
+      clusterStatus.add("collections", collectionProps);
     }
   }
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 330cd7c729b..c61951c76ac 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -979,7 +979,7 @@ public class CollectionsHandler extends RequestHandlerBase 
implements Permission
         CLUSTERSTATUS,
         (req, rsp, h) -> {
           new 
ClusterStatus(h.coreContainer.getZkController().getZkStateReader(), 
req.getParams())
-              .getClusterStatus(rsp.getValues());
+              .getClusterStatus(rsp.getValues(), 
req.getHttpSolrCall().getUserAgentSolrVersion());
           return null;
         }),
     ADDREPLICAPROP_OP(
diff --git 
a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java
 
b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java
index c286c15c016..6a161de5182 100644
--- 
a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java
+++ 
b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestCollectionAPI.java
@@ -135,10 +135,11 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
               .getResponse();
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertEquals(1, collections.size());
-      assertEquals("25", collections._getStr(List.of(COLLECTION_NAME, 
"replicationFactor"), null));
+      Map<?, ?> collectionProperties = (Map<?, ?>) 
collections.get(COLLECTION_NAME);
+      assertEquals("25", collectionProperties.get("replicationFactor"));
 
       params = new ModifiableSolrParams();
       params.set("action", 
CollectionParams.CollectionAction.MODIFYCOLLECTION.toString());
@@ -157,10 +158,11 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       System.out.println(rsp);
       cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      collections = (NamedList<?>) cluster.get("collections");
+      collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertEquals(1, collections.size());
-      assertNull(collections._getStr(List.of(COLLECTION_NAME, 
"replicationFactor"), null));
+      collectionProperties = (Map<?, ?>) collections.get(COLLECTION_NAME);
+      assertNull(collectionProperties.get("replicationFactor"));
 
       params = new ModifiableSolrParams();
       params.set("action", 
CollectionParams.CollectionAction.MODIFYCOLLECTION.toString());
@@ -259,7 +261,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<?> rsp = client.request(req);
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(
           "Testing to insure collections are returned", 
collections.get(COLLECTION_NAME1));
@@ -286,7 +288,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
     NamedList<Object> rsp = client.request(request);
     NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
     assertNotNull("Cluster state should not be null", cluster);
-    NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+    Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
     assertNotNull("Collections should not be null in cluster state", 
collections);
     assertEquals(1, collections.size());
     @SuppressWarnings({"unchecked"})
@@ -308,7 +310,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> rsp = client.request(request);
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(COLLECTION_NAME));
       assertEquals(1, collections.size());
@@ -334,7 +336,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> rsp = request.process(client).getResponse();
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(COLLECTION_NAME));
       assertEquals(1, collections.size());
@@ -469,7 +471,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> rsp = client.request(request);
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(COLLECTION_NAME1));
       assertEquals(4, collections.size());
@@ -491,7 +493,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> rsp = client.request(request);
       NamedList<?> cluster = (NamedList<?>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<?> collections = (NamedList<?>) cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertEquals(1, collections.size());
       @SuppressWarnings({"unchecked"})
@@ -521,7 +523,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> rsp = client.request(request);
       NamedList<Object> cluster = (NamedList<Object>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      NamedList<Object> collections = (NamedList<Object>) 
cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertEquals(1, collections.size());
       Map<String, Object> collection = (Map<String, Object>) 
collections.get(cname);
@@ -537,7 +539,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
 
       rsp = client.request(request);
       cluster = (NamedList<Object>) rsp.get("cluster");
-      collections = (NamedList<Object>) cluster.get("collections");
+      collections = (Map<?, ?>) cluster.get("collections");
       collection = (Map<String, Object>) collections.get(cname);
       Integer newVersion = (Integer) collection.get("znodeVersion");
       assertNotNull(newVersion);
@@ -581,7 +583,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
       NamedList<Object> cluster = (NamedList<Object>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
       @SuppressWarnings({"unchecked"})
-      NamedList<Object> collections = (NamedList<Object>) 
cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(DEFAULT_COLLECTION));
       assertEquals(1, collections.size());
@@ -628,7 +630,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
           DEFAULT_COLLECTION + "," + COLLECTION_NAME,
           aliases.get("myalias"));
 
-      NamedList<Object> collections = (NamedList<Object>) 
cluster.get("collections");
+      Map<?, ?> collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(DEFAULT_COLLECTION));
       Map<String, Object> collection = (Map<String, Object>) 
collections.get(DEFAULT_COLLECTION);
@@ -648,7 +650,7 @@ public class TestCollectionAPI extends 
ReplicaPropertiesBase {
 
       cluster = (NamedList<Object>) rsp.get("cluster");
       assertNotNull("Cluster state should not be null", cluster);
-      collections = (NamedList<Object>) cluster.get("collections");
+      collections = (Map<?, ?>) cluster.get("collections");
       assertNotNull("Collections should not be null in cluster state", 
collections);
       assertNotNull(collections.get(DEFAULT_COLLECTION));
       assertNotNull(collections.get(COLLECTION_NAME));
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
index a0075010862..8d956ca7a94 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
@@ -29,7 +29,6 @@ import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -46,7 +45,6 @@ import org.apache.solr.common.cloud.PerReplicaStates;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.common.util.EnvUtils;
-import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.URLUtil;
 import org.apache.solr.common.util.Utils;
@@ -160,15 +158,12 @@ public abstract class BaseHttpClusterStateProvider 
implements ClusterStateProvid
       liveNodesTimestamp = System.nanoTime();
     }
 
-    var collectionsNl = (NamedList<Map<String, Object>>) 
cluster.get("collections");
+    var collectionsMap = (Map<String, Map<String, Object>>) 
cluster.get("collections");
 
     Map<String, DocCollection> collStateByName =
-        CollectionUtil.newLinkedHashMap(collectionsNl.size());
-    for (Entry<String, Map<String, Object>> entry : collectionsNl) {
-      collStateByName.put(
-          entry.getKey(), getDocCollectionFromObjects(entry.getKey(), 
entry.getValue()));
-    }
-
+        CollectionUtil.newLinkedHashMap(collectionsMap.size());
+    collectionsMap.forEach(
+        (key, value) -> collStateByName.put(key, 
getDocCollectionFromObjects(key, value)));
     return new ClusterState(this.liveNodes, collStateByName);
   }
 
@@ -205,7 +200,7 @@ public abstract class BaseHttpClusterStateProvider 
implements ClusterStateProvid
     SimpleOrderedMap<?> cluster =
         submitClusterStateRequest(client, collection, 
ClusterStateRequestType.FETCH_COLLECTION);
 
-    var collStateMap = (Map<String, Object>) 
cluster.findRecursive("collections", collection);
+    var collStateMap = (Map<String, Object>) 
cluster._get(List.of("collections", collection), null);
     if (collStateMap == null) {
       throw new NotACollectionException(); // probably an alias
     }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java
index 41aa52e4817..9198956261e 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.equalTo;
 
 import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.InvocationTargetException;
 import java.time.Instant;
 import java.util.List;
@@ -37,6 +38,8 @@ import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.util.NamedList;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.BeforeClass;
@@ -137,7 +140,6 @@ public class ClusterStateProviderTest extends 
SolrCloudTestCase {
     try (ClusterStateProvider provider = createClusterStateProvider()) {
 
       ClusterState.CollectionRef collectionRef = 
provider.getState("testGetState");
-
       DocCollection docCollection = collectionRef.get();
       assertNotNull(docCollection);
       assertEquals(
@@ -161,7 +163,7 @@ public class ClusterStateProviderTest extends 
SolrCloudTestCase {
     NamedList<Object> response = clusterStatusResponse.getResponse();
 
     NamedList<Object> cluster = (NamedList<Object>) response.get("cluster");
-    NamedList<Object> collections = (NamedList<Object>) 
cluster.get("collections");
+    Map<String, Object> collections = (Map<String, Object>) 
cluster.get("collections");
     Map<String, Object> collection = (Map<String, Object>) 
collections.get(collectionName);
     return Instant.ofEpochMilli((long) collection.get("creationTimeMillis"));
   }
@@ -200,6 +202,70 @@ public class ClusterStateProviderTest extends 
SolrCloudTestCase {
     }
   }
 
+  @Test
+  public void testClusterStateProviderOldVersion() throws SolrServerException, 
IOException {
+    CollectionAdminRequest.setClusterProperty("ext.foo", 
"bar").process(cluster.getSolrClient());
+    createCollection("col1");
+    createCollection("col2");
+
+    try (var cspZk = zkClientClusterStateProvider();
+        var cspHttp = http2ClusterStateProvider()) {
+      // SolrJ < version 9.9.0 for non streamed response
+      cspHttp
+          .getHttpClient()
+          .getHttpClient()
+          .setUserAgentField(
+              new HttpField(
+                  HttpHeader.USER_AGENT,
+                  "Solr[" + MethodHandles.lookup().lookupClass().getName() + 
"] " + "9.8.0"));
+
+      assertThat(cspHttp.getCollection("col1"), 
equalTo(cspZk.getCollection("col1")));
+
+      final var clusterStateZk = cspZk.getClusterState();
+      final var clusterStateHttp = cspHttp.getClusterState();
+      assertThat(
+          clusterStateHttp.getLiveNodes(),
+          containsInAnyOrder(clusterStateHttp.getLiveNodes().toArray()));
+      assertEquals(2, clusterStateZk.size());
+      assertEquals(clusterStateZk.size(), clusterStateHttp.size());
+      assertThat(
+          clusterStateHttp.collectionStream().collect(Collectors.toList()),
+          containsInAnyOrder(clusterStateHttp.collectionStream().toArray()));
+
+      assertThat(
+          clusterStateZk.getCollection("col2"), 
equalTo(clusterStateHttp.getCollection("col2")));
+    }
+  }
+
+  @Test
+  public void testClusterStateProviderEmptySolrVersion() throws 
SolrServerException, IOException {
+    CollectionAdminRequest.setClusterProperty("ext.foo", 
"bar").process(cluster.getSolrClient());
+    createCollection("col1");
+    createCollection("col2");
+
+    try (var cspZk = zkClientClusterStateProvider();
+        var cspHttp = http2ClusterStateProvider()) {
+
+      cspHttp.getHttpClient().getHttpClient().setUserAgentField(null);
+
+      assertThat(cspHttp.getCollection("col1"), 
equalTo(cspZk.getCollection("col1")));
+
+      final var clusterStateZk = cspZk.getClusterState();
+      final var clusterStateHttp = cspHttp.getClusterState();
+      assertThat(
+          clusterStateHttp.getLiveNodes(),
+          containsInAnyOrder(clusterStateHttp.getLiveNodes().toArray()));
+      assertEquals(2, clusterStateZk.size());
+      assertEquals(clusterStateZk.size(), clusterStateHttp.size());
+      assertThat(
+          clusterStateHttp.collectionStream().collect(Collectors.toList()),
+          containsInAnyOrder(clusterStateHttp.collectionStream().toArray()));
+
+      assertThat(
+          clusterStateZk.getCollection("col2"), 
equalTo(clusterStateHttp.getCollection("col2")));
+    }
+  }
+
   @Test
   public void testClusterStateProviderDownedInitialLiveNodes() throws 
Exception {
     try (var cspHttp = http2ClusterStateProvider()) {

Reply via email to