diff --git a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapProcessor.java b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapProcessor.java index 8f22a1045b..21f2a2f853 100644 --- a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapProcessor.java +++ b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapProcessor.java @@ -83,7 +83,15 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw fullyRefreshCache(configMapGetter, fromConfigMapAnnotation, configMapDataCache); configMapKeyRefresher.scheduleAtFixedRate( () -> { - fullyRefreshCache(configMapGetter, fromConfigMapAnnotation, configMapDataCache); + try { + fullyRefreshCache(configMapGetter, fromConfigMapAnnotation, configMapDataCache); + } catch (RuntimeException e) { + log.warn( + "Failed refreshing ConfigMap {}/{}", + fromConfigMapAnnotation.namespace(), + fromConfigMapAnnotation.name(), + e); + } }, manifestsProperties.getRefreshInterval().getSeconds(), manifestsProperties.getRefreshInterval().getSeconds(), diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapTest.java index 1ce5a348f7..47dcfa35c9 100644 --- a/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapTest.java +++ b/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapTest.java @@ -29,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -104,6 +105,25 @@ void valueUpdate() { .until(() -> "bar2".equals(myBean.dynamicData.get("foo"))); } + @Test + void refreshRecoversAfterReadFailure() { + assertThat(myBean.dynamicData).containsEntry("foo", "bar1"); + int failedReads = mockAtomicConfigMapGetter.failedReads.get(); + mockAtomicConfigMapGetter.readFailure.set(new IllegalStateException("Transient API failure")); + try { + await().until(() -> mockAtomicConfigMapGetter.failedReads.get() > failedReads); + assertThat(myBean.dynamicData).containsExactlyEntriesOf(Map.of("foo", "bar1")); + } finally { + mockAtomicConfigMapGetter.readFailure.set(null); + } + + mockAtomicConfigMapGetter.configMapAtomicReference.set( + new V1ConfigMap().putDataItem("foo", "recovered")); + await() + .timeout(manifestsProperties.getRefreshInterval().multipliedBy(2)) + .until(() -> "recovered".equals(myBean.dynamicData.get("foo"))); + } + @Test void keyUpdate() { assertThat(manifestsProperties.getRefreshInterval()).isEqualTo(Duration.ofSeconds(1)); @@ -285,9 +305,16 @@ public V1ConfigMap get(String namespace, String name) { static class MockAtomicConfigMapGetter implements ConfigMapGetter { private final AtomicReference configMapAtomicReference = new AtomicReference<>(); + private final AtomicReference readFailure = new AtomicReference<>(); + private final AtomicInteger failedReads = new AtomicInteger(); @Override public V1ConfigMap get(String namespace, String name) { + RuntimeException failure = readFailure.get(); + if (failure != null) { + failedReads.incrementAndGet(); + throw failure; + } return configMapAtomicReference.get(); } }