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());