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 0ca062d92 fix(nameserver): report unreachable endpoints in the config
read tool (#5279)
0ca062d92 is described below
commit 0ca062d929a22c62c503e07f68dc33f5c2992f68
Author: Apulupie <[email protected]>
AuthorDate: Fri Oct 9 19:30:44 2026 +0800
fix(nameserver): report unreachable endpoints in the config read tool
(#5279)
`NameServerConfigDiffService.read()` logged and skipped a NameServer
endpoint it could not reach and
returned the reachable subset as if it were the whole picture, so the AI
tool
`rmq.nameserver.config.diff` reported a partial read as complete. The
result now carries the
unreachable endpoints and sets `resultMayBeTruncated`, and the tool output
schema documents both.
`tool-catalog/tools/nameserver.yaml` changed, so
`rmqctl/internal/catalog/catalog_gen.go` was
regenerated with `make catalog-generate`; `make catalog-verify` reports no
drift
(digest `66dfbf74…`), `go test ./internal/catalog/...` is green and `make
fmt` is clean.
`NameServerConfigDiffServiceTest` + `NameServerConfigToolHandlerTest` +
`ToolOutputSchemaContractTest`:
18 tests green, 0 checkstyle violations. Both new behaviours were
mutation-checked: dropping the
`unreachable.add(address)` call reddens the skip case, and hardcoding
`resultMayBeTruncated` to false
reddens the coverage case.
---
rmqctl/internal/catalog/catalog_gen.go | 2 +-
.../nameserver/NameServerConfigDiffService.java | 15 +++++++--
.../nameserver/NameserverConfigOutput.java | 38 ++++++++++++++++++++++
.../nameserver/NameServerConfigToolHandler.java | 12 ++++---
.../resources/tool-catalog/tools/nameserver.yaml | 14 ++++++++
.../NameServerConfigDiffServiceTest.java | 13 ++++----
.../NameServerConfigToolHandlerTest.java | 31 +++++++++++++++---
.../tool/service/ToolOutputSchemaContractTest.java | 7 ++--
8 files changed, 111 insertions(+), 21 deletions(-)
diff --git a/rmqctl/internal/catalog/catalog_gen.go
b/rmqctl/internal/catalog/catalog_gen.go
index df9af561a..187280e97 100644
--- a/rmqctl/internal/catalog/catalog_gen.go
+++ b/rmqctl/internal/catalog/catalog_gen.go
@@ -21,7 +21,7 @@ package catalog
var defaultDocument = Document{
Version: "2.0.0",
MinimumClientVersion: "2.0.0",
- Digest:
"098b84cd35cfe2dde3b71a1d4381863a54e187517564bbe004b6ea329e8f1814",
+ Digest:
"66dfbf74525c8efb00eb4752a5b569c06b024ea549861b306cd4d8bf8962c34e",
Tools: []Tool{
{
Name: "rmq.acl.list",
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffService.java
index 1cc7255a9..fce482b75 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffService.java
@@ -151,7 +151,7 @@ public class NameServerConfigDiffService {
* (fixes the §15.5.5 "Cluster details are unavailable" path that keyed on
the instance id).
* Secret-bearing keys are never exposed: only {@link #SAFE_CONFIG_KEYS}
are returned.
*/
- public List<NodeConfig> read(String clusterId, String instanceId) {
+ public NameServerConfigRead read(String clusterId, String instanceId) {
String normalizedClusterId = requireClusterId(clusterId);
String normalizedInstanceId = normalizeInstanceId(instanceId);
ClusterVO cluster = normalizedInstanceId == null
@@ -164,11 +164,13 @@ public class NameServerConfigDiffService {
}
String connectionEndpoint = connectionEndpoint(cluster, addresses);
List<NodeConfig> read = new ArrayList<>();
+ List<String> unreachable = new ArrayList<>();
for (String address : addresses) {
try {
Properties config = readConfig(normalizedInstanceId,
connectionEndpoint, address);
read.add(new NodeConfig(address, safeConfig(config)));
} catch (BusinessException exception) {
+ unreachable.add(address);
log.warn("Skipping unreachable NameServer {} while reading
config for cluster {}: {}",
address, normalizedClusterId, exception.getMessage());
}
@@ -177,7 +179,7 @@ public class NameServerConfigDiffService {
throw new BusinessException(502,
"No reachable NameServer endpoint to read config from: " +
normalizedClusterId);
}
- return read;
+ return new NameServerConfigRead(List.copyOf(read),
List.copyOf(unreachable));
}
private Map<String, String> safeConfig(Properties config) {
@@ -195,6 +197,15 @@ public class NameServerConfigDiffService {
public record NodeConfig(String addr, Map<String, String> config) {
}
+ /**
+ * Result of {@link #read}: the safe configuration of every reachable
endpoint plus the
+ * addresses that could not be read. Callers surface the unreachable set
instead of silently
+ * presenting a partial view as complete; {@code unreachableEndpoints} is
empty when every
+ * endpoint answered.
+ */
+ public record NameServerConfigRead(List<NodeConfig> nodes, List<String>
unreachableEndpoints) {
+ }
+
private Properties readConfig(String instanceId, String
connectionEndpoint, String address) {
if (instanceId != null) {
return runtimeAdminClientResolver.execute(instanceId, admin ->
readConfig(admin, address));
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/contract/nameserver/NameserverConfigOutput.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/contract/nameserver/NameserverConfigOutput.java
new file mode 100644
index 000000000..86eac38e5
--- /dev/null
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/contract/nameserver/NameserverConfigOutput.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver;
+
+import java.util.List;
+
+/**
+ * Output of {@code rmq.nameserver.config}. {@code items} covers only the
endpoints that answered;
+ * {@code resultMayBeTruncated} is true when some NameServer endpoints could
not be read and
+ * {@code unreachableEndpoints} names them, so the caller can weigh the
partial coverage instead of
+ * mistaking it for the full cluster configuration.
+ */
+public record NameserverConfigOutput(
+ List<NameserverConfigItem> items,
+ boolean resultMayBeTruncated,
+ List<String> unreachableEndpoints) {
+
+ public static NameserverConfigOutput of(List<NameserverConfigItem> items,
List<String> unreachableEndpoints) {
+ return new NameserverConfigOutput(
+ List.copyOf(items),
+ !unreachableEndpoints.isEmpty(),
+ List.copyOf(unreachableEndpoints));
+ }
+}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandler.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandler.java
index b63099b7f..7e53cb3e6 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandler.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandler.java
@@ -17,9 +17,9 @@
package org.apache.rocketmq.studio.ops.ai.tool.handler.nameserver;
import
org.apache.rocketmq.studio.cluster.nameserver.NameServerConfigDiffService;
-import org.apache.rocketmq.studio.ops.ai.tool.contract.common.ListOutput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigInput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigItem;
+import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigOutput;
import org.apache.rocketmq.studio.ops.ai.tool.core.ToolExecutionContext;
import org.apache.rocketmq.studio.ops.ai.tool.core.ToolHandler;
import org.apache.rocketmq.studio.ops.ai.tool.support.PlatformClusterResolver;
@@ -36,7 +36,7 @@ import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class NameServerConfigToolHandler
- implements ToolHandler<NameserverConfigInput,
ListOutput<NameserverConfigItem>> {
+ implements ToolHandler<NameserverConfigInput, NameserverConfigOutput> {
private final PlatformClusterResolver clusterResolver;
private final NameServerConfigDiffService configDiffService;
@@ -52,11 +52,13 @@ public class NameServerConfigToolHandler
}
@Override
- public ListOutput<NameserverConfigItem> execute(
+ public NameserverConfigOutput execute(
NameserverConfigInput input, ToolExecutionContext context) {
String instanceId =
clusterResolver.resolveInstanceId(input.clusterName());
- return new ListOutput<>(configDiffService.read(input.clusterName(),
instanceId).stream()
+ NameServerConfigDiffService.NameServerConfigRead read =
+ configDiffService.read(input.clusterName(), instanceId);
+ return NameserverConfigOutput.of(read.nodes().stream()
.map(node -> new NameserverConfigItem(node.addr(),
node.config()))
- .toList());
+ .toList(), read.unreachableEndpoints());
}
}
diff --git a/server/src/main/resources/tool-catalog/tools/nameserver.yaml
b/server/src/main/resources/tool-catalog/tools/nameserver.yaml
index 20eff13f6..9c7ae570f 100644
--- a/server/src/main/resources/tool-catalog/tools/nameserver.yaml
+++ b/server/src/main/resources/tool-catalog/tools/nameserver.yaml
@@ -83,10 +83,13 @@ tools:
type: object
required:
- items
+ - resultMayBeTruncated
+ - unreachableEndpoints
additionalProperties: false
properties:
items:
type: array
+ description: Configuration of the NameServer endpoints that answered.
items:
type: object
required:
@@ -101,5 +104,16 @@ tools:
type:
- string
- 'null'
+ resultMayBeTruncated:
+ type: boolean
+ description: >-
+ True when some NameServer endpoints of the cluster could not be
read, so items cover
+ only the reachable endpoints. Weigh the partial coverage before
drawing cluster-wide
+ conclusions, and retry or narrow the scope if needed.
+ unreachableEndpoints:
+ type: array
+ description: Addresses of the NameServer endpoints that could not be
read.
+ items:
+ type: string
viewHint: object
deprecated: false
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffServiceTest.java
index fb9f48256..96d42ec59 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerConfigDiffServiceTest.java
@@ -316,16 +316,16 @@ class NameServerConfigDiffServiceTest {
when(admin.getNameServerConfig(List.of("ns-b:9876")))
.thenReturn(Map.of("ns-b:9876", properties("listenPort",
"9876")));
- List<NameServerConfigDiffService.NodeConfig> nodes =
service.read("cluster-a", "instance-a");
+ NameServerConfigDiffService.NameServerConfigRead read =
service.read("cluster-a", "instance-a");
- assertThat(nodes)
+ assertThat(read.nodes())
.extracting(NameServerConfigDiffService.NodeConfig::addr)
.containsExactly("ns-a:9876", "ns-b:9876");
- assertThat(nodes.get(0).config())
+ assertThat(read.nodes().get(0).config())
.containsEntry("listenPort", "9876")
.containsEntry("serverWorkerThreads", "8")
.doesNotContainKey("password");
- assertThat(nodes.get(1).config()).containsEntry("listenPort", "9876");
+ assertThat(read.nodes().get(1).config()).containsEntry("listenPort",
"9876");
}
@Test
@@ -339,9 +339,10 @@ class NameServerConfigDiffServiceTest {
when(admin.getNameServerConfig(List.of("ns-b:9876")))
.thenThrow(new IllegalStateException("unreachable"));
- List<NameServerConfigDiffService.NodeConfig> nodes =
service.read("cluster-a", null);
+ NameServerConfigDiffService.NameServerConfigRead read =
service.read("cluster-a", null);
- assertThat(nodes).singleElement()
+ assertThat(read.unreachableEndpoints()).containsExactly("ns-b:9876");
+ assertThat(read.nodes()).singleElement()
.extracting(NameServerConfigDiffService.NodeConfig::addr)
.isEqualTo("ns-a:9876");
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandlerTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandlerTest.java
index 93269011e..ca36f7ddc 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandlerTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/nameserver/NameServerConfigToolHandlerTest.java
@@ -17,9 +17,9 @@
package org.apache.rocketmq.studio.ops.ai.tool.handler.nameserver;
import
org.apache.rocketmq.studio.cluster.nameserver.NameServerConfigDiffService;
-import org.apache.rocketmq.studio.ops.ai.tool.contract.common.ListOutput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigInput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigItem;
+import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigOutput;
import org.apache.rocketmq.studio.ops.ai.tool.support.PlatformClusterResolver;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -51,17 +51,38 @@ class NameServerConfigToolHandlerTest {
void readsConfigThroughResolvedInstanceTest() {
assertThat(handler.name()).isEqualTo("rmq.nameserver.config");
when(clusterResolver.resolveInstanceId("rmq-a")).thenReturn("instance-a");
- when(configDiffService.read("rmq-a", "instance-a")).thenReturn(List.of(
- new NameServerConfigDiffService.NodeConfig(
- "ns-a:9876", Map.of("listenPort", "9876"))));
+ when(configDiffService.read("rmq-a", "instance-a")).thenReturn(new
NameServerConfigDiffService.NameServerConfigRead(
+ List.of(new NameServerConfigDiffService.NodeConfig(
+ "ns-a:9876", Map.of("listenPort", "9876"))),
+ List.of()));
- ListOutput<NameserverConfigItem> result = handler.execute(
+ NameserverConfigOutput result = handler.execute(
new NameserverConfigInput("rmq-a"), context("instance-a"));
assertThat(result.items()).singleElement().satisfies(item -> {
assertThat(item.addr()).isEqualTo("ns-a:9876");
assertThat(item.config()).containsEntry("listenPort", "9876");
});
+ assertThat(result.resultMayBeTruncated()).isFalse();
+ assertThat(result.unreachableEndpoints()).isEmpty();
verify(configDiffService).read("rmq-a", "instance-a");
}
+
+ @Test
+ void reportsUnreachableEndpointsAsTruncatedCoverageTest() {
+
when(clusterResolver.resolveInstanceId("rmq-a")).thenReturn("instance-a");
+ when(configDiffService.read("rmq-a", "instance-a")).thenReturn(new
NameServerConfigDiffService.NameServerConfigRead(
+ List.of(new NameServerConfigDiffService.NodeConfig(
+ "ns-a:9876", Map.of("listenPort", "9876"))),
+ List.of("ns-b:9876")));
+
+ NameserverConfigOutput result = handler.execute(
+ new NameserverConfigInput("rmq-a"), context("instance-a"));
+
+ assertThat(result.resultMayBeTruncated()).isTrue();
+ assertThat(result.unreachableEndpoints()).containsExactly("ns-b:9876");
+ assertThat(result.items())
+ .extracting(NameserverConfigItem::addr)
+ .containsExactly("ns-a:9876");
+ }
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
index c99683808..b8dcaed9f 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
@@ -52,6 +52,7 @@ import
org.apache.rocketmq.studio.ops.ai.tool.contract.message.MessageRedelivery
import
org.apache.rocketmq.studio.ops.ai.tool.contract.message.MessageSendOutput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.message.MessageTraceOutput;
import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigItem;
+import
org.apache.rocketmq.studio.ops.ai.tool.contract.nameserver.NameserverConfigOutput;
import org.apache.rocketmq.studio.ops.ai.tool.contract.ops.AuditItem;
import org.apache.rocketmq.studio.ops.ai.tool.contract.proxy.ProxyConfigItem;
import org.apache.rocketmq.studio.ops.ai.tool.contract.topic.TopicDetailOutput;
@@ -226,8 +227,10 @@ class ToolOutputSchemaContractTest {
new NameserverListToolHandler.Item(
"127.0.0.1:9876", "127.0.0.1:9876", "127.0.0.1:9876",
null, null, "UNKNOWN", null)))));
- samples.put("rmq.nameserver.config", List.of(new ListOutput<>(List.of(
- new NameserverConfigItem("127.0.0.1:9876",
Map.of("orderMessageEnable", "false"))))));
+ samples.put("rmq.nameserver.config", List.of(new
NameserverConfigOutput(
+ List.of(new NameserverConfigItem("127.0.0.1:9876",
Map.of("orderMessageEnable", "false"))),
+ true,
+ List.of("127.0.0.1:9877"))));
samples.put("rmq.proxy.list", List.of(new
ListOutput<>(List.of(ProxyVO.builder()
.addr("127.0.0.1:8081")