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 eec263d7 fix: isolate control-plane audit failures (#1135)
eec263d7 is described below

commit eec263d72f99f2a9c408d09be7391b3ffbce05fc
Author: aias00 <[email protected]>
AuthorDate: Fri Aug 7 00:21:18 2026 -0700

    fix: isolate control-plane audit failures (#1135)
    
    * fix: isolate metadata audit failures
    
    * fix: isolate broker config audit failures
---
 .../studio/cluster/broker/ClusterService.java      |  6 +-
 .../provider/apache/RocketMQAdminClientImpl.java   | 53 +++++++++-------
 .../apache/RocketMQBrokerConfigService.java        | 13 +++-
 .../studio/cluster/broker/ClusterServiceTest.java  | 15 +++++
 .../apache/RocketMQAdminClientImplTest.java        | 46 ++++++++++++++
 .../apache/RocketMQBrokerConfigServiceTest.java    | 73 ++++++++++++++++++++++
 6 files changed, 179 insertions(+), 27 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
index 4b8c76ae..8d85d49e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterService.java
@@ -189,7 +189,11 @@ public class ClusterService {
                 + ", failedBrokers=" + failedBrokers.stream()
                 .map(failure -> failure.getAddress() + ": " + 
failure.getMessage())
                 .toList();
-        auditService.record("UPDATE_CLUSTER_CONFIG", "CLUSTER:" + clusterId, 
detail, status.name());
+        try {
+            auditService.record("UPDATE_CLUSTER_CONFIG", "CLUSTER:" + 
clusterId, detail, status.name());
+        } catch (Exception e) {
+            log.warn("Failed to record cluster config update audit for {}: 
{}", clusterId, e.getMessage());
+        }
     }
 
     private ClusterVO resolveCluster(String clusterId) {
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 f6fc162c..34939d5b 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
@@ -193,7 +193,7 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
                     topicMapper.updateById(entity);
                 }
 
-                auditService.record("CREATE_TOPIC", topicName,
+                recordAudit("CREATE_TOPIC", topicName,
                         "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
 
                 topic.setId(topicName);
@@ -201,10 +201,10 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                 topic.setReadQueues(readQueues);
                 return topic;
             } catch (BusinessException e) {
-                auditService.record("CREATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
+                recordAudit("CREATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
                 throw e;
             } catch (Exception e) {
-                auditService.record("CREATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
+                recordAudit("CREATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
                 throw new BusinessException(500, "Failed to create topic: " + 
e.getMessage());
             }
         });
@@ -252,7 +252,7 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
                     topicMapper.updateById(existing);
                 }
 
-                auditService.record("UPDATE_TOPIC", topicName,
+                recordAudit("UPDATE_TOPIC", topicName,
                         "queues=" + writeQueues + "/" + readQueues, "SUCCESS");
 
                 topic.setId(topicName);
@@ -260,10 +260,10 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                 topic.setReadQueues(readQueues);
                 return topic;
             } catch (BusinessException e) {
-                auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
+                recordAudit("UPDATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
                 throw e;
             } catch (Exception e) {
-                auditService.record("UPDATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
+                recordAudit("UPDATE_TOPIC", topicName, e.getMessage(), 
"FAILED");
                 throw new BusinessException(500, "Failed to update topic: " + 
e.getMessage());
             }
         });
@@ -294,13 +294,13 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                 // Delete from DB
                 topicMapper.delete(new 
LambdaQueryWrapper<RmqTopic>().eq(RmqTopic::getName, name));
 
-                auditService.record("DELETE_TOPIC", name, "", "SUCCESS");
+                recordAudit("DELETE_TOPIC", name, "", "SUCCESS");
                 return null;
             } catch (BusinessException e) {
-                auditService.record("DELETE_TOPIC", name, e.getMessage(), 
"FAILED");
+                recordAudit("DELETE_TOPIC", name, e.getMessage(), "FAILED");
                 throw e;
             } catch (Exception e) {
-                auditService.record("DELETE_TOPIC", name, e.getMessage(), 
"FAILED");
+                recordAudit("DELETE_TOPIC", name, e.getMessage(), "FAILED");
                 throw new BusinessException(500, "Failed to delete topic: " + 
e.getMessage());
             }
         });
@@ -336,12 +336,8 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
 
             // The message is already delivered by now; an audit write failure 
must not turn a
             // successful send into an error, or callers would retry and 
duplicate the message.
-            try {
-                auditService.record("SEND_MESSAGE", topic,
-                        "tag=" + tag + ", key=" + key + ", msgId=" + 
sendResult.getMsgId(), "SUCCESS");
-            } catch (Exception auditFailure) {
-                log.warn("Failed to record send message audit: {}", 
auditFailure.getMessage());
-            }
+            recordAudit("SEND_MESSAGE", topic,
+                    "tag=" + tag + ", key=" + key + ", msgId=" + 
sendResult.getMsgId(), "SUCCESS");
 
             return SendMessageVO.builder()
                     .msgId(sendResult.getMsgId())
@@ -349,7 +345,7 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
                     .offsetMsgId(sendResult.getOffsetMsgId())
                     .build();
         } catch (Exception e) {
-            auditService.record("SEND_MESSAGE", request.getTopic(), 
e.getMessage(), "FAILED");
+            recordAudit("SEND_MESSAGE", request.getTopic(), e.getMessage(), 
"FAILED");
             throw new BusinessException(500, "Failed to send message: " + 
e.getMessage());
         } finally {
             producer.shutdown();
@@ -416,16 +412,16 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                 groupMapper.updateById(entity);
             }
 
-            auditService.record("CREATE_GROUP", groupName,
+            recordAudit("CREATE_GROUP", groupName,
                     "retryMaxTimes=" + config.getRetryMaxTimes(), "SUCCESS");
 
             group.setId(groupName);
             return group;
         } catch (BusinessException e) {
-            auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
+            recordAudit("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
             throw e;
         } catch (Exception e) {
-            auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
+            recordAudit("CREATE_GROUP", groupName, e.getMessage(), "FAILED");
             throw new BusinessException(500, "Failed to create consumer group: 
" + e.getMessage());
         }
     }
@@ -456,12 +452,12 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
             // Delete from DB
             groupMapper.delete(new 
LambdaQueryWrapper<RmqGroup>().eq(RmqGroup::getName, name));
 
-            auditService.record("DELETE_GROUP", name, "", "SUCCESS");
+            recordAudit("DELETE_GROUP", name, "", "SUCCESS");
         } catch (BusinessException e) {
-            auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
+            recordAudit("DELETE_GROUP", name, e.getMessage(), "FAILED");
             throw e;
         } catch (Exception e) {
-            auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
+            recordAudit("DELETE_GROUP", name, e.getMessage(), "FAILED");
             throw new BusinessException(500, "Failed to delete consumer group: 
" + e.getMessage());
         }
     }
@@ -480,16 +476,25 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
                     return null;
                 });
             }
-            auditService.record("RESET_OFFSET", name,
+            recordAudit("RESET_OFFSET", name,
                     "instanceId=" + instanceId + ", topic=" + topic + ", 
timestamp=" + timestamp, "SUCCESS");
         } catch (Exception e) {
-            auditService.record("RESET_OFFSET", name, e.getMessage(), 
"FAILED");
+            recordAudit("RESET_OFFSET", name, e.getMessage(), "FAILED");
             throw new BusinessException(500, "Failed to reset offset: " + 
e.getMessage());
         }
     }
 
     // ── Helper methods ──────────────────────────────────────────────────
 
+    private void recordAudit(String action, String resource, String detail, 
String result) {
+        try {
+            auditService.record(action, resource, detail, result);
+        } catch (Exception auditFailure) {
+            log.warn("Failed to record audit action={} resource={}: {}", 
action, resource,
+                    auditFailure.getMessage());
+        }
+    }
+
     /**
      * Returns the configured default NameServer address, failing fast when 
the studio has no
      * RocketMQ endpoint configured (equivalent to the former absent admin 
bean).
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
index 15efe4d2..6dfb4577 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigService.java
@@ -60,18 +60,27 @@ public class RocketMQBrokerConfigService {
             try {
                 admin.updateBrokerConfig(brokerAddr, newConfig);
                 String detail = "brokerAddr=" + brokerAddr + ", config=" + 
newConfig;
-                auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + 
clusterId, detail, "SUCCESS");
+                recordAudit(clusterId, detail, "SUCCESS");
                 log.info("Broker config updated successfully: {}", brokerAddr);
                 return null;
             } catch (Exception e) {
                 log.error("Failed to update broker config at {}", brokerAddr, 
e);
                 String detail = "brokerAddr=" + brokerAddr + ", error=" + 
e.getMessage();
-                auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + 
clusterId, detail, "FAILURE");
+                recordAudit(clusterId, detail, "FAILURE");
                 throw new BusinessException(500, "Failed to update broker 
config: " + e.getMessage());
             }
         });
     }
 
+    private void recordAudit(String clusterId, String detail, String result) {
+        try {
+            auditService.record("UPDATE_BROKER_CONFIG", "CLUSTER:" + 
clusterId, detail, result);
+        } catch (Exception auditFailure) {
+            log.warn("Failed to record broker config audit for cluster {}: 
{}", clusterId,
+                    auditFailure.getMessage());
+        }
+    }
+
     private ClusterConfigVO mapToClusterConfigVO(Properties props) {
         ClusterConfigVO vo = new ClusterConfigVO();
         
vo.setFlushDiskType(parseFlushDiskType(props.getProperty("flushDiskType", 
"ASYNC_FLUSH")));
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
index 1e6bcb9c..ca9f193f 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterServiceTest.java
@@ -194,6 +194,21 @@ class ClusterServiceTest {
         verify(clusterRepository).updateConfig(eq("cluster-1"), 
any(ClusterConfigVO.class));
     }
 
+    @Test
+    void updateConfigShouldSucceedWhenAuditRecordingFails() {
+        
when(clusterRepository.findById("cluster-1")).thenReturn(Optional.of(sampleCluster));
+        doThrow(new IllegalStateException("audit storage 
unavailable")).when(auditService)
+                .record(any(), any(), any(), any());
+
+        ClusterConfigUpdateResultVO result = 
clusterService.updateClusterConfig(UpdateConfigDTO.builder()
+                .id("cluster-1")
+                .flushDiskType("SYNC_FLUSH")
+                .build());
+
+        
assertThat(result.getStatus()).isEqualTo(ClusterConfigUpdateResultVO.Status.SUCCESS);
+        verify(clusterRepository).updateConfig(eq("cluster-1"), 
any(ClusterConfigVO.class));
+    }
+
     @Test
     void updateConfigShouldUpdateMultipleFields() {
         
when(clusterRepository.findById("cluster-1")).thenReturn(Optional.of(sampleCluster));
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 0ad5d522..5877e888 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
@@ -234,6 +234,52 @@ class RocketMQAdminClientImplTest {
         verify(adminExt, never()).deleteSubscriptionGroup(anyString(), 
anyString(), org.mockito.ArgumentMatchers.anyBoolean());
     }
 
+    @Test
+    void createTopicSucceedsWhenAuditRecordingFails() throws Exception {
+        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqTopic.class);
+        ClusterInfo clusterInfo = clusterInfoWithMaster();
+        when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
+        when(topicMapper.selectOne(any())).thenReturn(null);
+        doNothing().when(adminExt).createAndUpdateTopicConfig(anyString(), 
any(TopicConfig.class));
+        doThrow(new RuntimeException("audit db down")).when(auditService)
+                .record(anyString(), anyString(), anyString(), anyString());
+
+        TopicVO topic = new TopicVO();
+        topic.setName("topicA");
+
+        assertThat(adminClient.createTopic(topic).getId()).isEqualTo("topicA");
+        verify(auditService).record("CREATE_TOPIC", "topicA", "queues=8/8", 
"SUCCESS");
+    }
+
+    @Test
+    void createTopicPreservesRemoteFailureWhenAuditRecordingFails() throws 
Exception {
+        when(adminExt.examineBrokerClusterInfo())
+                .thenThrow(new IllegalStateException("broker unavailable"));
+        doThrow(new RuntimeException("audit db down")).when(auditService)
+                .record(anyString(), anyString(), anyString(), anyString());
+
+        TopicVO topic = new TopicVO();
+        topic.setName("topicA");
+
+        assertThatThrownBy(() -> adminClient.createTopic(topic))
+                .isInstanceOf(BusinessException.class)
+                .hasMessageContaining("Failed to create topic: broker 
unavailable");
+    }
+
+    private ClusterInfo clusterInfoWithMaster() {
+        ClusterInfo clusterInfo = new ClusterInfo();
+        Map<String, Set<String>> clusterAddrTable = new HashMap<>();
+        clusterAddrTable.put("cluster-1", new HashSet<>(List.of("broker-1")));
+        clusterInfo.setClusterAddrTable(clusterAddrTable);
+        Map<String, BrokerData> brokerAddrTable = new HashMap<>();
+        BrokerData brokerData = new BrokerData();
+        brokerData.setBrokerName("broker-1");
+        brokerData.setBrokerAddrs(new HashMap<>(Map.of(0L, "10.0.0.1:10911")));
+        brokerAddrTable.put("broker-1", brokerData);
+        clusterInfo.setBrokerAddrTable(brokerAddrTable);
+        return clusterInfo;
+    }
+
     @Test
     void sendMessageShouldNotFailWhenAuditRecordingFails() throws Exception {
         when(properties.getNamesrvAddr()).thenReturn("10.0.0.1:9876");
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigServiceTest.java
new file mode 100644
index 00000000..83b8fe4d
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQBrokerConfigServiceTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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
+ */
+package org.apache.rocketmq.studio.provider.apache;
+
+import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.ops.audit.AuditService;
+import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+
+@ExtendWith(MockitoExtension.class)
+class RocketMQBrokerConfigServiceTest {
+
+    @Mock
+    private MqAdminExtFactory adminFactory;
+    @Mock
+    private RocketMQProperties properties;
+    @Mock
+    private DefaultMQAdminExt adminExt;
+    @Mock
+    private AuditService auditService;
+
+    private RocketMQBrokerConfigService brokerConfigService;
+
+    @BeforeEach
+    void setUp() {
+        
lenient().when(properties.getNamesrvAddr()).thenReturn("10.0.0.1:9876");
+        lenient().when(adminFactory.execute(anyString(), any(), 
any())).thenAnswer(invocation ->
+                
invocation.<MqAdminExtFactory.AdminAction<Object>>getArgument(2).apply(adminExt));
+        brokerConfigService = new RocketMQBrokerConfigService(adminFactory, 
properties, auditService);
+    }
+
+    @Test
+    void updateSucceedsWhenAuditRecordingFails() throws Exception {
+        Properties config = new Properties();
+        config.setProperty("flushDiskType", "ASYNC_FLUSH");
+        doNothing().when(adminExt).updateBrokerConfig("broker-a:10911", 
config);
+        doThrow(new IllegalStateException("audit db down")).when(auditService)
+                .record(anyString(), anyString(), anyString(), anyString());
+
+        brokerConfigService.updateBrokerConfig("broker-a:10911", "cluster-a", 
config);
+    }
+
+    @Test
+    void updatePreservesBrokerFailureWhenAuditRecordingFails() throws 
Exception {
+        Properties config = new Properties();
+        doThrow(new IllegalStateException("broker unavailable")).when(adminExt)
+                .updateBrokerConfig("broker-a:10911", config);
+        doThrow(new IllegalStateException("audit db down")).when(auditService)
+                .record(anyString(), anyString(), anyString(), anyString());
+
+        assertThatThrownBy(() -> 
brokerConfigService.updateBrokerConfig("broker-a:10911", "cluster-a", config))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Failed to update broker config: broker 
unavailable");
+    }
+}

Reply via email to