Reload functionality clean-up part 2 (#1033)

This commit is contained in:
erabii
2022-06-17 14:56:25 +03:00
committed by GitHub
parent db67cb6abc
commit cb4d6cd2c7
13 changed files with 89 additions and 99 deletions

View File

@@ -20,8 +20,8 @@ import java.io.IOException;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -47,7 +47,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator;
@@ -64,9 +63,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -110,7 +107,7 @@ class KubernetesClientEventBasedConfigMapChangeDetectorTests {
V1ConfigMap applicationConfig = new V1ConfigMap().kind("ConfigMap")
.metadata(new V1ObjectMeta().namespace("default").name("bar1")).data(data);
V1ConfigMapList configMapList = new V1ConfigMapList().metadata(new V1ListMeta().resourceVersion("0"))
.items(Arrays.asList(applicationConfig));
.items(List.of(applicationConfig));
stubFor(get(urlMatching("^/api/v1/namespaces/default/configmaps.*")).inScenario("watch")
.whenScenarioStateIs(STARTED).withQueryParam("watch", equalTo("false"))
.willReturn(aResponse().withStatus(200).withBody(gson.toJson(configMapList))).willSetStateTo("update"));
@@ -147,8 +144,13 @@ class KubernetesClientEventBasedConfigMapChangeDetectorTests {
OkHttpClient httpClient = apiClient.getHttpClient().newBuilder().readTimeout(0, TimeUnit.SECONDS).build();
apiClient.setHttpClient(httpClient);
CoreV1Api coreV1Api = new CoreV1Api(apiClient);
ConfigurationUpdateStrategy strategy = mock(ConfigurationUpdateStrategy.class);
when(strategy.getName()).thenReturn("strategy");
int[] howMany = new int[1];
Runnable run = () -> {
++howMany[0];
};
ConfigurationUpdateStrategy strategy = new ConfigurationUpdateStrategy("strategy", run);
KubernetesMockEnvironment environment = new KubernetesMockEnvironment(
mock(KubernetesClientConfigMapPropertySource.class)).withProperty("debug", "true");
KubernetesClientConfigMapPropertySourceLocator locator = mock(
@@ -162,15 +164,14 @@ class KubernetesClientEventBasedConfigMapChangeDetectorTests {
Thread controllerThread = new Thread(changeDetector::watch);
controllerThread.setDaemon(true);
controllerThread.start();
await().timeout(Duration.ofSeconds(5))
.until(() -> Mockito.mockingDetails(strategy).getInvocations().size() > 4);
verify(strategy, atLeast(3)).reload();
await().timeout(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(2)).until(() -> howMany[0] >= 4);
}
// This is needed when using JDK17 because GSON uses reflection to construct an
// OffsetDateTime but that constructor
// is protected.
public class GsonOffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {
public final static class GsonOffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {
@Override
public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IOException {

View File

@@ -20,8 +20,8 @@ import java.io.IOException;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -46,7 +46,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource;
import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator;
@@ -63,9 +62,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -107,7 +104,7 @@ class KubernetesClientEventBasedSecretsChangeDetectorTests {
.putStringDataItem("password", Base64.getEncoder().encodeToString("p455w0rd2".getBytes()))
.putStringDataItem("username", Base64.getEncoder().encodeToString("user".getBytes()));
V1SecretList secretList = new V1SecretList().kind("SecretList").metadata(new V1ListMeta().resourceVersion("0"))
.items(Arrays.asList(dbPassword));
.items(List.of(dbPassword));
stubFor(get(urlMatching("^/api/v1/namespaces/default/secrets.*")).inScenario("watch")
.whenScenarioStateIs(STARTED).withQueryParam("watch", equalTo("false"))
@@ -142,8 +139,13 @@ class KubernetesClientEventBasedSecretsChangeDetectorTests {
OkHttpClient httpClient = apiClient.getHttpClient().newBuilder().readTimeout(0, TimeUnit.SECONDS).build();
apiClient.setHttpClient(httpClient);
CoreV1Api coreV1Api = new CoreV1Api(apiClient);
ConfigurationUpdateStrategy strategy = mock(ConfigurationUpdateStrategy.class);
when(strategy.getName()).thenReturn("strategy");
int[] howMany = new int[1];
Runnable run = () -> {
++howMany[0];
};
ConfigurationUpdateStrategy strategy = new ConfigurationUpdateStrategy("strategy", run);
KubernetesMockEnvironment environment = new KubernetesMockEnvironment(
mock(KubernetesClientSecretsPropertySource.class)).withProperty("db-password", "p455w0rd");
KubernetesClientSecretsPropertySourceLocator locator = mock(KubernetesClientSecretsPropertySourceLocator.class);
@@ -160,15 +162,13 @@ class KubernetesClientEventBasedSecretsChangeDetectorTests {
controllerThread.setDaemon(true);
controllerThread.start();
await().timeout(Duration.ofSeconds(300))
.until(() -> Mockito.mockingDetails(strategy).getInvocations().size() > 4);
verify(strategy, atLeast(3)).reload();
await().timeout(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(2)).until(() -> howMany[0] >= 4);
}
// This is needed when using JDK17 because GSON uses reflection to construct an
// OffsetDateTime but that constructor
// is protected.
public class GsonOffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {
public final static class GsonOffsetDateTimeAdapter extends TypeAdapter<OffsetDateTime> {
@Override
public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IOException {

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.kubernetes.commons.config.reload;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,8 +35,8 @@ import org.springframework.cloud.kubernetes.commons.config.ConditionalOnKubernet
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
/**
* @author Ryan Baxter
@@ -56,13 +57,13 @@ public class ConfigReloadAutoConfiguration {
@Bean("springCloudKubernetesTaskScheduler")
@ConditionalOnMissingBean
public TaskSchedulerWrapper taskScheduler() {
public TaskSchedulerWrapper<TaskScheduler> taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setThreadNamePrefix("spring-cloud-kubernetes-ThreadPoolTaskScheduler-");
threadPoolTaskScheduler.setDaemon(true);
return new TaskSchedulerWrapper(threadPoolTaskScheduler);
return new TaskSchedulerWrapper<>(threadPoolTaskScheduler);
}
/**
@@ -79,7 +80,7 @@ public class ConfigReloadAutoConfiguration {
ContextRefresher refresher) {
switch (properties.getStrategy()) {
case RESTART_CONTEXT:
Assert.notNull(restarter, "Restart endpoint is not enabled");
Objects.requireNonNull(restarter, "Restart endpoint is not enabled");
return new ConfigurationUpdateStrategy(properties.getStrategy().name(), () -> {
wait(properties);
restarter.restart();

View File

@@ -44,7 +44,7 @@ public class ConfigReloadProperties {
private boolean monitoringSecrets = false;
/**
* Sets the reload strategy for Kubernetes configuration reload on change.
* Sets reload strategy for Kubernetes configuration reload on change.
*/
private ReloadStrategy strategy = ReloadStrategy.REFRESH;

View File

@@ -57,8 +57,8 @@ public abstract class ConfigurationChangeDetector {
}
public void reloadProperties() {
log.info("Reloading using strategy: " + this.strategy.getName());
this.strategy.reload();
log.info("Reloading using strategy: " + this.strategy.name());
strategy.reloadProcedure().run();
}
public boolean changed(List<? extends MapPropertySource> left, List<? extends MapPropertySource> right) {

View File

@@ -24,28 +24,16 @@ import java.util.Objects;
*
* @author Nicola Ferraro
*/
public class ConfigurationUpdateStrategy {
private final String name;
private final Runnable reloadProcedure;
public final record ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) {
public ConfigurationUpdateStrategy(String name, Runnable reloadProcedure) {
this.name = Objects.requireNonNull(name, "name cannot be null");
this.reloadProcedure = Objects.requireNonNull(reloadProcedure, "reloadProcedure cannot be null");
}
public String getName() {
return this.name;
}
public void reload() {
this.reloadProcedure.run();
}
@Override
public String toString() {
return "ConfigurationUpdateStrategy{name='" + this.name + "'}";
return this.getClass().getSimpleName() + "{name='" + this.name + "'}";
}
}

View File

@@ -41,16 +41,16 @@ public class PollingConfigMapChangeDetector extends ConfigurationChangeDetector
protected Log log = LogFactory.getLog(getClass());
private PropertySourceLocator propertySourceLocator;
private final PropertySourceLocator propertySourceLocator;
private Class propertySourceClass;
private final Class<? extends MapPropertySource> propertySourceClass;
private TaskScheduler taskExecutor;
private final TaskScheduler taskExecutor;
private Duration period = Duration.ofMillis(1500);
private final Duration period;
public PollingConfigMapChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties,
ConfigurationUpdateStrategy strategy, Class propertySourceClass,
ConfigurationUpdateStrategy strategy, Class<? extends MapPropertySource> propertySourceClass,
PropertySourceLocator propertySourceLocator, TaskScheduler taskExecutor) {
super(environment, properties, strategy);
this.propertySourceLocator = propertySourceLocator;
@@ -61,19 +61,17 @@ public class PollingConfigMapChangeDetector extends ConfigurationChangeDetector
@PostConstruct
public void init() {
this.log.info("Kubernetes polling configMap change detector activated");
log.info("Kubernetes polling configMap change detector activated");
PeriodicTrigger trigger = new PeriodicTrigger(period.toMillis());
trigger.setInitialDelay(period.toMillis());
taskExecutor.schedule(this::executeCycle, trigger);
}
public void executeCycle() {
private void executeCycle() {
boolean changedConfigMap = false;
if (this.properties.isMonitoringConfigMaps()) {
if (log.isDebugEnabled()) {
log.debug("Polling for changes in config maps");
}
if (properties.isMonitoringConfigMaps()) {
log.debug("Polling for changes in config maps");
List<? extends MapPropertySource> currentConfigMapSources = findPropertySources(propertySourceClass);
if (!currentConfigMapSources.isEmpty()) {
@@ -83,7 +81,7 @@ public class PollingConfigMapChangeDetector extends ConfigurationChangeDetector
}
if (changedConfigMap) {
this.log.info("Detected change in config maps");
log.info("Detected change in config maps");
reloadProperties();
}
}

View File

@@ -43,14 +43,14 @@ public class PollingSecretsChangeDetector extends ConfigurationChangeDetector {
private final PropertySourceLocator propertySourceLocator;
private Class propertySourceClass;
private final Class<? extends MapPropertySource> propertySourceClass;
private TaskScheduler taskExecutor;
private final TaskScheduler taskExecutor;
private Duration period = Duration.ofMillis(1500);
private final Duration period;
public PollingSecretsChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties,
ConfigurationUpdateStrategy strategy, Class propertySourceClass,
ConfigurationUpdateStrategy strategy, Class<? extends MapPropertySource> propertySourceClass,
PropertySourceLocator propertySourceLocator, TaskScheduler taskExecutor) {
super(environment, properties, strategy);
this.propertySourceLocator = propertySourceLocator;
@@ -61,7 +61,7 @@ public class PollingSecretsChangeDetector extends ConfigurationChangeDetector {
@PostConstruct
public void init() {
this.log.info("Kubernetes polling secrets change detector activated");
log.info("Kubernetes polling secrets change detector activated");
PeriodicTrigger trigger = new PeriodicTrigger(period.toMillis());
trigger.setInitialDelay(period.toMillis());
taskExecutor.schedule(this::executeCycle, trigger);
@@ -71,19 +71,17 @@ public class PollingSecretsChangeDetector extends ConfigurationChangeDetector {
boolean changedSecrets = false;
if (this.properties.isMonitoringSecrets()) {
if (log.isDebugEnabled()) {
log.debug("Polling for changes in secrets");
}
log.debug("Polling for changes in secrets");
List<MapPropertySource> currentSecretSources = locateMapPropertySources(this.propertySourceLocator,
this.environment);
if (currentSecretSources != null && !currentSecretSources.isEmpty()) {
List<MapPropertySource> propertySources = findPropertySources(this.propertySourceClass);
List<? extends MapPropertySource> propertySources = findPropertySources(this.propertySourceClass);
changedSecrets = changed(currentSecretSources, propertySources);
}
}
if (changedSecrets) {
this.log.info("Detected change in secrets");
log.info("Detected change in secrets");
reloadProperties();
}
}

View File

@@ -41,7 +41,7 @@ public abstract class ConfigMapWatcherChangeDetector extends EventBasedConfigMap
protected Log log = LogFactory.getLog(getClass());
private ScheduledExecutorService executorService;
private final ScheduledExecutorService executorService;
protected ConfigurationWatcherConfigurationProperties k8SConfigurationProperties;

View File

@@ -19,12 +19,12 @@ package org.springframework.cloud.kubernetes.configuration.watcher;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
@@ -42,15 +42,17 @@ import static org.mockito.Mockito.verify;
* @author Ryan Baxter
* @author Kris Iyer
*/
@RunWith(MockitoJUnitRunner.class)
public class BusEventBasedConfigMapWatcherChangeDetectorTests {
@ExtendWith(MockitoExtension.class)
class BusEventBasedConfigMapWatcherChangeDetectorTests {
private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy",
() -> {
});
@Mock
private KubernetesClient client;
@Mock
private ConfigurationUpdateStrategy updateStrategy;
@Mock
private Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator;
@@ -64,20 +66,20 @@ public class BusEventBasedConfigMapWatcherChangeDetectorTests {
private BusProperties busProperties;
@Before
public void setup() {
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
ConfigReloadProperties configReloadProperties = new ConfigReloadProperties();
ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
busProperties = new BusProperties();
changeDetector = new BusEventBasedConfigMapWatcherChangeDetector(mockEnvironment, configReloadProperties,
client, updateStrategy, fabric8ConfigMapPropertySourceLocator, busProperties,
client, UPDATE_STRATEGY, fabric8ConfigMapPropertySourceLocator, busProperties,
configurationWatcherConfigurationProperties, threadPoolTaskExecutor);
changeDetector.setApplicationEventPublisher(applicationEventPublisher);
}
@Test
public void triggerRefreshWithConfigMap() {
void triggerRefreshWithConfigMap() {
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName("foo");
ConfigMap configMap = new ConfigMap();

View File

@@ -19,12 +19,12 @@ package org.springframework.cloud.kubernetes.configuration.watcher;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
@@ -42,15 +42,17 @@ import static org.mockito.Mockito.verify;
* @author Ryan Baxter
* @author Kris Iyer
*/
@RunWith(MockitoJUnitRunner.class)
public class BusEventBasedSecretsWatcherChangeDetectorTests {
@ExtendWith(MockitoExtension.class)
class BusEventBasedSecretsWatcherChangeDetectorTests {
private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy",
() -> {
});
@Mock
private KubernetesClient client;
@Mock
private ConfigurationUpdateStrategy updateStrategy;
@Mock
private Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator;
@@ -62,24 +64,22 @@ public class BusEventBasedSecretsWatcherChangeDetectorTests {
private BusEventBasedSecretsWatcherChangeDetector changeDetector;
private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties;
private BusProperties busProperties;
@Before
public void setup() {
@BeforeEach
void setup() {
MockEnvironment mockEnvironment = new MockEnvironment();
ConfigReloadProperties configReloadProperties = new ConfigReloadProperties();
configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
busProperties = new BusProperties();
changeDetector = new BusEventBasedSecretsWatcherChangeDetector(mockEnvironment, configReloadProperties, client,
updateStrategy, fabric8SecretsPropertySourceLocator, busProperties,
UPDATE_STRATEGY, fabric8SecretsPropertySourceLocator, busProperties,
configurationWatcherConfigurationProperties, threadPoolTaskExecutor);
changeDetector.setApplicationEventPublisher(applicationEventPublisher);
}
@Test
public void triggerRefreshWithSecret() {
void triggerRefreshWithSecret() {
ObjectMeta objectMeta = new ObjectMeta();
objectMeta.setName("foo");
Secret secret = new Secret();

View File

@@ -16,17 +16,17 @@
package org.springframework.cloud.kubernetes.configuration.watcher;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
public class ConfigurationWatcherConfigurationPropertiesTests {
class ConfigurationWatcherConfigurationPropertiesTests {
@Test
public void setActuatorPath() {
void setActuatorPath() {
ConfigurationWatcherConfigurationProperties properties = new ConfigurationWatcherConfigurationProperties();
properties.setActuatorPath("foo");
assertThat(properties.getActuatorPath()).isEqualTo("/foo");

View File

@@ -74,7 +74,9 @@ class EventBasedConfigurationChangeDetectorTests {
Fabric8ConfigMapPropertySource fabric8ConfigMapPropertySource = new Fabric8ConfigMapPropertySource(context);
env.getPropertySources().addFirst(new BootstrapPropertySource<>(fabric8ConfigMapPropertySource));
ConfigurationUpdateStrategy configurationUpdateStrategy = mock(ConfigurationUpdateStrategy.class);
ConfigurationUpdateStrategy configurationUpdateStrategy = new ConfigurationUpdateStrategy("strategy", () -> {
});
Fabric8ConfigMapPropertySourceLocator configMapLocator = mock(Fabric8ConfigMapPropertySourceLocator.class);
EventBasedConfigMapChangeDetector detector = new EventBasedConfigMapChangeDetector(env, configReloadProperties,
k8sClient, configurationUpdateStrategy, configMapLocator);