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 88b7f3c3b fix(topic): preserve live permissions when updates omit them
(#7096)
88b7f3c3b is described below
commit 88b7f3c3b0472b41ff882e8df398534925d0a084
Author: Xiao Yang <[email protected]>
AuthorDate: Mon Oct 26 16:48:46 2026 +0800
fix(topic): preserve live permissions when updates omit them (#7096)
* fix(topic): preserve live permissions when updates omit them
* docs(api-spec): state the omitted-permission rule in the table's own
language
The perm row of POST /api/topics/update was replaced by a full English
paragraph
inside an otherwise Chinese table. Same semantics, matching the surrounding
cells.
---
docs/api-spec.md | 2 +-
.../provider/apache/RocketMQAdminClientImpl.java | 21 +++++--
.../apache/RocketMQAdminClientImplTest.java | 73 +++++++++++++++++++++-
3 files changed, 89 insertions(+), 7 deletions(-)
diff --git a/docs/api-spec.md b/docs/api-spec.md
index 0c5c7ec73..c32bd4d23 100644
--- a/docs/api-spec.md
+++ b/docs/api-spec.md
@@ -1042,7 +1042,7 @@ POST /api/topics/update
| `clusterId` | `string` | 否 | 所属集群;Apache 写路径按实例与所有权记录解析目标集群,不读取该字段 |
| `writeQueues` | `number` | 否 | 写队列数,须 ≥ 0;缺省或非正数时保留现值 |
| `readQueues` | `number` | 否 | 读队列数,须 ≥ 0;缺省或非正数时保留现值 |
-| `perm` | `string` | 否 | 权限,默认 `RW` |
+| `perm` | `string` | 否 | 权限。Apache:显式传值即更新权限;省略时各既有 Broker 保留自身运行时的权限掩码(含
Studio 不可编辑的位)、库内存值不变;新增 Broker 用库内存值兜底,取不到时用 `RW` |
| `remark` | `string` | 否 | 备注 |
**Response `data`:** `Topic`
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
index 3204b1677..e7fb3378b 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
@@ -414,10 +414,14 @@ public class RocketMQAdminClientImpl implements
AdminClient {
// Preflight every broker before any write so a failed read
cannot erase its
// configuration or leave earlier brokers updated. Each broker
keeps its flags.
for (String addr : brokerAddrs.stream().sorted().toList()) {
- TopicConfig topicConfig = topicConfigForUpdate(admin,
addr, topicName);
+ TopicConfig topicConfig = topicConfigForUpdate(admin,
addr, topicName, perm);
topicConfig.setWriteQueueNums(writeQueues);
topicConfig.setReadQueueNums(readQueues);
- topicConfig.setPerm(perm);
+ // A broker may have been made read-only outside Studio.
An omitted permission
+ // must keep its live mask, including non-editable bits,
rather than replay stale metadata.
+ if (topic.getPerm() != null) {
+ topicConfig.setPerm(perm);
+ }
// Broker attributes are merged separately and writes
require delta keys.
topicConfig.setAttributes(Map.of());
applyTopicType(topicConfig, topic.getType());
@@ -431,7 +435,9 @@ public class RocketMQAdminClientImpl implements AdminClient
{
if (existing != null) {
existing.setWriteQueueNums(writeQueues);
existing.setReadQueueNums(readQueues);
- existing.setPerm(perm);
+ if (topic.getPerm() != null) {
+ existing.setPerm(perm);
+ }
if (topic.getType() != null) {
existing.setTopicType(topic.getType().name());
}
@@ -474,7 +480,7 @@ public class RocketMQAdminClientImpl implements AdminClient
{
});
}
- private TopicConfig topicConfigForUpdate(MQAdminExt admin, String
brokerAddr, String topicName)
+ private TopicConfig topicConfigForUpdate(MQAdminExt admin, String
brokerAddr, String topicName, int fallbackPerm)
throws Exception {
TopicConfig current;
try {
@@ -486,7 +492,12 @@ public class RocketMQAdminClientImpl implements
AdminClient {
current = null;
}
// A newly added master may not hold the topic yet. Preserve update's
provisioning behavior.
- return current == null ? new TopicConfig(topicName) : new
TopicConfig(current);
+ if (current == null) {
+ TopicConfig created = new TopicConfig(topicName);
+ created.setPerm(fallbackPerm);
+ return created;
+ }
+ return new TopicConfig(current);
}
private void applyTopicType(TopicConfig topicConfig, TopicType topicType) {
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
index 6a1fd5947..dbe9c512a 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
@@ -1167,6 +1167,76 @@ class RocketMQAdminClientImplTest {
verify(topicMapper).updateById(existing);
}
+ @ParameterizedTest
+ @CsvSource({"0, 4", "2, 5", "3, 7"})
+ void updateTopicWithoutPermissionPreservesEachBrokersPermissionTest(int
firstPerm, int secondPerm) throws Exception {
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);
+
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfoWithTwoMasters());
+ RmqTopic existing = new RmqTopic();
+ existing.setName("orders");
+ existing.setWriteQueueNums(8);
+ existing.setReadQueueNums(8);
+ existing.setPerm(6);
+ when(topicMapper.selectOne(any())).thenReturn(existing);
+ Map<String, Integer> permissions = Map.of("10.0.0.1:10911", firstPerm,
"10.0.0.2:10911", secondPerm);
+ for (var entry : permissions.entrySet()) {
+ when(adminExt.examineTopicConfig(entry.getKey(), "orders"))
+ .thenReturn(new TopicConfig("orders", 8, 8,
entry.getValue()));
+ }
+ TopicVO topic = new TopicVO();
+ topic.setInstanceId("instance-a");
+ topic.setName("orders");
+ topic.setWriteQueues(16);
+ topic.setReadQueues(12);
+ topic.setRemark("updated remark");
+
+ adminClient.updateTopic(topic);
+
+ ArgumentCaptor<String> addresses =
ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor<TopicConfig> configs =
ArgumentCaptor.forClass(TopicConfig.class);
+ verify(adminExt,
times(2)).createAndUpdateTopicConfig(addresses.capture(), configs.capture());
+ for (int i = 0; i < addresses.getAllValues().size(); i++) {
+ TopicConfig sent = configs.getAllValues().get(i);
+
assertThat(sent.getPerm()).isEqualTo(permissions.get(addresses.getAllValues().get(i)));
+ assertThat(sent.getWriteQueueNums()).isEqualTo(16);
+ assertThat(sent.getReadQueueNums()).isEqualTo(12);
+ }
+ assertThat(existing.getPerm()).isEqualTo(6);
+ assertThat(existing.getRemark()).isEqualTo("updated remark");
+ verify(topicMapper).updateById(existing);
+ }
+
+ @ParameterizedTest
+ @CsvSource({"RO, 4", "WO, 2", "RW, 6"})
+ void updateTopicWithPermissionOverridesEachBrokersPermissionTest(TopicPerm
requested, int expected) throws Exception {
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);
+
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfoWithTwoMasters());
+ RmqTopic existing = new RmqTopic();
+ existing.setName("orders");
+ existing.setWriteQueueNums(8);
+ existing.setReadQueueNums(8);
+ existing.setPerm(6);
+ when(topicMapper.selectOne(any())).thenReturn(existing);
+ when(adminExt.examineTopicConfig("10.0.0.1:10911", "orders"))
+ .thenReturn(new TopicConfig("orders", 8, 8, 0));
+ when(adminExt.examineTopicConfig("10.0.0.2:10911", "orders"))
+ .thenReturn(new TopicConfig("orders", 8, 8, 5));
+ TopicVO topic = new TopicVO();
+ topic.setInstanceId("instance-a");
+ topic.setName("orders");
+ topic.setWriteQueues(8);
+ topic.setReadQueues(8);
+ topic.setPerm(requested);
+
+ adminClient.updateTopic(topic);
+
+ ArgumentCaptor<TopicConfig> configs =
ArgumentCaptor.forClass(TopicConfig.class);
+ verify(adminExt, times(2)).createAndUpdateTopicConfig(anyString(),
configs.capture());
+ assertThat(configs.getAllValues()).allSatisfy(config ->
assertThat(config.getPerm()).isEqualTo(expected));
+ assertThat(existing.getPerm()).isEqualTo(expected);
+ verify(topicMapper).updateById(existing);
+ }
+
@Test
void updateTopicReadsAllConfigurationsBeforeWritingTest() throws Exception
{
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new
MybatisConfiguration(), ""), RmqTopic.class);
@@ -1205,7 +1275,7 @@ class RocketMQAdminClientImplTest {
existing.setName("orders");
existing.setWriteQueueNums(8);
existing.setReadQueueNums(8);
- existing.setPerm(6);
+ existing.setPerm(4);
when(topicMapper.selectOne(any())).thenReturn(existing);
var reads = lenient().when(adminExt.examineTopicConfig(anyString(),
eq("orders")));
if (absentException) {
@@ -1225,6 +1295,7 @@ class RocketMQAdminClientImplTest {
verify(adminExt, times(2)).createAndUpdateTopicConfig(anyString(),
configs.capture());
assertThat(configs.getAllValues()).allSatisfy(config -> {
assertThat(config.getTopicName()).isEqualTo("orders");
+ assertThat(config.getPerm()).isEqualTo(4);
assertThat(config.getReadQueueNums()).isEqualTo(12);
assertThat(config.getWriteQueueNums()).isEqualTo(16);
assertThat(config.getTopicSysFlag()).isZero();