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 b71f48b6 fix: make K8s certificate updates atomic (#579)
b71f48b6 is described below

commit b71f48b6987fb209fa78a2396b7331f9fcaf58bd
Author: yx9o <[email protected]>
AuthorDate: Wed Jul 29 10:04:33 2026 +0800

    fix: make K8s certificate updates atomic (#579)
---
 .../studio/cluster/k8s/K8sCertService.java         | 53 +++++++++++++------
 .../studio/cluster/k8s/K8sCertServiceTest.java     | 61 +++++++++++++++++++++-
 2 files changed, 97 insertions(+), 17 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
index 898f4505..8230abdc 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
@@ -69,49 +69,51 @@ public class K8sCertService {
 
     public K8sCertVO updateCert(UpdateCertDTO command) {
         log.info("Updating K8s certificate: {}", command.getId());
-        K8sCertVO cert = k8sCertRepository.findById(command.getId())
+        K8sCertVO existing = k8sCertRepository.findById(command.getId())
                 .orElseThrow(() -> new BusinessException(404, "Certificate not 
found: " + command.getId()));
 
+        K8sCertVO updated = copyOf(existing);
         if (command.getName() != null) {
-            cert.setName(command.getName());
+            updated.setName(command.getName());
         }
         if (command.getNamespace() != null) {
-            cert.setNamespace(command.getNamespace());
+            updated.setNamespace(command.getNamespace());
         }
         if (command.getCluster() != null) {
-            cert.setCluster(command.getCluster());
+            updated.setCluster(command.getCluster());
         }
         if (command.getType() != null) {
-            cert.setType(CertType.valueOf(command.getType()));
+            updated.setType(CertType.valueOf(command.getType()));
         }
         if (command.getIssuer() != null) {
-            cert.setIssuer(command.getIssuer());
+            updated.setIssuer(command.getIssuer());
         }
         if (command.getSan() != null) {
-            cert.setSan(command.getSan());
+            updated.setSan(command.getSan());
         }
-        cert.setUpdatedAt(LocalDateTime.now());
+        updated.setUpdatedAt(LocalDateTime.now());
 
-        K8sCertVO saved = k8sCertRepository.save(cert);
+        K8sCertVO saved = k8sCertRepository.save(updated);
         log.info("K8s certificate updated: {} (id={})", saved.getName(), 
saved.getId());
         return saved;
     }
 
     public K8sCertVO renewCert(RenewCertDTO command) {
         log.info("Renewing K8s certificate: {}", command.getId());
-        K8sCertVO cert = k8sCertRepository.findById(command.getId())
+        K8sCertVO existing = k8sCertRepository.findById(command.getId())
                 .orElseThrow(() -> new BusinessException(404, "Certificate not 
found: " + command.getId()));
 
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime notAfter = now.plusYears(1);
 
-        cert.setNotBefore(now);
-        cert.setNotAfter(notAfter);
-        cert.setStatus(CertStatus.valid);
-        cert.setDaysRemaining((int) ChronoUnit.DAYS.between(now, notAfter));
-        cert.setUpdatedAt(now);
+        K8sCertVO renewed = copyOf(existing);
+        renewed.setNotBefore(now);
+        renewed.setNotAfter(notAfter);
+        renewed.setStatus(CertStatus.valid);
+        renewed.setDaysRemaining((int) ChronoUnit.DAYS.between(now, notAfter));
+        renewed.setUpdatedAt(now);
 
-        K8sCertVO saved = k8sCertRepository.save(cert);
+        K8sCertVO saved = k8sCertRepository.save(renewed);
         log.info("K8s certificate renewed: {} (id={}), new expiry: {}", 
saved.getName(), saved.getId(), notAfter);
         return saved;
     }
@@ -123,4 +125,23 @@ public class K8sCertService {
         k8sCertRepository.deleteById(command.getId());
         log.info("K8s certificate deleted: {}", command.getId());
     }
+
+    private K8sCertVO copyOf(K8sCertVO cert) {
+        K8sCertVO copy = K8sCertVO.builder()
+                .name(cert.getName())
+                .namespace(cert.getNamespace())
+                .cluster(cert.getCluster())
+                .type(cert.getType())
+                .issuer(cert.getIssuer())
+                .notBefore(cert.getNotBefore())
+                .notAfter(cert.getNotAfter())
+                .status(cert.getStatus())
+                .daysRemaining(cert.getDaysRemaining())
+                .san(cert.getSan())
+                .build();
+        copy.setId(cert.getId());
+        copy.setCreatedAt(cert.getCreatedAt());
+        copy.setUpdatedAt(cert.getUpdatedAt());
+        return copy;
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
index 4547b324..b023935f 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
@@ -65,6 +65,8 @@ class K8sCertServiceTest {
                 .san(Arrays.asList("rocketmq.example.com", 
"*.rocketmq.example.com"))
                 .build();
         sampleCert.setId("cert-1");
+        sampleCert.setCreatedAt(LocalDateTime.of(2024, 12, 1, 0, 0));
+        sampleCert.setUpdatedAt(LocalDateTime.of(2025, 1, 2, 0, 0));
     }
 
     @Test
@@ -175,7 +177,13 @@ class K8sCertServiceTest {
         assertThat(result.getType()).isEqualTo(CertType.mTLS);
         assertThat(result.getIssuer()).isEqualTo("new-issuer");
         assertThat(result.getSan()).containsExactly("new.example.com");
-        assertThat(result.getUpdatedAt()).isNotNull();
+        assertThat(result.getId()).isEqualTo("cert-1");
+        assertThat(result.getCreatedAt()).isEqualTo(LocalDateTime.of(2024, 12, 
1, 0, 0));
+        assertThat(result.getUpdatedAt()).isAfter(sampleCert.getUpdatedAt());
+        assertThat(result).isNotSameAs(sampleCert);
+        assertThat(sampleCert.getName()).isEqualTo("rocketmq-tls");
+        assertThat(sampleCert.getType()).isEqualTo(CertType.TLS);
+        assertThat(sampleCert.getUpdatedAt()).isEqualTo(LocalDateTime.of(2025, 
1, 2, 0, 0));
         verify(k8sCertRepository).save(any(K8sCertVO.class));
     }
 
@@ -213,10 +221,31 @@ class K8sCertServiceTest {
                 .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(404));
     }
 
+    @Test
+    void updateCertShouldNotMutateStoredCertWhenSaveFails() {
+        
when(k8sCertRepository.findById("cert-1")).thenReturn(Optional.of(sampleCert));
+        when(k8sCertRepository.save(any(K8sCertVO.class))).thenThrow(new 
IllegalStateException("save failed"));
+        UpdateCertDTO command = UpdateCertDTO.builder()
+                .id("cert-1")
+                .name("should-not-persist")
+                .type("mTLS")
+                .build();
+
+        assertThatThrownBy(() -> k8sCertService.updateCert(command))
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessage("save failed");
+
+        assertThat(sampleCert.getName()).isEqualTo("rocketmq-tls");
+        assertThat(sampleCert.getType()).isEqualTo(CertType.TLS);
+        assertThat(sampleCert.getUpdatedAt()).isEqualTo(LocalDateTime.of(2025, 
1, 2, 0, 0));
+    }
+
     @Test
     void renewCertShouldRenewCertValidity() {
         sampleCert.setStatus(CertStatus.expired);
         sampleCert.setDaysRemaining(0);
+        LocalDateTime originalNotBefore = sampleCert.getNotBefore();
+        LocalDateTime originalNotAfter = sampleCert.getNotAfter();
 
         
when(k8sCertRepository.findById("cert-1")).thenReturn(Optional.of(sampleCert));
         
when(k8sCertRepository.save(any(K8sCertVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
@@ -230,9 +259,39 @@ class K8sCertServiceTest {
         assertThat(result.getNotBefore()).isNotNull();
         assertThat(result.getNotAfter()).isAfter(result.getNotBefore());
         assertThat(result.getUpdatedAt()).isNotNull();
+        assertThat(result.getId()).isEqualTo("cert-1");
+        assertThat(result.getCreatedAt()).isEqualTo(sampleCert.getCreatedAt());
+        assertThat(result).isNotSameAs(sampleCert);
+        assertThat(sampleCert.getStatus()).isEqualTo(CertStatus.expired);
+        assertThat(sampleCert.getDaysRemaining()).isZero();
+        assertThat(sampleCert.getNotBefore()).isEqualTo(originalNotBefore);
+        assertThat(sampleCert.getNotAfter()).isEqualTo(originalNotAfter);
         verify(k8sCertRepository).save(any(K8sCertVO.class));
     }
 
+    @Test
+    void renewCertShouldNotMutateStoredCertWhenSaveFails() {
+        sampleCert.setStatus(CertStatus.expired);
+        sampleCert.setDaysRemaining(0);
+        LocalDateTime originalNotBefore = sampleCert.getNotBefore();
+        LocalDateTime originalNotAfter = sampleCert.getNotAfter();
+
+        
when(k8sCertRepository.findById("cert-1")).thenReturn(Optional.of(sampleCert));
+        when(k8sCertRepository.save(any(K8sCertVO.class))).thenThrow(new 
IllegalStateException("save failed"));
+
+        RenewCertDTO command = RenewCertDTO.builder().id("cert-1").build();
+
+        assertThatThrownBy(() -> k8sCertService.renewCert(command))
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessage("save failed");
+
+        assertThat(sampleCert.getStatus()).isEqualTo(CertStatus.expired);
+        assertThat(sampleCert.getDaysRemaining()).isZero();
+        assertThat(sampleCert.getNotBefore()).isEqualTo(originalNotBefore);
+        assertThat(sampleCert.getNotAfter()).isEqualTo(originalNotAfter);
+        assertThat(sampleCert.getUpdatedAt()).isEqualTo(LocalDateTime.of(2025, 
1, 2, 0, 0));
+    }
+
     @Test
     void renewCertShouldThrowWhenNotFound() {
         
when(k8sCertRepository.findById("nonexistent")).thenReturn(Optional.empty());

Reply via email to