This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 855cb5087 fix(cluster): order discovered clusters by name (#6388)
855cb5087 is described below
commit 855cb5087da1b4d0f7652fed922c48557f5e458f
Author: 0 <[email protected]>
AuthorDate: Thu Oct 15 20:19:03 2026 +0800
fix(cluster): order discovered clusters by name (#6388)
* fix(cluster): order discovered clusters by name
* fix(cluster): list a cluster brokers in name order and align the rationale
* test(cluster): pin broker name order and drop the fully-qualified TreeSet
The broker half of the discovery ordering fix shipped without coverage:
reverting `new TreeSet<>(brokerNames)` to the raw set iteration left the
whole suite green, because every existing fixture's broker names happened
to iterate in name order. Add a case whose LinkedHashSet is filled in
reverse name order, so the assertion can only hold when buildBrokerList
walks a name-sorted copy.
Also use the short `TreeSet` name: the file already imports it and the two
sibling call sites (discoverBrokers and the proxy-ip walk) use the short
form, so the qualified name was an inconsistency rather than a choice.
---------
Co-authored-by: lizhimins <[email protected]>
---
.../provider/apache/RocketMQClusterProvider.java | 11 +++-
.../apache/RocketMQClusterProviderTest.java | 62 ++++++++++++++++++++++
2 files changed, 71 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
index c21495703..3eb1df2ee 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
@@ -112,7 +112,12 @@ public class RocketMQClusterProvider implements
ClusterProvider {
List<ClusterVO> clusters = new ArrayList<>();
- for (Map.Entry<String, Set<String>> entry :
clusterAddrTable.entrySet()) {
+ // A HashMap iterates deterministically for a given key set,
but nothing agrees
+ // on that order across the providers; sort by cluster name so
the discovered list
+ // matches the ordering RealClusterProvider already applies to
its own table walk.
+ List<Map.Entry<String, Set<String>>> entries = new
ArrayList<>(clusterAddrTable.entrySet());
+ entries.sort(Map.Entry.comparingByKey());
+ for (Map.Entry<String, Set<String>> entry : entries) {
String clusterName = entry.getKey();
Set<String> brokerNames = entry.getValue();
@@ -242,7 +247,9 @@ public class RocketMQClusterProvider implements
ClusterProvider {
return brokers;
}
- for (String brokerName : brokerNames) {
+ // brokerNames is a deserialised HashSet, so its iteration order is
hash order while
+ // RealClusterProvider lists a cluster's brokers by name; walk a
sorted copy so the views agree.
+ for (String brokerName : new TreeSet<>(brokerNames)) {
BrokerData brokerData = brokerAddrTable.get(brokerName);
if (brokerData == null || brokerData.getBrokerAddrs() == null) {
continue;
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
index 54bed71ab..58acf901a 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
@@ -24,6 +24,7 @@ import org.apache.rocketmq.remoting.protocol.body.Connection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+import org.apache.rocketmq.studio.cluster.broker.BrokerVO;
import org.apache.rocketmq.studio.cluster.broker.ClusterVO;
import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
import org.apache.rocketmq.studio.cluster.broker.MqAdminProperties;
@@ -41,6 +42,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -359,6 +361,66 @@ class RocketMQClusterProviderTest {
.isEqualTo("10.0.3.5:8080");
}
+ @Test
+ void discoverClustersShouldOrderClustersByNameRegardlessOfTableOrderTest()
throws Exception {
+ DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+ RocketMQClusterProvider provider = newProvider(adminExt);
+
+ ClusterInfo clusterInfo = new ClusterInfo();
+ HashMap<Long, String> addrs = new HashMap<>();
+ addrs.put(0L, "10.0.0.11:10911");
+ HashMap<String, BrokerData> brokerAddrTable = new HashMap<>();
+ brokerAddrTable.put("broker-a", new BrokerData("zeta-cluster",
"broker-a", addrs));
+ HashMap<String, Set<String>> clusterAddrTable = new LinkedHashMap<>();
+ // Inserted in reverse order so the table's own iteration order
differs from name order.
+ clusterAddrTable.put("zeta-cluster", Set.of("broker-a"));
+ clusterAddrTable.put("alpha-cluster", Set.of("broker-a"));
+ clusterInfo.setBrokerAddrTable(brokerAddrTable);
+ clusterInfo.setClusterAddrTable(clusterAddrTable);
+ when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
+
when(adminExt.fetchBrokerRuntimeStats(anyString())).thenReturn(runtimeStats());
+
+ List<ClusterVO> clusters = provider.discoverClusters();
+
+ // RealClusterProvider already returns clusters sorted by name; the
instance-backed
+ // discovery must agree, so both views list the same clusters in the
same order.
+ assertThat(clusters).extracting(ClusterVO::getId)
+ .containsExactly("alpha-cluster", "zeta-cluster");
+ }
+
+ @Test
+ void discoverClustersShouldOrderBrokersByNameRegardlessOfSetOrderTest()
throws Exception {
+ DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+ RocketMQClusterProvider provider = newProvider(adminExt);
+
+ HashMap<Long, String> firstAddrs = new HashMap<>();
+ firstAddrs.put(0L, "10.0.0.11:10911");
+ HashMap<Long, String> secondAddrs = new HashMap<>();
+ secondAddrs.put(0L, "10.0.0.12:10911");
+ HashMap<String, BrokerData> brokerAddrTable = new HashMap<>();
+ brokerAddrTable.put("broker-a", new BrokerData("DefaultCluster",
"broker-a", firstAddrs));
+ brokerAddrTable.put("broker-b", new BrokerData("DefaultCluster",
"broker-b", secondAddrs));
+ // A LinkedHashSet keeps this insertion order, which is the reverse of
name order, so the
+ // assertion below can only hold when buildBrokerList walks a
name-sorted copy.
+ Set<String> brokerNames = new LinkedHashSet<>(List.of("broker-b",
"broker-a"));
+ HashMap<String, Set<String>> clusterAddrTable = new LinkedHashMap<>();
+ clusterAddrTable.put("DefaultCluster", brokerNames);
+ ClusterInfo clusterInfo = new ClusterInfo();
+ clusterInfo.setBrokerAddrTable(brokerAddrTable);
+ clusterInfo.setClusterAddrTable(clusterAddrTable);
+ when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
+
when(adminExt.fetchBrokerRuntimeStats(anyString())).thenReturn(runtimeStats());
+
+ List<ClusterVO> clusters = provider.discoverClusters();
+
+ // RealClusterProvider lists a cluster's brokers by name; the
instance-backed discovery
+ // must agree, so both views render the same broker order.
+ assertThat(clusters).hasSize(1);
+ assertThat(clusters.get(0).getBrokers())
+ .extracting(BrokerVO::getName)
+ .containsExactly("broker-a", "broker-b");
+ }
+
private RocketMQClusterProvider newProvider(DefaultMQAdminExt adminExt) {
MqAdminExtFactory adminFactory = mock(MqAdminExtFactory.class);
when(adminFactory.execute(anyString(), any(),
any())).thenAnswer(invocation ->