Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-17582 Stream CLUSTERSTATUS API response #2916

Merged
merged 12 commits into from
Jan 4, 2025
133 changes: 59 additions & 74 deletions solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ private void fetchClusterStatusForCollOrAlias(
String routeKey = solrParams.get(ShardParams._ROUTE_);
String shard = solrParams.get(ZkStateReader.SHARD_ID_PROP);

Set<String> requestedShards = new HashSet<>();
if (shard != null) {
String[] paramShards = shard.split(",");
requestedShards.addAll(Arrays.asList(paramShards));
}
dsmiley marked this conversation as resolved.
Show resolved Hide resolved

Stream<DocCollection> collectionStream;
if (collection == null) {
collectionStream = clusterState.collectionStream();
Expand Down Expand Up @@ -210,12 +216,20 @@ private void fetchClusterStatusForCollOrAlias(

MapWriter collectionPropsWriter =
ew -> {
SolrCollectionProperiesIterator collectionProps =
new SolrCollectionProperiesIterator(
collectionStream.iterator(), collectionVsAliases, routeKey, liveNodes, shard);
while (collectionProps.hasNext()) {
Map<String, Object> collectionPropsMap = (collectionProps.next().asMap());
collectionPropsMap.forEach(
Iterator<Map<String, Object>> it =
collectionStream
.map(
(collectionState) ->
collectionPropsResponse(
collectionState,
collectionVsAliases,
routeKey,
liveNodes,
requestedShards))
.iterator();
while (it.hasNext()) {
dsmiley marked this conversation as resolved.
Show resolved Hide resolved
Map<String, Object> props = it.next();
props.forEach(
(key, value) -> {
try {
ew.put(key, value);
Expand Down Expand Up @@ -280,8 +294,8 @@ private Map<String, Object> getCollectionStatus(
*/
@SuppressWarnings("unchecked")
protected void crossCheckReplicaStateWithLiveNodes(
List<String> liveNodes, NamedList<Object> collectionProps) {
for (Map.Entry<String, Object> next : collectionProps) {
List<String> liveNodes, Map<String, Object> collectionProps) {
for (Map.Entry<String, Object> next : collectionProps.entrySet()) {
Map<String, Object> collMap = (Map<String, Object>) next.getValue();
Map<String, Object> shards = (Map<String, Object>) collMap.get("shards");
for (Object nextShard : shards.values()) {
Expand Down Expand Up @@ -342,76 +356,47 @@ public static Map<String, Object> postProcessCollectionJSON(Map<String, Object>
return collection;
}

private class SolrCollectionProperiesIterator implements Iterator<NamedList<Object>> {

final Iterator<DocCollection> it;
Map<String, List<String>> collectionVsAliases;
String routeKey;
List<String> liveNodes;
String shard;

public SolrCollectionProperiesIterator(
Iterator<DocCollection> it,
Map<String, List<String>> collectionVsAliases,
String routeKey,
List<String> liveNodes,
String shard) {
this.it = it;
this.collectionVsAliases = collectionVsAliases;
this.routeKey = routeKey;
this.liveNodes = liveNodes;
this.shard = shard;
}

@Override
public boolean hasNext() {
return it.hasNext();
}

@Override
public NamedList<Object> next() {
NamedList<Object> collectionProps = new SimpleOrderedMap<>();
DocCollection clusterStateCollection = it.next();
Map<String, Object> collectionStatus;
String name = clusterStateCollection.getName();

Set<String> requestedShards = new HashSet<>();
if (routeKey != null) {
DocRouter router = clusterStateCollection.getRouter();
Collection<Slice> slices = router.getSearchSlices(routeKey, null, clusterStateCollection);
for (Slice slice : slices) {
requestedShards.add(slice.getName());
}
}
if (shard != null) {
String[] paramShards = shard.split(",");
requestedShards.addAll(Arrays.asList(paramShards));
private Map<String, Object> collectionPropsResponse(
dsmiley marked this conversation as resolved.
Show resolved Hide resolved
DocCollection clusterStateCollection,
Map<String, List<String>> collectionVsAliases,
String routeKey,
List<String> liveNodes,
Set<String> requestedShards) {
Map<String, Object> collectionProps = new HashMap<>();
Map<String, Object> collectionStatus;
String name = clusterStateCollection.getName();

if (routeKey != null) {
DocRouter router = clusterStateCollection.getRouter();
Collection<Slice> slices = router.getSearchSlices(routeKey, null, clusterStateCollection);
for (Slice slice : slices) {
requestedShards.add(slice.getName());
dsmiley marked this conversation as resolved.
Show resolved Hide resolved
}
}

byte[] bytes = Utils.toJSON(clusterStateCollection);
@SuppressWarnings("unchecked")
Map<String, Object> docCollection = (Map<String, Object>) Utils.fromJSON(bytes);
collectionStatus = getCollectionStatus(docCollection, name, requestedShards);
byte[] bytes = Utils.toJSON(clusterStateCollection);
@SuppressWarnings("unchecked")
Map<String, Object> docCollection = (Map<String, Object>) Utils.fromJSON(bytes);
collectionStatus = getCollectionStatus(docCollection, name, requestedShards);

collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
collectionStatus.put(
"creationTimeMillis", clusterStateCollection.getCreationTime().toEpochMilli());
collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
collectionStatus.put(
"creationTimeMillis", clusterStateCollection.getCreationTime().toEpochMilli());

if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
collectionStatus.put("aliases", collectionVsAliases.get(name));
}
String configName = clusterStateCollection.getConfigName();
collectionStatus.put("configName", configName);
if (solrParams.getBool("prs", false) && clusterStateCollection.isPerReplicaState()) {
PerReplicaStates prs = clusterStateCollection.getPerReplicaStates();
collectionStatus.put("PRS", prs);
}
collectionProps.add(name, collectionStatus);

// now we need to walk the collectionProps tree to cross-check replica state with live
// nodes
crossCheckReplicaStateWithLiveNodes(liveNodes, collectionProps);
return collectionProps;
if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
collectionStatus.put("aliases", collectionVsAliases.get(name));
}
String configName = clusterStateCollection.getConfigName();
collectionStatus.put("configName", configName);
if (solrParams.getBool("prs", false) && clusterStateCollection.isPerReplicaState()) {
PerReplicaStates prs = clusterStateCollection.getPerReplicaStates();
collectionStatus.put("PRS", prs);
}
collectionProps.put(name, collectionStatus);
dsmiley marked this conversation as resolved.
Show resolved Hide resolved

// now we need to walk the collectionProps tree to cross-check replica state with live
// nodes
crossCheckReplicaStateWithLiveNodes(liveNodes, collectionProps);
return collectionProps;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ private ClusterState fetchClusterState(SolrClient client)
liveNodesTimestamp = System.nanoTime();
}

var collectionsNl = (Map<String, 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.entrySet()) {
CollectionUtil.newLinkedHashMap(collectionsMap.size());
for (Entry<String, Map<String, Object>> entry : collectionsMap.entrySet()) {
collStateByName.put(
entry.getKey(), getDocCollectionFromObjects(entry.getKey(), entry.getValue()));
}
Expand Down Expand Up @@ -184,13 +184,12 @@ private DocCollection fetchCollectionState(SolrClient client, String collection)
SimpleOrderedMap<?> cluster =
submitClusterStateRequest(client, collection, ClusterStateRequestType.FETCH_COLLECTION);

var collState = (Map<String, Object>) cluster.findRecursive("collections");
var collStateMap = (Map<String, Object>) collState.get(collection);
var collState = (Map<String, Object>) cluster.findRecursive("collections", collection);

if (collStateMap == null) {
if (collState == null) {
throw new NotACollectionException(); // probably an alias
}
return getDocCollectionFromObjects(collection, collStateMap);
return getDocCollectionFromObjects(collection, collState);
}

private SimpleOrderedMap<?> submitClusterStateRequest(
Expand Down
10 changes: 6 additions & 4 deletions solr/solrj/src/java/org/apache/solr/common/util/NamedList.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ private void killAll(String name) {
/**
* Recursively parses the NamedList structure to arrive at a specific element. As you descend the
* NamedList tree, the last element can be any type, including NamedList, but the previous
* elements MUST be NamedList objects themselves. A null value is returned if the indicated
* elements MUST be NamedList or Map objects themselves. A null value is returned if the indicated
* hierarchy doesn't exist, but NamedList allows null values so that could be the actual value at
* the end of the path.
*
Expand Down Expand Up @@ -348,9 +348,9 @@ public Object findRecursive(String... args) {
* it to this list. Then we retrieve the first key from this list and
* assign it to value.
*
* On the next loop, we check whether the retrieved value is a NamedList.
* If it is, then we drop to that NamedList, grab the value of the
* next key, and start the loop over. If it is not a NamedList, then we
* On the next loop, we check whether the retrieved value is a NamedList or Map.
* If it is, then we drop to that NamedList/Map, grab the value of the
* next key, and start the loop over. If it is not a NamedList/Map, then we
* assign the value to null and break out of the loop.
*
* Assigning the value to null and then breaking out of the loop seems
Expand All @@ -363,6 +363,8 @@ public Object findRecursive(String... args) {
} else {
if (value instanceof NamedList) {
currentList = (NamedList<?>) value;
} else if (value instanceof Map) {
currentList = new NamedList<>((Map<String, Object>) value);
dsmiley marked this conversation as resolved.
Show resolved Hide resolved
} else {
value = null;
break;
Expand Down
33 changes: 33 additions & 0 deletions solr/solrj/src/test/org/apache/solr/common/util/NamedListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.apache.solr.common.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.SolrTestCase;
Expand Down Expand Up @@ -209,4 +210,36 @@ public void testShallowMap() {
assertEquals("Val2", nl.get("key2"));
assertEquals("Val2", m.get("key2"));
}

@Test
@SuppressWarnings("unchecked")
public void testFindRecursiveWithMap() {

// key1 (NamedList)
// - key2 (Map)
// -- key3 (NamedList)
// --- key4 (Map)

NamedList<Object> nl1 = new NamedList<>();
Map<String, Object> map2 = new HashMap<>();
NamedList<Object> nl3 = new NamedList<>();
Map<String, Object> map4 = new HashMap<>();

map4.put("key4", "value4");
nl3.add("key3", map4);
map2.put("key2", nl3);
nl1.add("key1", map2);

Map<String, Object> test1 = (Map<String, Object>) nl1.findRecursive("key1");
assertNotNull(test1);

NamedList<Object> test2 = (NamedList<Object>) nl1.findRecursive("key1", "key2");
assertNotNull(test2);

Map<String, Object> test3 = (Map<String, Object>) nl1.findRecursive("key1", "key2", "key3");
assertNotNull(test3);

String test4 = (String) nl1.findRecursive("key1", "key2", "key3", "key4");
assertEquals("value4", test4);
}
}
Loading