Fix/740 (#1067)
This commit is contained in:
@@ -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 ..
|
||||
|
||||
|
||||
|
||||
@@ -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 ..
|
||||
|
||||
@@ -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 ..
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -70,21 +70,21 @@ public class KubernetesClientEventBasedConfigMapChangeDetector extends Configura
|
||||
private final ResourceEventHandler<V1ConfigMap> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -70,21 +70,21 @@ public class KubernetesClientEventBasedSecretsChangeDetector extends Configurati
|
||||
private final ResourceEventHandler<V1Secret> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ final class BusEventBasedConfigMapWatcherChangeDetector extends ConfigMapWatcher
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap) {
|
||||
return busRefreshTrigger.triggerRefresh(configMap);
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
|
||||
return busRefreshTrigger.triggerRefresh(configMap, appName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ final class BusEventBasedSecretsWatcherChangeDetector extends SecretsWatcherChan
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject secret) {
|
||||
return busRefreshTrigger.triggerRefresh(secret);
|
||||
public Mono<Void> triggerRefresh(KubernetesObject secret, String appName) {
|
||||
return busRefreshTrigger.triggerRefresh(secret, appName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ final class BusRefreshTrigger implements RefreshTrigger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap) {
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
|
||||
applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(configMap, busId,
|
||||
new PathDestinationFactory().getDestination(configMap.getMetadata().getName())));
|
||||
new PathDestinationFactory().getDestination(appName)));
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,16 @@ import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationU
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -48,8 +48,8 @@ final class HttpBasedConfigMapWatchChangeDetector extends ConfigMapWatcherChange
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap) {
|
||||
return httpRefreshTrigger.triggerRefresh(configMap);
|
||||
public Mono<Void> triggerRefresh(KubernetesObject configMap, String appName) {
|
||||
return httpRefreshTrigger.triggerRefresh(configMap, appName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ final class HttpBasedSecretsWatchChangeDetector extends SecretsWatcherChangeDete
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject secret) {
|
||||
return httpRefreshTrigger.triggerRefresh(secret);
|
||||
public Mono<Void> triggerRefresh(KubernetesObject secret, String appName) {
|
||||
return httpRefreshTrigger.triggerRefresh(secret, appName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,16 +54,14 @@ final class HttpRefreshTrigger implements RefreshTrigger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> triggerRefresh(KubernetesObject kubernetesObject) {
|
||||
public Mono<Void> 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 @@ final class HttpRefreshTrigger implements RefreshTrigger {
|
||||
|
||||
// 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 :<port> 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(":", ""));
|
||||
|
||||
@@ -24,11 +24,13 @@ import reactor.core.publisher.Mono;
|
||||
*
|
||||
* @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<Void> triggerRefresh(KubernetesObject kubernetesObject);
|
||||
Mono<Void> triggerRefresh(KubernetesObject kubernetesObject, String appName);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,12 +30,15 @@ import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationU
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<KubernetesObject, Mono<Void>> triggerRefresh) {
|
||||
BiFunction<KubernetesObject, String, Mono<Void>> 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<String> 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<String> apps(KubernetesObject kubernetesObject, String annotationName) {
|
||||
|
||||
// mutable on purpose
|
||||
Set<String> apps = new HashSet<>(1);
|
||||
Map<String, String> 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<String, String> labels(KubernetesObject kubernetesObject) {
|
||||
V1ObjectMeta metadata = kubernetesObject.getMetadata();
|
||||
if (metadata == null) {
|
||||
return Map.of();
|
||||
}
|
||||
return Optional.ofNullable(metadata.getLabels()).orElse(Map.of());
|
||||
}
|
||||
|
||||
static Map<String, String> 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<KubernetesObject, String, Mono<Void>> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class BusEventBasedConfigMapWatcherChangeDetectorTests {
|
||||
objectMeta.setName("foo");
|
||||
V1ConfigMap configMap = new V1ConfigMap();
|
||||
configMap.setMetadata(objectMeta);
|
||||
changeDetector.triggerRefresh(configMap);
|
||||
changeDetector.triggerRefresh(configMap, configMap.getMetadata().getName());
|
||||
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
|
||||
.forClass(RefreshRemoteApplicationEvent.class);
|
||||
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
|
||||
|
||||
@@ -90,7 +90,7 @@ class BusEventBasedSecretsWatcherChangeDetectorTests {
|
||||
objectMeta.setName("foo");
|
||||
V1Secret secret = new V1Secret();
|
||||
secret.setMetadata(objectMeta);
|
||||
changeDetector.triggerRefresh(secret);
|
||||
changeDetector.triggerRefresh(secret, secret.getMetadata().getName());
|
||||
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
|
||||
.forClass(RefreshRemoteApplicationEvent.class);
|
||||
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
|
||||
|
||||
@@ -128,7 +128,8 @@ class HttpBasedConfigMapWatchChangeDetectorTests {
|
||||
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 @@ class HttpBasedConfigMapWatchChangeDetectorTests {
|
||||
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 @@ class HttpBasedConfigMapWatchChangeDetectorTests {
|
||||
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")));
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class HttpBasedSecretsWatchChangeDetectorTests {
|
||||
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 @@ class HttpBasedSecretsWatchChangeDetectorTests {
|
||||
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 @@ class HttpBasedSecretsWatchChangeDetectorTests {
|
||||
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")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, String> 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<String, String> res = WatcherUtil.labels(secret);
|
||||
Assertions.assertEquals(res.size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appsNoMetadata() {
|
||||
V1Secret secret = new V1SecretBuilder().build();
|
||||
Set<String> 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<String> 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<String> 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<String> 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<String> 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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,5 +119,7 @@
|
||||
<module>spring-cloud-kubernetes-core-k8s-client-it</module>
|
||||
<module>spring-cloud-kubernetes-client-secrets-event-reload</module>
|
||||
<module>spring-cloud-kubernetes-client-configmap-event-reload</module>
|
||||
<module>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</module>
|
||||
<module>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a</module>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b</module>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-configmap-app-a</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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<RefreshRemoteApplicationEvent> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-configmap-app-b</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -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<RefreshRemoteApplicationEvent> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-configmap-test-app</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
--------------------------------------------------- zookeeper ----------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
----------------------------------------------------- kafka ------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
----------------------------------------------------- app-a ------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
--------------------------------------------------- app-b --------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
------------------------------------------------ config-watcher --------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
------------------------------------------------ common ----------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a</module>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b</module>
|
||||
<module>spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-secrets-app-a</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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<RefreshRemoteApplicationEvent> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-secrets-app-b</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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<RefreshRemoteApplicationEvent> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-configuration-watcher-secrets-test-app</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
--------------------------------------------------- rabbitmq -----------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
----------------------------------------------------- app-a ------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
--------------------------------------------------- app-b --------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
------------------------------------------------ config-watcher --------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
------------------------------------------------ common ----------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------
|
||||
</pre>
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -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=
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user