Merge remote-tracking branch 'upstream/master' into load-balancer
This commit is contained in:
@@ -14,12 +14,11 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
version: 2
|
||||
orbs:
|
||||
kube-orb: circleci/kubernetes@0.11.0
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages
|
||||
machine: true
|
||||
environment:
|
||||
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
|
||||
@@ -34,7 +33,7 @@ jobs:
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
./mvnw -s .settings.xml -U --fail-never dependency:go-offline -Dservice.occurence=${_SERVICE_OCCURENCE} || true
|
||||
./mvnw -U --fail-never dependency:go-offline -Dservice.occurence=${_SERVICE_OCCURENCE} || true
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
@@ -42,7 +41,7 @@ jobs:
|
||||
- run:
|
||||
name: Run regular tests
|
||||
command: |
|
||||
./mvnw -s .settings.xml clean install -Dservice.occurence=${_SERVICE_OCCURENCE} #org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
./mvnw clean install -Dservice.occurence=${_SERVICE_OCCURENCE} #org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
mkdir -p $HOME/artifacts/junit/
|
||||
find . -type f -regex ".*/spring-cloud-*.*/target/*.*" -exec cp {} $HOME/artifacts/ \;
|
||||
find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $HOME/artifacts/junit/ \;
|
||||
@@ -52,6 +51,12 @@ jobs:
|
||||
command: |
|
||||
sudo apt update
|
||||
sudo apt install snapd
|
||||
- kube-orb/install-kubectl
|
||||
- run:
|
||||
name: Run Kind Integration Tests
|
||||
command: |
|
||||
cd spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it
|
||||
./run.sh
|
||||
- run:
|
||||
name: Launch Kubernetes with microk8s
|
||||
command: |
|
||||
|
||||
185
README.adoc
185
README.adoc
@@ -625,6 +625,10 @@ https://github.com/fabric8-quickstarts/spring-boot-camel-config[spring-boot-came
|
||||
|
||||
=== `PropertySource` Reload
|
||||
|
||||
WARNING: This functionality has been deprecated in the 2020.0 release. Please see
|
||||
the <<spring-cloud-kubernetes-configuration-watcher>> controller for an alternative way
|
||||
to achieve the same functionality.
|
||||
|
||||
Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
|
||||
The reload feature of Spring Cloud Kubernetes is able to trigger an application reload when a related `ConfigMap` or
|
||||
`Secret` changes.
|
||||
@@ -862,6 +866,187 @@ In Kubernetes service registration is controlled by the platform, the applicatio
|
||||
registration as it may do in other platforms. For this reason using `spring.cloud.service-registry.auto-registration.enabled`
|
||||
or setting `@EnableDiscoveryClient(autoRegister=false)` will have no effect in Spring Cloud Kubernetes.
|
||||
|
||||
[#spring-cloud-kubernetes-configuration-watcher]
|
||||
## Spring Cloud Kubernetes Configuration Watcher
|
||||
|
||||
Kubernetes provides the ability to https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume[mount a ConfigMap or Secret as a volume]
|
||||
in the container of your application. When the contents of the ConfigMap or Secret changes, the https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically[mounted volume will be updated with those changes].
|
||||
|
||||
However, Spring Boot will not automatically update those changes unless you restart the application. Spring Cloud
|
||||
provides the ability refresh the application context without restarting the application by either hitting the
|
||||
actuator endpoint `/refresh` or via publishing a `RefreshRemoteApplicationEvent` using Spring Cloud Bus.
|
||||
|
||||
To achieve this configuration refresh of a Spring Cloud app running on Kubernetes, you can deploy the Spring Cloud
|
||||
Kubernetes Configuration Watcher controller into your Kubernetes cluster.
|
||||
|
||||
The application is published as a container and is available on https://hub.docker.com/repository/docker/springcloud/spring-cloud-kubernetes-configuration-watcher[Docker Hub].
|
||||
|
||||
Spring Cloud Kubernetes Configuration Watcher can send refresh notifications to applications in two ways.
|
||||
|
||||
1. Over HTTP in which case the application being notified must of the `/refresh` actuator endpoint exposed and accessible from within the cluster
|
||||
2. Using Spring Cloud Bus, in which case you will need a message broker deployed to your custer for the application to use.
|
||||
|
||||
### Deployment YAML
|
||||
|
||||
Below is a sample deployment YAML you can use to deploy the Kubernetes Configuration Watcher to Kubernetes.
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- 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
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- 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:
|
||||
serviceAccount: spring-cloud-kubernetes-configuration-watcher
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configuration-watcher
|
||||
image: springcloud/spring-cloud-kubernetes-configuration-watcher:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
The Service Account and associated Role Binding is important for Spring Cloud Kubernetes Configuration to work properly.
|
||||
The controller needs access to read data about ConfigMaps, Pods, Services, Endpoints and Secrets in the Kubernetes cluster.
|
||||
|
||||
### Monitoring ConfigMaps and Secrets
|
||||
|
||||
Spring Cloud Kubernetes Configuration Watcher will react to changes in ConfigMaps with a label of `spring.cloud.kubernetes.config` with the value `true`
|
||||
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.
|
||||
|
||||
### HTTP Implementation
|
||||
|
||||
The HTTP implementation is what is used by default. When this implementation is used Spring Cloud Kubernetes Configuration Watcher and a
|
||||
change to a ConfigMap or Secret occurs then the HTTP implementation will use the Spring Cloud Kubernetes Discovery Client to fetch all
|
||||
instances of the application which match the name of the ConfigMap or Secret and send an HTTP POST request to the application's actuator
|
||||
`/refresh` endpoint. By default it will send the post request to `/actuator/refresh` using the port registered in the discovery client.
|
||||
|
||||
#### Non-Default Management Port and Actuator Path
|
||||
|
||||
If the application is using a non-default actuator path and/or using a different port for the management endpoints, the Kubernetes service for the application
|
||||
can add an annotation called `boot.spring.io/actuator` and set its value to the path and port used by the application. For example
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: config-map-demo
|
||||
name: config-map-demo
|
||||
annotations:
|
||||
boot.spring.io/actuator: http://:9090/myactuator/home
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: config-map-demo
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
Another way you can choose to configure the actuator path and/or management port is by setting
|
||||
`spring.cloud.kubernetes.configuration.watcher.actuatorPath` and `spring.cloud.kubernetes.configuration.watcher.actuatorPort`.
|
||||
|
||||
### Messaging Implementation
|
||||
|
||||
The messaging implementation can be enabled by setting the profile `bus` when the Spring Cloud Kubernetes Configuration Watcher
|
||||
application is deployed to Kubernetes.
|
||||
|
||||
WARNING: Currently the only supported message broker is RabbitMQ
|
||||
|
||||
### Configuring RabbitMQ
|
||||
|
||||
When the `bus` profile is enabled you will need to configure Spring RabbitMQ to point it to the location of the RabbitMQ
|
||||
instance you would like to use as well as any credentials necessary to authenticate. This can be done
|
||||
by setting the standard Spring RabbitMQ properties, for example
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
rabbitmq:
|
||||
username: user
|
||||
password: password
|
||||
host: rabbitmq
|
||||
----
|
||||
====
|
||||
|
||||
== Examples
|
||||
|
||||
Spring Cloud Kubernetes tries to make it transparent for your applications to consume Kubernetes Native Services by
|
||||
|
||||
@@ -466,6 +466,10 @@ https://github.com/fabric8-quickstarts/spring-boot-camel-config[spring-boot-came
|
||||
|
||||
=== `PropertySource` Reload
|
||||
|
||||
WARNING: This functionality has been deprecated in the 2020.0 release. Please see
|
||||
the <<spring-cloud-kubernetes-configuration-watcher>> controller for an alternative way
|
||||
to achieve the same functionality.
|
||||
|
||||
Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
|
||||
The reload feature of Spring Cloud Kubernetes is able to trigger an application reload when a related `ConfigMap` or
|
||||
`Secret` changes.
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
[#spring-cloud-kubernetes-configuration-watcher]
|
||||
## Spring Cloud Kubernetes Configuration Watcher
|
||||
|
||||
Kubernetes provides the ability to https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume[mount a ConfigMap or Secret as a volume]
|
||||
in the container of your application. When the contents of the ConfigMap or Secret changes, the https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically[mounted volume will be updated with those changes].
|
||||
|
||||
However, Spring Boot will not automatically update those changes unless you restart the application. Spring Cloud
|
||||
provides the ability refresh the application context without restarting the application by either hitting the
|
||||
actuator endpoint `/refresh` or via publishing a `RefreshRemoteApplicationEvent` using Spring Cloud Bus.
|
||||
|
||||
To achieve this configuration refresh of a Spring Cloud app running on Kubernetes, you can deploy the Spring Cloud
|
||||
Kubernetes Configuration Watcher controller into your Kubernetes cluster.
|
||||
|
||||
The application is published as a container and is available on https://hub.docker.com/repository/docker/springcloud/spring-cloud-kubernetes-configuration-watcher[Docker Hub].
|
||||
|
||||
Spring Cloud Kubernetes Configuration Watcher can send refresh notifications to applications in two ways.
|
||||
|
||||
1. Over HTTP in which case the application being notified must of the `/refresh` actuator endpoint exposed and accessible from within the cluster
|
||||
2. Using Spring Cloud Bus, in which case you will need a message broker deployed to your custer for the application to use.
|
||||
|
||||
### Deployment YAML
|
||||
|
||||
Below is a sample deployment YAML you can use to deploy the Kubernetes Configuration Watcher to Kubernetes.
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- 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
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- 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:
|
||||
serviceAccount: spring-cloud-kubernetes-configuration-watcher
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configuration-watcher
|
||||
image: springcloud/spring-cloud-kubernetes-configuration-watcher:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
The Service Account and associated Role Binding is important for Spring Cloud Kubernetes Configuration to work properly.
|
||||
The controller needs access to read data about ConfigMaps, Pods, Services, Endpoints and Secrets in the Kubernetes cluster.
|
||||
|
||||
### Monitoring ConfigMaps and Secrets
|
||||
|
||||
Spring Cloud Kubernetes Configuration Watcher will react to changes in ConfigMaps with a label of `spring.cloud.kubernetes.config` with the value `true`
|
||||
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.
|
||||
|
||||
### HTTP Implementation
|
||||
|
||||
The HTTP implementation is what is used by default. When this implementation is used Spring Cloud Kubernetes Configuration Watcher and a
|
||||
change to a ConfigMap or Secret occurs then the HTTP implementation will use the Spring Cloud Kubernetes Discovery Client to fetch all
|
||||
instances of the application which match the name of the ConfigMap or Secret and send an HTTP POST request to the application's actuator
|
||||
`/refresh` endpoint. By default it will send the post request to `/actuator/refresh` using the port registered in the discovery client.
|
||||
|
||||
#### Non-Default Management Port and Actuator Path
|
||||
|
||||
If the application is using a non-default actuator path and/or using a different port for the management endpoints, the Kubernetes service for the application
|
||||
can add an annotation called `boot.spring.io/actuator` and set its value to the path and port used by the application. For example
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: config-map-demo
|
||||
name: config-map-demo
|
||||
annotations:
|
||||
boot.spring.io/actuator: http://:9090/myactuator/home
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: config-map-demo
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
Another way you can choose to configure the actuator path and/or management port is by setting
|
||||
`spring.cloud.kubernetes.configuration.watcher.actuatorPath` and `spring.cloud.kubernetes.configuration.watcher.actuatorPort`.
|
||||
|
||||
### Messaging Implementation
|
||||
|
||||
The messaging implementation can be enabled by setting the profile `bus` when the Spring Cloud Kubernetes Configuration Watcher
|
||||
application is deployed to Kubernetes.
|
||||
|
||||
WARNING: Currently the only supported message broker is RabbitMQ
|
||||
|
||||
### Configuring RabbitMQ
|
||||
|
||||
When the `bus` profile is enabled you will need to configure Spring RabbitMQ to point it to the location of the RabbitMQ
|
||||
instance you would like to use as well as any credentials necessary to authenticate. This can be done
|
||||
by setting the standard Spring RabbitMQ properties, for example
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
rabbitmq:
|
||||
username: user
|
||||
password: password
|
||||
host: rabbitmq
|
||||
----
|
||||
====
|
||||
@@ -26,6 +26,8 @@ include::security-service-accounts.adoc[]
|
||||
|
||||
include::service-registry.adoc[]
|
||||
|
||||
include::spring-cloud-kubernetes-configuration-watcher.adoc[]
|
||||
|
||||
include::examples.adoc[]
|
||||
|
||||
include::other-resources.adoc[]
|
||||
|
||||
23
pom.xml
23
pom.xml
@@ -65,12 +65,14 @@
|
||||
<!-- Dependency Versions -->
|
||||
<spring-cloud-commons.version>3.0.0-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-config.version>3.0.0-SNAPSHOT</spring-cloud-config.version>
|
||||
<spring-cloud-bus.version>3.0.0-SNAPSHOT</spring-cloud-bus.version>
|
||||
<spring-cloud-contract.version>3.0.0-SNAPSHOT</spring-cloud-contract.version>
|
||||
|
||||
<!-- Maven Plugin Versions -->
|
||||
<maven-compiler-plugin.version>3.5</maven-compiler-plugin.version>
|
||||
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
|
||||
<maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version>
|
||||
<maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version>
|
||||
<maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version>
|
||||
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
||||
<fabric8.maven.plugin.version>4.4.0</fabric8.maven.plugin.version>
|
||||
<groovy.version>2.4.12</groovy.version>
|
||||
<restassured.version>3.0.2</restassured.version>
|
||||
@@ -94,6 +96,7 @@
|
||||
<module>spring-cloud-kubernetes-examples</module>
|
||||
<module>spring-cloud-kubernetes-leader</module>
|
||||
<module>spring-cloud-kubernetes-istio</module>
|
||||
<module>spring-cloud-kubernetes-controllers</module>
|
||||
<module>spring-cloud-kubernetes-integration-tests</module>
|
||||
<module>docs</module>
|
||||
<module>spring-cloud-kubernetes-loadbalancer</module>
|
||||
@@ -127,6 +130,22 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-bus-dependencies</artifactId>
|
||||
<version>${spring-cloud-bus.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-dependencies</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
|
||||
@@ -54,7 +54,12 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.config.reload;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(value = "spring.cloud.kubernetes.enabled", matchIfMissing = true)
|
||||
@ConditionalOnMissingBean(ConfigReloadAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(ConfigReloadProperties.class)
|
||||
public class ConfigReloadDefaultAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Configuration reload must be enabled explicitly.
|
||||
*/
|
||||
@ConditionalOnProperty("spring.cloud.kubernetes.reload.enabled")
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
protected static class ConfigReloadAutoConfigurationBeans {
|
||||
|
||||
@Autowired
|
||||
private AbstractEnvironment environment;
|
||||
|
||||
@Autowired
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
@Autowired
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
@Autowired
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
/**
|
||||
* @param properties config reload properties
|
||||
* @param strategy configuration update strategy
|
||||
* @return a bean that listen to configuration changes and fire a reload.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConfigurationChangeDetector propertyChangeWatcher(
|
||||
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy) {
|
||||
switch (properties.getMode()) {
|
||||
case POLLING:
|
||||
return new PollingConfigurationChangeDetector(this.environment,
|
||||
properties, this.kubernetesClient, strategy,
|
||||
this.configMapPropertySourceLocator,
|
||||
this.secretsPropertySourceLocator);
|
||||
case EVENT:
|
||||
return new EventBasedConfigurationChangeDetector(this.environment,
|
||||
properties, this.kubernetesClient, strategy,
|
||||
this.configMapPropertySourceLocator,
|
||||
this.secretsPropertySourceLocator);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unsupported configuration reload mode: " + properties.getMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties config reload properties
|
||||
* @param ctx application context
|
||||
* @param restarter restart endpoint
|
||||
* @param refresher context refresher
|
||||
* @return provides the action to execute when the configuration changes.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ConfigurationUpdateStrategy configurationUpdateStrategy(
|
||||
ConfigReloadProperties properties, ConfigurableApplicationContext ctx) {
|
||||
switch (properties.getStrategy()) {
|
||||
case SHUTDOWN:
|
||||
return new ConfigurationUpdateStrategy(properties.getStrategy().name(),
|
||||
() -> {
|
||||
wait(properties);
|
||||
ctx.close();
|
||||
});
|
||||
}
|
||||
throw new IllegalStateException("Unsupported configuration update strategy: "
|
||||
+ properties.getStrategy());
|
||||
}
|
||||
|
||||
private static void wait(ConfigReloadProperties properties) {
|
||||
final long waitMillis = ThreadLocalRandom.current()
|
||||
.nextLong(properties.getMaxWaitForRestart().toMillis());
|
||||
try {
|
||||
Thread.sleep(waitMillis);
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,8 +99,9 @@ public abstract class ConfigurationChangeDetector {
|
||||
List<? extends MapPropertySource> l2) {
|
||||
|
||||
if (l1.size() != l2.size()) {
|
||||
this.log.warn("The current number of Confimap PropertySources does not match "
|
||||
+ "the ones loaded from the Kubernetes - No reload will take place");
|
||||
this.log.warn(
|
||||
"The current number of ConfigMap PropertySources does not match "
|
||||
+ "the ones loaded from the Kubernetes - No reload will take place");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,10 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
|
||||
@Override
|
||||
public void eventReceived(Action action,
|
||||
ConfigMap configMap) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(name + " received event for ConfigMap "
|
||||
+ configMap.getMetadata().getName());
|
||||
}
|
||||
onEvent(configMap);
|
||||
}
|
||||
|
||||
@@ -99,6 +103,10 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
|
||||
this.kubernetesClient.secrets().watch(new Watcher<Secret>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, Secret secret) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(name + " received and event for Secret "
|
||||
+ secret.getMetadata().getName());
|
||||
}
|
||||
onEvent(secret);
|
||||
}
|
||||
|
||||
@@ -138,7 +146,7 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
|
||||
}
|
||||
}
|
||||
|
||||
private void onEvent(ConfigMap configMap) {
|
||||
protected void onEvent(ConfigMap configMap) {
|
||||
boolean changed = changed(
|
||||
locateMapPropertySources(this.configMapPropertySourceLocator,
|
||||
this.environment),
|
||||
@@ -149,7 +157,7 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
|
||||
}
|
||||
}
|
||||
|
||||
private void onEvent(Secret secret) {
|
||||
protected void onEvent(Secret secret) {
|
||||
boolean changed = changed(
|
||||
locateMapPropertySources(this.secretsPropertySourceLocator,
|
||||
this.environment),
|
||||
|
||||
19
spring-cloud-kubernetes-controllers/pom.xml
Normal file
19
spring-cloud-kubernetes-controllers/pom.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-controllers</artifactId>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-kubernetes-configuration-watcher</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
application.yaml: |-
|
||||
spring:
|
||||
rabbitmq:
|
||||
username: rbaxter
|
||||
password: password
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework:
|
||||
cloud:
|
||||
kubernetes: TRACE
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- 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
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-configuration-watcher:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- 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:
|
||||
serviceAccount: spring-cloud-kubernetes-configuration-watcher
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configuration-watcher
|
||||
image: springcloud/spring-cloud-kubernetes-configuration-watcher:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
@@ -0,0 +1,173 @@
|
||||
<?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-controllers</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-configuration-watcher</artifactId>
|
||||
|
||||
<properties>
|
||||
<jib.version>1.8.0</jib.version>
|
||||
<base.image>openjdk:8u222-slim</base.image>
|
||||
<docker.registry.organization>springcloud</docker.registry.organization>
|
||||
<plexus-archiver.version>4.1.0</plexus-archiver.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-kubernetes</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-kubernetes-all</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>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<image>
|
||||
<name>${env.IMAGE}</name>
|
||||
</image>
|
||||
<goal>build-image</goal>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dockerpush</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.spotify</groupId>
|
||||
<artifactId>dockerfile-maven-plugin</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<configuration>
|
||||
<repository>${docker.registry.organization}/${artifactId}</repository>
|
||||
<tag>${project.version}</tag>
|
||||
<username>${env.DOCKER_HUB_USERNAME}</username>
|
||||
<password>${env.DOCKER_HUB_PASSWORD}</password>
|
||||
<build>
|
||||
<noCache>true</noCache>
|
||||
</build>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-archiver</artifactId>
|
||||
<version>${plexus-archiver.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>imagename</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!env.IMAGE</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jib</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>${jib.version}</version>
|
||||
<configuration>
|
||||
<from>
|
||||
<image>${base.image}</image>
|
||||
</from>
|
||||
<to>
|
||||
<image>spring-cloud/${project.artifactId}</image>
|
||||
</to>
|
||||
<container>
|
||||
<user>nobody:nogroup</user>
|
||||
<environment>
|
||||
</environment>
|
||||
</container>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>dockerBuild</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: skaffold/v2alpha3
|
||||
kind: Config
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
build:
|
||||
artifacts:
|
||||
- image: springcloud/spring-cloud-kubernetes-configuration-watcher
|
||||
custom:
|
||||
buildCommand: "../../mvnw clean install"
|
||||
dependencies:
|
||||
paths:
|
||||
- src
|
||||
- pom.xml
|
||||
# jib: {
|
||||
# args: ["-Pjib"]
|
||||
# }
|
||||
deploy:
|
||||
kubectl:
|
||||
manifests:
|
||||
- k8s/deployment.yaml
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class BusEventBasedConfigurationWatcherChangeDetector extends
|
||||
ConfigurationWatcherChangeDetector implements ApplicationEventPublisherAware {
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private BusProperties busProperties;
|
||||
|
||||
public BusEventBasedConfigurationWatcherChangeDetector(
|
||||
AbstractEnvironment environment, ConfigReloadProperties properties,
|
||||
KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
BusProperties busProperties,
|
||||
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
|
||||
ThreadPoolTaskExecutor threadPoolTaskExecutor) {
|
||||
super(environment, properties, kubernetesClient, strategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator,
|
||||
k8SConfigurationProperties, threadPoolTaskExecutor);
|
||||
|
||||
this.busProperties = busProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> triggerRefresh(Secret secret) {
|
||||
this.applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(
|
||||
secret, busProperties.getId(), secret.getMetadata().getName()));
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> triggerRefresh(ConfigMap configMap) {
|
||||
this.applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(
|
||||
configMap, busProperties.getId(), configMap.getMetadata().getName()));
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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 org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@SpringBootApplication(exclude = { ContextFunctionCatalogAutoConfiguration.class,
|
||||
RabbitHealthContributorAutoConfiguration.class })
|
||||
public class ConfigurationWatcherApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(ConfigurationWatcherApplication.class).run(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.cloud.kubernetes.discovery.reactive.KubernetesReactiveDiscoveryClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties({ ConfigurationWatcherConfigurationProperties.class })
|
||||
public class ConfigurationWatcherAutoConfiguration {
|
||||
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebClient webClient(WebClient.Builder webClientBuilder) {
|
||||
return webClientBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConfigurationWatcherChangeDetector.class)
|
||||
public ConfigurationWatcherChangeDetector httpBasedConfigurationWatchChangeDetector(
|
||||
AbstractEnvironment environment, KubernetesClient kubernetesClient,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
|
||||
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
|
||||
ThreadPoolTaskExecutor threadFactory, WebClient webClient,
|
||||
KubernetesReactiveDiscoveryClient kubernetesReactiveDiscoveryClient) {
|
||||
return new HttpBasedConfigurationWatchChangeDetector(environment, properties,
|
||||
kubernetesClient, strategy, configMapPropertySourceLocator,
|
||||
secretsPropertySourceLocator, k8SConfigurationProperties, threadFactory,
|
||||
webClient, kubernetesReactiveDiscoveryClient);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Profile("bus")
|
||||
@Import({ ContextFunctionCatalogAutoConfiguration.class,
|
||||
RabbitHealthContributorAutoConfiguration.class })
|
||||
static class BusConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConfigurationWatcherChangeDetector.class)
|
||||
public ConfigurationWatcherChangeDetector busPropertyChangeWatcher(
|
||||
BusProperties busProperties, AbstractEnvironment environment,
|
||||
KubernetesClient kubernetesClient,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
|
||||
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
|
||||
ThreadPoolTaskExecutor threadFactory) {
|
||||
return new BusEventBasedConfigurationWatcherChangeDetector(environment,
|
||||
properties, kubernetesClient, strategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator,
|
||||
busProperties, k8SConfigurationProperties, threadFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.cloud.kubernetes.config.reload.EventBasedConfigurationChangeDetector;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class ConfigurationWatcherChangeDetector
|
||||
extends EventBasedConfigurationChangeDetector {
|
||||
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
protected ConfigurationWatcherConfigurationProperties k8SConfigurationProperties;
|
||||
|
||||
public ConfigurationWatcherChangeDetector(AbstractEnvironment environment,
|
||||
ConfigReloadProperties properties, KubernetesClient kubernetesClient,
|
||||
ConfigurationUpdateStrategy strategy,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
|
||||
ThreadPoolTaskExecutor threadPoolTaskExecutor) {
|
||||
super(environment, properties, kubernetesClient, strategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator);
|
||||
this.executorService = Executors.newScheduledThreadPool(
|
||||
k8SConfigurationProperties.getThreadPoolSize(), threadPoolTaskExecutor);
|
||||
this.k8SConfigurationProperties = k8SConfigurationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvent(ConfigMap configMap) {
|
||||
if (isSpringCloudKubernetesConfig(configMap)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Scheduling remote refresh event to be published for ConfigMap "
|
||||
+ configMap.getMetadata().getName() + " to be published in "
|
||||
+ k8SConfigurationProperties.getRefreshDelay().toMillis()
|
||||
+ " milliseconds");
|
||||
}
|
||||
executorService.schedule(() -> triggerRefresh(configMap).subscribe(),
|
||||
k8SConfigurationProperties.getRefreshDelay().toMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Not publishing event. ConfigMap "
|
||||
+ configMap.getMetadata().getName()
|
||||
+ " does not contain the label "
|
||||
+ k8SConfigurationProperties.getConfigLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSpringCloudKubernetesConfig(ConfigMap configMap) {
|
||||
if (configMap.getMetadata() == null
|
||||
|| configMap.getMetadata().getLabels() == null) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.parseBoolean(configMap.getMetadata().getLabels()
|
||||
.getOrDefault(k8SConfigurationProperties.getConfigLabel(), "false"));
|
||||
}
|
||||
|
||||
protected boolean isSpringCloudKubernetesSecret(Secret secret) {
|
||||
if (secret.getMetadata() == null || secret.getMetadata().getLabels() == null) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.parseBoolean(secret.getMetadata().getLabels()
|
||||
.getOrDefault(k8SConfigurationProperties.getSecretLabel(), "false"));
|
||||
}
|
||||
|
||||
protected abstract Mono<Void> triggerRefresh(Secret secret);
|
||||
|
||||
protected abstract Mono<Void> triggerRefresh(ConfigMap configMap);
|
||||
|
||||
@Override
|
||||
protected void onEvent(Secret secret) {
|
||||
if (isSpringCloudKubernetesSecret(secret)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Scheduling remote refresh event to be published for Secret "
|
||||
+ secret.getMetadata().getName() + " to be published in "
|
||||
+ k8SConfigurationProperties.getRefreshDelay().toMillis()
|
||||
+ " milliseconds");
|
||||
}
|
||||
executorService.schedule(() -> triggerRefresh(secret).subscribe(),
|
||||
k8SConfigurationProperties.getRefreshDelay().toMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Not publishing event. Secret " + secret.getMetadata().getName()
|
||||
+ " does not contain the label "
|
||||
+ k8SConfigurationProperties.getSecretLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.convert.DurationUnit;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.configuration.watcher")
|
||||
public class ConfigurationWatcherConfigurationProperties {
|
||||
|
||||
/**
|
||||
* Amount of time to delay the posting of the event to allow the app volume to update
|
||||
* data.
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.MILLIS)
|
||||
private Duration refreshDelay = Duration.ofMillis(120000);
|
||||
|
||||
private int threadPoolSize = 1;
|
||||
|
||||
private String configLabel = "spring.cloud.kubernetes.config";
|
||||
|
||||
private String secretLabel = "spring.cloud.kubernetes.secret";
|
||||
|
||||
private String actuatorPath = "/actuator";
|
||||
|
||||
private Integer actuatorPort = -1;
|
||||
|
||||
public String getActuatorPath() {
|
||||
return actuatorPath;
|
||||
}
|
||||
|
||||
public void setActuatorPath(String actuatorPath) {
|
||||
String normalizedPath = actuatorPath;
|
||||
if (!normalizedPath.startsWith("/")) {
|
||||
normalizedPath = "/" + normalizedPath;
|
||||
}
|
||||
if (normalizedPath.endsWith("/")) {
|
||||
normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
|
||||
}
|
||||
this.actuatorPath = normalizedPath;
|
||||
}
|
||||
|
||||
public Integer getActuatorPort() {
|
||||
return actuatorPort;
|
||||
}
|
||||
|
||||
public void setActuatorPort(Integer actuatorPort) {
|
||||
this.actuatorPort = actuatorPort;
|
||||
}
|
||||
|
||||
public String getSecretLabel() {
|
||||
return secretLabel;
|
||||
}
|
||||
|
||||
public void setSecretLabel(String secretLabel) {
|
||||
this.secretLabel = secretLabel;
|
||||
}
|
||||
|
||||
public String getConfigLabel() {
|
||||
return configLabel;
|
||||
}
|
||||
|
||||
public void setConfigLabel(String configLabel) {
|
||||
this.configLabel = configLabel;
|
||||
}
|
||||
|
||||
public Duration getRefreshDelay() {
|
||||
return refreshDelay;
|
||||
}
|
||||
|
||||
public void setRefreshDelay(Duration refreshDelay) {
|
||||
this.refreshDelay = refreshDelay;
|
||||
}
|
||||
|
||||
public int getThreadPoolSize() {
|
||||
return threadPoolSize;
|
||||
}
|
||||
|
||||
public void setThreadPoolSize(int threadPoolSize) {
|
||||
this.threadPoolSize = threadPoolSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.net.URI;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.cloud.kubernetes.discovery.reactive.KubernetesReactiveDiscoveryClient;
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class HttpBasedConfigurationWatchChangeDetector
|
||||
extends ConfigurationWatcherChangeDetector {
|
||||
|
||||
/**
|
||||
* Annotation key for actuator port and path.
|
||||
*/
|
||||
public static String ANNOTATION_KEY = "boot.spring.io/actuator";
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
private KubernetesReactiveDiscoveryClient kubernetesReactiveDiscoveryClient;
|
||||
|
||||
public HttpBasedConfigurationWatchChangeDetector(AbstractEnvironment environment,
|
||||
ConfigReloadProperties properties, KubernetesClient kubernetesClient,
|
||||
ConfigurationUpdateStrategy strategy,
|
||||
ConfigMapPropertySourceLocator configMapPropertySourceLocator,
|
||||
SecretsPropertySourceLocator secretsPropertySourceLocator,
|
||||
ConfigurationWatcherConfigurationProperties k8SConfigurationProperties,
|
||||
ThreadPoolTaskExecutor threadPoolTaskExecutor, WebClient webClient,
|
||||
KubernetesReactiveDiscoveryClient k8sReactiveDiscoveryClient) {
|
||||
super(environment, properties, kubernetesClient, strategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator,
|
||||
k8SConfigurationProperties, threadPoolTaskExecutor);
|
||||
this.webClient = webClient;
|
||||
this.kubernetesReactiveDiscoveryClient = k8sReactiveDiscoveryClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> triggerRefresh(Secret secret) {
|
||||
return refresh(secret.getMetadata()).then();
|
||||
}
|
||||
|
||||
private void setActuatorUriFromAnnotation(UriComponentsBuilder actuatorUriBuilder,
|
||||
String metadataUri) {
|
||||
URI annotationUri = URI.create(metadataUri);
|
||||
actuatorUriBuilder.path(annotationUri.getPath() + "/refresh");
|
||||
|
||||
// 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
|
||||
if (annotationUri.getPort() < 0) {
|
||||
if (annotationUri.getAuthority() != null) {
|
||||
actuatorUriBuilder
|
||||
.port(annotationUri.getAuthority().replaceFirst(":", ""));
|
||||
}
|
||||
}
|
||||
else {
|
||||
actuatorUriBuilder.port(annotationUri.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
private URI getActuatorUri(ServiceInstance si) {
|
||||
String metadataUri = si.getMetadata().getOrDefault(ANNOTATION_KEY, "");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Metadata actuator uri is: " + metadataUri);
|
||||
}
|
||||
|
||||
UriComponentsBuilder actuatorUriBuilder = UriComponentsBuilder.newInstance()
|
||||
.scheme(si.getScheme()).host(si.getHost());
|
||||
|
||||
if (!StringUtils.isEmpty(metadataUri)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found actuator URI in service instance metadata");
|
||||
}
|
||||
setActuatorUriFromAnnotation(actuatorUriBuilder, metadataUri);
|
||||
}
|
||||
else {
|
||||
Integer port = k8SConfigurationProperties.getActuatorPort() < 0 ? si.getPort()
|
||||
: k8SConfigurationProperties.getActuatorPort();
|
||||
actuatorUriBuilder = actuatorUriBuilder
|
||||
.path(k8SConfigurationProperties.getActuatorPath() + "/refresh")
|
||||
.port(port);
|
||||
}
|
||||
|
||||
return actuatorUriBuilder.build().toUri();
|
||||
}
|
||||
|
||||
protected Flux<ResponseEntity<Void>> refresh(ObjectMeta objectMeta) {
|
||||
|
||||
return kubernetesReactiveDiscoveryClient.getInstances(objectMeta.getName())
|
||||
.flatMap(si -> {
|
||||
URI actuatorUri = getActuatorUri(si);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Sending refresh request for " + objectMeta.getName()
|
||||
+ " to URI " + actuatorUri.toString());
|
||||
}
|
||||
Mono<ResponseEntity<Void>> response = webClient.post()
|
||||
.uri(actuatorUri).retrieve().toBodilessEntity()
|
||||
.doOnSuccess(re -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Refresh sent to " + objectMeta.getName()
|
||||
+ " at URI address " + actuatorUri
|
||||
+ " returned a "
|
||||
+ re.getStatusCode().toString());
|
||||
}
|
||||
}).doOnError(t -> {
|
||||
log.warn("Refresh sent to " + objectMeta.getName()
|
||||
+ " failed", t);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> triggerRefresh(ConfigMap configMap) {
|
||||
return refresh(configMap.getMetadata()).then();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.kubernetes.configuration.watcher.ConfigurationWatcherAutoConfiguration
|
||||
@@ -0,0 +1,19 @@
|
||||
spring:
|
||||
application:
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
cloud:
|
||||
kubernetes:
|
||||
reload:
|
||||
enabled: true
|
||||
monitoring-secrets: true
|
||||
strategy: shutdown
|
||||
bus:
|
||||
enabled: false
|
||||
server:
|
||||
port: 8888
|
||||
---
|
||||
spring:
|
||||
profiles: bus
|
||||
cloud:
|
||||
bus:
|
||||
enabled: true
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BusEventBasedConfigurationWatcherChangeDetectorTests {
|
||||
|
||||
@Mock
|
||||
private KubernetesClient client;
|
||||
|
||||
@Mock
|
||||
private ConfigurationUpdateStrategy updateStrategy;
|
||||
|
||||
@Mock
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
@Mock
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
@Mock
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
||||
@Mock
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private BusEventBasedConfigurationWatcherChangeDetector changeDetector;
|
||||
|
||||
private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties;
|
||||
|
||||
private BusProperties busProperties;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockEnvironment mockEnvironment = new MockEnvironment();
|
||||
ConfigReloadProperties configReloadProperties = new ConfigReloadProperties();
|
||||
configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
|
||||
busProperties = new BusProperties();
|
||||
changeDetector = new BusEventBasedConfigurationWatcherChangeDetector(
|
||||
mockEnvironment, configReloadProperties, client, updateStrategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator,
|
||||
busProperties, configurationWatcherConfigurationProperties,
|
||||
threadPoolTaskExecutor);
|
||||
changeDetector.setApplicationEventPublisher(applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerRefreshWithConfigMap() {
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
configMap.setMetadata(objectMeta);
|
||||
changeDetector.triggerRefresh(configMap);
|
||||
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
|
||||
.forClass(RefreshRemoteApplicationEvent.class);
|
||||
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
|
||||
assertThat(argumentCaptor.getValue().getSource()).isEqualTo(configMap);
|
||||
assertThat(argumentCaptor.getValue().getOriginService())
|
||||
.isEqualTo(busProperties.getId());
|
||||
assertThat(argumentCaptor.getValue().getDestinationService()).isEqualTo("foo:**");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerRefreshWithSecret() {
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
Secret secret = new Secret();
|
||||
secret.setMetadata(objectMeta);
|
||||
changeDetector.triggerRefresh(secret);
|
||||
ArgumentCaptor<RefreshRemoteApplicationEvent> argumentCaptor = ArgumentCaptor
|
||||
.forClass(RefreshRemoteApplicationEvent.class);
|
||||
verify(applicationEventPublisher).publishEvent(argumentCaptor.capture());
|
||||
assertThat(argumentCaptor.getValue().getSource()).isEqualTo(secret);
|
||||
assertThat(argumentCaptor.getValue().getOriginService())
|
||||
.isEqualTo(busProperties.getId());
|
||||
assertThat(argumentCaptor.getValue().getDestinationService()).isEqualTo("foo:**");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ConfigurationWatcherConfigurationPropertiesTests {
|
||||
|
||||
@Test
|
||||
public void setActuatorPath() {
|
||||
ConfigurationWatcherConfigurationProperties properties = new ConfigurationWatcherConfigurationProperties();
|
||||
properties.setActuatorPath("foo");
|
||||
assertThat(properties.getActuatorPath()).isEqualTo("/foo");
|
||||
properties.setActuatorPath("/foo/bar/");
|
||||
assertThat(properties.getActuatorPath()).isEqualTo("/foo/bar");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.EndpointAddress;
|
||||
import io.fabric8.kubernetes.api.model.EndpointPort;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigReloadProperties;
|
||||
import org.springframework.cloud.kubernetes.config.reload.ConfigurationUpdateStrategy;
|
||||
import org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.discovery.reactive.KubernetesReactiveDiscoveryClient;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.cloud.kubernetes.configuration.watcher.HttpBasedConfigurationWatchChangeDetector.ANNOTATION_KEY;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HttpBasedConfigurationWatchChangeDetectorTests {
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(0);
|
||||
|
||||
@Mock
|
||||
private KubernetesClient client;
|
||||
|
||||
@Mock
|
||||
private ConfigurationUpdateStrategy updateStrategy;
|
||||
|
||||
@Mock
|
||||
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
|
||||
|
||||
@Mock
|
||||
private SecretsPropertySourceLocator secretsPropertySourceLocator;
|
||||
|
||||
@Mock
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
||||
@Mock
|
||||
private KubernetesReactiveDiscoveryClient reactiveDiscoveryClient;
|
||||
|
||||
private HttpBasedConfigurationWatchChangeDetector changeDetector;
|
||||
|
||||
private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
EndpointAddress fooEndpointAddress = new EndpointAddress();
|
||||
fooEndpointAddress.setIp("127.0.0.1");
|
||||
fooEndpointAddress.setHostname("localhost");
|
||||
EndpointPort fooEndpointPort = new EndpointPort();
|
||||
fooEndpointPort.setPort(wireMockRule.port());
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
KubernetesServiceInstance fooServiceInstance = new KubernetesServiceInstance(
|
||||
"foo", "foo", fooEndpointAddress, fooEndpointPort, new HashMap<>(),
|
||||
false);
|
||||
instances.add(fooServiceInstance);
|
||||
when(reactiveDiscoveryClient.getInstances(eq("foo")))
|
||||
.thenReturn(Flux.fromIterable(instances));
|
||||
MockEnvironment mockEnvironment = new MockEnvironment();
|
||||
ConfigReloadProperties configReloadProperties = new ConfigReloadProperties();
|
||||
configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties();
|
||||
WebClient webClient = WebClient.builder().build();
|
||||
changeDetector = new HttpBasedConfigurationWatchChangeDetector(mockEnvironment,
|
||||
configReloadProperties, client, updateStrategy,
|
||||
configMapPropertySourceLocator, secretsPropertySourceLocator,
|
||||
configurationWatcherConfigurationProperties, threadPoolTaskExecutor,
|
||||
webClient, reactiveDiscoveryClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerConfigMapRefresh() {
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
configMap.setMetadata(objectMeta);
|
||||
WireMock.configureFor("localhost", wireMockRule.port());
|
||||
stubFor(post(WireMock.urlEqualTo("/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/actuator/refresh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerSecretRefresh() throws InterruptedException {
|
||||
Secret secret = new Secret();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
secret.setMetadata(objectMeta);
|
||||
WireMock.configureFor("localhost", wireMockRule.port());
|
||||
stubFor(post(WireMock.urlEqualTo("/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/actuator/refresh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerConfigMapRefreshWithPropertiesBasedActuatorPath()
|
||||
throws InterruptedException {
|
||||
configurationWatcherConfigurationProperties
|
||||
.setActuatorPath("/my/custom/actuator");
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
configMap.setMetadata(objectMeta);
|
||||
WireMock.configureFor("localhost", wireMockRule.port());
|
||||
stubFor(post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/my/custom/actuator/refresh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerSecretRefreshWithPropertiesBasedActuatorPath()
|
||||
throws InterruptedException {
|
||||
configurationWatcherConfigurationProperties
|
||||
.setActuatorPath("/my/custom/actuator");
|
||||
Secret secret = new Secret();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
secret.setMetadata(objectMeta);
|
||||
WireMock.configureFor("localhost", wireMockRule.port());
|
||||
stubFor(post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/my/custom/actuator/refresh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerConfigMapRefreshWithAnnotationActuatorPath() {
|
||||
Map<String, String> metadata = new HashMap<>();
|
||||
metadata.put(ANNOTATION_KEY,
|
||||
"http://:" + wireMockRule.port() + "/my/custom/actuator");
|
||||
EndpointAddress fooEndpointAddress = new EndpointAddress();
|
||||
fooEndpointAddress.setIp("127.0.0.1");
|
||||
fooEndpointAddress.setHostname("localhost");
|
||||
EndpointPort fooEndpointPort = new EndpointPort();
|
||||
fooEndpointPort.setPort(wireMockRule.port());
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
KubernetesServiceInstance fooServiceInstance = new KubernetesServiceInstance(
|
||||
"foo", "foo", fooEndpointAddress, fooEndpointPort, metadata, false);
|
||||
instances.add(fooServiceInstance);
|
||||
when(reactiveDiscoveryClient.getInstances(eq("foo")))
|
||||
.thenReturn(Flux.fromIterable(instances));
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
configMap.setMetadata(objectMeta);
|
||||
stubFor(post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(configMap)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/my/custom/actuator/refresh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerSecretRefreshWithAnnotationActuatorPath() {
|
||||
Map<String, String> metadata = new HashMap<>();
|
||||
metadata.put(ANNOTATION_KEY,
|
||||
"http://:" + wireMockRule.port() + "/my/custom/actuator");
|
||||
EndpointAddress fooEndpointAddress = new EndpointAddress();
|
||||
fooEndpointAddress.setIp("127.0.0.1");
|
||||
fooEndpointAddress.setHostname("localhost");
|
||||
EndpointPort fooEndpointPort = new EndpointPort();
|
||||
fooEndpointPort.setPort(wireMockRule.port());
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
KubernetesServiceInstance fooServiceInstance = new KubernetesServiceInstance(
|
||||
"foo", "foo", fooEndpointAddress, fooEndpointPort, metadata, false);
|
||||
instances.add(fooServiceInstance);
|
||||
when(reactiveDiscoveryClient.getInstances(eq("foo")))
|
||||
.thenReturn(Flux.fromIterable(instances));
|
||||
Secret secret = new Secret();
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setName("foo");
|
||||
secret.setMetadata(objectMeta);
|
||||
stubFor(post(WireMock.urlEqualTo("/my/custom/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
StepVerifier.create(changeDetector.triggerRefresh(secret)).verifyComplete();
|
||||
verify(postRequestedFor(urlEqualTo("/my/custom/actuator/refresh")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,10 +34,10 @@
|
||||
<properties>
|
||||
<arquillian.version>1.4.0.Final</arquillian.version>
|
||||
<arquillian-cube.version>1.15.2</arquillian-cube.version>
|
||||
<kubernetes-client.version>4.9.0</kubernetes-client.version>
|
||||
<kubernetes-client.version>4.10.3</kubernetes-client.version>
|
||||
<istio-client.version>1.1.1</istio-client.version>
|
||||
<mockwebserver.version>0.1.2</mockwebserver.version>
|
||||
<okhttp.version>3.12.0</okhttp.version>
|
||||
<okhttp.version>3.14.4</okhttp.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
@@ -71,8 +71,10 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware {
|
||||
// not all pods participate in the service discovery. only those that have
|
||||
// endpoints.
|
||||
List<Endpoints> endpoints = this.properties.isAllNamespaces()
|
||||
? this.kubernetesClient.endpoints().inAnyNamespace().list().getItems()
|
||||
: this.kubernetesClient.endpoints().list().getItems();
|
||||
? this.kubernetesClient.endpoints().inAnyNamespace()
|
||||
.withLabels(properties.getServiceLabels()).list().getItems()
|
||||
: this.kubernetesClient.endpoints()
|
||||
.withLabels(properties.getServiceLabels()).list().getItems();
|
||||
List<String> endpointsPodNames = endpoints.stream().map(Endpoints::getSubsets)
|
||||
.filter(Objects::nonNull).flatMap(Collection::stream)
|
||||
.map(EndpointSubset::getAddresses).filter(Objects::nonNull)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.kubernetes.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -102,13 +101,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
Assert.notNull(serviceId,
|
||||
"[Assertion failed] - the object argument must not be null");
|
||||
|
||||
List<Endpoints> endpointsList = this.properties.isAllNamespaces()
|
||||
? this.client.endpoints().inAnyNamespace()
|
||||
.withField("metadata.name", serviceId).list().getItems()
|
||||
: Collections
|
||||
.singletonList(this.client.endpoints().withName(serviceId).get());
|
||||
|
||||
List<EndpointSubsetNS> subsetsNS = endpointsList.stream()
|
||||
List<EndpointSubsetNS> subsetsNS = this.getEndPointsList(serviceId).stream()
|
||||
.map(endpoints -> getSubsetsFromEndpoints(endpoints))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -122,6 +115,15 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
|
||||
return instances;
|
||||
}
|
||||
|
||||
public List<Endpoints> getEndPointsList(String serviceId) {
|
||||
return this.properties.isAllNamespaces()
|
||||
? this.client.endpoints().inAnyNamespace()
|
||||
.withField("metadata.name", serviceId)
|
||||
.withLabels(properties.getServiceLabels()).list().getItems()
|
||||
: this.client.endpoints().withField("metadata.name", serviceId)
|
||||
.withLabels(properties.getServiceLabels()).list().getItems();
|
||||
}
|
||||
|
||||
private List<ServiceInstance> getNamespaceServiceInstances(EndpointSubsetNS es,
|
||||
String serviceId) {
|
||||
String namespace = es.getNamespace();
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -54,6 +55,9 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KubernetesCatalogWatchTest {
|
||||
|
||||
@Mock
|
||||
private KubernetesDiscoveryProperties properties;
|
||||
|
||||
@Mock
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
@@ -85,6 +89,28 @@ public class KubernetesCatalogWatchTest {
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod",
|
||||
"api-pod"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomOrderChangePodsAllNamespaces() throws Exception {
|
||||
when(this.endpointsOperation.list())
|
||||
.thenReturn(
|
||||
createSingleEndpointEndpointListByPodName("api-pod", "other-pod"))
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod",
|
||||
"api-pod"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -101,6 +127,28 @@ public class KubernetesCatalogWatchTest {
|
||||
.thenReturn(
|
||||
createEndpointsListByServiceName("other-service", "api-service"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomOrderChangeServicesAllNamespaces() throws Exception {
|
||||
when(this.endpointsOperation.list())
|
||||
.thenReturn(
|
||||
createEndpointsListByServiceName("api-service", "other-service"))
|
||||
.thenReturn(
|
||||
createEndpointsListByServiceName("other-service", "api-service"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -114,6 +162,30 @@ public class KubernetesCatalogWatchTest {
|
||||
when(this.endpointsOperation.list()).thenReturn(
|
||||
createSingleEndpointEndpointListByPodName("api-pod", "other-pod"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher)
|
||||
.publishEvent(this.heartbeatEventArgumentCaptor.capture());
|
||||
|
||||
HeartbeatEvent event = this.heartbeatEventArgumentCaptor.getValue();
|
||||
assertThat(event.getValue()).isInstanceOf(List.class);
|
||||
|
||||
List<String> expectedPodsList = Arrays.asList("api-pod", "other-pod");
|
||||
assertThat(event.getValue()).isEqualTo(expectedPodsList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventBodyAllNamespaces() throws Exception {
|
||||
when(this.endpointsOperation.list()).thenReturn(
|
||||
createSingleEndpointEndpointListByPodName("api-pod", "other-pod"));
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
@@ -134,6 +206,27 @@ public class KubernetesCatalogWatchTest {
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointsWithoutSubsetsAllNamespaces() {
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets();
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -150,6 +243,28 @@ public class KubernetesCatalogWatchTest {
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointsWithoutAddressesAllNamespaces() {
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).setAddresses(null);
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -167,6 +282,29 @@ public class KubernetesCatalogWatchTest {
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
this.underTest.catalogServicesWatch();
|
||||
|
||||
verify(this.applicationEventPublisher).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointsWithoutTargetRefsAllNamespaces() {
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).getAddresses().get(0)
|
||||
.setTargetRef(null);
|
||||
|
||||
when(this.endpointsOperation.list()).thenReturn(endpoints);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace())
|
||||
.thenReturn(this.endpointsOperation);
|
||||
when(this.kubernetesClient.endpoints().inAnyNamespace().withLabels(anyMap()))
|
||||
.thenReturn(this.endpointsOperation);
|
||||
|
||||
this.underTest.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.discovery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -34,6 +35,9 @@ import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.dsl.ServiceResource;
|
||||
@@ -49,7 +53,9 @@ import org.springframework.cloud.client.ServiceInstance;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -79,6 +85,9 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
@Mock
|
||||
private Resource<Endpoints, DoneableEndpoints> endpointsResource;
|
||||
|
||||
@Mock
|
||||
FilterWatchListDeletable<Endpoints, EndpointsList, Boolean, Watch, Watcher<Endpoints>> filter;
|
||||
|
||||
@InjectMocks
|
||||
private KubernetesDiscoveryClient underTest;
|
||||
|
||||
@@ -360,10 +369,16 @@ public class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
.addNewSubset().addAllToPorts(getEndpointPorts(ports)).addNewAddress()
|
||||
.endAddress().endSubset().build();
|
||||
|
||||
when(this.endpointsResource.get()).thenReturn(endpoints);
|
||||
when(this.endpointsOperation.withName(serviceId))
|
||||
.thenReturn(this.endpointsResource);
|
||||
when(this.kubernetesClient.endpoints()).thenReturn(this.endpointsOperation);
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList(null,
|
||||
Collections.singletonList(endpoints), null, null);
|
||||
when(filter.list()).thenReturn(endpointsList);
|
||||
when(filter.withLabels(anyMap())).thenReturn(filter);
|
||||
|
||||
when(this.kubernetesClient.endpoints().withField(eq("metadata.name"),
|
||||
eq(serviceId))).thenReturn(filter);
|
||||
|
||||
}
|
||||
|
||||
private List<ServicePort> getServicePorts(Map<Integer, String> ports) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.discovery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
@@ -61,106 +62,44 @@ public class KubernetesDiscoveryClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsFromMultipleNamespaces() {
|
||||
Endpoints endPoints1 = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
|
||||
.build();
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsSingleAddress() {
|
||||
Map<String, String> labels = new HashMap();
|
||||
labels.put("l", "v");
|
||||
|
||||
Endpoints endpoints2 = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test2").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip2").withNewTargetRef().withUid("uid2")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
|
||||
.build();
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("10")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
List<Endpoints> endpointsList = new ArrayList<>();
|
||||
endpointsList.add(endPoints1);
|
||||
endpointsList.add(endpoints2);
|
||||
endpointsList.add(endPoint);
|
||||
|
||||
EndpointsList endpoints = new EndpointsList();
|
||||
endpoints.setItems(endpointsList);
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?labelSelector=l%3Dv&fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/endpoints?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint")
|
||||
.andReturn(200, endPoints1).once();
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test2/endpoints/endpoint")
|
||||
.andReturn(200, endpoints2).once();
|
||||
|
||||
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build();
|
||||
|
||||
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test2").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build();
|
||||
|
||||
List<Service> servicesList = new ArrayList<>();
|
||||
servicesList.add(service1);
|
||||
servicesList.add(service2);
|
||||
|
||||
ServiceList services = new ServiceList();
|
||||
services.setItems(servicesList);
|
||||
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/services?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, services).once();
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).endMetadata().build();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services/endpoint")
|
||||
.andReturn(200, service1).always();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test2/services/endpoint")
|
||||
.andReturn(200, service2).always();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setAllNamespaces(true);
|
||||
|
||||
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
properties, KubernetesClient::services,
|
||||
new DefaultIsServicePortSecureResolver(properties));
|
||||
|
||||
final List<ServiceInstance> instances = discoveryClient.getInstances("endpoint");
|
||||
|
||||
assertThat(instances).hasSize(2);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure())
|
||||
.hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip2") && !s.isSecure())
|
||||
.hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("uid1"))
|
||||
.hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("uid2"))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsSingleAddress() {
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint")
|
||||
.andReturn(200, new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").endMetadata().addNewSubset().addNewAddress()
|
||||
.withIp("ip1").withNewTargetRef().withUid("uid1").endTargetRef()
|
||||
.endAddress().addNewPort("http", 80, "TCP").endSubset().build())
|
||||
.once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/services/endpoint")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata()
|
||||
.withName("endpoint").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build())
|
||||
.always();
|
||||
.andReturn(200, service).always();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setServiceLabels(labels);
|
||||
properties.getMetadata().setAddLabels(false);
|
||||
properties.getMetadata().setAddAnnotations(false);
|
||||
|
||||
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
properties, KubernetesClient::services,
|
||||
@@ -170,30 +109,45 @@ public class KubernetesDiscoveryClientTest {
|
||||
|
||||
assertThat(instances).hasSize(1)
|
||||
.filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("uid1")).hasSize(1);
|
||||
.filteredOn(s -> s.getInstanceId().equals("10")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsSingleAddressAndMultiplePorts() {
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint")
|
||||
.andReturn(200, new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").endMetadata().addNewSubset().addNewAddress()
|
||||
.withIp("ip1").withNewTargetRef().withUid("uid").endTargetRef()
|
||||
.endAddress().addNewPort("mgmt", 9000, "TCP")
|
||||
.addNewPort("http", 80, "TCP").endSubset().build())
|
||||
.once();
|
||||
Map<String, String> labels = new HashMap();
|
||||
labels.put("l2", "v2");
|
||||
|
||||
Endpoints endPoint1 = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").withLabels(labels)
|
||||
.endMetadata().addNewSubset().addNewAddress().withIp("ip1")
|
||||
.withNewTargetRef().withUid("20").endTargetRef().endAddress()
|
||||
.addNewPort("mgmt", "mgmt_tcp", 900, "TCP")
|
||||
.addNewPort("http", "http_tcp", 80, "TCP").endSubset().build();
|
||||
|
||||
List<Endpoints> endpointsList = new ArrayList<>();
|
||||
endpointsList.add(endPoint1);
|
||||
|
||||
EndpointsList endpoints = new EndpointsList();
|
||||
endpoints.setItems(endpointsList);
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?labelSelector=l2%3Dv2&fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).withAnnotations(labels)
|
||||
.endMetadata().build();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services/endpoint")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata()
|
||||
.withName("endpoint").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build())
|
||||
.always();
|
||||
.andReturn(200, service).always();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setPrimaryPortName("http");
|
||||
properties.setPrimaryPortName("http_tcp");
|
||||
|
||||
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
properties, KubernetesClient::services,
|
||||
new DefaultIsServicePortSecureResolver(properties));
|
||||
@@ -202,29 +156,81 @@ public class KubernetesDiscoveryClientTest {
|
||||
|
||||
assertThat(instances).hasSize(1)
|
||||
.filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("uid")).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("20")).hasSize(1)
|
||||
.filteredOn(s -> 80 == s.getPort()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsMultipleAddresses() {
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint")
|
||||
.andReturn(200, new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").endMetadata().addNewSubset().addNewAddress()
|
||||
.withIp("ip1").endAddress().addNewAddress().withIp("ip2")
|
||||
.endAddress().addNewPort("https", 443, "TCP").endSubset().build())
|
||||
.once();
|
||||
public void getEndPointsListTest() {
|
||||
Map<String, String> labels = new HashMap();
|
||||
labels.put("l", "v");
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services/endpoint")
|
||||
.andReturn(200, new ServiceBuilder().withNewMetadata()
|
||||
.withName("endpoint").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build())
|
||||
.always();
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("30")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
List<Endpoints> endpointsList = new ArrayList<>();
|
||||
endpointsList.add(endPoint);
|
||||
|
||||
EndpointsList endpoints = new EndpointsList();
|
||||
endpoints.setItems(endpointsList);
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?labelSelector=l%3Dv&fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setServiceLabels(labels);
|
||||
|
||||
final KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(
|
||||
mockClient, properties, KubernetesClient::services,
|
||||
new DefaultIsServicePortSecureResolver(properties));
|
||||
|
||||
final List<Endpoints> result_endpoints = discoveryClient
|
||||
.getEndPointsList("endpoint");
|
||||
|
||||
assertThat(result_endpoints).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsMultipleAddresses() {
|
||||
Map<String, String> labels = new HashMap();
|
||||
labels.put("l1", "v1");
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("40")
|
||||
.endTargetRef().endAddress().addNewAddress().withIp("ip2")
|
||||
.withNewTargetRef().withUid("50").endTargetRef().endAddress()
|
||||
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
|
||||
|
||||
List<Endpoints> endpointsList = new ArrayList<>();
|
||||
endpointsList.add(endPoint);
|
||||
|
||||
EndpointsList endpoints = new EndpointsList();
|
||||
endpoints.setItems(endpointsList);
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?labelSelector=l1%3Dv1&fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get().withPath(
|
||||
"/api/v1/namespaces/test/endpoints?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(labels).endMetadata().build();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services/endpoint")
|
||||
.andReturn(200, service).always();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setServiceLabels(labels);
|
||||
properties.getMetadata().setAddAnnotations(false);
|
||||
properties.getMetadata().setAddLabels(false);
|
||||
|
||||
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
properties, KubernetesClient::services,
|
||||
new DefaultIsServicePortSecureResolver(properties));
|
||||
@@ -295,4 +301,84 @@ public class KubernetesDiscoveryClientTest {
|
||||
assertThat(services).containsOnly("s1", "s2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstancesShouldBeAbleToHandleEndpointsFromMultipleNamespaces() {
|
||||
Endpoints endPoints1 = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("60")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
Endpoints endpoints2 = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test2").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip2").withNewTargetRef().withUid("70")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
List<Endpoints> endpointsList = new ArrayList<>();
|
||||
endpointsList.add(endPoints1);
|
||||
endpointsList.add(endpoints2);
|
||||
|
||||
EndpointsList endpoints = new EndpointsList();
|
||||
endpoints.setItems(endpointsList);
|
||||
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/endpoints?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, endpoints).once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/endpoints/endpoint")
|
||||
.andReturn(200, endPoints1).once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test2/endpoints/endpoint")
|
||||
.andReturn(200, endpoints2).once();
|
||||
|
||||
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build();
|
||||
|
||||
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint")
|
||||
.withNamespace("test2").withLabels(new HashMap<String, String>() {
|
||||
{
|
||||
put("l", "v");
|
||||
}
|
||||
}).endMetadata().build();
|
||||
|
||||
List<Service> servicesList = new ArrayList<>();
|
||||
servicesList.add(service1);
|
||||
servicesList.add(service2);
|
||||
|
||||
ServiceList services = new ServiceList();
|
||||
services.setItems(servicesList);
|
||||
|
||||
mockServer.expect().get()
|
||||
.withPath("/api/v1/services?fieldSelector=metadata.name%3Dendpoint")
|
||||
.andReturn(200, services).once();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test/services/endpoint")
|
||||
.andReturn(200, service1).always();
|
||||
|
||||
mockServer.expect().get().withPath("/api/v1/namespaces/test2/services/endpoint")
|
||||
.andReturn(200, service2).always();
|
||||
|
||||
final KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
properties.setAllNamespaces(true);
|
||||
|
||||
final DiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
properties, KubernetesClient::services,
|
||||
new DefaultIsServicePortSecureResolver(properties));
|
||||
|
||||
final List<ServiceInstance> instances = discoveryClient.getInstances("endpoint");
|
||||
|
||||
assertThat(instances).hasSize(2);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure())
|
||||
.hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip2") && !s.isSecure())
|
||||
.hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("60")).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("70")).hasSize(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -112,6 +113,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // see gh-603
|
||||
public void shouldReturnEmptyFluxForNonExistingService(
|
||||
@Client KubernetesClient kubernetesClient) {
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties();
|
||||
@@ -122,6 +124,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // see gh-603
|
||||
public void shouldReturnEmptyFluxWhenServiceHasNoSubsets(
|
||||
@Client KubernetesClient kubernetesClient,
|
||||
@Server KubernetesServer kubernetesServer) {
|
||||
@@ -144,6 +147,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // see gh-603
|
||||
public void shouldReturnFlux(@Client KubernetesClient kubernetesClient,
|
||||
@Server KubernetesServer kubernetesServer) {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services")
|
||||
@@ -160,8 +164,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
|
||||
.build();
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
kubernetesServer.expect().get()
|
||||
.withPath("/api/v1/namespaces/test/endpoints/existing-service")
|
||||
@@ -187,6 +191,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // see gh-603
|
||||
public void shouldReturnFluxWithPrefixedMetadata(
|
||||
@Client KubernetesClient kubernetesClient,
|
||||
@Server KubernetesServer kubernetesServer) {
|
||||
@@ -204,8 +209,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP").endSubset()
|
||||
.build();
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.endSubset().build();
|
||||
|
||||
kubernetesServer.expect().get()
|
||||
.withPath("/api/v1/namespaces/test/endpoints/existing-service")
|
||||
@@ -234,6 +239,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // see gh-603
|
||||
public void shouldReturnFluxWhenServiceHasMultiplePortsAndPrimaryPortNameIsSet(
|
||||
@Client KubernetesClient kubernetesClient,
|
||||
@Server KubernetesServer kubernetesServer) {
|
||||
@@ -251,8 +257,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
Endpoints endPoints = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP")
|
||||
.addNewPort("https", 443, "TCP").endSubset().build();
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
|
||||
|
||||
kubernetesServer.expect().get()
|
||||
.withPath("/api/v1/namespaces/test/endpoints/existing-service")
|
||||
@@ -296,8 +302,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata()
|
||||
.withName("endpoint").withNamespace("test").endMetadata().addNewSubset()
|
||||
.addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", 80, "TCP")
|
||||
.addNewPort("https", 443, "TCP").endSubset().build();
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
endpointsList.setItems(singletonList(endpoints));
|
||||
|
||||
@@ -83,6 +83,14 @@
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ package org.springframework.cloud.kubernetes.examples;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -36,7 +36,7 @@ import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderControllerTest {
|
||||
|
||||
@Mock
|
||||
@@ -52,7 +52,7 @@ public class LeaderControllerTest {
|
||||
|
||||
private LeaderController leaderController;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() throws UnknownHostException {
|
||||
this.host = InetAddress.getLocalHost().getHostName();
|
||||
this.leaderController = new LeaderController();
|
||||
|
||||
@@ -134,6 +134,8 @@
|
||||
<!-- <module>istio</module>-->
|
||||
<module>discovery</module>
|
||||
<module>load-balancer</module>
|
||||
<!-- Add this module back once we convert everything over to kind -->
|
||||
<!-- <module>spring-cloud-kubernetes-configuration-watcher-it</module>-->
|
||||
</modules>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: k8s-demo-app
|
||||
name: k8s-demo-app
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: k8s-demo-app
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: k8s-demo-app
|
||||
spec:
|
||||
containers:
|
||||
- image: docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher-it:2.0.0-SNAPSHOT
|
||||
name: spring-cloud-kubernetes-configuration-watcher-it
|
||||
resources: {}
|
||||
env:
|
||||
- name: SPRING_RABBITMQ_HOST
|
||||
value: rabbitmq-service
|
||||
status: {}
|
||||
@@ -0,0 +1,28 @@
|
||||
# this config file contains all config fields with comments
|
||||
# NOTE: this is not a particularly useful config file
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
containerdConfigPatches:
|
||||
- |-
|
||||
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
|
||||
endpoint = ["http://kind-registry:5000"]
|
||||
|
||||
# 1 control plane node and 3 workers
|
||||
nodes:
|
||||
- role: control-plane
|
||||
image: kindest/node:v1.14.10@sha256:6cd43ff41ae9f02bb46c8f455d5323819aec858b99534a290517ebc181b443c6
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
protocol: TCP
|
||||
- role: worker
|
||||
image: kindest/node:v1.14.10@sha256:6cd43ff41ae9f02bb46c8f455d5323819aec858b99534a290517ebc181b443c6
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher
|
||||
name: spring-cloud-kubernetes-serviceaccount:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: namespace-reader
|
||||
rules:
|
||||
- apiGroups: ["", "extensions", "apps"]
|
||||
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
@@ -0,0 +1,106 @@
|
||||
<?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>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-configuration-watcher-it</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<wiremock.version>2.26.3</wiremock.version>
|
||||
<kubernetes-client.version>6.0.1</kubernetes-client.version>
|
||||
<docker-java.version>3.2.2</docker-java.version>
|
||||
<awaitility.version>4.0.3</awaitility.version>
|
||||
</properties>
|
||||
|
||||
<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>
|
||||
<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>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
<version>${kubernetes-client.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
<version>${kubernetes-client.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<version>${docker-java.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<version>${docker-java.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<image>
|
||||
<name>springcloud/${project.artifactId}:${project.version}</name>
|
||||
</image>
|
||||
<goal>build-image</goal>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
|
||||
# standard bash error handling
|
||||
set -o errexit;
|
||||
set -o pipefail;
|
||||
set -o nounset;
|
||||
# debug commands
|
||||
set -x;
|
||||
|
||||
# working dir to install binaries etc, cleaned up on exit
|
||||
BIN_DIR="$(mktemp -d)"
|
||||
# kind binary will be here
|
||||
KIND="${BIN_DIR}/kind"
|
||||
|
||||
ISTIOCTL="${BIN_DIR}/istio-1.6.2/bin/istioctl"
|
||||
|
||||
CURRENT_DIR="$(pwd)"
|
||||
|
||||
# cleanup on exit (useful for running locally)
|
||||
cleanup() {
|
||||
"${KIND}" delete cluster || true
|
||||
rm -rf "${BIN_DIR}"
|
||||
|
||||
docker kill kind-registry
|
||||
docker rm kind-registry
|
||||
docker network rm kind
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# util to install the latest kind version into ${BIN_DIR}
|
||||
install_latest_kind() {
|
||||
# clone kind into a tempdir within BIN_DIR
|
||||
local tmp_dir
|
||||
tmp_dir="$(TMPDIR="${BIN_DIR}" mktemp -d "${BIN_DIR}/kind-source.XXXXX")"
|
||||
cd "${tmp_dir}" || exit
|
||||
git clone https://github.com/kubernetes-sigs/kind && cd ./kind
|
||||
make install INSTALL_DIR="${BIN_DIR}"
|
||||
}
|
||||
|
||||
# util to install a released kind version into ${BIN_DIR}
|
||||
install_kind_release() {
|
||||
VERSION="v0.5.1"
|
||||
KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-linux-amd64"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-darwin-amd64"
|
||||
elif [[ "$OSTYPE" == "cygwin" ]]; then
|
||||
KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64"
|
||||
elif [[ "$OSTYPE" == "msys" ]]; then
|
||||
KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64"
|
||||
elif [[ "$OSTYPE" == "win32" ]]; then
|
||||
KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64"
|
||||
else
|
||||
echo "Uknown OS, using linux binary"
|
||||
fi
|
||||
wget -O "${KIND}" "${KIND_BINARY_URL}"
|
||||
chmod +x "${KIND}"
|
||||
}
|
||||
|
||||
setup_registry() {
|
||||
set -o errexit
|
||||
|
||||
# create registry container unless it already exists
|
||||
reg_name='kind-registry'
|
||||
reg_port='5000'
|
||||
running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)"
|
||||
if [ "${running}" != 'true' ]; then
|
||||
docker run \
|
||||
-d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \
|
||||
registry:2
|
||||
fi
|
||||
|
||||
# create a cluster with the local registry enabled in containerd
|
||||
cat <<EOF | "${KIND}" create cluster --config=-
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
containerdConfigPatches:
|
||||
- |-
|
||||
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
|
||||
endpoint = ["http://${reg_name}:${reg_port}"]
|
||||
EOF
|
||||
|
||||
# connect the registry to the cluster network
|
||||
docker network connect "kind" "${reg_name}"
|
||||
|
||||
# tell https://tilt.dev to use the registry
|
||||
# https://docs.tilt.dev/choosing_clusters.html#discovering-the-registry
|
||||
for node in $("${KIND}" get nodes); do
|
||||
kubectl annotate node "${node}" "kind.x-k8s.io/registry=localhost:${reg_port}";
|
||||
done
|
||||
|
||||
}
|
||||
|
||||
main() {
|
||||
# get kind
|
||||
install_latest_kind
|
||||
|
||||
# create a cluster
|
||||
cd $CURRENT_DIR
|
||||
|
||||
# create registry container unless it already exists
|
||||
reg_name='kind-registry'
|
||||
reg_port='5000'
|
||||
running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)"
|
||||
if [ "${running}" != 'true' ]; then
|
||||
docker run \
|
||||
-d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \
|
||||
registry:2
|
||||
fi
|
||||
|
||||
#TODO what happens if cluster is already there????
|
||||
"${KIND}" create cluster --config kind-config.yaml --loglevel=debug
|
||||
|
||||
# connect the registry to the cluster network
|
||||
docker network connect "kind" "${reg_name}"
|
||||
|
||||
# tell https://tilt.dev to use the registry
|
||||
# https://docs.tilt.dev/choosing_clusters.html#discovering-the-registry
|
||||
for node in $("${KIND}" get nodes); do
|
||||
kubectl annotate node "${node}" "kind.x-k8s.io/registry=localhost:${reg_port}";
|
||||
done
|
||||
|
||||
# set KUBECONFIG to point to the cluster
|
||||
kubectl cluster-info --context kind-kind
|
||||
|
||||
#setup nginx ingress
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/kind/deploy.yaml
|
||||
kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=360s
|
||||
|
||||
# This creates the service account, role, and role binding necessary for Spring Cloud k8s apps
|
||||
kubectl apply -f ./permissions.yaml
|
||||
|
||||
# cd ${BIN_DIR}
|
||||
# curl -L https://istio.io/downloadIstio | sh -
|
||||
#"${ISTIOCTL}" install --set profile=demo
|
||||
|
||||
cd $CURRENT_DIR
|
||||
|
||||
# TODO: invoke your tests here
|
||||
../../mvnw clean install -P it
|
||||
# teardown will happen automatically on exit
|
||||
}
|
||||
|
||||
main
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 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;
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
public class ConfigWatcherTestApplication
|
||||
implements ApplicationListener<RefreshRemoteApplicationEvent> {
|
||||
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private boolean value = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ConfigWatcherTestApplication.class, args);
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public boolean index() {
|
||||
log.warn("Current value: " + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(
|
||||
RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) {
|
||||
log.warn("Received remote refresh event");
|
||||
this.value = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
spring:
|
||||
application:
|
||||
name: spring-cloud-kubernetes-configuration-watcher-it
|
||||
cloud:
|
||||
bus:
|
||||
refresh:
|
||||
enabled: false #disable this because we are going to provide our own refresh listener for testing purposes
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.time.Duration;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.ApiClient;
|
||||
import io.kubernetes.client.Configuration;
|
||||
import io.kubernetes.client.apis.AppsV1Api;
|
||||
import io.kubernetes.client.apis.CoreV1Api;
|
||||
import io.kubernetes.client.apis.NetworkingV1beta1Api;
|
||||
import io.kubernetes.client.models.NetworkingV1beta1Ingress;
|
||||
import io.kubernetes.client.models.V1ConfigMap;
|
||||
import io.kubernetes.client.models.V1ConfigMapBuilder;
|
||||
import io.kubernetes.client.models.V1Deployment;
|
||||
import io.kubernetes.client.models.V1Service;
|
||||
import io.kubernetes.client.util.Config;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.findAll;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ActuatorRefreshIT {
|
||||
|
||||
private static final String KIND_REPO_HOST_PORT = "localhost:5000";
|
||||
|
||||
private static final String KIND_REPO_URL = "http://" + KIND_REPO_HOST_PORT;
|
||||
|
||||
private static final String IMAGE = "spring-cloud-kubernetes-configuration-watcher";
|
||||
|
||||
private static final String IMAGE_TAG = "2.0.0-SNAPSHOT";
|
||||
|
||||
private static final String LOCAL_REPO = "docker.io/springcloud";
|
||||
|
||||
private static final String LOCAL_IMAGE = LOCAL_REPO + "/" + IMAGE + ":" + IMAGE_TAG;
|
||||
|
||||
private static final String KIND_IMAGE = KIND_REPO_HOST_PORT + "/" + IMAGE;
|
||||
|
||||
private static final String KIND_IMAGE_WITH_TAG = KIND_IMAGE + ":" + IMAGE_TAG;
|
||||
|
||||
private static final String CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME = "config-watcher-wiremock-deployment";
|
||||
|
||||
private static final String CONFIG_WATCHER_WIREMOCK_APP_NAME = "config-watcher-wiremock";
|
||||
|
||||
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 WIREMOCK_HOST = "localhost";
|
||||
|
||||
private static final String WIREMOCK_PATH = "/wiremock";
|
||||
|
||||
private static final int WIREMOCK_PORT = 80;
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private ApiClient client;
|
||||
|
||||
private CoreV1Api api;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
|
||||
private NetworkingV1beta1Api networkingApi;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.client = Config.defaultClient();
|
||||
// client.setDebugging(true);
|
||||
Configuration.setDefaultApiClient(client);
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1beta1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withRegistryUrl(KIND_REPO_URL).build();
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost()).sslConfig(config.getSSLConfig())
|
||||
.build();
|
||||
|
||||
DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
|
||||
dockerClient.tagImageCmd(LOCAL_IMAGE, KIND_IMAGE, IMAGE_TAG).exec();
|
||||
dockerClient.pushImageCmd(KIND_IMAGE_WITH_TAG).start();
|
||||
|
||||
deployWiremock();
|
||||
|
||||
// Check to make sure the wiremock deployment is ready
|
||||
k8SUtils.waitForDeployment(CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
// Check to see if endpoint is ready
|
||||
k8SUtils.waitForEndpointReady(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE);
|
||||
|
||||
deployConfigWatcher();
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME,
|
||||
NAMESPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActuatorRefresh() throws Exception {
|
||||
// Configure wiremock to point at the server
|
||||
WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH);
|
||||
|
||||
// Setup stubs for actuator refresh
|
||||
stubFor(post(urlEqualTo("/actuator/refresh"))
|
||||
.willReturn(aResponse().withStatus(200)));
|
||||
|
||||
// Create new configmap to trigger controller to signal app to refresh
|
||||
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata()
|
||||
.withName(CONFIG_WATCHER_WIREMOCK_APP_NAME)
|
||||
.addToLabels("spring.cloud.kubernetes.config", "true").endMetadata()
|
||||
.addToData("foo", "bar").build();
|
||||
api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null);
|
||||
|
||||
// Wait a bit before we verify
|
||||
await().atMost(Duration.ofMillis(3400))
|
||||
.until(() -> !findAll(postRequestedFor(urlEqualTo("/actuator/refresh")))
|
||||
.isEmpty());
|
||||
|
||||
verify(postRequestedFor(urlEqualTo("/actuator/refresh")));
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, null,
|
||||
null, null, null, null);
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null,
|
||||
"metadata.name=" + CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, null, null,
|
||||
null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE,
|
||||
null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE, null,
|
||||
null, null, null, null, null);
|
||||
networkingApi.deleteNamespacedIngress("nginx-ingress", NAMESPACE, null, null,
|
||||
null, null, null, null);
|
||||
api.deleteNamespacedConfigMap(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE,
|
||||
null, null, null, null, null, null);
|
||||
api.deleteNamespacedConfigMap(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE, null,
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void deployConfigWatcher() throws Exception {
|
||||
api.createNamespacedConfigMap(NAMESPACE, getConfigWatcherConfigMap(), null, null,
|
||||
null);
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null,
|
||||
null, null);
|
||||
api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
private V1Service getConfigWatcherService() throws Exception {
|
||||
V1Service service = (V1Service) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
return service;
|
||||
}
|
||||
|
||||
private V1ConfigMap getConfigWatcherConfigMap() throws Exception {
|
||||
V1ConfigMap configMap = (V1ConfigMap) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
return configMap;
|
||||
}
|
||||
|
||||
private V1Deployment getConfigWatcherDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml");
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private void deployWiremock() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getWireockDeployment(), null, null,
|
||||
null);
|
||||
api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
private NetworkingV1beta1Ingress getWiremockIngress() throws Exception {
|
||||
NetworkingV1beta1Ingress ingress = (NetworkingV1beta1Ingress) k8SUtils
|
||||
.readYamlFromClasspath("wiremock-ingress.yaml");
|
||||
return ingress;
|
||||
}
|
||||
|
||||
private V1Service getWiremockAppService() throws Exception {
|
||||
V1Service service = (V1Service) k8SUtils
|
||||
.readYamlFromClasspath("wiremock-service.yaml");
|
||||
return service;
|
||||
}
|
||||
|
||||
private V1Deployment getWireockDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath("wiremock-deployment.yaml");
|
||||
return deployment;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.time.Duration;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import io.kubernetes.client.ApiClient;
|
||||
import io.kubernetes.client.Configuration;
|
||||
import io.kubernetes.client.apis.AppsV1Api;
|
||||
import io.kubernetes.client.apis.CoreV1Api;
|
||||
import io.kubernetes.client.apis.NetworkingV1beta1Api;
|
||||
import io.kubernetes.client.models.NetworkingV1beta1Ingress;
|
||||
import io.kubernetes.client.models.V1ConfigMap;
|
||||
import io.kubernetes.client.models.V1ConfigMapBuilder;
|
||||
import io.kubernetes.client.models.V1Deployment;
|
||||
import io.kubernetes.client.models.V1ReplicationController;
|
||||
import io.kubernetes.client.models.V1Service;
|
||||
import io.kubernetes.client.util.Config;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
private Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private static final String KIND_REPO_HOST_PORT = "localhost:5000";
|
||||
|
||||
private static final String KIND_REPO_URL = "http://" + KIND_REPO_HOST_PORT;
|
||||
|
||||
private static final String CONFIG_WATCHER_IMAGE = "spring-cloud-kubernetes-configuration-watcher";
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_IMAGE = "spring-cloud-kubernetes-configuration-watcher-it";
|
||||
|
||||
private static final String IMAGE_TAG = "2.0.0-SNAPSHOT";
|
||||
|
||||
private static final String LOCAL_REPO = "docker.io/springcloud";
|
||||
|
||||
private static final String CONFIG_WATCHER_LOCAL_IMAGE = LOCAL_REPO + "/"
|
||||
+ CONFIG_WATCHER_IMAGE + ":" + IMAGE_TAG;
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_LOCAL_IMAGE = LOCAL_REPO + "/"
|
||||
+ CONFIG_WATCHER_IT_IMAGE + ":" + IMAGE_TAG;
|
||||
|
||||
private static final String CONFIG_WATCHER_KIND_IMAGE = KIND_REPO_HOST_PORT + "/"
|
||||
+ CONFIG_WATCHER_IMAGE;
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_KIND_IMAGE = KIND_REPO_HOST_PORT + "/"
|
||||
+ CONFIG_WATCHER_IT_IMAGE;
|
||||
|
||||
private static final String CONFIG_WATCHER_KIND_IMAGE_WITH_TAG = CONFIG_WATCHER_KIND_IMAGE
|
||||
+ ":" + IMAGE_TAG;
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_KIND_IMAGE_WITH_TAG = CONFIG_WATCHER_IT_KIND_IMAGE
|
||||
+ ":" + IMAGE_TAG;
|
||||
|
||||
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_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-configuration-watcher-it-deployment";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String RABBIT_MQ_CONTROLLER_NAME = "rabbitmq-controller";
|
||||
|
||||
private ApiClient client;
|
||||
|
||||
private CoreV1Api api;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
|
||||
private NetworkingV1beta1Api networkingApi;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.client = Config.defaultClient();
|
||||
// client.setDebugging(true);
|
||||
Configuration.setDefaultApiClient(client);
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1beta1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withRegistryUrl(KIND_REPO_URL).build();
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost()).sslConfig(config.getSSLConfig())
|
||||
.build();
|
||||
|
||||
DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
|
||||
dockerClient.tagImageCmd(CONFIG_WATCHER_LOCAL_IMAGE, CONFIG_WATCHER_KIND_IMAGE,
|
||||
IMAGE_TAG).exec();
|
||||
dockerClient.pushImageCmd(CONFIG_WATCHER_KIND_IMAGE_WITH_TAG).start();
|
||||
|
||||
dockerClient.tagImageCmd(CONFIG_WATCHER_IT_LOCAL_IMAGE,
|
||||
CONFIG_WATCHER_IT_KIND_IMAGE, IMAGE_TAG).exec();
|
||||
dockerClient.pushImageCmd(CONFIG_WATCHER_IT_KIND_IMAGE_WITH_TAG).start();
|
||||
|
||||
deployRabbitMQ();
|
||||
|
||||
k8SUtils.waitForReplicationController(RABBIT_MQ_CONTROLLER_NAME, NAMESPACE);
|
||||
|
||||
deployTestApp();
|
||||
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME,
|
||||
NAMESPACE);
|
||||
|
||||
deployConfigWatcher();
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME,
|
||||
NAMESPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() throws Exception {
|
||||
// Create new configmap to trigger controller to signal app to refresh
|
||||
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata()
|
||||
.withName(CONFIG_WATCHER_IT_IMAGE)
|
||||
.addToLabels("spring.cloud.kubernetes.config", "true").endMetadata()
|
||||
.addToData("foo", "hello world").build();
|
||||
api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null);
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
// Wait a bit before we verify
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(90))
|
||||
.until(() -> {
|
||||
Boolean value = rest.getForObject("http://localhost:80/it",
|
||||
Boolean.class);
|
||||
log.info("Returned " + value + " from http://localhost:80/it");
|
||||
return value;
|
||||
});
|
||||
|
||||
assertThat(rest.getForObject("http://localhost:80/it", Boolean.class)).isTrue();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
api.deleteNamespacedService("rabbitmq-service", NAMESPACE, null, null, null, null,
|
||||
null, null);
|
||||
api.deleteNamespacedService(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null,
|
||||
null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE,
|
||||
null, null, null, null, null, null);
|
||||
|
||||
appsApi.deleteNamespacedDeployment(
|
||||
SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE, null, null,
|
||||
null, null, null, null);
|
||||
appsApi.deleteNamespacedDeployment(
|
||||
SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE, null, null,
|
||||
null, null, null, null);
|
||||
|
||||
try {
|
||||
api.deleteNamespacedReplicationController(RABBIT_MQ_CONTROLLER_NAME,
|
||||
NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// swallowing this exception, the delete does actually happen, its a problem
|
||||
// downstream from the k8s
|
||||
// client
|
||||
// see
|
||||
// https://github.com/kubernetes-client/java/issues/86#issuecomment-411234259
|
||||
}
|
||||
|
||||
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null,
|
||||
null, null, null);
|
||||
|
||||
api.deleteNamespacedConfigMap(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE,
|
||||
null, null, null, null, null, null);
|
||||
api.deleteNamespacedConfigMap(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null,
|
||||
null, null, null, null);
|
||||
|
||||
}
|
||||
|
||||
private void deployTestApp() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getItDeployment(), null, null,
|
||||
null);
|
||||
api.createNamespacedService(NAMESPACE, getItAppService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getItIngress(), null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
private void deployConfigWatcher() throws Exception {
|
||||
api.createNamespacedConfigMap(NAMESPACE, getConfigWatcherConfigMap(), null, null,
|
||||
null);
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null,
|
||||
null, null);
|
||||
api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
private void deployRabbitMQ() throws Exception {
|
||||
api.createNamespacedService(NAMESPACE, getRabbitMQService(), null, null, null);
|
||||
api.createNamespacedReplicationController(NAMESPACE,
|
||||
getRabbitMQRepplicationController(), null, null, null);
|
||||
}
|
||||
|
||||
private V1Service getConfigWatcherService() throws Exception {
|
||||
V1Service service = (V1Service) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
return service;
|
||||
}
|
||||
|
||||
private V1ConfigMap getConfigWatcherConfigMap() throws Exception {
|
||||
V1ConfigMap configMap = (V1ConfigMap) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
return configMap;
|
||||
}
|
||||
|
||||
private V1Deployment getConfigWatcherDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils.readYamlFromClasspath(
|
||||
"spring-cloud-kubernetes-configuration-watcher-bus-deployment.yaml");
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private V1Service getItAppService() throws Exception {
|
||||
String urlString = "spring-cloud-kubernetes-configuration-watcher-it-service.yaml";
|
||||
V1Service service = (V1Service) k8SUtils.readYamlFromClasspath(urlString);
|
||||
return service;
|
||||
}
|
||||
|
||||
private V1Deployment getItDeployment() throws Exception {
|
||||
String urlString = "spring-cloud-kubernetes-configuration-watcher-it-deployment.yaml";
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath(urlString);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private NetworkingV1beta1Ingress getItIngress() throws Exception {
|
||||
String urlString = "spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml";
|
||||
NetworkingV1beta1Ingress ingress = (NetworkingV1beta1Ingress) k8SUtils
|
||||
.readYamlFromClasspath(urlString);
|
||||
return ingress;
|
||||
}
|
||||
|
||||
private V1ReplicationController getRabbitMQRepplicationController() throws Exception {
|
||||
String urlString = "rabbitmq-controller.yaml";
|
||||
V1ReplicationController replicationController = (V1ReplicationController) k8SUtils
|
||||
.readYamlFromClasspath(urlString);
|
||||
return replicationController;
|
||||
}
|
||||
|
||||
private V1Service getRabbitMQService() throws Exception {
|
||||
String urlString = "rabbitmq-service.yaml";
|
||||
V1Service service = (V1Service) k8SUtils.readYamlFromClasspath(urlString);
|
||||
return service;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.kubernetes.client.ApiException;
|
||||
import io.kubernetes.client.apis.AppsV1Api;
|
||||
import io.kubernetes.client.apis.CoreV1Api;
|
||||
import io.kubernetes.client.models.V1Deployment;
|
||||
import io.kubernetes.client.models.V1DeploymentBuilder;
|
||||
import io.kubernetes.client.models.V1DeploymentList;
|
||||
import io.kubernetes.client.models.V1Endpoints;
|
||||
import io.kubernetes.client.models.V1EndpointsList;
|
||||
import io.kubernetes.client.models.V1EnvVar;
|
||||
import io.kubernetes.client.models.V1ReplicationController;
|
||||
import io.kubernetes.client.models.V1ReplicationControllerList;
|
||||
import io.kubernetes.client.models.V1Service;
|
||||
import io.kubernetes.client.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.util.Yaml;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class K8SUtils {
|
||||
|
||||
private Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private CoreV1Api api;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
|
||||
public K8SUtils(CoreV1Api api, AppsV1Api appsApi) {
|
||||
this.api = api;
|
||||
this.appsApi = appsApi;
|
||||
}
|
||||
|
||||
public Object readYaml(String urlString) throws Exception {
|
||||
// create the url
|
||||
URL url = new URL(urlString);
|
||||
BufferedReader reader = null;
|
||||
Object yamlObj = null;
|
||||
try {
|
||||
// open the url stream, wrap it an a few "readers"
|
||||
reader = new BufferedReader(new InputStreamReader(url.openStream()));
|
||||
yamlObj = Yaml.load(reader);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
return yamlObj;
|
||||
}
|
||||
|
||||
public Object readYamlFromClasspath(String fileName) throws Exception {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
File file = new File(classLoader.getResource(fileName).getFile());
|
||||
BufferedReader reader = null;
|
||||
Object yamlObj = null;
|
||||
try {
|
||||
// open the url stream, wrap it an a few "readers"
|
||||
reader = new BufferedReader(new FileReader(file));
|
||||
yamlObj = Yaml.load(reader);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
return yamlObj;
|
||||
}
|
||||
|
||||
public V1Service createService(String name, Map<String, String> labels,
|
||||
Map<String, String> specSelectors, String type, String portName, int port,
|
||||
int targetPort, String namespace) throws ApiException {
|
||||
V1Service wiremockService = new V1ServiceBuilder().editOrNewMetadata()
|
||||
.withName(name).addToLabels(labels).endMetadata().editOrNewSpec()
|
||||
.addToSelector(specSelectors).withNewType(type).addNewPort()
|
||||
.withName(portName).withPort(port).withNewTargetPort(targetPort).endPort()
|
||||
.endSpec().build();
|
||||
return api.createNamespacedService(namespace, wiremockService, null, null, null);
|
||||
}
|
||||
|
||||
public V1Deployment createDeployment(String name,
|
||||
Map<String, String> selectorMatchLabels,
|
||||
Map<String, String> templateMetadataLabels, String containerName,
|
||||
String image, String pullPolicy, int containerPort, int readinessProbePort,
|
||||
String readinessProbePath, int livenessProbePort, String livenessProbePath,
|
||||
String serviceAccountName, Collection<V1EnvVar> envVars, String namespace)
|
||||
throws ApiException {
|
||||
|
||||
V1Deployment wiremockDeployment = new V1DeploymentBuilder().editOrNewMetadata()
|
||||
.withName(name).endMetadata().editOrNewSpec().withNewSelector()
|
||||
.addToMatchLabels(selectorMatchLabels).endSelector().editOrNewTemplate()
|
||||
.editOrNewMetadata().addToLabels(templateMetadataLabels).endMetadata()
|
||||
.editOrNewSpec().withServiceAccountName(serviceAccountName)
|
||||
.addNewContainer().withName(containerName).withImage(image)
|
||||
.withImagePullPolicy(pullPolicy).addNewPort()
|
||||
.withContainerPort(containerPort).endPort().editOrNewReadinessProbe()
|
||||
.editOrNewHttpGet().withNewPort(readinessProbePort)
|
||||
.withNewPath(readinessProbePath).endHttpGet().endReadinessProbe()
|
||||
.editOrNewLivenessProbe().editOrNewHttpGet()
|
||||
.withNewPort(livenessProbePort).withNewPath(livenessProbePath)
|
||||
.endHttpGet().endLivenessProbe().addAllToEnv(envVars).endContainer()
|
||||
.endSpec().endTemplate().endSpec().build();
|
||||
return appsApi.createNamespacedDeployment(namespace, wiremockDeployment, null,
|
||||
null, null);
|
||||
|
||||
}
|
||||
|
||||
public void waitForEndpointReady(String name, String namespace) throws Exception {
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(90, TimeUnit.SECONDS)
|
||||
.until(() -> isEndpointReady(name, namespace));
|
||||
}
|
||||
|
||||
public boolean isEndpointReady(String name, String namespace) throws ApiException {
|
||||
V1EndpointsList endpoints = api.listNamespacedEndpoints(namespace, null, null,
|
||||
"metadata.name=" + name, null, null, null, null, null);
|
||||
if (endpoints.getItems().isEmpty()) {
|
||||
fail("no endpoints for " + name);
|
||||
}
|
||||
V1Endpoints endpoint = endpoints.getItems().get(0);
|
||||
return endpoint.getSubsets().get(0).getAddresses().size() >= 1;
|
||||
}
|
||||
|
||||
public void waitForReplicationController(String name, String namespace) {
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(90, TimeUnit.SECONDS)
|
||||
.until(() -> isReplicationControllerReady(name, namespace));
|
||||
}
|
||||
|
||||
public boolean isReplicationControllerReady(String name, String namespace)
|
||||
throws ApiException {
|
||||
V1ReplicationControllerList controllerList = api
|
||||
.listNamespacedReplicationController(namespace, null, null,
|
||||
"metadata.name=" + name, null, null, null, null, null);
|
||||
if (controllerList.getItems().size() < 1) {
|
||||
fail("Replication controller with name " + name + "could not be found");
|
||||
}
|
||||
|
||||
V1ReplicationController replicationController = controllerList.getItems().get(0);
|
||||
Integer availableReplicas = replicationController.getStatus()
|
||||
.getAvailableReplicas();
|
||||
log.info("Available replicas for " + name + ": " + availableReplicas);
|
||||
return availableReplicas != null && availableReplicas >= 1;
|
||||
|
||||
}
|
||||
|
||||
public void waitForDeployment(String deploymentName, String namespace) {
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(90, TimeUnit.SECONDS)
|
||||
.until(() -> isDeployentReady(deploymentName, namespace));
|
||||
}
|
||||
|
||||
public boolean isDeployentReady(String deploymentName, String namespace)
|
||||
throws ApiException {
|
||||
V1DeploymentList deployments = appsApi.listNamespacedDeployment(namespace, null,
|
||||
null, "metadata.name=" + deploymentName, null, null, null, null, null);
|
||||
if (deployments.getItems().size() < 1) {
|
||||
fail("No deployments with the name " + deploymentName);
|
||||
}
|
||||
V1Deployment deployment = deployments.getItems().get(0);
|
||||
Integer availableReplicas = deployment.getStatus().getAvailableReplicas();
|
||||
log.info("Available replicas for " + deploymentName + ": " + availableReplicas);
|
||||
return availableReplicas != null && availableReplicas >= 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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=
|
||||
# livenessProbe:
|
||||
# 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
|
||||
@@ -0,0 +1,33 @@
|
||||
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: localhost:5000/spring-cloud-kubernetes-configuration-watcher:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: bus
|
||||
- name: SPRING_RABBITMQ_HOST
|
||||
value: rabbitmq-service
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
application.properties: |-
|
||||
# Set the refresh interval to 0 so the refresh event gets sent immediately
|
||||
spring.cloud.kubernetes.configuration.watcher.refreshDelay=0
|
||||
logging.level.org.springframework.cloud.kubernetes=TRACE
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configuration-watcher
|
||||
@@ -0,0 +1,28 @@
|
||||
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: localhost:5000/spring-cloud-kubernetes-configuration-watcher:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8888
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-configuration-watcher-it-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher-it
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher-it
|
||||
spec:
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-configuration-watcher-it
|
||||
image: localhost:5000/spring-cloud-kubernetes-configuration-watcher-it:2.0.0-SNAPSHOT
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- 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: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /it(/|$)(.*)
|
||||
backend:
|
||||
serviceName: spring-cloud-kubernetes-configuration-watcher-it
|
||||
servicePort: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-configuration-watcher-it
|
||||
name: spring-cloud-kubernetes-configuration-watcher-it
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-configuration-watcher-it
|
||||
type: ClusterIP
|
||||
@@ -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,27 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: config-watcher-wiremock-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: config-watcher-wiremock
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: config-watcher-wiremock
|
||||
spec:
|
||||
containers:
|
||||
- name: config-watcher-wiremock
|
||||
image: rodolpheche/wiremock
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /__admin/mappings
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /__admin/mappings
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /wiremock(/|$)(.*)
|
||||
backend:
|
||||
serviceName: config-watcher-wiremock
|
||||
servicePort: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: config-watcher-wiremock
|
||||
name: config-watcher-wiremock
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: config-watcher-wiremock
|
||||
type: ClusterIP
|
||||
@@ -61,15 +61,21 @@
|
||||
<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>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.leader;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -25,12 +26,11 @@ import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
|
||||
// Make sure test passes without Kubernetes cluster
|
||||
"spring.cloud.kubernetes.leader.autoStartup=false" })
|
||||
|
||||
@@ -18,11 +18,11 @@ package org.springframework.cloud.kubernetes.leader;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.integration.leader.Candidate;
|
||||
|
||||
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.verify;
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderContextTest {
|
||||
|
||||
@Mock
|
||||
@@ -47,7 +47,7 @@ public class LeaderContextTest {
|
||||
|
||||
private LeaderContext leaderContext;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.leaderContext = new LeaderContext(this.mockCandidate,
|
||||
this.mockLeadershipController);
|
||||
|
||||
@@ -19,11 +19,11 @@ package org.springframework.cloud.kubernetes.leader;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.boot.actuate.info.Info;
|
||||
import org.springframework.integration.leader.Candidate;
|
||||
@@ -31,7 +31,7 @@ import org.springframework.integration.leader.Candidate;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderInfoContributorTest {
|
||||
|
||||
@Mock
|
||||
@@ -45,7 +45,7 @@ public class LeaderInfoContributorTest {
|
||||
|
||||
private LeaderInfoContributor leaderInfoContributor;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.leaderInfoContributor = new LeaderInfoContributor(
|
||||
this.mockLeadershipController, this.mockCandidate);
|
||||
|
||||
@@ -18,12 +18,12 @@ package org.springframework.cloud.kubernetes.leader;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -33,7 +33,7 @@ import static org.mockito.internal.verification.VerificationModeFactory.atLeastO
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderInitiatorTest {
|
||||
|
||||
@Mock
|
||||
@@ -53,14 +53,14 @@ public class LeaderInitiatorTest {
|
||||
|
||||
private LeaderInitiator leaderInitiator;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.leaderInitiator = new LeaderInitiator(this.mockLeaderProperties,
|
||||
this.mockLeadershipController, this.mockLeaderRecordWatcher,
|
||||
this.mockPodReadinessWatcher);
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void after() {
|
||||
this.leaderInitiator.stop();
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -39,7 +39,7 @@ import static org.mockito.Mockito.verify;
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderRecordWatcherTest {
|
||||
|
||||
@Mock
|
||||
@@ -71,22 +71,15 @@ public class LeaderRecordWatcherTest {
|
||||
|
||||
private LeaderRecordWatcher watcher;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.watcher = new LeaderRecordWatcher(this.mockLeaderProperties,
|
||||
this.mockLeadershipController, this.mockKubernetesClient);
|
||||
|
||||
given(this.mockKubernetesClient.configMaps())
|
||||
.willReturn(this.mockConfigMapsOperation);
|
||||
given(this.mockConfigMapsOperation.inNamespace(null))
|
||||
.willReturn(this.mockInNamespaceOperation);
|
||||
given(this.mockInNamespaceOperation.withName(null))
|
||||
.willReturn(this.mockWithNameResource);
|
||||
given(this.mockWithNameResource.watch(this.watcher)).willReturn(this.mockWatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStartOnce() {
|
||||
initStubs();
|
||||
this.watcher.start();
|
||||
this.watcher.start();
|
||||
|
||||
@@ -95,6 +88,7 @@ public class LeaderRecordWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldStopOnce() {
|
||||
initStubs();
|
||||
this.watcher.start();
|
||||
this.watcher.stop();
|
||||
this.watcher.stop();
|
||||
@@ -120,6 +114,7 @@ public class LeaderRecordWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldHandleClose() {
|
||||
initStubs();
|
||||
this.watcher.onClose(this.mockKubernetesClientException);
|
||||
|
||||
verify(this.mockWithNameResource).watch(this.watcher);
|
||||
@@ -132,4 +127,14 @@ public class LeaderRecordWatcherTest {
|
||||
verify(this.mockWithNameResource, times(0)).watch(this.watcher);
|
||||
}
|
||||
|
||||
private void initStubs() {
|
||||
given(this.mockKubernetesClient.configMaps())
|
||||
.willReturn(this.mockConfigMapsOperation);
|
||||
given(this.mockConfigMapsOperation.inNamespace(null))
|
||||
.willReturn(this.mockInNamespaceOperation);
|
||||
given(this.mockInNamespaceOperation.withName(null))
|
||||
.willReturn(this.mockWithNameResource);
|
||||
given(this.mockWithNameResource.watch(this.watcher)).willReturn(this.mockWatch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.leader;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.integration.leader.Candidate;
|
||||
|
||||
@@ -30,7 +30,7 @@ import static org.mockito.BDDMockito.given;
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeaderTest {
|
||||
|
||||
private static final String ROLE = "test-role";
|
||||
@@ -42,7 +42,7 @@ public class LeaderTest {
|
||||
|
||||
private Leader leader;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.leader = new Leader(ROLE, ID);
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package org.springframework.cloud.kubernetes.leader;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.integration.leader.Candidate;
|
||||
import org.springframework.integration.leader.event.LeaderEventPublisher;
|
||||
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LeadershipControllerTest {
|
||||
|
||||
@Mock
|
||||
@@ -48,7 +48,7 @@ public class LeadershipControllerTest {
|
||||
|
||||
private LeadershipController leadershipController;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.leadershipController = new LeadershipController(this.mockCandidate,
|
||||
this.mockLeaderProperties, this.mockLeaderEventPublisher,
|
||||
|
||||
@@ -26,11 +26,11 @@ import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.PodResource;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -39,7 +39,7 @@ import static org.mockito.Mockito.verify;
|
||||
/**
|
||||
* @author Gytis Trikleris
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class PodReadinessWatcherTest {
|
||||
|
||||
private static final String POD_NAME = "test-pod";
|
||||
@@ -70,18 +70,15 @@ public class PodReadinessWatcherTest {
|
||||
|
||||
private PodReadinessWatcher watcher;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.watcher = new PodReadinessWatcher(POD_NAME, this.mockKubernetesClient,
|
||||
this.mockLeadershipController);
|
||||
|
||||
given(this.mockKubernetesClient.pods()).willReturn(this.mockPodsOperation);
|
||||
given(this.mockPodsOperation.withName(POD_NAME)).willReturn(this.mockPodResource);
|
||||
given(this.mockPodResource.watch(this.watcher)).willReturn(this.mockWatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStartOnce() {
|
||||
initStubs();
|
||||
this.watcher.start();
|
||||
this.watcher.start();
|
||||
|
||||
@@ -90,6 +87,7 @@ public class PodReadinessWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldStopOnce() {
|
||||
initStubs();
|
||||
this.watcher.start();
|
||||
this.watcher.stop();
|
||||
this.watcher.stop();
|
||||
@@ -99,6 +97,7 @@ public class PodReadinessWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldHandleEventWithStateChange() {
|
||||
initStubs();
|
||||
given(this.mockPodResource.isReady()).willReturn(true);
|
||||
given(this.mockPod.getStatus()).willReturn(this.mockPodStatus);
|
||||
|
||||
@@ -110,6 +109,7 @@ public class PodReadinessWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldIgnoreEventIfStateDoesNotChange() {
|
||||
initStubs();
|
||||
given(this.mockPod.getStatus()).willReturn(this.mockPodStatus);
|
||||
|
||||
this.watcher.start();
|
||||
@@ -120,6 +120,7 @@ public class PodReadinessWatcherTest {
|
||||
|
||||
@Test
|
||||
public void shouldHandleClose() {
|
||||
initStubs();
|
||||
this.watcher.onClose(this.mockKubernetesClientException);
|
||||
|
||||
verify(this.mockPodResource).watch(this.watcher);
|
||||
@@ -132,4 +133,10 @@ public class PodReadinessWatcherTest {
|
||||
verify(this.mockPodResource, times(0)).watch(this.watcher);
|
||||
}
|
||||
|
||||
private void initStubs() {
|
||||
given(this.mockKubernetesClient.pods()).willReturn(this.mockPodsOperation);
|
||||
given(this.mockPodsOperation.withName(POD_NAME)).willReturn(this.mockPodResource);
|
||||
given(this.mockPodResource.watch(this.watcher)).willReturn(this.mockWatch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@
|
||||
<suppress files=".*KubernetesDiscoveryClientFilterTest.*" checks="LineLength*"/>
|
||||
<suppress files=".*PollingConfigurationChangeDetector.*" checks="LineLength*"/>
|
||||
<suppress files=".*LeaderRecordWatcherTest.*" checks="LineLength*"/>
|
||||
<suppress files=".*ConfigurationWatcherApplication\.java" checks="HideUtilityClassConstructor"/>
|
||||
</suppressions>
|
||||
|
||||
Reference in New Issue
Block a user