diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c28a8bebc..0eda34f652 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,12 @@ commands: cd spring-cloud-kubernetes-integration-tests while read integ_test; do docker save -o /tmp/docker/images/${integ_test}.tar docker.io/springcloud/${integ_test}:$TAG - done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q | grep -v 'spring-cloud-kubernetes-integration-tests') + done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q \ + | grep -v 'spring-cloud-kubernetes-integration-tests' \ + | grep -v 'spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app' \ + | grep -v 'spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app' ) cd .. VIEW=$(ls -l /tmp/docker/images) @@ -120,7 +125,12 @@ commands: cd spring-cloud-kubernetes-integration-tests while read integration_test_image; do docker load -i /tmp/docker/images/${integration_test_image}.tar - done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q | grep -v 'spring-cloud-kubernetes-integration-tests') + done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q \ + | grep -v 'spring-cloud-kubernetes-integration-tests' \ + | grep -v 'spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app' \ + | grep -v 'spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app' ) cd .. diff --git a/.github/workflows/composites/load-docker-images/action.yaml b/.github/workflows/composites/load-docker-images/action.yaml index 6ebb7651d6..f31a4d24c7 100644 --- a/.github/workflows/composites/load-docker-images/action.yaml +++ b/.github/workflows/composites/load-docker-images/action.yaml @@ -18,5 +18,10 @@ runs: cd spring-cloud-kubernetes-integration-tests while read integration_test_image; do docker load -i /tmp/docker/images/${integration_test_image}.tar - done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q | grep -v 'spring-cloud-kubernetes-integration-tests') + done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q \ + | grep -v 'spring-cloud-kubernetes-integration-tests' \ + | grep -v 'spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app' \ + | grep -v 'spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app' ) cd .. diff --git a/.github/workflows/composites/save-integration-tests-images/action.yaml b/.github/workflows/composites/save-integration-tests-images/action.yaml index 72699f9748..cbc92e55f7 100644 --- a/.github/workflows/composites/save-integration-tests-images/action.yaml +++ b/.github/workflows/composites/save-integration-tests-images/action.yaml @@ -11,5 +11,10 @@ runs: cd spring-cloud-kubernetes-integration-tests while read integ_test; do docker save -o /tmp/docker/images/${integ_test}.tar docker.io/springcloud/${integ_test}:$TAG - done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q | grep -v 'spring-cloud-kubernetes-integration-tests') + done < <(mvn -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec -q \ + | grep -v 'spring-cloud-kubernetes-integration-tests' \ + | grep -v 'spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app' \ + | grep -v 'spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps' \ + | grep -v 'spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app' ) cd .. diff --git a/docs/src/main/asciidoc/spring-cloud-kubernetes-configuration-watcher.adoc b/docs/src/main/asciidoc/spring-cloud-kubernetes-configuration-watcher.adoc index e8764c74fa..1cad3c67f8 100644 --- a/docs/src/main/asciidoc/spring-cloud-kubernetes-configuration-watcher.adoc +++ b/docs/src/main/asciidoc/spring-cloud-kubernetes-configuration-watcher.adoc @@ -112,11 +112,32 @@ Spring Cloud Kubernetes Configuration Watcher will react to changes in ConfigMap or any Secret with a label of `spring.cloud.kubernetes.secret` with the value `true`. If the ConfigMap or Secret does not have either of those labels or the values of those labels is not `true` then any changes will be ignored. -The labels Spring Cloud Kubernetes Configuration Watcher looks for on ConfigMaps and Secrets can be changed by setting -`spring.cloud.kubernetes.configuration.watcher.configLabel` and `spring.cloud.kubernetes.configuration.watcher.secretLabel` respectively. - If a change is made to a ConfigMap or Secret with valid labels then Spring Cloud Kubernetes Configuration Watcher will take the name of the ConfigMap or Secret -and send a notification to the application with that name. +and send a notification to the application with that name. This might not be enough for your use-case though, you could for example what to: + +- bind a config-map to multiple applications, so that a change inside a single configmap triggers a refresh for many services +- have profile based sources trigger events for your application + +For that reasons there is an addition annotation you could specify: + +`spring.cloud.kubernetes.configmap.apps` or `spring.cloud.kubernetes.secret.apps`. It takes a String of apps separated by comma, +that specifies the names of applications that will receive a notification when changes happen in this secret/configmap. + +For example: + +==== +[source,yaml] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: example-configmap + labels: + spring.cloud.kubernetes.config: "true" + annotations: + spring.cloud.kubernetes.configmap.apps: "app-a, app-b" +---- +==== ### HTTP Implementation diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java index f25cf6a2e4..d16de59aa5 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java @@ -70,21 +70,21 @@ public class KubernetesClientEventBasedConfigMapChangeDetector extends Configura private final ResourceEventHandler handler = new ResourceEventHandler<>() { @Override - public void onAdd(V1ConfigMap obj) { - LOG.debug(() -> "ConfigMap " + obj.getMetadata().getName() + " was added."); - onEvent(obj); + public void onAdd(V1ConfigMap configMap) { + LOG.debug(() -> "ConfigMap " + configMap.getMetadata().getName() + " was added."); + onEvent(configMap); } @Override - public void onUpdate(V1ConfigMap oldObj, V1ConfigMap newObj) { - LOG.debug(() -> "ConfigMap " + newObj.getMetadata().getName() + " was updated."); - onEvent(newObj); + public void onUpdate(V1ConfigMap oldConfigMap, V1ConfigMap newConfigMap) { + LOG.debug(() -> "ConfigMap " + newConfigMap.getMetadata().getName() + " was updated."); + onEvent(newConfigMap); } @Override - public void onDelete(V1ConfigMap obj, boolean deletedFinalStateUnknown) { - LOG.debug(() -> "ConfigMap " + obj.getMetadata() + " was deleted."); - onEvent(obj); + public void onDelete(V1ConfigMap configMap, boolean deletedFinalStateUnknown) { + LOG.debug(() -> "ConfigMap " + configMap.getMetadata().getName() + " was deleted."); + onEvent(configMap); } }; diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java index bfa69d5bd7..1333086e5a 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java @@ -70,21 +70,21 @@ public class KubernetesClientEventBasedSecretsChangeDetector extends Configurati private final ResourceEventHandler handler = new ResourceEventHandler<>() { @Override - public void onAdd(V1Secret obj) { - LOG.debug(() -> "Secret " + obj.getMetadata().getName() + " was added."); - onEvent(obj); + public void onAdd(V1Secret secret) { + LOG.debug(() -> "Secret " + secret.getMetadata().getName() + " was added."); + onEvent(secret); } @Override - public void onUpdate(V1Secret oldObj, V1Secret newObj) { - LOG.debug(() -> "Secret " + newObj.getMetadata().getName() + " was updated."); - onEvent(newObj); + public void onUpdate(V1Secret oldSecret, V1Secret newSecret) { + LOG.debug(() -> "Secret " + newSecret.getMetadata().getName() + " was updated."); + onEvent(newSecret); } @Override - public void onDelete(V1Secret obj, boolean deletedFinalStateUnknown) { - LOG.debug(() -> "Secret " + obj.getMetadata() + " was deleted."); - onEvent(obj); + public void onDelete(V1Secret secret, boolean deletedFinalStateUnknown) { + LOG.debug(() -> "Secret " + secret.getMetadata().getName() + " was deleted."); + onEvent(secret); } }; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java index 0b786baa69..b30ca09f71 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java @@ -47,8 +47,8 @@ final class BusEventBasedConfigMapWatcherChangeDetector extends ConfigMapWatcher } @Override - public Mono triggerRefresh(KubernetesObject configMap) { - return busRefreshTrigger.triggerRefresh(configMap); + public Mono triggerRefresh(KubernetesObject configMap, String appName) { + return busRefreshTrigger.triggerRefresh(configMap, appName); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java index b4d070977e..19efe8dc9c 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java @@ -47,8 +47,8 @@ final class BusEventBasedSecretsWatcherChangeDetector extends SecretsWatcherChan } @Override - public Mono triggerRefresh(KubernetesObject secret) { - return busRefreshTrigger.triggerRefresh(secret); + public Mono triggerRefresh(KubernetesObject secret, String appName) { + return busRefreshTrigger.triggerRefresh(secret, appName); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusRefreshTrigger.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusRefreshTrigger.java index da3e6d94df..411fa68404 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusRefreshTrigger.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusRefreshTrigger.java @@ -40,9 +40,9 @@ final class BusRefreshTrigger implements RefreshTrigger { } @Override - public Mono triggerRefresh(KubernetesObject configMap) { + public Mono triggerRefresh(KubernetesObject configMap, String appName) { applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(configMap, busId, - new PathDestinationFactory().getDestination(configMap.getMetadata().getName()))); + new PathDestinationFactory().getDestination(appName))); return Mono.empty(); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java index 769ca0fb8c..e0253761d3 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java @@ -30,12 +30,16 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_APPS_ANNOTATION; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_LABEL; + /** * @author Ryan Baxter * @author Kris Iyer */ -abstract class ConfigMapWatcherChangeDetector extends KubernetesClientEventBasedConfigMapChangeDetector - implements RefreshTrigger { +abstract sealed class ConfigMapWatcherChangeDetector extends KubernetesClientEventBasedConfigMapChangeDetector + implements + RefreshTrigger permits BusEventBasedConfigMapWatcherChangeDetector, HttpBasedConfigMapWatchChangeDetector { private final ScheduledExecutorService executorService; @@ -56,8 +60,8 @@ abstract class ConfigMapWatcherChangeDetector extends KubernetesClientEventBased @Override protected final void onEvent(KubernetesObject configMap) { // this::refreshTrigger is coming from BusEventBasedConfigMapWatcherChangeDetector - WatcherUtil.onEvent(configMap, ConfigurationWatcherConfigurationProperties.CONFIG_LABEL, refreshDelay, - executorService, "config-map", this::triggerRefresh); + WatcherUtil.onEvent(configMap, CONFIG_MAP_LABEL, CONFIG_MAP_APPS_ANNOTATION, refreshDelay, executorService, + "config-map", this::triggerRefresh); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationProperties.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationProperties.java index a25cee16ea..ef39258427 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationProperties.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationProperties.java @@ -31,13 +31,23 @@ public class ConfigurationWatcherConfigurationProperties { /** * label to enable refresh/restart when using configmaps. */ - public static final String CONFIG_LABEL = "spring.cloud.kubernetes.config"; + public static final String CONFIG_MAP_LABEL = "spring.cloud.kubernetes.config"; /** * label to enable refresh/restart when using secrets. */ public static final String SECRET_LABEL = "spring.cloud.kubernetes.secret"; + /** + * annotation name to enable refresh/restart for specific apps when using configmaps. + */ + public static final String CONFIG_MAP_APPS_ANNOTATION = "spring.cloud.kubernetes.configmap.apps"; + + /** + * annotation name to enable refresh/restart for specific apps when using secrets. + */ + public static final String SECRET_APPS_ANNOTATION = "spring.cloud.kubernetes.secret.apps"; + /** * Annotation key for actuator port and path. */ diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetector.java index 331b7614f1..08cd685752 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetector.java @@ -48,8 +48,8 @@ final class HttpBasedConfigMapWatchChangeDetector extends ConfigMapWatcherChange } @Override - public Mono triggerRefresh(KubernetesObject configMap) { - return httpRefreshTrigger.triggerRefresh(configMap); + public Mono triggerRefresh(KubernetesObject configMap, String appName) { + return httpRefreshTrigger.triggerRefresh(configMap, appName); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetector.java index e25f276772..2844f29215 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetector.java @@ -48,8 +48,8 @@ final class HttpBasedSecretsWatchChangeDetector extends SecretsWatcherChangeDete } @Override - public Mono triggerRefresh(KubernetesObject secret) { - return httpRefreshTrigger.triggerRefresh(secret); + public Mono triggerRefresh(KubernetesObject secret, String appName) { + return httpRefreshTrigger.triggerRefresh(secret, appName); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpRefreshTrigger.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpRefreshTrigger.java index dc032ec37f..18e868a2f7 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpRefreshTrigger.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpRefreshTrigger.java @@ -54,16 +54,14 @@ final class HttpRefreshTrigger implements RefreshTrigger { } @Override - public Mono triggerRefresh(KubernetesObject kubernetesObject) { + public Mono triggerRefresh(KubernetesObject kubernetesObject, String appName) { - String name = kubernetesObject.getMetadata().getName(); - - return kubernetesReactiveDiscoveryClient.getInstances(name).flatMap(si -> { + return kubernetesReactiveDiscoveryClient.getInstances(appName).flatMap(si -> { URI actuatorUri = getActuatorUri(si, k8SConfigurationProperties.getActuatorPath(), k8SConfigurationProperties.getActuatorPort()); - LOG.debug(() -> "Sending refresh request for " + name + " to URI " + actuatorUri); + LOG.debug(() -> "Sending refresh request for " + appName + " to URI " + actuatorUri); return webClient.post().uri(actuatorUri).retrieve().toBodilessEntity() - .doOnSuccess(onSuccess(name, actuatorUri)).doOnError(onError(name)); + .doOnSuccess(onSuccess(appName, actuatorUri)).doOnError(onError(appName)); }).then(); } @@ -102,8 +100,7 @@ private void setActuatorUriFromAnnotation(UriComponentsBuilder actuatorUriBuilde // The URI may not contain a host so if that is the case the port in the URI will // be -1. The authority of the URI will be : for example :9090, we just need - // the - // 9090 in this case + // the 9090 in this case if (annotationUri.getPort() < 0) { if (annotationUri.getAuthority() != null) { actuatorUriBuilder.port(annotationUri.getAuthority().replaceFirst(":", "")); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/RefreshTrigger.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/RefreshTrigger.java index 9844ab8915..f4430bba02 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/RefreshTrigger.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/RefreshTrigger.java @@ -24,11 +24,13 @@ * * @author wind57 */ -interface RefreshTrigger { +sealed interface RefreshTrigger permits BusRefreshTrigger, ConfigMapWatcherChangeDetector, HttpRefreshTrigger, SecretsWatcherChangeDetector { /** * @param kubernetesObject either a config-map or secret at the moment. + * @param appName which is not necessarily equal to + * kubernetesObject.getMetadata().getName() */ - Mono triggerRefresh(KubernetesObject kubernetesObject); + Mono triggerRefresh(KubernetesObject kubernetesObject, String appName); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java index 2d8a942d6b..0b8f69e865 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java @@ -30,12 +30,15 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_APPS_ANNOTATION; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_LABEL; + /** * @author Ryan Baxter * @author Kris Iyer */ -abstract class SecretsWatcherChangeDetector extends KubernetesClientEventBasedSecretsChangeDetector - implements RefreshTrigger { +abstract sealed class SecretsWatcherChangeDetector extends KubernetesClientEventBasedSecretsChangeDetector implements + RefreshTrigger permits BusEventBasedSecretsWatcherChangeDetector, HttpBasedSecretsWatchChangeDetector { private final ScheduledExecutorService executorService; @@ -56,8 +59,8 @@ abstract class SecretsWatcherChangeDetector extends KubernetesClientEventBasedSe @Override protected final void onEvent(KubernetesObject secret) { // this::refreshTrigger is coming from BusEventBasedSecretsWatcherChangeDetector - WatcherUtil.onEvent(secret, ConfigurationWatcherConfigurationProperties.SECRET_LABEL, refreshDelay, - executorService, "secret", this::triggerRefresh); + WatcherUtil.onEvent(secret, SECRET_LABEL, SECRET_APPS_ANNOTATION, refreshDelay, executorService, "secret", + this::triggerRefresh); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtil.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtil.java index 25fbe408f7..a04ff6454e 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtil.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtil.java @@ -16,13 +16,18 @@ package org.springframework.cloud.kubernetes.configuration.watcher; -import java.util.Collections; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Function; +import java.util.function.BiFunction; +import java.util.stream.Collectors; import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.openapi.models.V1ObjectMeta; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; @@ -40,37 +45,93 @@ final class WatcherUtil { private WatcherUtil() { } - static void onEvent(KubernetesObject kubernetesObject, String label, long refreshDelay, + static void onEvent(KubernetesObject kubernetesObject, String label, String annotationName, long refreshDelay, ScheduledExecutorService executorService, String type, - Function> triggerRefresh) { + BiFunction> triggerRefresh) { String name = kubernetesObject.getMetadata().getName(); boolean isSpringCloudKubernetes = isSpringCloudKubernetes(kubernetesObject, label); if (isSpringCloudKubernetes) { - LOG.debug(() -> "Scheduling remote refresh event to be published for " + type + ": " + name - + " to be published in " + refreshDelay + " milliseconds"); - executorService.schedule(() -> { - try { - triggerRefresh.apply(kubernetesObject).subscribe(); - } - catch (Throwable t) { - LOG.warn(t, "Error when refreshing ConfigMap " + name); - } - }, refreshDelay, TimeUnit.MILLISECONDS); + Set apps = apps(kubernetesObject, annotationName); + + if (apps.isEmpty()) { + apps.add(name); + } + + LOG.info(() -> "will schedule remote refresh based on apps : " + apps); + apps.forEach(appName -> schedule(type, appName, refreshDelay, executorService, triggerRefresh, + kubernetesObject)); + } else { - LOG.debug(() -> "Not publishing event." + type + ": + name + does not contain the label " + label); + LOG.debug(() -> "Not publishing event." + type + ": " + name + " does not contain the label " + label); } } - private static boolean isSpringCloudKubernetes(KubernetesObject kubernetesObject, String label) { + static boolean isSpringCloudKubernetes(KubernetesObject kubernetesObject, String label) { if (kubernetesObject.getMetadata() == null) { return false; } - return Boolean.parseBoolean(Optional.ofNullable(kubernetesObject.getMetadata().getLabels()) - .orElse(Collections.emptyMap()).getOrDefault(label, "false")); + return Boolean.parseBoolean(labels(kubernetesObject).getOrDefault(label, "false")); + } + + static Set apps(KubernetesObject kubernetesObject, String annotationName) { + + // mutable on purpose + Set apps = new HashSet<>(1); + Map annotations = annotations(kubernetesObject); + + if (annotations.isEmpty()) { + LOG.debug(() -> annotationName + " not present (empty data)"); + return apps; + } + + String appsValue = annotations.get(annotationName); + + if (appsValue == null) { + LOG.debug(() -> annotationName + " not present (missing in annotations)"); + return apps; + } + + if (appsValue.isBlank()) { + LOG.debug(() -> appsValue + " not present (blanks only)"); + return apps; + } + + return Arrays.stream(appsValue.split(",")).map(String::trim).collect(Collectors.toSet()); + } + + static Map labels(KubernetesObject kubernetesObject) { + V1ObjectMeta metadata = kubernetesObject.getMetadata(); + if (metadata == null) { + return Map.of(); + } + return Optional.ofNullable(metadata.getLabels()).orElse(Map.of()); + } + + static Map annotations(KubernetesObject kubernetesObject) { + V1ObjectMeta metadata = kubernetesObject.getMetadata(); + if (metadata == null) { + return Map.of(); + } + return Optional.ofNullable(metadata.getAnnotations()).orElse(Map.of()); + } + + private static void schedule(String type, String appName, long refreshDelay, + ScheduledExecutorService executorService, BiFunction> triggerRefresh, + KubernetesObject kubernetesObject) { + LOG.debug(() -> "Scheduling remote refresh event to be published for " + type + ": with appName : " + appName + + " to be published in " + refreshDelay + " milliseconds"); + executorService.schedule(() -> { + try { + triggerRefresh.apply(kubernetesObject, appName).subscribe(); + } + catch (Throwable t) { + LOG.warn(t, "Error when refreshing appName " + appName); + } + }, refreshDelay, TimeUnit.MILLISECONDS); } } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java index 2505a4a1a1..f75b7006a0 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java @@ -90,7 +90,7 @@ void triggerRefreshWithConfigMap() { objectMeta.setName("foo"); V1ConfigMap configMap = new V1ConfigMap(); configMap.setMetadata(objectMeta); - changeDetector.triggerRefresh(configMap); + changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName()); ArgumentCaptor argumentCaptor = ArgumentCaptor .forClass(RefreshRemoteApplicationEvent.class); verify(applicationEventPublisher).publishEvent(argumentCaptor.capture()); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java index 6b96219f66..1eb7f2f02e 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java @@ -90,7 +90,7 @@ void triggerRefreshWithSecret() { objectMeta.setName("foo"); V1Secret secret = new V1Secret(); secret.setMetadata(objectMeta); - changeDetector.triggerRefresh(secret); + changeDetector.triggerRefresh(secret, secret.getMetadata().getName()); ArgumentCaptor argumentCaptor = ArgumentCaptor .forClass(RefreshRemoteApplicationEvent.class); verify(applicationEventPublisher).publishEvent(argumentCaptor.capture()); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetectorTests.java index dff7947e37..dae0ad6022 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedConfigMapWatchChangeDetectorTests.java @@ -128,7 +128,8 @@ void triggerConfigMapRefresh() { WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port()); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName())) + .verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); } @@ -143,7 +144,8 @@ void triggerConfigMapRefreshWithPropertiesBasedActuatorPath() { WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port()); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName())) + .verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh"))); } @@ -170,7 +172,8 @@ void triggerConfigMapRefreshWithAnnotationActuatorPath() { configMap.setMetadata(objectMeta); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName())) + .verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh"))); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetectorTests.java index 25eb9b9a24..210ee52ae7 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/HttpBasedSecretsWatchChangeDetectorTests.java @@ -125,7 +125,7 @@ void triggerSecretRefresh() { WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port()); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/actuator/refresh"))); } @@ -140,7 +140,7 @@ void triggerSecretRefreshWithPropertiesBasedActuatorPath() { WireMock.configureFor("localhost", WIRE_MOCK_SERVER.port()); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh"))); } @@ -166,7 +166,7 @@ void triggerSecretRefreshWithAnnotationActuatorPath() { secret.setMetadata(objectMeta); WireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/my/custom/actuator/refresh")) .willReturn(WireMock.aResponse().withStatus(200))); - StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete(); + StepVerifier.create(changeDetector.triggerRefresh(secret, secret.getMetadata().getName())).verifyComplete(); WireMock.verify(WireMock.postRequestedFor(WireMock.urlEqualTo("/my/custom/actuator/refresh"))); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtilTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtilTests.java new file mode 100644 index 0000000000..eef9c9b95d --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/WatcherUtilTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher; + +import java.util.Map; +import java.util.Set; + +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.CONFIG_MAP_LABEL; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_APPS_ANNOTATION; +import static org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherConfigurationProperties.SECRET_LABEL; + +class WatcherUtilTests { + + @Test + void isSpringCloudKubernetesConfigFalse() { + V1ConfigMap configMap = new V1ConfigMapBuilder().withMetadata(new V1ObjectMeta().labels(Map.of())).build(); + boolean present = WatcherUtil.isSpringCloudKubernetes(configMap, CONFIG_MAP_LABEL); + Assertions.assertFalse(present); + } + + @Test + void isSpringCloudKubernetesConfigTrue() { + V1ConfigMap configMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMeta().labels(Map.of(CONFIG_MAP_LABEL, "true"))).build(); + boolean present = WatcherUtil.isSpringCloudKubernetes(configMap, CONFIG_MAP_LABEL); + Assertions.assertTrue(present); + } + + @Test + void isSpringCloudKubernetesSecretFalse() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of())).build(); + boolean present = WatcherUtil.isSpringCloudKubernetes(secret, SECRET_LABEL); + Assertions.assertFalse(present); + } + + @Test + void isSpringCloudKubernetesSecretTrue() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of(SECRET_LABEL, "true"))) + .build(); + boolean present = WatcherUtil.isSpringCloudKubernetes(secret, SECRET_LABEL); + Assertions.assertTrue(present); + } + + @Test + void labelsMissing() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta()).build(); + Map res = WatcherUtil.labels(secret); + Assertions.assertEquals(res.size(), 0); + } + + @Test + void labelsPresent() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().labels(Map.of("a", "b"))).build(); + Map res = WatcherUtil.labels(secret); + Assertions.assertEquals(res.size(), 1); + } + + @Test + void appsNoMetadata() { + V1Secret secret = new V1SecretBuilder().build(); + Set apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION); + Assertions.assertEquals(apps.size(), 0); + } + + @Test + void appsNoAnnotations() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().annotations(Map.of())).build(); + Set apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION); + Assertions.assertEquals(apps.size(), 0); + } + + @Test + void appsAnnotationNotFound() { + V1Secret secret = new V1SecretBuilder().withMetadata(new V1ObjectMeta().annotations(Map.of("a", "b"))).build(); + Set apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION); + Assertions.assertEquals(apps.size(), 0); + } + + @Test + void appsSingleResult() { + V1Secret secret = new V1SecretBuilder() + .withMetadata(new V1ObjectMeta().annotations(Map.of(SECRET_APPS_ANNOTATION, "one-app"))).build(); + Set apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION); + Assertions.assertEquals(apps.size(), 1); + Assertions.assertEquals(apps.iterator().next(), "one-app"); + } + + @Test + void appsMultipleResults() { + V1Secret secret = new V1SecretBuilder() + .withMetadata(new V1ObjectMeta().annotations(Map.of(SECRET_APPS_ANNOTATION, "one, two, three "))) + .build(); + Set apps = WatcherUtil.apps(secret, SECRET_APPS_ANNOTATION); + Assertions.assertEquals(apps.size(), 3); + Assertions.assertTrue(apps.contains("one")); + Assertions.assertTrue(apps.contains("two")); + Assertions.assertTrue(apps.contains("three")); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml index 54d930691e..5d81a2136d 100644 --- a/spring-cloud-kubernetes-integration-tests/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/pom.xml @@ -119,5 +119,7 @@ spring-cloud-kubernetes-core-k8s-client-it spring-cloud-kubernetes-client-secrets-event-reload spring-cloud-kubernetes-client-configmap-event-reload + spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/pom.xml new file mode 100644 index 0000000000..7607e7ff43 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/pom.xml @@ -0,0 +1,20 @@ + + + + spring-cloud-kubernetes-integration-tests + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + pom + + + spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a + spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b + spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/pom.xml new file mode 100644 index 0000000000..874e614226 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/pom.xml @@ -0,0 +1,78 @@ + + + + spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-bus-kafka + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + build-image + + ${skip.build.image} + + package + + build-image + + + + repackage + package + + repackage + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java new file mode 100644 index 0000000000..e15b20322d --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.appA; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@SpringBootApplication +@RestController +public class AppATestApplication implements ApplicationListener { + + private final Log LOG = LogFactory.getLog(getClass()); + + private boolean value = false; + + public static void main(String[] args) { + SpringApplication.run(AppATestApplication.class, args); + } + + @GetMapping("/app-a") + public boolean index() { + LOG.info("Current value: " + value); + return value; + } + + @Override + public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) { + LOG.info("Received remote refresh event from origin: " + refreshRemoteApplicationEvent.getOriginService() + + " to destination : " + refreshRemoteApplicationEvent.getDestinationService()); + this.value = true; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/resources/application.yaml new file mode 100644 index 0000000000..74fa5b1d48 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a/src/main/resources/application.yaml @@ -0,0 +1,22 @@ +#logging: +# level: +# org.springframework: DEBUG +# +spring: + application: + name: spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a + cloud: + bus: + refresh: + enabled: false + enabled: true + destination: multiple-apps + stream: + default-binder: kafka +management: + endpoint: + health: + probes: + enabled: true +server: + port: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/pom.xml new file mode 100644 index 0000000000..34b1d7618e --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/pom.xml @@ -0,0 +1,78 @@ + + + + spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + ../../spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-bus-kafka + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + build-image + + ${skip.build.image} + + package + + build-image + + + + repackage + package + + repackage + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java new file mode 100644 index 0000000000..ead81ba0db --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.appB; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@SpringBootApplication +@RestController +public class AppBTestApplication implements ApplicationListener { + + private final Log LOG = LogFactory.getLog(getClass()); + + private boolean value = false; + + public static void main(String[] args) { + SpringApplication.run(AppBTestApplication.class, args); + } + + @GetMapping("/app-b") + public boolean index() { + LOG.info("Current value: " + value); + return value; + } + + @Override + public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) { + LOG.info("Received remote refresh event from origin: " + refreshRemoteApplicationEvent.getOriginService() + + " to destination : " + refreshRemoteApplicationEvent.getDestinationService()); + this.value = true; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/resources/application.yaml new file mode 100644 index 0000000000..8491ca7fc1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b/src/main/resources/application.yaml @@ -0,0 +1,22 @@ +#logging: +# level: +# org.springframework: DEBUG +# +spring: + application: + name: spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b + cloud: + bus: + refresh: + enabled: false + enabled: true + destination: multiple-apps + stream: + default-binder: kafka +management: + endpoint: + health: + probes: + enabled: true +server: + port: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/pom.xml new file mode 100644 index 0000000000..062f54290a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/pom.xml @@ -0,0 +1,123 @@ + + + + spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + test + + + io.kubernetes + client-java + test + + + io.kubernetes + client-java-extended + test + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + + + + + + + ../../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppsIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppsIT.java new file mode 100644 index 0000000000..4744f0fc0c --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppsIT.java @@ -0,0 +1,383 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.multiple.apps; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1Service; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * @author wind57 + */ +class ConfigurationWatcherMultipleAppsIT { + + private static final String CONFIG_WATCHER_APP_A_IMAGE = "spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a"; + + private static final String CONFIG_WATCHER_APP_B_IMAGE = "spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b"; + + private static final String CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME = "app-a-deployment"; + + private static final String CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME = "app-b-deployment"; + + private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-configuration-watcher-deployment"; + + private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher"; + + private static final String CONFIG_MAP_NAME = "multiple-apps"; + + private static final String NAMESPACE = "default"; + + private static final String KAFKA_BROKER = "kafka-broker"; + + private static final String KAFKA_SERVICE = "kafka"; + + private static final String ZOOKEEPER_SERVICE = "zookeeper"; + + private static final String ZOOKEEPER_DEPLOYMENT = "zookeeper"; + + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + + Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + + Commons.validateImage(CONFIG_WATCHER_APP_A_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_APP_A_IMAGE, K3S); + + Commons.validateImage(CONFIG_WATCHER_APP_B_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_APP_B_IMAGE, K3S); + + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + networkingApi = new NetworkingV1Api(); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.cleanUp(CONFIG_WATCHER_APP_A_IMAGE, K3S); + Commons.cleanUp(CONFIG_WATCHER_APP_B_IMAGE, K3S); + } + + @BeforeEach + void setup() throws Exception { + + deployZookeeper(); + deployKafka(); + deployAppA(); + deployAppB(); + deployIngress(); + deployConfigWatcher(); + + waitForDeployment(ZOOKEEPER_DEPLOYMENT); + waitForDeployment(KAFKA_BROKER); + waitForDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME); + waitForDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME); + } + + @AfterEach + void after() throws Exception { + + cleanUpKafka(); + cleanUpZookeeper(); + cleanUpServices(); + cleanUpDeployments(); + cleanUpIngress(); + cleanUpConfigMaps(); + + k8SUtils.waitForDeploymentToBeDeleted(KAFKA_BROKER, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(ZOOKEEPER_DEPLOYMENT, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME, NAMESPACE); + } + + @Test + void testRefresh() throws Exception { + + // configmap has one label, one that says that we should refresh + // and one annotation that says that we should refresh some specific services + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_MAP_NAME) + .addToLabels("spring.cloud.kubernetes.config", "true") + .addToAnnotations("spring.cloud.kubernetes.configmap.apps", + "spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a, " + + "spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b") + .endMetadata().addToData("foo", "hello world").build(); + api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); + + WebClient.Builder builderA = builder(); + WebClient serviceClientA = builderA.baseUrl("http://localhost:80/app-a").build(); + + WebClient.Builder builderB = builder(); + WebClient serviceClientB = builderB.baseUrl("http://localhost:80/app-b").build(); + + Boolean[] valueA = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(240)).until(() -> { + valueA[0] = serviceClientA.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class) + .retryWhen(retrySpec()).block(); + return valueA[0]; + }); + + Assertions.assertThat(valueA[0]).isTrue(); + + Boolean[] valueB = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(240)).until(() -> { + valueB[0] = serviceClientB.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class) + .retryWhen(retrySpec()).block(); + return valueB[0]; + }); + + Assertions.assertThat(valueB[0]).isTrue(); + } + + /** + *
+	 --------------------------------------------------- zookeeper ----------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployZookeeper() throws Exception { + api.createNamespacedService(NAMESPACE, getZookeeperService(), null, null, null); + V1Deployment deployment = getZookeeperDeployment(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "zookeeper", K3S); + appsApi.createNamespacedDeployment(NAMESPACE, deployment, null, null, null); + } + + private V1Deployment getZookeeperDeployment() throws Exception { + return (V1Deployment) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-deployment.yaml"); + } + + private V1Service getZookeeperService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-service.yaml"); + } + + /** + *
+	 ----------------------------------------------------- kafka ------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployKafka() throws Exception { + api.createNamespacedService(NAMESPACE, getKafkaService(), null, null, null); + V1Deployment deployment = getKafkaDeployment(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "kafka", K3S); + appsApi.createNamespacedDeployment(NAMESPACE, getKafkaDeployment(), null, null, null); + } + + private V1Deployment getKafkaDeployment() throws Exception { + return (V1Deployment) K8SUtils.readYamlFromClasspath("kafka/kafka-deployment.yaml"); + } + + private V1Service getKafkaService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("kafka/kafka-service.yaml"); + } + + /** + *
+	 ----------------------------------------------------- app-a ------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployAppA() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getAppADeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getAppAService(), null, null, null); + } + + private V1Deployment getAppADeployment() throws Exception { + String urlString = "app-a/app-a-deployment.yaml"; + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getAppAService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("app-a/app-a-service.yaml"); + } + + /** + *
+	 --------------------------------------------------- app-b --------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployAppB() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getAppBDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getAppBService(), null, null, null); + } + + private V1Deployment getAppBDeployment() throws Exception { + String urlString = "app-b/app-b-deployment.yaml"; + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getAppBService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("app-b/app-b-service.yaml"); + } + + /** + *
+	 ------------------------------------------------ config-watcher --------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployConfigWatcher() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null, null); + } + + private V1Deployment getConfigWatcherDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath( + "config-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml"); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getConfigWatcherService() throws Exception { + return (V1Service) K8SUtils + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); + } + + /** + *
+		------------------------------------------------ common ----------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+	 
+ */ + + private void deployIngress() throws Exception { + V1Ingress ingress = (V1Ingress) K8SUtils.readYamlFromClasspath( + "ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml"); + + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private void waitForDeployment(String deploymentName) { + await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS) + .until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE)); + } + + private void cleanUpKafka() throws Exception { + appsApi.deleteNamespacedDeployment(KAFKA_BROKER, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(KAFKA_SERVICE, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpZookeeper() throws Exception { + appsApi.deleteNamespacedDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(ZOOKEEPER_SERVICE, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpServices() throws Exception { + api.deleteNamespacedService("app-a", NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService("app-b", NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + } + + private void cleanUpDeployments() throws Exception { + appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE, null, null, null, + null, null, null); + appsApi.deleteNamespacedDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME, NAMESPACE, null, null, null, null, + null, null); + + appsApi.deleteNamespacedDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME, NAMESPACE, null, null, null, null, + null, null); + } + + private void cleanUpConfigMaps() throws Exception { + api.deleteNamespacedConfigMap(CONFIG_MAP_NAME, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpIngress() throws Exception { + networkingApi.deleteNamespacedIngress("it-ingress-multiple-apps", NAMESPACE, null, null, null, null, null, + null); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(240, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-deployment.yaml new file mode 100644 index 0000000000..3211344e44 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-a-deployment +spec: + selector: + matchLabels: + app: app-a + template: + metadata: + labels: + app: app-a + spec: + containers: + - name: app-a + image: docker.io/springcloud/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-kafka + - name: spring.kafka.bootstrap-servers + value: kafka:9092 + readinessProbe: + httpGet: + port: 8080 + path: /actuator/health/readiness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + port: 8080 + path: /actuator/health/liveness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + ports: + - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-service.yaml new file mode 100644 index 0000000000..04cd68a2d1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-a/app-a-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: app-a + name: app-a +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: app-a + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-deployment.yaml new file mode 100644 index 0000000000..0e0bb0e4ba --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-b-deployment +spec: + selector: + matchLabels: + app: app-b + template: + metadata: + labels: + app: app-b + spec: + containers: + - name: app-b + image: docker.io/springcloud/spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-kafka + - name: spring.kafka.bootstrap-servers + value: kafka:9092 + readinessProbe: + httpGet: + port: 8081 + path: /actuator/health/readiness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + port: 8081 + path: /actuator/health/liveness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + ports: + - containerPort: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-service.yaml new file mode 100644 index 0000000000..779fce4531 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/app-b/app-b-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: app-b + name: app-b +spec: + ports: + - name: http + port: 8081 + targetPort: 8081 + selector: + app: app-b + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml new file mode 100644 index 0000000000..35878d547a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-kubernetes-configuration-watcher-deployment +spec: + selector: + matchLabels: + app: spring-cloud-kubernetes-configuration-watcher + template: + metadata: + labels: + app: spring-cloud-kubernetes-configuration-watcher + spec: + serviceAccountName: spring-cloud-kubernetes-serviceaccount + containers: + - name: spring-cloud-kubernetes-configuration-watcher + image: docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-kafka + - name: SPRING_CLOUD_BUS_DESTINATION + value: multiple-apps + - name: spring.kafka.bootstrap-servers + value: kafka:9092 + - name: SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_REFRESHDELAY + value: 1 + readinessProbe: + httpGet: + port: 8888 + path: /actuator/health/readiness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + port: 8888 + path: /actuator/health/liveness + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + ports: + - containerPort: 8888 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml new file mode 100644 index 0000000000..c8496317b1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-configuration-watcher + name: spring-cloud-kubernetes-configuration-watcher +spec: + ports: + - name: http + port: 8888 + targetPort: 8888 + selector: + app: spring-cloud-kubernetes-configuration-watcher + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml new file mode 100644 index 0000000000..cae12b115b --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: it-ingress-multiple-apps + namespace: default +spec: + rules: + - http: + paths: + - path: /app-a + pathType: Prefix + backend: + service: + name: app-a + port: + number: 8080 + + - http: + paths: + - path: /app-b + pathType: Prefix + backend: + service: + name: app-b + port: + number: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-deployment.yaml new file mode 100644 index 0000000000..6cce9e9181 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-deployment.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kafka + component: kafka-broker + name: kafka-broker +spec: + replicas: 1 + selector: + matchLabels: + app: kafka + component: kafka-broker + template: + metadata: + labels: + app: kafka + component: kafka-broker + spec: + # otherwise we will get an env var "KAFKA_PORT" (from service name: "kafka" and appended with "_PORT") + # and this will cause this problem: https://github.com/confluentinc/cp-docker-images/blob/master/debian/kafka/include/etc/confluent/docker/configure#L58-L62 + # Another solution is to rename the service. + enableServiceLinks: false + containers: + - name: kafka + image: confluentinc/cp-kafka:7.2.1 + ports: + - containerPort: 9092 + env: + - name: KAFKA_LISTENERS + value: "INTERNAL://0.0.0.0:9092,OUTSIDE://0.0.0.0:9094" + + - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP + value: "INTERNAL:PLAINTEXT,OUTSIDE:PLAINTEXT" + + - name: KAFKA_ADVERTISED_LISTENERS + value: "INTERNAL://kafka:9092,OUTSIDE://localhost:9094" + + - name: KAFKA_INTER_BROKER_LISTENER_NAME + value: "INTERNAL" + + - name: KAFKA_ADVERTISED_HOST_NAME + valueFrom: + fieldRef: + fieldPath: status.podIP + + - name: KAFKA_ZOOKEEPER_CONNECT + value: zookeeper:2181 + + # we have enabled auto creation of topics and when this happens there is a replication factor of 3 + # that is set automatically. Since we don't have that many, producers will fail. + # This setting ensures that there is just one replication + - name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR + value: "1" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-service.yaml new file mode 100644 index 0000000000..88bea315ec --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/kafka/kafka-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: kafka + labels: + app: kafka + component: kafka-broker +spec: + ports: + - port: 9092 + name: kafka-port + targetPort: 9092 + protocol: TCP + selector: + app: kafka + component: kafka-broker diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-deployment.yaml new file mode 100644 index 0000000000..3c285c30f4 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kafka + component: zookeeper + name: zookeeper +spec: + replicas: 1 + selector: + matchLabels: + app: kafka + component: zookeeper + template: + metadata: + labels: + app: kafka + component: zookeeper + spec: + containers: + - name: zookeeper + image: confluentinc/cp-zookeeper:7.2.1 + ports: + - containerPort: 2181 + env: + - name: ZOOKEEPER_ID + value: "1" + - name: ZOOKEEPER_SERVER_1 + value: zookeeper + - name: ZOOKEEPER_CLIENT_PORT + value: 2181 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-service.yaml new file mode 100644 index 0000000000..eb025d3c4a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app/src/test/resources/zookeeper/zookeeper-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: zookeeper + labels: + app: kafka + component: zookeeper +spec: + ports: + - port: 2181 + name: zookeeper-port + targetPort: 2181 + protocol: TCP + selector: + app: kafka + component: zookeeper diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/pom.xml new file mode 100644 index 0000000000..4d13c8b168 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/pom.xml @@ -0,0 +1,22 @@ + + + + spring-cloud-kubernetes-integration-tests + org.springframework.cloud + 3.0.0-SNAPSHOT + + + 4.0.0 + spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps + pom + + + spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a + spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b + spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/pom.xml new file mode 100644 index 0000000000..539a9bf9a9 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/pom.xml @@ -0,0 +1,78 @@ + + + + spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-bus-amqp + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + build-image + + ${skip.build.image} + + package + + build-image + + + + repackage + package + + repackage + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java new file mode 100644 index 0000000000..e15b20322d --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appA/AppATestApplication.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.appA; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@SpringBootApplication +@RestController +public class AppATestApplication implements ApplicationListener { + + private final Log LOG = LogFactory.getLog(getClass()); + + private boolean value = false; + + public static void main(String[] args) { + SpringApplication.run(AppATestApplication.class, args); + } + + @GetMapping("/app-a") + public boolean index() { + LOG.info("Current value: " + value); + return value; + } + + @Override + public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) { + LOG.info("Received remote refresh event from origin: " + refreshRemoteApplicationEvent.getOriginService() + + " to destination : " + refreshRemoteApplicationEvent.getDestinationService()); + this.value = true; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/resources/application.yaml new file mode 100644 index 0000000000..c31741d965 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a/src/main/resources/application.yaml @@ -0,0 +1,24 @@ +#logging: +# level: +# org.springframework: DEBUG +# +spring: + application: + name: spring-cloud-kubernetes-client-configuration-watcher-secret-app-a + cloud: + bus: + refresh: + enabled: false + enabled: true + destination: multiple-apps + stream: + default-binder: rabbit + autoconfigure: + exclude: org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration +management: + endpoint: + health: + probes: + enabled: true +server: + port: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/pom.xml new file mode 100644 index 0000000000..635cbd17e0 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/pom.xml @@ -0,0 +1,78 @@ + + + + spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-bus-amqp + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + build-image + + ${skip.build.image} + + package + + build-image + + + + repackage + package + + repackage + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java new file mode 100644 index 0000000000..ead81ba0db --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/appB/AppBTestApplication.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.appB; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@SpringBootApplication +@RestController +public class AppBTestApplication implements ApplicationListener { + + private final Log LOG = LogFactory.getLog(getClass()); + + private boolean value = false; + + public static void main(String[] args) { + SpringApplication.run(AppBTestApplication.class, args); + } + + @GetMapping("/app-b") + public boolean index() { + LOG.info("Current value: " + value); + return value; + } + + @Override + public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) { + LOG.info("Received remote refresh event from origin: " + refreshRemoteApplicationEvent.getOriginService() + + " to destination : " + refreshRemoteApplicationEvent.getDestinationService()); + this.value = true; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/resources/application.yaml new file mode 100644 index 0000000000..132e62b08d --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b/src/main/resources/application.yaml @@ -0,0 +1,24 @@ +#logging: +# level: +# org.springframework: DEBUG +# +spring: + application: + name: spring-cloud-kubernetes-client-configuration-watcher-secret-app-b + cloud: + bus: + refresh: + enabled: false + enabled: true + destination: multiple-apps + stream: + default-binder: rabbit + autoconfigure: + exclude: org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration +management: + endpoint: + health: + probes: + enabled: true +server: + port: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/pom.xml new file mode 100644 index 0000000000..171b7fe76f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/pom.xml @@ -0,0 +1,123 @@ + + + + spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + jar + + spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + test + + + io.kubernetes + client-java + test + + + io.kubernetes + client-java-extended + test + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + + + + + + + ../../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppIT.java new file mode 100644 index 0000000000..8eb4142747 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/multiple/apps/ConfigurationWatcherMultipleAppIT.java @@ -0,0 +1,349 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configuration.watcher.multiple.apps; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1ReplicationController; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import io.kubernetes.client.openapi.models.V1Service; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * @author wind57 + */ +class ConfigurationWatcherMultipleAppIT { + + private static final String CONFIG_WATCHER_APP_A_IMAGE = "spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a"; + + private static final String CONFIG_WATCHER_APP_B_IMAGE = "spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b"; + + private static final String CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME = "app-a-deployment"; + + private static final String CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME = "app-b-deployment"; + + private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-configuration-watcher-deployment"; + + private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher"; + + private static final String SECRET_NAME = "multiple-apps"; + + private static final String NAMESPACE = "default"; + + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + + Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + + Commons.validateImage(CONFIG_WATCHER_APP_A_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_APP_A_IMAGE, K3S); + + Commons.validateImage(CONFIG_WATCHER_APP_B_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_APP_B_IMAGE, K3S); + + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + networkingApi = new NetworkingV1Api(); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.cleanUp(CONFIG_WATCHER_APP_A_IMAGE, K3S); + Commons.cleanUp(CONFIG_WATCHER_APP_B_IMAGE, K3S); + } + + @BeforeEach + void setup() throws Exception { + + deployRabbitMq(); + deployAppA(); + deployAppB(); + deployIngress(); + deployConfigWatcher(); + + k8SUtils.waitForReplicationController("rabbitmq-controller", NAMESPACE); + waitForDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME); + waitForDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME); + } + + @AfterEach + void after() throws Exception { + + cleanRabbitMq(); + cleanUpServices(); + cleanUpDeployments(); + cleanUpIngress(); + cleanUpConfigMaps(); + + k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME, NAMESPACE); + k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME, NAMESPACE); + } + + @Test + void testRefresh() throws Exception { + + // secret has one label, one that says that we should refresh + // and one annotation that says that we should refresh some specific services + V1Secret secret = new V1SecretBuilder().editOrNewMetadata().withName(SECRET_NAME) + .addToLabels("spring.cloud.kubernetes.secret", "true") + .addToAnnotations("spring.cloud.kubernetes.secret.apps", + "spring-cloud-kubernetes-client-configuration-watcher-secret-app-a, " + + "spring-cloud-kubernetes-client-configuration-watcher-secret-app-b") + .endMetadata().build(); + api.createNamespacedSecret(NAMESPACE, secret, null, null, null); + + WebClient.Builder builderA = builder(); + WebClient serviceClientA = builderA.baseUrl("http://localhost:80/app-a").build(); + + WebClient.Builder builderB = builder(); + WebClient serviceClientB = builderB.baseUrl("http://localhost:80/app-b").build(); + + Boolean[] valueA = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(240)).until(() -> { + valueA[0] = serviceClientA.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class) + .retryWhen(retrySpec()).block(); + return valueA[0]; + }); + + Assertions.assertThat(valueA[0]).isTrue(); + + Boolean[] valueB = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(240)).until(() -> { + valueB[0] = serviceClientB.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class) + .retryWhen(retrySpec()).block(); + return valueB[0]; + }); + + Assertions.assertThat(valueB[0]).isTrue(); + } + + /** + *
+	 --------------------------------------------------- rabbitmq -----------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployRabbitMq() throws Exception { + api.createNamespacedService(NAMESPACE, getRabbitMqService(), null, null, null); + String[] image = getRabbitMQReplicationController().getSpec().getTemplate().getSpec().getContainers().get(0) + .getImage().split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "rabbitmq", K3S); + api.createNamespacedReplicationController(NAMESPACE, getRabbitMQReplicationController(), null, null, null); + } + + private V1ReplicationController getRabbitMQReplicationController() throws Exception { + return (V1ReplicationController) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-controller.yaml"); + } + + private V1Service getRabbitMqService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-service.yaml"); + } + + /** + *
+	 ----------------------------------------------------- app-a ------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployAppA() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getAppADeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getAppAService(), null, null, null); + } + + private V1Deployment getAppADeployment() throws Exception { + String urlString = "app-a/app-a-deployment.yaml"; + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getAppAService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("app-a/app-a-service.yaml"); + } + + /** + *
+	 --------------------------------------------------- app-b --------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployAppB() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getAppBDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getAppBService(), null, null, null); + } + + private V1Deployment getAppBDeployment() throws Exception { + String urlString = "app-b/app-b-deployment.yaml"; + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getAppBService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("app-b/app-b-service.yaml"); + } + + /** + *
+	 ------------------------------------------------ config-watcher --------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 ------------------------------------------------------------------------------------------------------------
+	 
+ */ + private void deployConfigWatcher() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null, null); + } + + private V1Deployment getConfigWatcherDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath( + "config-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml"); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getConfigWatcherService() throws Exception { + return (V1Service) K8SUtils + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); + } + + /** + *
+		------------------------------------------------ common ----------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+		------------------------------------------------------------------------------------------------------------
+	 
+ */ + + private void deployIngress() throws Exception { + V1Ingress ingress = (V1Ingress) K8SUtils.readYamlFromClasspath( + "ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml"); + + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private void waitForDeployment(String deploymentName) { + await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS) + .until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE)); + } + + private void cleanRabbitMq() throws Exception { + api.deleteNamespacedService("rabbitmq-service", NAMESPACE, null, null, null, null, null, null); + try { + api.deleteNamespacedReplicationController("rabbitmq-controller", NAMESPACE, null, null, null, null, null, + null); + } + catch (Exception e) { + // swallowing this exception, delete does actually happen, it's a problem + // downstream from the k8s client; see: + // https://github.com/kubernetes-client/java/issues/86#issuecomment-411234259 + } + } + + private void cleanUpServices() throws Exception { + api.deleteNamespacedService("app-a", NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService("app-b", NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + } + + private void cleanUpDeployments() throws Exception { + appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE, null, null, null, + null, null, null); + appsApi.deleteNamespacedDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_A_NAME, NAMESPACE, null, null, null, null, + null, null); + + appsApi.deleteNamespacedDeployment(CONFIG_WATCHER_DEPLOYMENT_APP_B_NAME, NAMESPACE, null, null, null, null, + null, null); + } + + private void cleanUpConfigMaps() throws Exception { + api.deleteNamespacedSecret(SECRET_NAME, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpIngress() throws Exception { + networkingApi.deleteNamespacedIngress("it-ingress-multiple-apps", NAMESPACE, null, null, null, null, null, + null); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(240, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-deployment.yaml new file mode 100644 index 0000000000..6f76090f0f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-a-deployment +spec: + selector: + matchLabels: + app: app-a + template: + metadata: + labels: + app: app-a + spec: + containers: + - name: app-a + image: docker.io/springcloud/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-amqp + - name: SPRING_RABBITMQ_HOST + value: rabbitmq-service + readinessProbe: + httpGet: + port: 8080 + path: /actuator/health/readiness + livenessProbe: + httpGet: + port: 8080 + path: /actuator/health/liveness + ports: + - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-service.yaml new file mode 100644 index 0000000000..04cd68a2d1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-a/app-a-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: app-a + name: app-a +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: app-a + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-deployment.yaml new file mode 100644 index 0000000000..83c87a1995 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-b-deployment +spec: + selector: + matchLabels: + app: app-b + template: + metadata: + labels: + app: app-b + spec: + containers: + - name: app-b + image: docker.io/springcloud/spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-amqp + - name: SPRING_RABBITMQ_HOST + value: rabbitmq-service + readinessProbe: + httpGet: + port: 8081 + path: /actuator/health/readiness + livenessProbe: + httpGet: + port: 8081 + path: /actuator/health/liveness + ports: + - containerPort: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-service.yaml new file mode 100644 index 0000000000..779fce4531 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/app-b/app-b-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: app-b + name: app-b +spec: + ports: + - name: http + port: 8081 + targetPort: 8081 + selector: + app: app-b + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml new file mode 100644 index 0000000000..e4f95bc867 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-kubernetes-configuration-watcher-deployment +spec: + selector: + matchLabels: + app: spring-cloud-kubernetes-configuration-watcher + template: + metadata: + labels: + app: spring-cloud-kubernetes-configuration-watcher + spec: + serviceAccountName: spring-cloud-kubernetes-serviceaccount + containers: + - name: spring-cloud-kubernetes-configuration-watcher + image: docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher + imagePullPolicy: IfNotPresent + env: + - name: SPRING_PROFILES_ACTIVE + value: bus-amqp + - name: SPRING_RABBITMQ_HOST + value: rabbitmq-service + - name: SPRING_CLOUD_BUS_DESTINATION + value: multiple-apps + - name: SPRING_CLOUD_KUBERNETES_CONFIGURATION_WATCHER_REFRESHDELAY + value: 1 + readinessProbe: + httpGet: + port: 8888 + path: /actuator/health/readiness + livenessProbe: + httpGet: + port: 8888 + path: /actuator/health/liveness + ports: + - containerPort: 8888 + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml new file mode 100644 index 0000000000..c8496317b1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-configuration-watcher + name: spring-cloud-kubernetes-configuration-watcher +spec: + ports: + - name: http + port: 8888 + targetPort: 8888 + selector: + app: spring-cloud-kubernetes-configuration-watcher + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml new file mode 100644 index 0000000000..cae12b115b --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/ingress/spring-cloud-kubernetes-configuration-watcher-multiple-apps-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: it-ingress-multiple-apps + namespace: default +spec: + rules: + - http: + paths: + - path: /app-a + pathType: Prefix + backend: + service: + name: app-a + port: + number: 8080 + + - http: + paths: + - path: /app-b + pathType: Prefix + backend: + service: + name: app-b + port: + number: 8081 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-controller.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-controller.yaml new file mode 100644 index 0000000000..6fa3b0f17e --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-controller.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + labels: + component: rabbitmq + name: rabbitmq-controller +spec: + replicas: 1 + template: + metadata: + labels: + app: taskQueue + component: rabbitmq + spec: + containers: + - image: rabbitmq:3-management + name: rabbitmq + ports: + - name: amqp + containerPort: 5672 + - name: http-stats + containerPort: 15672 + readinessProbe: + httpGet: + port: 15672 + path: /api/healthchecks/node + httpHeaders: + - name: Authorization + value: Basic Z3Vlc3Q6Z3Vlc3Q= diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-service.yaml new file mode 100644 index 0000000000..e960e5f416 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps/spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app/src/test/resources/rabbitmq/rabbitmq-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + component: rabbitmq + name: rabbitmq-service +spec: + ports: + - port: 5672 + name: amqp + targetPort: 5672 + - port: 15672 + name: http-stats + targetPort: 15672 + selector: + app: taskQueue + component: rabbitmq